以太坊卡牌游戏多维度余额查询性能优化方案咨询
解决以太坊卡牌游戏初始余额批量查询的方案
嘿,你的这个问题在NFT/卡牌类DApp里挺常见的,完全不用等到Solidity更新就能解决,给你几个实用的方案,你可以根据自己的需求选:
1. 在合约中维护用户的持有卡牌列表
最直接的优化是给每个用户维护一个已持有卡牌的ID和数量列表,代替每次遍历所有卡牌ID查询。具体做法:
- 在合约里新增一个映射:
mapping(address => Card[]) public ownedCards;,同时定义结构体:struct Card { uint256 id; uint256 amount; } - 再加一个辅助映射用于快速定位:
mapping(address => mapping(uint256 => uint256)) public cardIndex;(值为数组索引,0表示未持有该卡牌) - 当用户卡牌余额变化时:
- 若余额从0变为正数:把卡牌ID和数量加入
ownedCards数组,更新cardIndex记录索引 - 若余额变为0:找到该卡牌在数组中的位置,将数组最后一个元素移到这个位置,再
pop数组末尾元素,同步更新cardIndex - 若只是余额增减:直接找到数组中对应元素更新数量
- 若余额从0变为正数:把卡牌ID和数量加入
- 新增批量查询方法:
function getOwnedCards(address _owner) external view returns(Card[] memory) { return ownedCards[_owner]; }
这样DApp启动时只需要调用一次这个方法,就能拿到用户所有持有卡牌的信息,速度直接拉满。缺点是合约逻辑会复杂一点,每次交易的gas会略有增加,但对于卡牌游戏的交互频率来说,这个成本完全可控。
2. 利用事件日志构建客户端本地缓存
如果不想大幅修改合约,可以通过事件日志来同步客户端状态:
- 在合约中新增余额更新事件:
event BalanceUpdated(address indexed owner, uint256 indexed cardId, uint256 newBalance); - 每次修改
balances映射时,都触发这个事件 - DApp首次启动时,直接从区块链节点查询该用户所有的
BalanceUpdated历史事件(用ethers.js/web3.js的事件查询API,支持批量获取),然后在客户端构建本地余额缓存 - 之后只需要监听新的
BalanceUpdated事件,实时更新本地缓存即可
这个方案的优点是合约改动极小,不需要额外的服务器,完全由客户端处理。唯一需要注意的是,首次加载要处理好历史事件的去重(比如同一个卡牌多次更新的情况),确保缓存状态和链上一致。
3. 使用Multicall批量调用现有balanceOf
如果完全不想修改原有合约,最快的优化是用Multicall合约把几百次balanceOf调用打包成一次请求:
- 可以直接使用已部署的开源Multicall合约,或者自己写一个极简版:
contract Multicall { function aggregate(address[] calldata targets, bytes[] calldata data) external view returns(bytes[] memory results) { results = new bytes[](targets.length); for(uint256 i = 0; i < targets.length; i++) { (bool success, bytes memory res) = targets[i].staticcall(data[i]); require(success, "Call failed"); results[i] = res; } } } - 客户端把所有需要查询的
balanceOf调用参数(目标合约地址、调用数据)打包成数组,调用Multicall的aggregate方法,一次请求就能拿到所有余额结果
这个方案的好处是零合约改动,只是把几百次网络请求合并成一次,速度会提升非常多,适合快速迭代优化。
总结一下:如果想长期优化,推荐方案1;如果想最小改动,选方案2或3,都能解决你现在启动时调用几百次的问题,不用等Solidity更新~
内容的提问来源于stack exchange,提问作者Chi
相关产品推荐
相关产品推荐

