整合信任:开发者的 Resume Protocol 指南

发布: (2025年12月22日 GMT+8 07:59)
8 min read
原文: Dev.to

I’m happy to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you’ve already provided) here? Once I have the text, I’ll translate it into Simplified Chinese while preserving the original formatting, markdown, and any code blocks or URLs.

Resume Integrator – 参考实现

在我之前的文章中,我介绍了 Resume Protocol:一个旨在让职业声誉可验证、绑定灵魂且由个人拥有的系统。
但协议的价值取决于我们构建的交互工具。

为了弥合复杂智能合约与日常实用之间的鸿沟,我开发了 Resume Integrator。这不仅仅是一个脚本;它是一个展示 Web3 工程可靠性与卓越性的参考实现。

无论你是在构建自由职业者市场还是大学认证门户,挑战始终相同:将丰富的链下证据(PDF、图片)与链上真相(不可篡改的账本)关联起来。

在本指南中,我将带你了解此集成背后的深思熟虑的架构决策。

工程挑战

层级描述
Evidence (off‑chain)详细描述、设计作品集、证书等——大量数据,直接存链效率低下。
Truth (on‑chain)所有权和背书的加密证明。

我在 Resume Integrator 中的目标是将二者无缝拼接,打造一个既稳健又以用户为中心的系统。

架构概览

步骤 1 – 使用意图(元数据)结构化数据

我们遵循 ERC‑721 元数据标准 并通过严格的 TypeScript 接口强制执行它。我们不会去猜测数据的形状;我们明确定义它。

// src/types.ts
export interface Attribute {
  trait_type: string;
  value: string | number | Date;
}

/**
 * Standard ERC‑721 Metadata Schema
 * Strict typing ensures every credential we mint
 * is readable by standard wallets.
 */
export interface CredentialMetadata {
  name: string;
  description: string;
  image: string;
  attributes: Attribute[];
}

步骤 2 – 存储层(Pinata SDK)

我们通过 Pinata SDK 使用 IPFS。Pinata 为我们提供了受托管服务的可靠性,同时不牺牲内容寻址的去中心化特性。

下面的 “两步” 模式确保数据完整性:

  1. 首先上传可视化凭证。
  2. 将该凭证的 URI 嵌入到元数据中。
// src/storage.ts
/**
 * Creates NFT‑compatible metadata for a credential
 * and uploads it to IPFS via Pinata.
 *
 * This function optionally uploads an image first,
 * then embeds its IPFS URL into the metadata JSON.
 *
 * @param input Credential metadata fields
 * @returns A public IPFS gateway URL pointing to the metadata JSON
 */
export async function createCredentialMetadata(
  input: CredentialMetadataInput
): Promise<string> {
  console.log('Authenticating with Pinata...');
  await pinata.testAuthentication();
  console.log('Pinata authentication successful');

  // Will store the IPFS URL of the uploaded image (if any)
  let image = '';

  // If an image path is provided, upload the image to IPFS first
  if (input.imagePath && fs.existsSync(input.imagePath)) {
    console.log(`Uploading image: ${input.imagePath}`);

    // Read the image file from disk into a buffer
    const buffer = fs.readFileSync(input.imagePath);

    // Convert the buffer into a File object (Node 18+ compatible)
    const file = new File([buffer], 'credential.png', {
      type: 'image/png',
    });

    // Upload the image to Pinata's public IPFS network
    const upload = await pinata.upload.public.file(file);

    // Construct a gateway‑accessible URL using the returned CID
    image = `https://${CONFIG.PINATA_GATEWAY}/ipfs/${upload.cid}`;
    console.log(`   Image URL: ${image}`);
  } else if (input.imagePath) {
    console.warn(
      `Warning: Image path provided but file not found: ${input.imagePath}`
    );
  }

  // Construct ERC‑721 compatible metadata JSON
  const metadata: CredentialMetadata = {
    name: input.skillName,
    description: input.description,
    image,
    attributes: [
      { trait_type: 'Recipient', value: input.recipientName },
      { trait_type: 'Endorser', value: input.issuerName },
      {
        trait_type: 'Date',
        value: new Date(
          input.endorsementDate.toISOString().split('T')[0]!
        ),
      },
      { trait_type: 'Token Standard', value: 'Soulbound (SBT)' },
    ],
  };

  // Upload the metadata JSON to IPFS
  console.log('Uploading metadata JSON...');
  const result = await pinata.upload.public.json(metadata);

  // Return a public gateway URL pointing to the metadata
  // This URL can be used directly as a tokenURI on‑chain
  return `https://${CONFIG.PINATA_GATEWAY}/ipfs/${result.cid}`;
}

步骤 3 – 发行层(Viem)

我选择 Viem 是因为它轻量、类型安全,并且符合我对精确性胜于臃肿的偏好。

这里最关键的工程决策是 等待确认。仅仅广播交易是不够的;我们必须确保它已被最终确定。这可以防止 UI 异常,并保证用户明确知道他们的声誉何时得到保障。

// src/contract.ts
/**
 * Mint a new endorsement on‑chain
 */
export async function mintEndorsement(
  recipient: string,
  skill: string,
  dataURI: string
): Promise<string> {
  if (!CONFIG.CONTRACT_ADDRESS) {
    throw new Error('Contract address

> **Source:** ...

```javascript
 not set in .env');
  }

  console.log(`Minting endorsement for ${skill}...`);

  const hash = await walletClient.writeContract({
    address: CONFIG.CONTRACT_ADDRESS,
    abi: CONTRACT_ABI,
    functionName: 'endorsePeer',
    args: [recipient, skill, dataURI],
  });

  console.log(`   Tx Sent: ${hash}`);

  // Wait for confirmation
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  console.log(`Confirmed in block ${receipt.blockNumber}`);

  return hash;
}

第 4 步 – 验证(读取)

逐个查询区块链状态变量既慢又昂贵。我们改用 事件日志。通过监听 EndorsementMinted 事件,可以在一次高效查询中重建用户的完整职业历史。这种做法兼顾了网络资源和用户体验。

就这样! Resume Integrator 展示了一种简洁、可投入生产的方式,将链下证据绑定到链上声誉,同时保持开发者体验愉快、终端用户体验流畅。欢迎 fork 代码仓库,进行实验,并构建下一代凭证驱动的应用。

src/contract.ts

/**
 * Retrieves all endorsements for a given user address.
 *
 * @param userAddress - The address of the user whose endorsements are being queried.
 * @throws Will throw an error if the contract address is not defined in the environment.
 * @returns An array of endorsement objects containing tokenId, skill, issuer, and status.
 */
export async function getEndorsementsFor(userAddress: string) {
  if (!CONFIG.CONTRACT_ADDRESS) {
    throw new Error('Contract Address not set in .env');
  }

  console.log(`Querying endorsements for ${userAddress}...`);

  const logs = await publicClient.getLogs({
    address: CONFIG.CONTRACT_ADDRESS,
    event: parseAbiItem(
      'event EndorsementMinted(uint256 indexed tokenId, address indexed issuer, address indexed recipient, bytes32 skillId, string skill, uint8 status)'
    ),
    args: {
      recipient: userAddress as Hex,
    },
    fromBlock: 'earliest',
  });

  return logs.map((log) => ({
    tokenId: log.args.tokenId,
    skill: log.args.skill,
    issuer: log.args.issuer,
    status: log.args.status === 1 ? 'Active' : 'Pending',
  }));
}

Conclusion

Resume Integrator 不仅仅是一个代码库——它是一个有目的构建的蓝图。

Separation of concerns

  • IPFS – 存储大量数据。
  • Blockchain – 提供信任。

Benefits

  • 高效、不可变且可扩展的系统。
  • 严格的类型检查和确认等待确保用户的可靠性。

The Resume Protocol 是基础;the Integrator 是桥梁。现在由你来构建界面。

仓库

  • The Protocol (Smart Contracts)
  • The Integrator (Sample Client)

让我们构建一个您可以信赖的、具备清晰、目标和卓越的东西。

Back to Blog

相关文章

阅读更多 »

为什么‘Single Key’钱包已过时

每日痛点:Phishing、seed‑phrase 泄露以及 “approve” 诈骗是不可避免的。在当前的 Web3 模型中,Signer 即 Owner。这是一个致命的设计……

# `@xchainjs/xchain-ethereum`

@xchainjs/xchain-ethereum 是 XChainJS 生态系统的官方 Ethereum 客户端——一个模块化、TypeScript‑first SDK,用于构建跨链钱包、crypto…

# `@xchainjs/xchain-litecoin`

Litecoin LTC 客户端和 XChainJS 实用工具:轻量级 TypeScript SDK,用于构建跨链钱包、加密支付流程和 DeFi 工具。