You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solidity中是否存在可查看代币持有者地址及对应代币ID的函数?

Solidity中查看代币持有者及对应代币ID的实现方式

Solidity 标准代币合约(比如OpenZeppelin的ERC721/ERC1155)本身没有内置函数直接返回所有持有者地址列表及对应代币ID,因为区块链合约不会默认存储全局持有者集合,但可以通过以下两种方式实现需求:

一、合约内自定义存储(链上方式)

通过在合约中额外维护数据结构,主动跟踪所有持有者及他们的代币ID,适合需要直接从合约读取数据的场景。

核心逻辑:

  • 用数组holders存储所有唯一持有者地址
  • 用映射isHolder标记地址是否已在持有者列表中,避免重复添加
  • 用映射holderTokens存储每个地址对应的代币ID列表
  • 在mint、transfer等修改持仓的关键函数中,同步更新这些数据结构

示例代码(ERC721合约):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract CustomNFT is ERC721 {
    // 存储所有持有者地址
    address[] public holders;
    // 标记地址是否已在持有者列表中
    mapping(address => bool) public isHolder;
    // 存储每个地址对应的代币ID列表
    mapping(address => uint256[]) public holderTokens;

    constructor() ERC721("CustomNFT", "CNFT") {}

    // 铸造代币时更新持有者数据
    function mint(address to, uint256 tokenId) public {
        _mint(to, tokenId);
        
        if (!isHolder[to]) {
            isHolder[to] = true;
            holders.push(to);
        }
        holderTokens[to].push(tokenId);
    }

    // 重写转账逻辑,同步更新持有者数据
    function _transfer(address from, address to, uint256 tokenId) internal override {
        super._transfer(from, to, tokenId);

        // 处理转出地址:移除对应代币ID,若持仓为空则从持有者列表删除
        _removeTokenFromHolder(from, tokenId);
        if (balanceOf(from) == 0) {
            isHolder[from] = false;
            _removeAddressFromHolders(from);
        }

        // 处理转入地址:若不在列表则添加,再加入代币ID
        if (!isHolder[to]) {
            isHolder[to] = true;
            holders.push(to);
        }
        holderTokens[to].push(tokenId);
    }

    // 辅助函数:从地址的代币列表中移除指定ID
    function _removeTokenFromHolder(address holder, uint256 tokenId) private {
        uint256[] storage tokens = holderTokens[holder];
        for (uint256 i = 0; i < tokens.length; i++) {
            if (tokens[i] == tokenId) {
                tokens[i] = tokens[tokens.length - 1];
                tokens.pop();
                break;
            }
        }
    }

    // 辅助函数:从持有者列表中移除指定地址
    function _removeAddressFromHolders(address holder) private {
        for (uint256 i = 0; i < holders.length; i++) {
            if (holders[i] == holder) {
                holders[i] = holders[holders.length - 1];
                holders.pop();
                break;
            }
        }
    }

    // 获取所有持有者地址
    function getAllHolders() public view returns (address[] memory) {
        return holders;
    }

    // 获取指定地址的所有代币ID
    function getTokensByHolder(address holder) public view returns (uint256[] memory) {
        return holderTokens[holder];
    }
}

调用getAllHolders()可获取所有持有者地址,再逐个调用getTokensByHolder(address)就能得到对应地址的代币ID列表,输出格式可匹配你给出的示例。

二、链下事件监听(链下方式)

如果不想修改合约,可以通过监听代币合约的Transfer事件,在链下收集所有持有者及对应代币ID,适合前端或第三方数据服务使用。

核心逻辑:

  • 监听ERC721合约的Transfer(address from, address to, uint256 tokenId)事件,或ERC1155合约的TransferSingle(address operator, address from, address to, uint256 id, uint256 value)事件
  • 维护链下数据库,根据事件记录更新持仓:
    • 当from为零地址时,是铸造代币,给to地址添加对应代币ID
    • 当to为零地址时,是销毁代币,从from地址移除对应代币ID
    • 其他情况是转账,从from移除代币ID,给to添加代币ID

这种方式不消耗链上存储资源,数据准确性依赖对事件的完整监听。


内容的提问来源于stack exchange,提问作者JAWKAR FF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 22:45:38