本文详细介绍了如何使用QuickNode的Streams、Functions和Key-Value Store构建一个ERC721合约的Token持有者索引器和钱包API。步骤清晰地指导用户设置流、过滤转账数据、及实现API读取Token元数据,适合希望监控和检索区块链上Token持有情况的开发者。
数据流对所有拥有 QuickNode 计划的用户可用。对于有独特需求的团队,我们提供定制数据集、专属支持和自定义集成。 联系我们 以获取更多信息。
Token Holder Indexer 和 Wallet API 提供了一种全面的解决方案,用于跟踪与特定 ERC721 合约相关的代币所有权。本指南将指导你设置一个索引系统,该系统监控代币转移,并通过 QuickNode 的 Streams、Functions、Key-Value Store 和 RPC 按钱包地址返回持有的代币和代币元数据。
Functions 是一个服务器无关的计算平台,用于部署轻量级、可扩展的代码,针对 web3 进行了优化,并内置对常见 web3 包(如 ethers.js、web3.js 和 QuickNode SDK)的访问。
首先,导航至 Dashboard 上的 QuickNode Streams 页面,然后单击“创建 Stream”。
接下来,使用以下设置创建一个 Stream:
选择在流式传输前修改负载的选项。这允许你过滤流式数据并与 Key-Value Store 进行交互。
接下来,复制并粘贴以下代码,以过滤(PIRATE)代币的 ERC721 合约的转移事件。该过滤器监听我们正在监控的代币合约上的转移事件,然后创建一个负载将代币持有量更新到 Key-Value Store 中。该过滤器将在 Key-Value Store 中创建以钱包地址为标识的 lists
,并在将转移发送到钱包时以 contractAddress-tokenId
格式向列表中添加 items
,并从发送者的持有地址列表中删除该项。
const test = false; // 如果你正在测试流,请将此值更改为 true
function stripPadding(logTopic) {
return '0x' + logTopic.slice(-40).toLowerCase();
}
function main(stream) {
var results = {
fromActions: [],
toActions: []
};
var actionsTaken = false;
try {
var stream = stream.data[0];
var erc721TransferEvent = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
var contractAddress = '0x1B41d54B3F8de13d58102c50D7431Fd6Aa1a2c48'.toLowerCase(); // 代币合约
stream.forEach(receipt => {
receipt.logs.forEach(log => {
if (log.topics[0] === erc721TransferEvent && log.address.toLowerCase() === contractAddress) {
var from = stripPadding(log.topics[1]);
var to = stripPadding(log.topics[2]);
var tokenId = BigInt(log.topics[3]).toString();
var item = `${contractAddress}-${tokenId}`;
if (from !== '0x0000000000000000000000000000000000000000') {
results.fromActions.push({
wallet: from,
removed: [item],
action: test ? "模拟删除操作" : qnUpsertList(from, {
remove_items: [item]
})
});
actionsTaken = true;
}
if (to !== '0x0000000000000000000000000000000000000000') {
results.toActions.push({
wallet: to,
added: [item],
action: test ? "模拟添加操作" : qnUpsertList(to, {
add_items: [item]
})
});
actionsTaken = true;
}
}
});
});
if (!actionsTaken) {
return null;
} else {
return {
timestamp: new Date().toISOString(),
actions: results
};
}
} catch (e) {
return { error: e.message };
}
}
Key-Value Store
从此 Stream 中创建的 Key-Value Store 列表可通过 Streams、Functions 和 Key-Value Store REST API 全球访问。当我们在此步骤中测试 Stream 时,请确保将 const test = true;
设置为避免实际编辑 Key-Value Store 中的列表。完成测试后,请确保将 const test = false;
设回。
你可以通过在过滤器中设置 const test = true;
并在测试块字段中使用区块编号 20290986
来测试你的 Stream。然后单击运行测试按钮以测试你的过滤器。测试完成后,记得在单击下一步之前更新过滤器代码 const test = false;
。
单击“下一步”按钮,然后选择 "webhook" 作为你的 Stream 目的地。输入你的 webhook URL,以接收操作总结。你可以在 webhook.site 生成一个免费的 webhook URL。注意:保持任何未提及的设置不变。
使用以下代码 实现函数,该函数通过 RPC 检索代币元数据。确保将 your-quicknode-rpc-endpoint
替换为实际的端点 URL。
const { Web3 } = require('web3');
const axios = require('axios');
const https = require('https');
const QUICKNODE_URL = 'your-quicknode-rpc-endpoint';
const web3 = new Web3(QUICKNODE_URL);
const httpsAgent = new https.Agent({
rejectUnauthorized: false, // 警告:这会绕过 SSL 证书验证。请谨慎使用。
secureProtocol: 'TLSv1_2_method'
});
/**
*
* main() 将在你调用此操作时运行。
*
* @param params 传递给函数的参数。
*
* @return 此操作的输出,必须是 JSON 对象。
*
*/
async function main(params) {
let wallet = params.user_data.wallet;
let tokens = await qnLib.qnGetList(wallet); // 假设这返回一个代币字符串数组
if (!tokens || tokens.length === 0) {
return {
message: '未找到代币'
};
} else {
const tokenMetadata = await getTokenMetadata(tokens);
return {
message: wallet,
tokens: tokenMetadata
};
}
}
async function getTokenMetadata(tokens) {
const tokenMetadata = [];
for (const token of tokens) {
const [contractAddress, tokenId] = token.split('-');
const metadata = await fetchTokenMetadata(contractAddress, tokenId);
tokenMetadata.push({ contractAddress, tokenId, metadata });
}
return tokenMetadata;
}
async function fetchTokenMetadata(contractAddress, tokenId) {
const contractABI = [\
// 获取代币元数据的函数的 ABI 片段\
{\
"constant": true,\
"inputs": [\
{\
"name": "tokenId",\
"type": "uint256"\
}\
],\
"name": "tokenURI",\
"outputs": [\
{\
"name": "",\
"type": "string"\
}\
],\
"payable": false,\
"stateMutability": "view",\
"type": "function"\
}\
];
const contract = new web3.eth.Contract(contractABI, contractAddress);
try {
const tokenURI = await contract.methods.tokenURI(tokenId).call();
// 使用 axios 从 tokenURI 获取元数据
const response = await axios.get(tokenURI, { httpsAgent });
return response.data;
} catch (error) {
console.error(`获取代币 ${tokenId} 在 ${contractAddress} 的元数据时出错:`, error);
return null;
}
}
module.exports = { main };
接下来,在 Functions 代码编辑器的右列中找到“用户数据”框,在该文本框中添加一个测试钱包,然后单击 Functions 代码编辑器中的“保存并测试”按钮。
{"Wallet":"0xde27d2e6b5009ead76ebc07452b54364fb54fdcd"}
一旦你的 Stream 已赶上当前区块,你可以检查一个钱包中持有的(PIRATE)代币。你将通过发送 wallet
地址到 user_data
中调用你的函数 API。如果钱包包含来自指定合约的代币,并且基本 URL 可访问,API 将返回钱包的持有代币和元数据。
curl -X POST "https://api.quicknode.com/functions/rest/v1/namespaces/{your-namespace-id}/functions/{your-function-name}/call?result_only=true" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "x-api-key: QN_your-api-key" \
-d '{
"user_data": {
"Wallet":"0xde27d2e6b5009ead76ebc07452b54364fb54fdcd"
}
}'
{
"wallet": "0xde27d2e6b5009ead76ebc07452b54364fb54fdcd",
"tokens": [\
{\
"contractAddress": "0x1b41d54b3f8de13d58102c50d7431fd6aa1a2c48",\
"metadata": {\
"animation_url": "ipfs://QmQYSdcxKKkWcSb2jMiii6nY5x67gsifRxZihMuSH3EEem/1005",\
"attributes": [\
{"trait_type": "背景", "value": "弯刀"},\
{"trait_type": "角色类型", "value": "人类男性"},\
{"trait_type": "外套", "value": "紫色"},\
{"trait_type": "掷骰子结果 1", "value": 5},\
{"trait_type": "掷骰子结果 2", "value": 4},\
{"trait_type": "眼睛", "value": "深邃"},\
{"trait_type": "面部毛发", "value": "复古现代"},\
{"trait_type": "头饰", "value": "刀具"},\
{"trait_type": "肤色", "value": "浅棕"},\
{"trait_type": "星座", "value": "狮子"},\
{"trait_type": "世代", "value": 0},\
{"trait_type": "经验值", "value": 28100},\
{"trait_type": "等级", "value": 30},\
{"trait_type": "已领取的宝箱", "value": 5},\
{"trait_type": "元素亲和力", "value": "土"},\
{"trait_type": "专长", "value": "健康"},\
{"trait_type": "等级", "value": 30},\
{"trait_type": "经验值", "value": 28100}\
],\
"description": "与你的海盗团队一起出海!探索世界,收集经验值、战利品和无尽的财富,争当世界上最伟大的海盗船长!请访问 https://piratenation.game 游戏",\
"external_url": "https://piratenation.game/pirate/0x1E52c21b9DfCd947d03E9546448f513F1EE8706c/1005",\
"image": "ipfs://QmRjgEp89ovHdr1M8rkjoC6iEgbNna5kBDjbfubm4zeVDd/1005",\
"image_png_1024": "ipfs://QmRjgEp89ovHdr1M8rkjoC6iEgbNna5kBDjbfubm4zeVDd/1005",\
"image_png_128": "ipfs://QmdqWrDHCH2VoXpwkGskBjooVj99Sqw5zqTBKvwEAYuF3J/1005",\
"image_png_2048": "ipfs://QmRjgEp89ovHdr1M8rkjoC6iEgbNna5kBDjbfubm4zeVDd/1005",\
"image_png_256": "ipfs://Qmdfts291hNpr8SmqkN6sQgKLzpfabHQxXme7oBekHWDo3/1005",\
"image_png_32": "ipfs://Qma2rcLonwgUR7dmz6zUVRHeTTfT8FwnNsrC7bmKnQrScU/1005",\
"image_png_512": "ipfs://QmUZ4JXK5VhoAMt3UTMyGML9jE4TaTx5EakMHdU4o5V5Cz/1005",\
"image_png_64": "ipfs://QmfHwFYQY5rV3pQ2J5UbNyeWstx384UWV16K1PvCoDmV7q/1005",\
"image_svg": "ipfs://QmdnsH8mf1rN8C8w9Km3Et84VEqfTgDb2yJP5CWtKVwFxw/1005",\
"model_gltf_url": "ipfs://QmeBen9iF2RQQP7h5haQjWKKxBUmmVMVzc3wGmveENuEZ5/1005",\
"name": "创始人海盗 #1005"\
},\
"tokenId": "1005"\
}\
]
}
在本指南中,我们详细介绍了针对特定 ERC721 合约的代币所有权跟踪解决方案的设置。通过利用 QuickNode Streams、Functions 和 Key-Value Store,你可以有效地监控和检索按钱包地址提供的代币持有量和元数据,为各种基于区块链的应用程序提供一个强大的工具。
让我们知道 如果你有任何反馈或新的主题请求。我们期待你的声音。
- 原文链接: quicknode.com/guides/qui...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!