新消息:Helius 收购 Light Protocol
即插即用的代币扩展
博客/开发

即插即用的代币扩展

集成工程师X 上的 0xSolanaGirl
阅读需 8 分钟

简介

代币扩展让你可以轻松在 Solana 上创建可编程代币。开发者可以使用新的代币扩展程序,组合十多种铸币和账户扩展。完成本教程后,你将能够创建可替代和不可替代代币、生息代币、冻结代币、由你拥有绝对控制权的代币(包括将其移出不属于你的账户),并能关闭铸币账户以取回资金。 

即插即用的代币扩展教程提供交互式 Solana Playground 示例,帮助你使用 TypeScript 学习代币扩展并创建以下代币:

本教程使用的代码改编自 Solana 开发者指南。用于创建代币扩展的脚本可在其他资源部分找到。 

代币扩展账户

代币扩展构建于代币程序之上,并引入了新的指令。使用这两个程序创建的账户在初始长度上完全相同,直到代币程序指令结束、代币扩展指令开始。不了解账户数据的读者可以通过Anchor 简介了解账户空间。简而言之,在 Solana 上创建账户时,必须明确分配用于在链上存储数据的空间。有了空间大小,我们就能计算出使账户免租所需的 lamport 数量。

以下是创建带扩展的铸币账户的方式。要为代币扩展分配空间,请使用 getMintLen 函数获取带扩展的铸币账户大小。在 NonFungibleToken.ts 示例中,获取 MetadataPointer 和 MintCloseAuthority 扩展类型的铸币长度。

NonFungibleToken.ts 第 54 行:

代码
  // Size of Mint Account with extension
  const mintLen = getMintLen([
    ExtensionType.MetadataPointer,
    ExtensionType.MintCloseAuthority,
  ]);

元数据和元数据指针扩展让你无需依赖第三方协议即可存储和指向元数据。TokenMetadata 是 @solana/spl-token-metadata 提供的接口,包含 updateAuthority、mint、name、symbol、uri 和 additionalMetadata 等字段。对元数据对象使用 pack 函数即可得到元数据长度。 

‍NonFungibleToken.ts 第 60 行:

代码
  // Metadata to store in Mint Account
  const metaData: TokenMetadata = {
    updateAuthority: updateAuthority,
    mint: mint,
    name: "Solana Soldier",
    symbol: "SOLD",
    uri: "https://shdw-drive.genesysgo.net/G1Tzt42SDqCV3x9vPY5X826foA8fEk8BR4bB5wARh75d/OIG2.jpg",
    additionalMetadata: [["armor", null]],
  };
  // Size of metadata
  const metadataLen = pack(metaData).length;
  // Size of MetadataExtension 2 bytes for type, 2 bytes for length
  const metadataExtension = TYPE_SIZE + LENGTH_SIZE;

将铸币长度、元数据扩展和元数据长度相加,即可得到铸币账户所需的最少 lamport 数量。

NonFungibleToken.ts 第 75 行:

代码
  // Minimum lamports required for Mint Account
  const lamports = await connection.getMinimumBalanceForRentExemption(
    mintLen + metadataExtension + metadataLen
  );

代币扩展指令

@solana/spl-token 和 @solana/spl-token-metadata 等 Solana 库为代币扩展程序提供 TypeScript 支持。首先,使用 @solana/web3.js 中的 SystemProgram,创建一个具有适当空间和免租 lamport 数量的账户。

NonFungibleToken.ts 第 80 行:

代码
  // Instruction to invoke System Program to create new account
  const createAccountInstruction = SystemProgram.createAccount({
    fromPubkey: payer.publicKey, // Account that will transfer lamports to created account
    newAccountPubkey: mint, // Address of the account to create
    space: mintLen, // Amount of bytes to allocate to the created account
    lamports, // Amount of lamports transferred to created account
    programId: TOKEN_2022_PROGRAM_ID, // Program assigned as owner of created account
  });

接下来,我们可以创建用于指定铸币的指令。必须先执行以下指令,再调用 initializeMint。即插即用中使用的铸币前指令包括:

元数据账户可以独立于 initializeMint 进行初始化和更新。但元数据指针必须在铸币之前初始化。交易中的指令顺序决定其执行顺序。首先创建铸币账户并初始化元数据指针,然后使用铸币账户的公钥初始化铸币关闭权限。接着初始化铸币账户和元数据账户,并更新其他元数据。

‍NonFungibleToken.ts 第 135 行:

代码
  transaction = new Transaction().add(
    createAccountInstruction,
    initializeMetadataPointerInstruction,
    initializeMintCloseAuthorityInstruction,
    initializeMintInstruction,
    initializeMetadataInstruction,
    updateFieldInstruction
  );

nonFungibleToken.ts、soulboundToken.ts、interestBearingToken.ts 和 delegateToken.ts 文件使用相同的代币创建结构,区别在于使用的扩展和指令。 

首先,从所需的 Solana 库中导入所有必要函数,定义付款方并生成铸币地址。然后,计算带有所需代币扩展的铸币地址需要的空间和 lamport 数量。使用 @solana/spl-token 和 @solana/spl-token-metadata 库创建指令,将其添加到交易中,把交易发送到网络并验证结果。 

转账和销毁等代币操作可用于不同的代币扩展。带有元数据扩展的代币可以分别使用 createUpdateFieldInstruction 和 createRemoveKeyInstruction 指令来更新或移除元数据。 

即插即用 

在 Solana Playground 中,客户端包含与代币扩展交互所需的函数。例如,灵魂绑定代币使用不可转让扩展和元数据扩展来创建灵魂绑定 NFT。用于创建代币并与之交互的函数位于同一文件中,可以直接导入(在 Solana Playground 中,你必须复制粘贴代码,而不能导入)。 

quest.ts 文件让你可以体验 Solana 士兵任务。你也可以运行每个包含代币扩展的文件,查看其具体功能。Playground 钱包需要 Devnet SOL 才能运行任务。你还可以使用我们的 Solana Devnet 水龙头空投 Devnet SOL。

任务从创建一名 Solana 士兵开始。这是一个 NFT,付款方(pg.wallet.keypair)拥有铸币关闭权限,因此可以关闭铸币账户并取回其中的 SOL。这种机制有助于应用从不再活跃的账户中收回 SOL。

创建 Solana 士兵后,使用其铸币地址更新 NFT 的元数据。让我们把士兵的护甲更新为 iron。你可以在 NonFungibleToken.ts 文件中从第 167 行开始找到 getMetadata、updateMetadata 和 removeMetadata 函数。对于使用元数据扩展的铸币账户,可以调用这些函数。为简单起见,元数据函数只会包含在 NonFungibleToken.ts 文件中。

接下来,该开始升级了。生息代币将代表 Solana 士兵的等级。从 InterestBearingToken.ts 导入 createInterestBearingToken,并使用该指令创建代币。账户利息将按照 InterestBearingToken.ts 第 43 行中设置的利率增长。可以使用 amountToUiAmount 函数确定包含累计利息的代币数量。利息会随时间累积,我们可以调用 amountToUiAmount 来查看 Solana 士兵的等级。

士兵开始升级后,需要接收一项任务。该任务由灵魂绑定代币表示——士兵无法转让它(尝试调用转账函数并捕获错误),摆脱任务的唯一方法是销毁它。与其放弃任务,不如先看看任务内容。提示:检查元数据!

阅读任务后,即插即用教程会将一支军队部署到某个代币地址。代币部署后,Solana 士兵必须能够将军队召回付款方。代币扩展通过引入委托权限实现这一点。无论代币归谁所有,委托权限都可以转移和销毁代币。请将永久委托人设为你可以访问的密钥对;销毁和转移代币时,该密钥对需要签名。

委托代币将代表 Solana 军队。使用 DelegateToken.ts 第 33 行中的 createDelegateTokens,为敌人的代币账户创建委托代币。代币铸造后,Solana 军队会与敌人展开一场假想战斗。战斗结束后,使用 DelegateTokens.ts 第 176 行中的 transferDelegateTokens 函数,从敌人的账户召回士兵。

请注意,并非所有士兵都从敌人的地址被召回。假设他们已经阵亡。使用 @solana/spl-token 中的 burn 函数,销毁敌人代币账户(sourceTokenAccount)中剩余的代币。 

随着战斗获胜,Solana 士兵的任务也告一段落。我们使用 @solana/spl-token 中的 closeAccount 函数关闭账户并退还租金。本文未讨论的代币扩展包括保密转账、转账费用和转账钩子。几乎所有代币扩展都可以混合搭配,但转账钩子与不可转让代币等少数组合除外。本教程介绍的代币扩展所对应的 Solana Playground 参考项目已列在其他资源中。 

总结

受 CryptoZombies 启发,这篇即插即用教程介绍了通过组合扩展创建常用代币扩展的基础知识。本教程通过亲手参与的游戏体验,展示代币扩展的可组合性。这些示例既能直接即插即用,也能为开发者编写自己的代币提供指导。从计算账户空间到安排指令顺序,这些示例为创建带有代币扩展的账户提供了精简流程。 如果你觉得这种游戏化学习体验有所帮助,请在我们的 Discord 中分享反馈,或在 X 上提及 @helius。

其他资源

订阅 Helius

及时了解 Solana 开发的最新动态,并在我们发布新内容时收到更新

放大图片