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

如何通过web3swift与Infura API获取ETH交易实时状态

问题背景

通过web3swift框架对接Infura端点发起ETH交易时,调用getTransactionReceipt接口需要等交易发起数秒后、完成链上打包才能拿到返回结果,无法获取交易从广播到上链的全流程实时状态。
发起交易生成哈希的原有实现代码:

guard
        let fromAddress = walletAddress,
        let walletAddress = EthereumAddress(fromAddress),
        let toaddress = EthereumAddress(toAddress),
        let amountDouble = Web3.Utils.parseToBigUInt(eth, units: .eth),
        let gasPrice = Web3.Utils.parseToBigUInt(String(format: "%.10f", gasPrice), units: .eth)
    else { throw LocalError.walletError }
    var options = TransactionOptions.defaultOptions
    options.gasLimit = .manual(BigUInt(gasLimit))
    options.from = walletAddress
    options.value = amountDouble
    options.gasPrice = .manual(gasPrice)
    options.to = toaddress
    let param: [ AnyObject ] = [toaddress, amountDouble] as [ AnyObject ]

    guard
        let intermediateSend = self.web3Instance?.contract(Web3.Utils.coldWalletABI, at: toaddress, abiVersion: 2),
        let transaction = intermediateSend.write(parameters: param, extraData: Data(), transactionOptions: options),
        let walletPassword = mainAccount.walletPassword
    else { throw LocalError.walletError }
    DispatchQueue.main.async {
        NotificationCenter.default.post(name: Notification.transactionInitiated, object: nil)
    }
    let sendResult = try transaction.send(password: walletPassword)
    Log.s(sendResult)

原有获取交易回执的实现代码:

let receipt = try self.web3Instance.eth.getTransactionReceipt(sendResult.hash)

解决方案

首先明确ETH交易不存在绝对的"零延迟实时状态":交易从发起到最终确认需要经过「节点接收进入内存池→矿工打包进区块→区块累积确认数」三个阶段,以太坊主网平均出块间隔为12秒左右,可通过全链路状态跟踪把感知延迟降到最低。

原有代码的致命问题

你当前解析gasPrice时使用了.eth单位,会把gasPrice数值放大10^9倍,直接导致手续费超额扣除,必须修正为.gwei单位:

// 错误写法
// let gasPrice = Web3.Utils.parseToBigUInt(String(format: "%.10f", gasPrice), units: .eth)
// 正确写法
let gasPrice = Web3.Utils.parseToBigUInt(String(format: "%.10f", gasPrice), units: .gwei)

可落地的两种状态跟踪方案

方案1:短间隔轮询(实现成本最低,兼容现有HTTP端点)

直接调用getTransactionReceipt拿不到结果,是因为交易未被打包进区块之前,回执接口会返回空值。正确的轮询逻辑需要覆盖交易全生命周期:

  • 交易广播成功拿到哈希后,先调用getTransactionDetails判断交易是否被Infura节点接收进入内存池
  • 以2秒为间隔轮询,依次判断「交易是否进入内存池→是否被打包进区块→回执状态是否成功→确认数是否达标」
  • 拿到最终状态(成功/失败/确认数达标)后立即终止轮询,避免内存和请求资源浪费
    可直接复用的实现代码:
func trackTransaction(txHash: EthereumData, requiredConfirmations: Int = 2) {
    var timer: Timer?
    timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] t in
        guard let self = self else {
            t.invalidate()
            return
        }
        do {
            // 查询交易是否被节点接收
            guard let tx = try self.web3Instance.eth.getTransactionDetails(txHash) else {
                // 交易还未同步到节点,继续等待
                return
            }
            // 无区块号说明交易还在内存池待打包
            guard let blockNum = tx.blockNumber else {
                // 可更新UI状态:交易已广播,等待区块打包
                return
            }
            // 交易已打包,查询执行回执
            guard let receipt = try self.web3Instance.eth.getTransactionReceipt(txHash) else {
                // 区块刚生成,回执还未同步,继续等待
                return
            }
            // 判断交易执行结果
            guard receipt.status == .ok else {
                t.invalidate()
                timer = nil
                // 可更新UI状态:交易执行失败(常见原因:gas不足、合约执行报错)
                return
            }
            // 计算区块确认数
            let currentBlock = try self.web3Instance.eth.blockNumber()
            let confirmations = currentBlock - blockNum
            if confirmations >= requiredConfirmations {
                t.invalidate()
                timer = nil
                // 可更新UI状态:交易已最终确认
            } else {
                // 可更新UI状态:交易已打包,当前确认数\(confirmations)
            }
        } catch {
            // 可添加重试计数,超过阈值后终止轮询并提示网络错误
        }
    }
    timer?.fire()
}

注意:轮询间隔不要小于1秒,否则容易触发Infura的免费额度限流。普通ETH转账设置1-2个确认数即可,涉及合约资产的交易建议设置6个确认数。

方案2:WebSocket订阅(延迟更低,无需主动轮询)

如果对状态实时性要求更高,可以把Infura连接端点从HTTP换成WebSocket,通过Infura的事件订阅能力接收节点主动推送的状态变化,不需要定时发请求轮询。
实现步骤:

  • 把web3实例的初始化地址换成Infura的WebSocket端点(格式为wss://<网络名>.infura.io/ws/v3/<你的项目ID>)
  • 订阅两个核心事件:待打包交易事件、新块头事件
  • 收到推送后匹配你发起的交易哈希,更新对应状态
    核心实现代码参考:
// 初始化WebSocket连接的web3实例
guard let wsUrl = URL(string: "wss://mainnet.infura.io/ws/v3/替换为你的Infura项目ID") else { return }
let web3Ws = try Web3.new(wsUrl)

// 订阅待打包交易,匹配交易哈希判断是否进入内存池
_ = try web3Ws.subscribeToPendingTransactions { [weak self] result in
    guard let self = self else { return }
    switch result {
    case .success(let pendingTxHash):
        if pendingTxHash == yourSendResult.hash {
            // 更新状态:交易已进入节点内存池,等待打包
        }
    case .failure(let err):
        print("待打包交易订阅错误: \(err)")
        // 此处添加断连重连逻辑
    }
}

// 订阅新块事件,检查交易是否被打包
_ = try web3Ws.subscribeToNewHeads { [weak self] result in
    guard let self = self else { return }
    switch result {
    case .success(let newBlock):
        // 新块产出,查询目标交易是否在当前块内,后续回执、确认数逻辑和轮询方案一致
        break
    case .failure(let err):
        print("新块订阅错误: \(err)")
        // 此处添加断连重连逻辑
    }
}

注意:WebSocket连接需要添加心跳保活、断连自动重连逻辑,稳定性比轮询方案稍差,仅适合对实时性要求高的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 19:48:29