TRC-721 合约交互

使用 TronWeb 读取和写入已部署的 TRC-721 合约——查询名称、符号、余额、转账 NFT 以及管理授权。

📘

前置阅读

TRC-721 合约部署后,可用 TronWeb 对其读写。本文通过一组可在 Shasta 测试网直接跑通的 JavaScript 示例,演示常见操作。

共用配置

本页所有示例使用相同的 TronWeb 配置。定义一次,复用即可:

const { TronWeb } = require('tronweb');

const tronWeb = new TronWeb({
  fullHost: 'https://api.shasta.trongrid.io',
  // headers: { 'TRON-PRO-API-KEY': 'YOUR_API_KEY' },   // 仅主网(api.trongrid.io)需要;Shasta 和 Nile 测试网不需要 API key
  privateKey: process.env.PRIVATE_KEY
});

// 替换为你已部署的 TRC-721 合约地址。
const trc721ContractAddress = 'TRio4Fwn...TLwWMNs'; // TRON 地址示例,Base58,共 34 位
🚧

切勿提交真实私钥

将私钥存储在环境变量(process.env.PRIVATE_KEY)或密钥管理器中,参见安全最佳实践

📘

feeLimit 单位

下方 .send() 调用中使用的 feeLimit 参数单位为 sun(1 TRX = 1,000,000 sun)。示例中使用 100_000_000,即 100 TRX。

1. 查询 Token 名称

调用 name() 获取名称。这是一个 view 函数——不消耗能量。

async function trc721Name() {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    // call() 用于运行 view/pure 方法,无需广播交易。
    const name = await contract.name().call();
    console.log('name:', name);
  } catch (error) {
    console.error('TRC-721 query error', error);
  }
}

trc721Name();

输出示例:

name: TRC721TEST

2. 查询 Token 符号

调用 symbol() 获取简称。

async function trc721Symbol() {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const symbol = await contract.symbol().call();
    console.log('symbol:', symbol);
  } catch (error) {
    console.error('TRC-721 query error', error);
  }
}

trc721Symbol();

输出示例:

symbol: TEST

3. 查询地址持有的 NFT 数量

调用 balanceOf(address) 获取某账户在该系列中持有的 NFT 总数。

async function trc721BalanceOf() {
  const owner = 'TA1g2WQi...jT3ug9uK'; // TRON 地址示例,Base58,共 34 位

  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const result = await contract.balanceOf(owner).call();
    console.log('balance:', tronWeb.toDecimal(result));
  } catch (error) {
    console.error('TRC-721 query error', error);
  }
}

trc721BalanceOf();

输出示例:

balance: 1

4. 转账 NFT

调用 transferFrom(from, to, tokenId) 转账特定 NFT。

async function trc721TransferFrom() {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    // send() 执行改变状态的方法并广播交易。
    const txHash = await contract
      .transferFrom(
        'TA1g2WQi...jT3ug9uK', // from
        'TM8vRheb...rHjBgC',   // to
        666                     // tokenId
      )
      .send({ feeLimit: 100_000_000 });
    console.log('transferFrom tx hash:', txHash);
  } catch (error) {
    console.error('TRC-721 transaction error', error);
  }
}

trc721TransferFrom();

输出示例:

transferFrom tx hash: 9f4d10713cb0406adb7c729013b941d35597afeeba56faf2a1bc7647fc0a92bd

Shasta TronScan 上验证该交易。

🚧

transferFromsafeTransferFrom

transferFrom 不检查接收方是否是合约,也不检查该合约是否能接受 NFT。如果将 NFT 发送到未实现 TRC721TokenReceiver 的合约,该 Token 将永久丢失。对于未知接收方,请使用 safeTransferFrom

安全转账替代方案

对于未知接收方,请使用 safeTransferFrom——它会在目标合约上调用接收者钩子,若合约未实现 TRC721TokenReceiver 则 revert:

async function trc721SafeTransferFrom() {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const txHash = await contract
      .safeTransferFrom(
        'TA1g2WQi...jT3ug9uK', // from
        'TM8vRheb...rHjBgC',   // to(可能是合约)
        666                     // tokenId
      )
      .send({ feeLimit: 100_000_000 });
    console.log('safeTransferFrom tx hash:', txHash);
  } catch (error) {
    console.error('TRC-721 transaction error', error);
  }
}

5. 授权对特定 NFT 的操作权

调用 approve(spender, tokenId) 授权另一个地址代表持有人转账特定 NFT。每个 Token 同一时间只能授权一个地址;再次调用 approve 会替换之前的授权。

async function trc721Approve() {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const txHash = await contract
      .approve(
        'TA1g2WQi...jT3ug9uK', // spender
        666                     // tokenId
      )
      .send({ feeLimit: 100_000_000 });
    console.log('approve tx hash:', txHash);
  } catch (error) {
    console.error('TRC-721 transaction error', error);
  }
}

trc721Approve();

输出示例:

approve tx hash: d7cb1451ed962667f3e24323655dadd8c650ee80d171ea5e44ea97b97eaa3118

为交易市场授权所有 NFT

对于交易市场集成,使用 setApprovalForAll(operator, true) 授权操作员转账当前持有和将来持有的所有 NFT:

async function trc721SetApprovalForAll() {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const txHash = await contract
      .setApprovalForAll(
        'TA1g2WQi...jT3ug9uK', // operator(例如交易市场合约)
        true                    // 授权全部
      )
      .send({ feeLimit: 100_000_000 });
    console.log('setApprovalForAll tx hash:', txHash);
  } catch (error) {
    console.error('TRC-721 transaction error', error);
  }
}

6. 查询授权状态

使用 getApproved(tokenId) 查询当前的单 Token 授权,或使用 isApprovedForAll(owner, operator) 查询操作员级别的授权。

async function trc721GetApproved() {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const approved = await contract.getApproved(666).call();
    console.log('approved address for tokenId 666:', approved);
  } catch (error) {
    console.error('TRC-721 query error', error);
  }
}

async function trc721IsApprovedForAll() {
  const owner = 'TM8vRheb...rHjBgC';
  const operator = 'TA1g2WQi...jT3ug9uK';

  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const approved = await contract.isApprovedForAll(owner, operator).call();
    console.log('isApprovedForAll:', approved);
  } catch (error) {
    console.error('TRC-721 query error', error);
  }
}

7. 列出地址持有的所有 NFT

若合约实现了 TRC721Enumerable 扩展,可分三步枚举某地址的全部 NFT:查余额、遍历每个索引、获取每个 tokenId 的 tokenURI。

步骤 7.1 — 获取余额

async function ownerBalance() {
  const owner = 'TM8vRheb...rHjBgC';
  const contract = await tronWeb.contract().at(trc721ContractAddress);
  const result = await contract.balanceOf(owner).call();
  console.log('count:', tronWeb.toDecimal(result));
}
ownerBalance();

假设结果为 2——该所有者持有两个 NFT。

步骤 7.2 — 遍历每个索引

对从 0balance - 1 的每个索引调用 tokenOfOwnerByIndex(owner, index)

async function tokenOfOwnerByIndex(index) {
  const owner = 'TM8vRheb...rHjBgC';

  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const tokenId = await contract.tokenOfOwnerByIndex(owner, index).call();
    console.log('token_id:', tronWeb.toDecimal(tokenId));
  } catch (error) {
    console.error('TRC-721 query error', error);
  }
}

tokenOfOwnerByIndex(0);
tokenOfOwnerByIndex(1);

输出示例:

token_id: 666
token_id: 555

步骤 7.3 — 解析每个 Token 的元数据 URI

调用 tokenURI(tokenId) 获取指向链下元数据 JSON 的指针:

async function trc721TokenURI(tokenId) {
  try {
    const contract = await tronWeb.contract().at(trc721ContractAddress);
    const uri = await contract.tokenURI(tokenId).call();
    console.log(`${tokenId} tokenURI:`, uri);
  } catch (error) {
    console.error('TRC-721 query error', error);
  }
}

trc721TokenURI(666);
trc721TokenURI(555);

不同的 tokenId 对应不同的 tokenURI 值;系列中每个 NFT 都有自己的元数据文件。离线获取每个 URI(通过 HTTPS 或 BTFS 网关)即可获得描述该 NFT 的 JSON,包括名称、描述、图片 URL 以及其他属性。


相关资源