{"content":{"title":"如何在Solana上销毁SPL代币","body":"### 概述 \r\n\r\n🔥🔥🔥\r\n\r\n构建一个通缩代币协议？想要销毁一个已被盗版的 NFT？只是想和你的社区一起玩得开心？Solana SPL Token Program 的销毁功能是你提升 Solana 社区的绝佳选择。\r\n\r\n在本指南中，你将使用 TypeScript 和 Solana SPL Token Program 来销毁你钱包中的 SPL 代币。\r\n\r\n#### 你需要准备的 \r\n\r\n- 安装 [Nodejs](https://nodejs.org/en/)（版本 16.15 及以上）\r\n- 具备 TypeScript 经验，并安装最新版本的 [ts-node](https://www.npmjs.com/package/ts-node)\r\n- 一个带有 Devnet SOL 的 Solana 纸钱包 (.json)（[示例脚本](https://github.com/quiknode-labs/qn-guide-examples/blob/main/solana/new-wallet-airdrop/wallet.ts)）\r\n- 你 Devnet 钱包中的一个或多个 Devnet SPL 代币或 Solana NFTs：\r\n  - 查看我们的指南：[如何使用 Metaplex Token Standard 创建可替代的 SPL 代币](https://www.quicknode.com/guides/solana-development/how-to-create-a-fungible-spl-token-with-the-new-metaplex-token-standard) 和 [如何使用 TypeScript 在 Solana 上铸造 NFT](https://www.quicknode.com/guides/solana-development/how-to-mint-an-nft-on-solana-using-typescript) 以获取更多信息\r\n  - 或从 [SPL token Faucet](https://spl-token-faucet.com/?token-name=USDC-Dev) 销毁一些\r\n\r\n### 设置你的项目 \r\n\r\n在终端中创建一个新项目目录，输入以下命令：\r\n\r\n```\r\nmkdir burn-spl\r\ncd burn-spl\r\n\r\n```\r\n\r\n为你的应用创建一个文件，命名为 **app.ts**：\r\n\r\n```\r\necho > app.ts\r\n\r\n```\r\n\r\n初始化你的项目，使用 “yes” 标志来使用默认值创建新包：\r\n\r\n```\r\nyarn init --yes\r\n#or\r\nnpm init --yes\r\n\r\n```\r\n\r\n创建一个启用 .json 导入的 **tsconfig.json**：\r\n\r\n```\r\ntsc -init --resolveJsonModule true\r\n\r\n```\r\n\r\n将你的纸钱包保存为 **guideSecret.json**，其中包含 devnet SOL 和一个或多个 SPL 代币（格式应该是 8 位整数的数组，例如 \\[27,218,103, ...\\]）。如果你的钱包中还没有 devnet SOL，你可以通过以下表单请求：\r\n\r\n🪂请求 Devnet SOL\r\n\r\n空投 1 SOL（Devnet）\r\n\r\n#### 安装 Solana Web3 依赖 \r\n\r\n我们需要添加 Solana Web3 和 SPL Token 库。在终端中输入：\r\n\r\n```\r\nyarn add @solana/web3.js@1 @solana/spl-token\r\n#or\r\nnpm install @solana/web3.js@1 @solana/spl-token\r\n\r\n```\r\n\r\n我们需要从这些库和我们的密钥获取一些组件。在 **app.ts** 的第 1 行，添加以下导入：\r\n\r\n```\r\nimport { Connection, PublicKey, Keypair, TransactionMessage, VersionedTransaction, SignatureStatus, TransactionConfirmationStatus, TransactionSignature } from \"@solana/web3.js\";\r\nimport { createBurnCheckedInstruction, TOKEN_PROGRAM_ID, getAssociatedTokenAddress } from \"@solana/spl-token\";\r\nimport secret from './guideSecret.json';\r\n\r\nconst WALLET = Keypair.fromSecretKey(new Uint8Array(secret));\r\n\r\n```\r\n\r\n \r\n#### 设置你的 QuickNode 端点 \r\n\r\n要在 Solana 上构建，你需要一个 API 端点来连接到网络。你可以使用公共节点或部署和管理自己的基础设施；但是，如果你希望响应时间快 8 倍，可以将繁重的工作交给我们。\r\n\r\n \r\n\r\n![New Node](https://img.learnblockchain.cn/2025/02/24/4-9b8c136f77deeffa7ef3d7e66edc9bb8.png)\r\n\r\n在 **app.ts** 的导入语句下，声明你的 RPC 并建立与 Solana 的 _Connection_ ：\r\n\r\n```\r\nconst QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';\r\nconst SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);\r\n\r\n```\r\n\r\n你的环境应该如下所示。\r\n\r\n![Ready to Build](https://img.learnblockchain.cn/2025/02/24/0-66167a964534909d3bda532d1e8007e4.png)\r\n\r\n准备好了吗？让我们开始构建吧！\r\n\r\n### 设置一个销毁应用 \r\n\r\n#### 声明铸币地址 \r\n\r\n要销毁一个 SPL 代币，你必须在正确的集群（在本例中为 Devnet）中拥有一些代币。我们需要获取代币的铸造地址。要找到这一点，如果你还没有，请访问 [Solana Explorer](https://explorer.solana.com/address/YOUR_WALLET_ADDRESS/tokens?cluster=devnet&display=detail)。你应该能够看到你所有代币持有的列表。\r\n\r\n![User Token List](https://img.learnblockchain.cn/2025/02/24/1-eee9fb822930659914e58ac847a02216.png)\r\n\r\n复制你希望使用的代币的铸币地址。点击铸币地址，并记下小数位数。这对于进行任何与 SPL Token 程序的交互非常重要。如果你的 devnet 钱包中没有任何 SPL 代币（如果你已经有一个，可以跳过此步骤），你可以在 [SPL Token Faucet](https://spl-token-faucet.com/?token-name=USDC-Dev) 为这个演示铸造一个。你需要连接你的钱包，所以你可能需要将纸钱包导入 Phantom 来实现这一点：\r\n\r\n- 在 Phantom 中，点击左上角的圆圈以进入“设置”\r\n- 点击你的钱包以查看所有钱包的列表，然后点击“添加/连接钱包”\r\n![Add Wallet](https://img.learnblockchain.cn/2025/02/24/2-fa349f665ddd0d54b31f720769c2ee68.png)\r\n- 为你的新钱包命名并粘贴密钥（_guideSecret.json_ 的内容）\r\n![New Wallet](https://img.learnblockchain.cn/2025/02/24/3-116ff40fd86c79200d44bad63357ddd3.png)\r\n\r\n按照站点说明铸造 1,000 USDC-DEV。该代币的铸币地址是 _Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr_（它有 6 位小数）。\r\n\r\n一旦你拥有了计划要销毁的代币及其铸币地址，打开 **app.ts** 并添加以下行：\r\n\r\n```\r\nconst MINT_ADDRESS = 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'; // 从 spl-token-faucet.com 获取的 USDC-Dev | 替换为你想要销毁的铸币\r\nconst MINT_DECIMALS = 6; // 从 spl-token-faucet.com 获取的 USDC-Dev 的值 | 替换为你想要销毁的铸币的小数位数\r\nconst BURN_QUANTITY = 1; // 要销毁的代币数量（随意替换为任何数字 - 只需确保你有足够的代币）\r\n\r\n```\r\n\r\n#### 定义助手函数 \r\n\r\n我们需要一个事务来确认该事务已经成功通过区块链处理。在，你的 _Connection_ 定义下，添加以下代码：\r\n\r\n```\r\nasync function confirmTransaction(\r\n    connection: Connection,\r\n    signature: TransactionSignature,\r\n    desiredConfirmationStatus: TransactionConfirmationStatus = 'confirmed',\r\n    timeout: number = 30000,\r\n    pollInterval: number = 1000,\r\n    searchTransactionHistory: boolean = false\r\n): Promise<SignatureStatus> {\r\n    const start = Date.now();\r\n\r\n    while (Date.now() - start < timeout) {\r\n        const { value: statuses } = await connection.getSignatureStatuses([signature], { searchTransactionHistory });\r\n\r\n        if (!statuses || statuses.length === 0) {\r\n            throw new Error('未能获取签名状态');\r\n        }\r\n\r\n        const status = statuses[0];\r\n\r\n        if (status === null) {\r\n            await new Promise(resolve => setTimeout(resolve, pollInterval));\r\n            continue;\r\n        }\r\n\r\n        if (status.err) {\r\n            throw new Error(`事务失败: ${JSON.stringify(status.err)}`);\r\n        }\r\n\r\n        if (status.confirmationStatus && status.confirmationStatus === desiredConfirmationStatus) {\r\n            return status;\r\n        }\r\n\r\n        if (status.confirmationStatus === 'finalized') {\r\n            return status;\r\n        }\r\n\r\n        await new Promise(resolve => setTimeout(resolve, pollInterval));\r\n    }\r\n\r\n    throw new Error(`事务确认超时，超出了 ${timeout}ms`);\r\n}\r\n\r\n```\r\n\r\n此函数将查询 Solana 网络以获取事务状态，直到它被确认或超时。我们包含了一些超时和查询间隔的默认值，但你可以根据需要进行调整。\r\n\r\n#### 创建一个异步代码块 \r\n\r\n在 **app.ts** 中添加以下 _async_ 代码块。这将构建我们的应用程序并创建一些步骤，我们将一起完成：\r\n\r\n```\r\n(async () => {\r\n    console.log(`尝试从所有者钱包销毁 ${BURN_QUANTITY} [${MINT_ADDRESS}] 代币: ${WALLET.publicKey.toString()}`);\r\n    // 第一步 - 获取关联代币账户地址\r\n\r\n    // 第二步 - 创建销毁指令\r\n\r\n    // 第三步 - 获取最新块哈希\r\n\r\n    // 第四步 - 组装事务\r\n\r\n    // 第五步 - 执行并确认事务\r\n\r\n})()\r\n\r\n```\r\n\r\n### 第一步 - 获取关联代币地址 \r\n\r\n在代码块的第一步中，我们需要找到保存我们要销毁代币的账户地址。提醒一下，用户的钱包并不直接保留 SPL 代币。用户的钱包拥有一个单独的 SPL 代币账户以存储这些代币。我们必须找到该地址（即关联代币地址 ATA）。SPL 代币账户是根据用户钱包的公钥和代币铸造地址生成的程序派生地址。我们可以将这两者传入 **getAssociatedTokenAddress** 来找到我们的地址：\r\n\r\n```\r\n    // 第一步 - 获取关联代币账户地址\r\n    console.log(`步骤 1 - 获取代币账户`);\r\n    const account = await getAssociatedTokenAddress(new PublicKey(MINT_ADDRESS), WALLET.publicKey);\r\n    console.log(`    ✅ - 关联代币账户地址: ${account.toString()}`);\r\n\r\n```\r\n\r\n> 注意：你也可以使用 [Solana Explorer](https://explorer.solana.com/address/YOUR_WALLET_ADDRESS/tokens?cluster=devnet&display=detail) 找到账户的 ATA。如果你查看代币账户的详细信息，你将看到“铸币地址”和“账户地址”。账户地址是 ATA。你可以点击以获取更多信息，或者直接复制/粘贴到你的 **account** 变量中，而不是使用 **getAssociatedTokenAddress**。我们的代币地址是以 _F9Se..._ 开头的：\r\n> ![Explorer Token Account](https://img.learnblockchain.cn/2025/02/24/1-eee9fb822930659914e58ac847a02216.png)\r\n\r\n你可以通过在终端中运行 **ts-node app** 来测试你的代码。你应该能看到有关你的 ATA 的日志。\r\n\r\n### 第二步 - 创建销毁指令 \r\n\r\nSolana SPL 代币程序提供了一个便利的方法，**createBurnCheckedInstruction**，该方法会根据我们收集的输入生成 _TransactionInstruction_。在应用程序的第二步中，添加以下代码：\r\n\r\n```\r\n    // 第二步 - 创建销毁指令\r\n    console.log(`步骤 2 - 创建销毁指令`);\r\n    const burnIx = createBurnCheckedInstruction(\r\n      account, // 公共密钥 - 所有者的关联代币账户\r\n      new PublicKey(MINT_ADDRESS), // 代币铸造地址的公共密钥\r\n      WALLET.publicKey, // 所有者钱包的公共密钥\r\n      BURN_QUANTITY * (10**MINT_DECIMALS), // 要销毁的代币数量\r\n      MINT_DECIMALS // 代币铸造的小数位数\r\n    );\r\n    console.log(`    ✅ - 销毁指令创建成功`);\r\n\r\n```\r\n\r\n这里最大的技巧是确保你的类型和参数顺序正确。在这个指令中的地址是 _公共密钥_，所以我们需要将我们的 _铸币地址_ 和 _钱包_ 转换为公共密钥。你可以将鼠标悬停在 **createBurnCheckedInstruction** 上查看参数输入，或按 ctrl/cmd + 点击以获取关于此方法的更多信息。\r\n\r\nTypeScript 应该会检查你是否输入了所有正确类型，但你需要确保你的参数按正确顺序输入。\r\n\r\n> 重要提示，注意我们将 **BURN_QUANTITY** 乘以 10 的小数位数。这对于与 SPL 代币程序的交互是必要的，因为它们用于表征代币价值的精度级别。如果没有小数值，这些操作可能无法产生准确结果，并可能导致代币价值中的错误。\r\n\r\n### 第三步 - 获取最新块哈希 \r\n\r\n为了确保你的交易有效传播到网络中，你需要最新的块哈希。使用 **getLatestBlockhash** 方法从集群获取最新的块哈希信息。这是确认事务成功（而不是超时）所必要的。在应用程序的第三步中，添加以下代码：\r\n\r\n```\r\n    // 第三步 - 获取块哈希\r\n    console.log(`步骤 3 - 获取块哈希`);\r\n    const { blockhash, lastValidBlockHeight } = await SOLANA_CONNECTION.getLatestBlockhash('finalized');\r\n    console.log(`    ✅ - 最新块哈希: ${blockhash}`);\r\n\r\n```\r\n\r\n你可以运行你的代码以确保一切正常。在终端中输入 **ts-node app**。你应该会看到最新的块哈希！\r\n\r\n### 第四步 - 组装事务 \r\n\r\n我们将使用版本化交易来销毁我们的代币（尽管遗留方法也同样有效）。如果版本化交易对你来说是新的，请查看我们的 [如何使用版本化交易的指南](https://www.quicknode.com/guides/solana-development/how-to-use-versioned-transactions-on-solana)。在 **app.ts** 的第四步中添加：\r\n\r\n```\r\n    // 第四步 - 组装事务\r\n    console.log(`步骤 4 - 组装事务`);\r\n    const messageV0 = new TransactionMessage({\r\n      payerKey: WALLET.publicKey,\r\n      recentBlockhash: blockhash,\r\n      instructions: [burnIx]\r\n    }).compileToV0Message();\r\n    const transaction = new VersionedTransaction(messageV0);\r\n    transaction.sign([WALLET]);\r\n    console.log(`    ✅ - 事务创建并签名成功`);\r\n\r\n```\r\n\r\n使用我们的 **burnIx** 参数和 **latestBlockhash**，我们可以通过构造新的 _Message_ 并执行 _.compileToV0Message()_ 方法来创建一个新的 _MessageV0_。然后，我们将消息传递给 _VersionedTransaction_ 的新实例。最后，我们通过将 **WALLET** 传递给 **transaction.sign** 进行签名，以形成一个 _数组_（这允许多个签名者在更复杂的事务中使用）。\r\n\r\n如果你在代码中遇到任何错误，TypeScript 应该会提醒你，但如果你遇到问题，可以加入我们的 [Discord](https://discord.gg/quicknode)，我们会很乐意提供帮助。\r\n\r\n### 第五步 - 执行并确认交易 \r\n\r\n好的，一切都准备好了。我们只需将交易发送到集群并确认它已经成功添加到链上。我们可以使用 **sendTransaction** 方法和之前定义的 **confirmTransaction** 函数来实现。在 **app.ts** 的第五步，添加：\r\n\r\n```\r\n    // 第五步 - 执行与确认事务\r\n    console.log(`步骤 5 - 执行与确认事务`);\r\n    const txid = await SOLANA_CONNECTION.sendTransaction(transaction);\r\n    console.log(\"    ✅ - 事务发送到网络\");\r\n    const confirmation = await confirmTransaction(SOLANA_CONNECTION, txid);\r\n    if (confirmation.err) { throw new Error(\"    ❌ - 事务未确认.\") }\r\n    console.log('🔥 成功销毁!🔥', '\\n', `https://explorer.solana.com/tx/${txid}?cluster=devnet`);\r\n\r\n```\r\n\r\n以下是发生了什么的简要总结：\r\n\r\n- **sendTransaction** 将我们的事务发送到网络，并将返回一个事务 ID 的 promise，**txid**。\r\n- 我们将 **txid** 传递给 **confirmTransaction** 以检查我们的事务是否已被确认。\r\n- 如果我们的确认返回错误，则抛出一个错误。\r\n\r\n### 运行你的代码 \r\n\r\n你应该准备得差不多了--让我们来点火吧！在终端中输入：\r\n\r\n```\r\nts-node app.ts\r\n\r\n```\r\n\r\n![Burn](https://img.learnblockchain.cn/2025/02/24/0-13fb2017b039b867fe592e04b7b375eb.gif)\r\n\r\n你应该能在终端中看到事务进展：\r\n\r\n![Burn Confirmation](https://img.learnblockchain.cn/2025/02/24/6-e168bf55c20def844d5817f214e8fbfa.png)\r\n\r\n你应该能够打开你的 Solana Explorer 链接，并向下滚动到指令中以验证燃烧操作成功！\r\n\r\n![Burn Confirmation 2](https://img.learnblockchain.cn/2025/02/24/7-8149eb55914ff707aec4bf9791f3a610.png)\r\n\r\n干得不错！\r\n\r\n### 总结 \r\n\r\n让它燃烧。你现在拥有在 Solana 上构建销毁工具的工具！这适用于任何 SPL 代币，甚至是 NFTs（NFT 的小数位数 = 0）。以下是一些扩展此示例的起始想法：\r\n\r\n- 更新此脚本以包含代币的前后余额（提示：[如何获取由钱包持有的所有代币](https://learnblockchain.cn/article/11158)）\r\n- 创建一个简单的销毁页面，允许用户选择他们账户中的代币并销毁它（提示：[如何使用 Solana 钱包适配器和脚手架将用户连接到你的 dApp](https://www.quicknode.com/guides/solana-development/how-to-connect-users-to-your-dapp-with-the-solana-wallet-adapter-and-scaffold)）\r\n\r\n我们喜欢构建，想听听你的想法。请在 [Discord](https://discord.gg/quicknode) 或 [Twitter](https://twitter.com/QuickNode) 上给我们留言，告诉我们你正在做什么！\r\n\r\n \r\n>- 原文链接： [quicknode.com/guides/sol...](https://www.quicknode.com/guides/solana-development/spl-tokens/how-to-burn-spl-tokens-on-solana)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/25306","address":null},"history":null,"timestamp":1740450631,"version":1}