{"content":{"title":"如何在Solana中获取钱包持有的所有代币","body":"## 概述\r\n\r\n你好，读者们！为了开启 Solana 夏季和当前的白名单元，我们认为深入了解你和你的用户使用 **getParsedProgramAccounts** 方法的所有Token账户将会很有帮助。该工具方便用于查询 Solana 上的不同程序，包括 Solana SPL Token Account 库。\r\n\r\n> 更喜欢视频讲解？跟随 Noah 学习 [如何通过钱包地址获取 Solana Token账户 | QuickCodes](https://www.youtube.com/watch?v=h3Qad6k0xFc)\r\n\r\n \r\n本指南将指导你创建一个简单的脚本，该脚本将查询 Solana 的主网，并返回该钱包拥有的所有Token账户及其账户余额。\r\n \r\n\r\n## 你需要的工具\r\n\r\n- [Nodejs](https://nodejs.org/en/)（版本 16.15 或更高）\r\n\r\n- [Solana Web3](https://www.npmjs.com/package/@solana/web3.js)\r\n\r\n- [Solana SPL Token Library](https://www.npmjs.com/package/@solana/spl-token)\r\n\r\n- 具备 Typescript 经验，并安装 [ts-node](https://www.npmjs.com/package/ts-node)\r\n\r\n- 对一些 Solana 原则有基本理解：\r\n\r\n> **程序**：Solana 上的智能合约。在本示例中，我们将使用 [SPL Token Program](https://spl.solana.com/)，它定义了 Solana 上可替代和不可替代代币的典型用例。\r\n>\r\n> **程序过滤器**：许多链上程序查询都会拉取大量数据集，因此，缩小搜索范围非常重要。我们将使用 _GetProgramAccountsFilter_ 类型来帮助我们缩小搜索范围。\r\n\r\n## 设置你的环境\r\n\r\n在终端中创建一个新的项目目录和文件，**index.ts**，命令如下：\r\n\r\n```\r\nmkdir sol-get-accounts\r\ncd sol-get-accounts\r\necho > index.ts\r\n```\r\n\r\n初始化你的项目：\r\n\r\n```\r\nyarn init --yes\r\n```\r\n\r\n或\r\n\r\n```\r\nnpm init --yes\r\n```\r\n\r\n安装 Solana Web3 依赖：\r\n\r\n```\r\nyarn add @solana/web3.js@1 @solana/spl-token\r\n```\r\n\r\n或\r\n\r\n```\r\nnpm install @solana/web3.js@1 @solana/spl-token\r\n```\r\n\r\n在代码编辑器中打开 **index.ts** 并添加以下依赖：\r\n\r\n```\r\nimport { Connection, GetProgramAccountsFilter } from \"@solana/web3.js\";\r\nimport { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\r\n```\r\n\r\n\r\n\r\n**建立与 Solana 主网的连接**\r\n\r\n为了在 Solana 上构建，你需要一个 API 端点来连接网络。你可以使用公共节点或部署和管理自己的基础设施，不过如果你希望获得 8 倍的响应速度，你可以把繁重的工作交给我们。\r\n \r\n\r\n![获取 Solana 主网端点](https://img.learnblockchain.cn/2025/02/24/0-9a383a21010f3bea7fc8aec95f5cd5e4.png)\r\n\r\n```\r\nconst rpcEndpoint = 'https://example.solana-mainnet.quiknode.pro/000000/';\r\nconst solanaConnection = new Connection(rpcEndpoint);\r\n```\r\n\r\n将你想要查询的钱包定义为字符串：\r\n\r\n```\r\nconst walletToQuery = 'YOUR_PUBLIC_KEY'; //示例: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg\r\n```\r\n\r\n太棒了！我们准备构建我们的代币查询。\r\n\r\n## 创建你的代币账户查询\r\n\r\n创建一个新的 **async** 函数，_getTokenAccounts_，并要求接受参数 _wallet_ 和 _solanaConnection_：\r\n\r\n```\r\nasync function getTokenAccounts(wallet: string, solanaConnection: Connection) {\r\n}\r\n```\r\n\r\n#### **建立过滤器**\r\n\r\n首先，让我们定义我们的过滤器。我们将使用的过滤器为：\r\n\r\n- _dataSize_ 是用于查找特定大小账户的过滤器。对于代币账户，这个已知的数量是 165。\r\n\r\n- _memcmp_，或“内存比较”过滤器，用于在账户内缩小我们的搜索范围。具体而言，我们将使用 _offset_ 来确定搜索我们账户的 165 字节中的哪个位置（这是该程序的另一个已知值：拥有者的公钥从 32 开始）和 _bytes_ 来设置我们要查找的内容：在本例中是用户的钱包地址。\r\n\r\n- 关于 SPL 代币账户数据的来源信息可以在 [这里](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L124) 找到。\r\n\r\n在 _getTokenAccounts_ 内部，定义一个名为 _filters_ 的变量。\r\n\r\n```\r\n    const filters:GetProgramAccountsFilter[] = [\\\r\n        {\\\r\n          dataSize: 165,    //账户的大小（字节）\\\r\n        },\\\r\n        {\\\r\n          memcmp: {\\\r\n            offset: 32,     //我们在账户中的查询位置（字节）\\\r\n            bytes: wallet,  //我们的搜索标准，一个 base58 编码的字符串\\\r\n          }\\\r\n        }\\\r\n     ];\r\n```\r\n\r\n这应该将我们的查询减少到只搜索由我们的钱包拥有的代币账户。\r\n\r\n#### **获取程序账户**\r\n\r\n现在调用 **getParsedProgramAccounts** 方法，传入 _SPL Token Program ID_ 和我们的 _filters_。\r\n\r\n```\r\n    const accounts = await solanaConnection.getParsedProgramAccounts(\r\n        TOKEN_PROGRAM_ID,   //SPL Token 程序，new PublicKey(\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\")\r\n        {filters: filters}\r\n    );\r\n```\r\n\r\n之前的调用返回一个包含所有匹配的代币账户的数组，结构如下：\r\n\r\n```\r\n{\r\n    pubkey: PublicKey,      //代币账户公钥\r\n    account: AccountInfo    //包括有关我们的代币账户的信息的对象\r\n}[]\r\n```\r\n\r\n你可以记录 _accounts.length_ 来查看用户拥有多少个代币账户：\r\n\r\n```\r\nconsole.log(`Found ${accounts.length} token account(s) for wallet ${wallet}.`);\r\n```\r\n\r\n#### **解析结果**\r\n\r\n构建一个简单的 _forEach_ 循环来迭代我们的结果并记录我们的结果。在你的 _getTokenAccounts_ 函数中添加：\r\n\r\n```\r\n    accounts.forEach((account, i) => {\r\n        //解析账户数据\r\n        const parsedAccountInfo:any = account.account.data;\r\n        const mintAddress:string = parsedAccountInfo[\"parsed\"][\"info\"][\"mint\"];\r\n        const tokenBalance: number = parsedAccountInfo[\"parsed\"][\"info\"][\"tokenAmount\"][\"uiAmount\"];\r\n        //记录结果\r\n        console.log(`Token Account No. ${i + 1}: ${account.pubkey.toString()}`);\r\n        console.log(`--Token Mint: ${mintAddress}`);\r\n        console.log(`--Token Balance: ${tokenBalance}`);\r\n    });\r\n```\r\n\r\n## 运行你的代码\r\n\r\n最后，调用 **getTokenAccounts(walletToQuery,solanaConnection)**。你的最终脚本应如下所示：\r\n\r\n```\r\nimport { Connection, GetProgramAccountsFilter } from \"@solana/web3.js\";\r\nimport { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\r\n\r\nconst rpcEndpoint = 'https://example.solana-mainnet.quiknode.pro/000000/';\r\nconst solanaConnection = new Connection(rpcEndpoint);\r\n\r\nconst walletToQuery = 'YOUR_PUBLIC_KEY'; //示例: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg\r\n\r\nasync function getTokenAccounts(wallet: string, solanaConnection: Connection) {\r\n    const filters:GetProgramAccountsFilter[] = [\\\r\n        {\\\r\n          dataSize: 165,    //账户的大小（字节）\\\r\n        },\\\r\n        {\\\r\n          memcmp: {\\\r\n            offset: 32,     //我们在账户中的查询位置（字节）\\\r\n            bytes: wallet,  //我们的搜索标准，一个 base58 编码的字符串\\\r\n          },\\\r\n        }];\r\n    const accounts = await solanaConnection.getParsedProgramAccounts(\r\n        TOKEN_PROGRAM_ID, //new PublicKey(\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\")\r\n        {filters: filters}\r\n    );\r\n    console.log(`Found ${accounts.length} token account(s) for wallet ${wallet}.`);\r\n    accounts.forEach((account, i) => {\r\n        //解析账户数据\r\n        const parsedAccountInfo:any = account.account.data;\r\n        const mintAddress:string = parsedAccountInfo[\"parsed\"][\"info\"][\"mint\"];\r\n        const tokenBalance: number = parsedAccountInfo[\"parsed\"][\"info\"][\"tokenAmount\"][\"uiAmount\"];\r\n        //记录结果\r\n        console.log(`Token Account No. ${i + 1}: ${account.pubkey.toString()}`);\r\n        console.log(`--Token Mint: ${mintAddress}`);\r\n        console.log(`--Token Balance: ${tokenBalance}`);\r\n    });\r\n}\r\ngetTokenAccounts(walletToQuery,solanaConnection);\r\n```\r\n\r\n运行 **ts-node index.ts**，你应该会在终端上看到如下日志：\r\n\r\n![示例输出：终端中的代币日志](https://img.learnblockchain.cn/2025/02/24/1-c5cef96cbb48414acc492b3ebcefea93.png)\r\n\r\n## 奖励：添加更多过滤器\r\n\r\n玩得开心吗？想尝试更多过滤器吗？比如，你想查看一个钱包是否包含特定铸造的代币。你可以在上面的 forEach 循环中包含该过滤器，但你可能考虑将其添加到原始查询中的过滤器变量中，以减少搜索时间。如下所示。\r\n\r\n在你应用程序顶部的定义中，添加一个铸造地址进行搜索，例如：\r\n\r\n```\r\n    const MINT_TO_SEARCH = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; //USDC 铸造地址\r\n```\r\n\r\n现在，回到我们的 _getTokenAccounts_ 函数中，向 _filters_ 添加一个额外的过滤器，寻找 _MINT_TO_SEARCH_ 在第 0 字节位置（已知该程序中铸币公钥的位置）：\r\n\r\n```\r\n    const filters:GetProgramAccountsFilter[] = [\\\r\n        {\\\r\n          dataSize: 165,    //账户的大小（字节）\\\r\n        },\\\r\n        {\\\r\n          memcmp: {\\\r\n            offset: 32,     //我们在账户中的查询位置（字节）\\\r\n            bytes: wallet,  //我们的搜索标准，一个 base58 编码的字符串\\\r\n          },\\\r\n        },\\\r\n        //添加此搜索参数\\\r\n        {\\\r\n            memcmp: {\\\r\n            offset: 0, //字节数\\\r\n            bytes: MINT_TO_SEARCH, //base58 编码字符串\\\r\n            },\\\r\n        }];\r\n```\r\n\r\n重新运行你的代码，你应该会看到结果现在限制为你搜索的铸造：\r\n\r\n![示例输出：单一铸造搜索结果](https://img.learnblockchain.cn/2025/02/24/2-6ea7782cd9da778b0837e660df112777.png)\r\n\r\n## 结论\r\n\r\n完成了！你现在应该了解如何查询一个钱包的所有Token账户，并具备坚实的基础，使你能够在未来查询其他 Solana 程序。\r\n\r\n觉得对你有用吗？请查看我们其他的一些 Solana 教程 [这里](https://www.quicknode.com/guides/)。注册我们的 [通讯](https://go.quicknode.com/newsletter) 获取更多关于 Solana 的文章和指南。如果你有任何反馈，随时通过 [Twitter](https://twitter.com/QuickNode) 与我们联系。你也可以在我们的 [Discord](https://discord.gg/quicknode) 社区服务器上与我们聊天，遇见一些你见过的最酷的开发者 :)\r\n\r\n#### 我们 ❤️ 反馈！\r\n\r\n[让我们知道](https://airtable.com/shrKKKP7O1Uw3ZcUB?prefill_Guide+Name=How%20to%20Get%20All%20Tokens%20Held%20by%20a%20Wallet%20in%20Solana) 如果你有任何反馈或对新主题的请求。我们乐意倾听你的声音。\r\n\r\n>- 原文链接： [quicknode.com/guides/sol...](https://www.quicknode.com/guides/solana-development/spl-tokens/how-to-get-all-tokens-held-by-a-wallet-in-solana)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/25306","address":null},"history":null,"timestamp":1740450733,"version":1}