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

无交易收据时如何确认Avalanche子网ERC20转账状态?

问题

我是Alice,需要通过部署在Avalanche子网的ERC20智能合约将N枚代币转给客户Bob,使用Rust结合ethers-rs调用合约transfer函数的代码如下:

let tx = self.contract.transfer(addr_to, amount).tx;
let _res: TypedTransaction = tx;
if let Some(receipt) = self
    .contract
    .transfer(addr_to, amount)
    .send()
    .await
    .map_err(|e| EthersTransferError(e.to_string()))?
    .await
    .map_err(|e| EthersTransferError(e.to_string()))?
{
    return Ok(receipt);
}

交易约5秒完成并返回包含交易哈希、状态的收据,但如果程序在这5秒内崩溃(刚好在收到收据前终止),我仅持有from地址、to地址、转账金额和合约地址,无法确认交易是否成功,且需要确保代币仅转账一次。

我曾尝试通过抓取区块事件查询交易,代码如下:

async fn find_tx(&self, to: Address, amount: U256, from_block: BlockNumber) -> Result<()> {
    let filter = Filter::new()
        .address(self.contract_addr)
        .event("Transfer(address,address,uint256)")
        .topic2(H256::from(to))
        .from_block(from_block);

    let mut interval = time::interval(time::Duration::from_secs(600));
    loop {
        interval.tick().await;
        let logs = self
            .client
            .get_logs(&filter)
            .await
            .map_err(|e| AvalancheGetLogsError(e.to_string()))?;

        for log in logs.iter() {
            if self.client.address() == Address::from(log.topics[1])
                && to == Address::from(log.topics[2])
                && amount == U256::from(log.data.as_bytes_ref())
            {
                return Ok(());
            }
        }
    }
}

但该方案存在两个问题:一是部分链(如Fuji测试网)限制过滤的最大区块数(2048块),交易可能超出该范围;二是若交易失败,代码会陷入无限循环,需等待多天才能确认交易未发生。

请问在仅拥有有限信息的情况下,最优的程序化解决方案是什么?是否需要依赖子网托管服务的API、索引器或浏览器?是否要扩展智能合约功能?Geth是否具备相关能力?

最优解决方案

1. 优化事件查询逻辑,解决区块范围限制与无限循环问题

针对现有事件查询的缺陷,做两点关键改进:

  • 分块查询日志:将大区块范围拆分为多个2048块的小批次,循环遍历每个批次查询,避免触发链的区块数限制。比如从from_block开始,每次查询[current_block, current_block+2047],直到当前区块达到链上最新高度。
  • 设置超时终止条件:定义合理的超时窗口(比如Avalanche子网出块时间约2秒,可设置30分钟超时),若超过该时间仍未找到目标Transfer事件,则判定交易未发生,终止循环。

示例优化代码片段:

async fn find_tx(&self, to: Address, amount: U256, from_block: u64) -> Result<bool> {
    let mut latest_block = self.client.get_block_number().await?;
    let batch_size = 2048u64;
    let timeout = time::Duration::from_secs(1800); // 30分钟超时
    let start_time = time::Instant::now();

    let mut current_block = from_block;
    while current_block <= latest_block && start_time.elapsed() < timeout {
        let end_block = std::cmp::min(current_block + batch_size - 1, latest_block);
        let filter = Filter::new()
            .address(self.contract_addr)
            .event("Transfer(address,address,uint256)")
            .topic1(H256::from(self.client.address())) // 明确from地址作为topic1
            .topic2(H256::from(to))
            .from_block(BlockNumber::Number(current_block.into()))
            .to_block(BlockNumber::Number(end_block.into()));

        let logs = self.client.get_logs(&filter).await?;
        for log in logs {
            let parsed_amount = U256::from(log.data.as_bytes_ref());
            if parsed_amount == amount {
                return Ok(true); // 找到匹配交易,返回成功
            }
        }

        // 更新当前区块,同时轮询最新区块高度
        current_block = end_block + 1;
        latest_block = self.client.get_block_number().await?;
        time::sleep(time::Duration::from_secs(2)).await; // 等待新区块生成
    }

    // 超时或遍历完所有区块仍未找到,返回未发生
    Ok(false)
}

2. 利用账户交易历史查询补充验证

除了事件日志,还可以查询from地址的交易历史,筛选与目标合约交互的交易,再验证交易详情:

  • 调用get_transaction_count获取from地址的交易总数,然后遍历每个交易哈希(通过get_transaction_by_index),筛选出目标合约地址的交易。
  • 对筛选出的交易,解析输入数据(ERC20的transfer函数签名是0xa9059cbb),验证接收地址和转账金额是否匹配,再通过get_transaction_receipt确认交易是否成功。

这种方式可以直接定位到具体交易,避免事件日志可能的遗漏(比如交易成功但未触发Transfer事件的极端情况)。

3. 扩展智能合约添加幂等性控制(推荐长期方案)

如果需要从根源上避免重复转账问题,可以给ERC20合约添加幂等转账接口:

  • 新增一个transferWithId函数,要求传入唯一的transferId(比如UUID哈希),合约先检查该ID是否已被使用,未使用则执行转账并标记ID为已用。
  • 这样即使程序崩溃,后续可以通过查询合约中transferId的状态,直接判断该笔转账是否已执行,无需依赖链上交易日志。

示例合约片段(Solidity):

contract IdempotentERC20 is ERC20 {
    mapping(bytes32 => bool) public executedTransfers;

    function transferWithId(address to, uint256 amount, bytes32 transferId) external returns (bool) {
        require(!executedTransfers[transferId], "Transfer already executed");
        executedTransfers[transferId] = true;
        return transfer(to, amount);
    }
}

关于依赖与工具的说明

  • 子网托管服务/索引器:使用Avalanche子网的托管服务(比如AvalancheGo节点的API)即可通过原生RPC接口实现查询,无需额外依赖第三方索引器;若追求更高查询效率,可使用预索引Transfer事件的索引器服务,避免分块遍历的开销。
  • Geth能力:Geth支持所有标准以太坊RPC接口,包括getLogs、getTransactionByIndex等,完全可以用来实现上述查询逻辑,无需额外特殊功能。
  • 浏览器API:不建议依赖区块链浏览器的公开API,其速率限制和稳定性无法保障程序化查询需求,优先使用节点原生RPC或合规索引器服务。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 12:03:28