如何通过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
相关产品推荐
相关产品推荐

