docs: add CCTP receiveMessage fee guidance#184
Conversation
|
Thanks for documenting this — the fee-race failure mode on A few technical points worth addressing before merge: 1. const maxFeePerGas = fees.maxFeePerGas ?? fees.gasPrice;
// then always returns: { maxFeePerGas: bumpFee(maxFeePerGas) }On a legacy-gas chain, if (fees.maxFeePerGas != null) {
return {
maxFeePerGas: bumpFee(fees.maxFeePerGas),
...(fees.maxPriorityFeePerGas != null
? { maxPriorityFeePerGas: fees.maxPriorityFeePerGas }
: {}),
};
} else if (fees.gasPrice != null) {
return { gasPrice: bumpFee(fees.gasPrice) }; // legacy chain
} else {
return {};
}2. Arc Testnet as destination is a gap in the guide The doc covers the case where Arc is the source (burns land on Sepolia/Base/Fuji). But the reverse direction — Sepolia → Arc Testnet — hits a different failure mode: Arc uses a legacy gas model, 3. The tip is forwarded as-is from the estimate. On low-tip chains this is harmless, but if the priority fee market spikes concurrently with the base fee, a stale tip can also cause the tx to sit unconfirmed. A modest bump (e.g. 110-120%) on the priority fee as well would be more robust and is consistent with the "add headroom" philosophy of the rest of the guide. Minor note: the ceiling-division in |
|
The update addresses all three points from the previous review — the EIP-1559/legacy branching is now correct, the Arc-as-destination section is a solid addition, and the priority-fee bump is a good defensive change. A few remaining observations on the updated code: 1. When neither Consider throwing instead of returning silently: // instead of: return {};
throw new Error(
`estimateFeesPerGas returned no usable fee fields on ${destinationChain.name} — cannot construct safe fee overrides`
);This forces the caller to handle the exceptional case explicitly (e.g., retry after a short delay) rather than silently regressing to the unsafe default path. 2. Arc gasPrice staleness is not limited to rapid transaction sequences The new section reads:
Issue #87 affects individual transactions too — the Arc RPC's
3. If the destination RPC is temporarily unreachable, // at the top of estimateReceiveMessageFees:
let fees: Awaited<ReturnType<typeof destinationClient.estimateFeesPerGas>>;
try {
fees = await destinationClient.estimateFeesPerGas();
} catch (err) {
throw new Error(`fee estimation failed on ${destinationChain.name}: ${err}`);
}Wrapping it with a contextual message makes the failure origin obvious in relay logs, especially when a relayer monitors multiple destination chains. On the math: bumping |
Summary
Adds a CCTP v2 troubleshooting note for
MessageTransmitterV2.receiveMessage()relays from Arc Testnet to destination chains.The doc explains why viem/MetaMask submissions can fail with:
and shows how to estimate fees on the destination chain immediately before
receiveMessage(), then addmaxFeePerGasheadroom before callingwalletClient.writeContract().Fixes #153.
Changes
docs/cctp-v2-receive-message.mdmaxFeePerGaspaddingDuplicate check
.context/pre-pr-gates/circlefin-arc-node-153-20260621T142653Z.mdmax fee per gas less than block base feereceiveMessage estimateFeesPerGasCCTP v2 receiveMessageTesting