Web3 与进阶 App:交易管理与 Mempool 监听

·

在钱包应用中,让用户清晰了解实时交易状态并掌握控制权至关重要。这不仅能增强用户的确定性感知,还能显著提升整体体验。因此,交易管理成为不可或缺的功能。本文将介绍钱包应用如何管理已发出的交易,包括取消与加速操作,以及如何通过监听 Mempool 数据实时追踪链上滞留交易。

Gas 费用过低的常见问题

多数钱包应用允许用户自定义交易 Gas 费用,当执行速度非首要考虑时,用户可设置较低费用以节约成本。然而,过低的 Gas 费用会导致交易滞留(Pending),例如在 Etherscan 的合约交易列表中会显示为等待状态。

Gas Price 越低,交易滞留时间越长——只有当区块链网络的 Gas Price 降至该交易设定值时,交易才可能上链。极端情况下,一笔低 Gas 交易可能卡滞数天。

可视化工具 TxStreet 可实时展示比特币和以太坊网络的区块状态与交易打包过程,并标识交易来源 DApp。图中左侧以太坊网络数据显示,当前Pending交易数量超过 75,000 笔。

交易滞留常困扰以太坊新用户:若首笔交易未确认,后续交易将同样卡滞,导致用户误认为所有交易均发送失败。如何优化此类场景下的用户体验?

滞留交易管理策略

既然用户可能主动选择低 Gas 费用,解决方案之一是明确告知预计上链时间并动态更新。Etherscan 提供 gasestimate API,可预估给定 Gas Price 的交易确认所需时间。

例如,当前建议 Gas Price 为 24 Gwei时:

该结果符合预期:若设定 Gas Price 接近当前值,难保后续区块费用波动;而 20 Gwei 的预估时长源于无法预测费用下降时间,此时 Etherscan 显示“>1 小时”。钱包应用可沿用相同逻辑实现时间预估。

此外,当用户存在滞留交易并发起新交易时,需避免新旧交易使用相同 Nonce 导致覆盖。回顾 Token 转账交易实现代码:

final transferTx = Transaction.callContract(
  contract: contract,
  function: transferFunction,
  parameters: [EthereumAddress.fromHex(toAddress), amount],
  maxFeePerGas: await web3Client.getGasPrice(),
  maxPriorityFeePerGas: await getMaxPriorityFee(),
);
final tx = await signTransaction(
  privateKey: privateKey,
  transaction: transferTx,
);

代码未显式指定 Nonce,由 web3dart 库自动处理。追踪其内部实现可见 _fillMissingData() 函数会补全未设置的 Nonce 值:

Future<_SigningInput> _fillMissingData({
  required Credentials credentials,
  required Transaction transaction,
  int? chainId,
  bool loadChainIdFromNetwork = false,
  Web3Client? client,
}) async {
  // ...
  final nonce = transaction.nonce ??
      await client!
          .getTransactionCount(sender, atBlock: const BlockNum.pending());
  // ...
}

该代码调用 RPC 节点的 eth_getTransactionCount 方法,并传入 atBlock = pending 参数。此参数指定查询数据的时间点,也可用于查询历史区块的交易计数。

pending 参数的含义是将未确认交易纳入地址交易计数——因为 RPC 节点可在其 Mempool 中追踪地址的滞留交易。至此我们理解 web3dart 的细粒度行为:当用户连续发送多笔交易且前序交易滞留时,库能正确计算下一笔交易的 Nonce,避免覆盖旧交易。

交易取消与加速实操

若用户不希望发送新交易,而是希望加速确认或取消前序交易,可利用以太坊“相同 Nonce 仅一笔交易上链”的特性覆盖原交易。

取消交易

常见方法是向自己发送 0 ETH 转账交易,且 Gas 费用至少比原交易高 10%(否则触发 Replacement Transaction Underpriced 错误)。选择转账交易因其 Gas 消耗量最低(21,000),能最大限度节省成本。基于 Day 13 代码的实例如下:

class TransactionWithHash {
  final String hash;
  final Transaction transaction;
  TransactionWithHash({
    required this.hash,
    required this.transaction,
  });
}

Future sendCancelTransaction({
  required EthPrivateKey privateKey,
  required int nonce,
  required EtherAmount lastGasPrice,
}) async {
  try {
    // 20% up
    final newGasPrice =
        lastGasPrice.getInWei * BigInt.from(6) ~/ BigInt.from(5);
    final cancelTx = Transaction(
      from: privateKey.address,
      to: privateKey.address,
      maxFeePerGas: EtherAmount.inWei(newGasPrice),
      maxPriorityFeePerGas: EtherAmount.inWei(newGasPrice),
      maxGas: 21000,
      value: EtherAmount.zero(),
      nonce: nonce,
    );
    final tx = await signTransaction(
      privateKey: privateKey,
      transaction: cancelTx,
    );
    print('tx: $tx , nonce: $nonce');
    final txHash = await sendRawTransaction(tx);
    print('txHash: $txHash');
    return TransactionWithHash(hash: txHash, transaction: cancelTx);
  } catch (e) {
    rethrow;
  }
}

加速交易

操作逻辑类似:保留原交易所有参数,仅提高 Gas Price 重新发送。代码此处从略。

为演示取消功能,在发送代币交易时故意设置低 Gas 费用:

// in sendTokenTransaction()
final nonce = await web3Client.getTransactionCount(
  EthereumAddress.fromHex(privateKey.address.hex),
  atBlock: const BlockNum.pending(),
);
var maxFeePerGas = await web3Client.getGasPrice();
maxFeePerGas = EtherAmount.inWei(maxFeePerGas.getInWei - BigInt.from(1));
var maxPriorityFeePerGas = EtherAmount.zero();
final transferTx = Transaction.callContract(
  contract: contract,
  function: transferFunction,
  parameters: [EthereumAddress.fromHex(toAddress), amount],
  maxFeePerGas: maxFeePerGas,
  maxPriorityFeePerGas: maxPriorityFeePerGas,
  nonce: nonce,
);

界面添加取消按钮后,点击“Send Tx”生成低 Gas 交易(滞留不上链),再点击“Send Cancel Tx”发送高 Gas 取消交易(成功上链覆盖原交易)。

👉 获取实时链上交易工具

Mempool 机制深度解析

若用户通过其他平台(如浏览器插件)以同一钱包地址发送低 Gas 交易,如何在应用中呈现这些交易以便管理?这需引入 Mempool 监听机制。

Mempool(内存池)暂存所有已广播但未上链的交易。调用 RPC 节点的 eth_sendRawTransaction 方法即请求节点将交易广播至全网,各节点将这些交易存储于内存中形成 Mempool。

开启挖矿功能的节点(矿工)负责决定下一批打包交易(通常按 Gas Price 降序排列),未上链交易即来源于 Mempool。

由于 Mempool 公开透明,任何人均可通过 API 获取其中交易。但这也导致 MEV(矿工可提取价值)攻击风险:攻击者发现特定交易后,可通过抢先交易(Front run)或尾随交易(Back run)套利。由此衍生出私人交易(Private Transactions)需求——不经 Mempool 直接发送交易至矿工。相关技术可参考 Flashbots 及 Alchemy 的私人交易解说。

Mempool 数据获取方案

市场提供多种获取 Mempool 数据的服务,例如 Blocknative 和 Quicknode,均支持订阅未确认交易功能。以 Blocknative 为例:

  1. 访问 Blocknative Explorer
  2. 输入欲监控的地址(如 USDT 合约地址)
  3. 系统开始监听该合约所有未确认交易并实时显示
  4. 可创建复杂过滤器:按合约函数、来源地址等条件筛选

Blocknative 同时提供 Mempool 监听 API,更适合后端监控全网未确认交易并过滤出目标交易。完整实现虽超出本文范围,但开发者可尝试对接其 API 监控自有地址交易。

常见问题

什么是 Gas Price?

Gas Price 决定交易执行优先级。矿工优先打包高 Gas Price 交易,低 Gas Price 交易可能延迟确认或始终无法上链。

为何取消交易需支付 Gas 费用?

取消交易本质是覆盖原交易的新交易,需消耗网络资源。转账 0 ETH 至自身地址是成本最低的覆盖方式。

Mempool 数据是否完全可靠?

Mempool 数据源自节点内存,不同节点可能存在数据差异。且交易在未确认前可能被节点丢弃,建议结合多源数据校验。

私人交易如何避免 MEV 风险?

私人交易通过专属通道直接发送给矿工,不经过公开 Mempool,有效防止交易策略被窥探与恶意套利。

如何选择交易加速服务?

需评估服务的节点覆盖率、推送延迟与可靠性。主流方案包括 Blocknative、Quicknode 及 Flashbots Protect。

非 EVM 链是否存在类似机制?

其他区块链(如 Solana、BNB Chain)均有类似交易池设计,但具体实现与工具链存在差异。

结语

通过交易管理与 Mempool 监听,钱包应用可显著提升用户体验。本文介绍了交易取消、加速的实现原理,以及监控未确认交易的技术方案。合理运用这些技术,能使链上交互更加流畅可靠。

👉 探索更多链上策略