如何使用Web3.js在Solana链上获取代币元数据(已获取代币账户信息)
获取Solana代币元数据的方法
好问题!既然你已经通过getParsedTokenAccountsByOwner拿到了非零余额的代币账户,接下来要获取对应代币的元数据,核心是利用每个代币账户里的mint地址——Solana上的代币元数据(比如名称、符号、图标链接等)基本都和代币的mint账户绑定,通常存储在Metaplex的元数据程序中。
下面给你两种常用的实现方式,你可以根据项目需求选择:
方法一:使用Metaplex JS SDK(推荐,简化开发)
Metaplex提供了现成的工具处理元数据,不用自己手动解析复杂的账户结构。
首先确保安装依赖:
npm install @metaplex-foundation/js @solana/web3.js
然后你可以修改现有代码,在拿到nonZeroAccounts后遍历获取元数据:
import { Connection, PublicKey, TOKEN_PROGRAM_ID } from '@solana/web3.js'; import { Metaplex } from '@metaplex-foundation/js'; // 假设你已经初始化好connection和用户publicKey const connection = new Connection('https://api.mainnet-beta.solana.com'); const metaplex = Metaplex.make(connection); connection.getParsedTokenAccountsByOwner( new PublicKey(publicKey.toBase58()), { programId: TOKEN_PROGRAM_ID } ) .then(async (b) => { const nonZeroAccounts = b?.value?.filter( (obj) => obj.account.data.parsed.info.tokenAmount.uiAmount > 0 ); // 遍历每个代币账户,获取对应的元数据 const tokensWithMetadata = await Promise.all( nonZeroAccounts.map(async (tokenAccount) => { const mintAddress = new PublicKey(tokenAccount.account.data.parsed.info.mint); // 通过mint地址获取元数据 const metadata = await metaplex.nfts().findByMint({ mintAddress }); return { tokenAccountInfo: tokenAccount, tokenMetadata: { name: metadata.name, symbol: metadata.symbol, iconUrl: metadata.json?.image, description: metadata.json?.description, uri: metadata.uri } }; }) ); setTokens(JSON.stringify(tokensWithMetadata, null, 2)); }) .catch(err => console.error('获取元数据出错:', err));
这个方法的优势是SDK帮你处理了元数据账户地址推导和数据解析,直接就能拿到结构化的代币信息,省了很多手动编码的麻烦。
方法二:手动获取元数据(不依赖SDK,更底层)
如果你不想引入额外依赖,可以手动计算元数据账户地址,再调用getAccountInfo解析数据。
元数据账户地址是通过Metaplex的PDA规则生成的:用元数据程序ID(metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s)和mint地址作为种子推导。
代码示例如下:
import { Connection, PublicKey, TOKEN_PROGRAM_ID } from '@solana/web3.js'; const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'); connection.getParsedTokenAccountsByOwner( new PublicKey(publicKey.toBase58()), { programId: TOKEN_PROGRAM_ID } ) .then(async (b) => { const nonZeroAccounts = b?.value?.filter( (obj) => obj.account.data.parsed.info.tokenAmount.uiAmount > 0 ); const tokensWithMetadata = await Promise.all( nonZeroAccounts.map(async (tokenAccount) => { const mintAddress = new PublicKey(tokenAccount.account.data.parsed.info.mint); // 推导元数据账户的PDA地址 const [metadataAddress] = await PublicKey.findProgramAddress( [Buffer.from('metadata'), METADATA_PROGRAM_ID.toBuffer(), mintAddress.toBuffer()], METADATA_PROGRAM_ID ); // 获取元数据账户信息 const metadataAccount = await connection.getAccountInfo(metadataAddress); if (!metadataAccount) { return { tokenAccountInfo: tokenAccount, tokenMetadata: null }; } // 手动解析元数据核心字段(完整结构可参考Metaplex文档) const data = metadataAccount.data; const nameLength = data.readUInt32LE(64); const name = data.slice(68, 68 + nameLength).toString('utf8'); const symbolLength = data.readUInt32LE(68 + nameLength); const symbol = data.slice(72 + nameLength, 72 + nameLength + symbolLength).toString('utf8'); const uriLength = data.readUInt32LE(72 + nameLength + symbolLength); const uri = data.slice(76 + nameLength + symbolLength, 76 + nameLength + symbolLength + uriLength).toString('utf8'); // 如果需要获取uri里的详细内容(比如图片、描述),需要发起HTTP请求 let detailedMetadata = null; if (uri) { try { // 处理IPFS链接,添加网关前缀 const accessibleUri = uri.startsWith('ipfs://') ? uri.replace('ipfs://', 'https://ipfs.io/ipfs/') : uri; detailedMetadata = await fetch(accessibleUri).then(res => res.json()); } catch (err) { console.error(`解析uri内容失败: ${err}`); } } return { tokenAccountInfo: tokenAccount, tokenMetadata: { name, symbol, uri, ...detailedMetadata } }; }) ); setTokens(JSON.stringify(tokensWithMetadata, null, 2)); }) .catch(err => console.error('获取元数据出错:', err));
这种方法更灵活,但需要自己处理数据解析,适合不需要完整SDK功能的轻量场景。
注意事项
- 少数代币可能不遵循Metaplex元数据标准,这种情况下会获取不到信息,但绝大多数主流代币都适配这个标准。
- 如果元数据的
uri是IPFS链接,需要替换成可访问的网关地址才能获取内容。
内容的提问来源于stack exchange,提问作者matt
相关产品推荐
相关产品推荐

