A confirmed source deposit is not a completed cross-chain swap. Your integration must track destination execution separately, make retries safe, and reconcile each operation against authoritative chain data; a single “success” flag hides failures users need to understand.
Source confirmation and destination settlement are separate facts
A source transaction proves that an asset was sent or locked; it does not prove that the intended asset reached the recipient on the destination chain. Ethereum.org describes Ethereum finality as a consensus condition: blocks become finalized through checkpoint votes representing at least two-thirds of staked ETH. Until the route’s source-finality condition is met, a source event may still be vulnerable to a reorganization.
Model the operation as a sequence of independently observable facts:
- Source submitted: the transaction hash is known, but inclusion is unconfirmed.
- Source accepted: the deposit event is included and has met the route’s confirmation or finality rule.
- Destination pending: the operation is eligible for execution, but no successful destination effect is verified.
- Destination settled: the expected output is present and the destination transaction meets its own finality rule.
Store the source transaction hash and event log index, the source block hash, the destination transaction hash when available, and the observed block heights. If the source block is reorganized before acceptance, roll back the pending operation; do not leave a stale “deposit received” state that can later trigger delivery.
These states also distinguish execution from user-facing status. A relayer acknowledgement means a delivery attempt was accepted for processing, not that the destination call succeeded. EIP-7786’s cross-chain messaging properties make the same distinction: safety concerns valid, non-duplicate delivery, while liveness and timeliness concern eventual delivery and its expected bound.
Retries need one intent identity and an idempotent effect
Every retry for one deposit should resolve to the same operation identity. A practical key can hash the source chain identifier, source transaction hash, log index, destination chain identifier, recipient, input asset and amount, plus the intended output constraints. Bind the key to the original intent; do not let a retry silently change the recipient or quote terms.
Enforce uniqueness at the point that creates the destination effect. For a contract you control, record the operation key as consumed in the same transaction that transfers or mints the output. If execution reverts, that transaction rolls back the consumed marker too, allowing a safe retry. If execution succeeds but the relayer loses its receipt, submitting the same key again must return or emit the existing outcome rather than deliver twice.
Keep a durable off-chain record as well, but treat chain state as authoritative. A database timeout after a successful destination transaction is a common ambiguity: query for the operation key or destination event before resubmitting. A transaction hash alone is insufficient if the replacement transaction uses a different nonce or the first attempt was never broadcast.
Timeouts should trigger diagnosis before resubmission
Use bounded retries with exponential backoff and jitter for transient RPC errors, unavailable relayers, and destination congestion. For example, an integrator might retry after 2, 4, 8, then 16 seconds, cap the interval at a few minutes, and alert when the route’s published delivery bound is exceeded; those are application policy values, not universal chain parameters.
Before each retry, classify the failure: source event absent, source not final, proof or message unavailable, destination transaction pending, destination call reverted, or destination RPC out of date. A revert caused by expired swap terms is not repaired by repeatedly sending the same call. It needs the route’s defined recovery outcome, such as a refund or an explicit manual-resolution state. Never tell the user “refunded” until the refund itself is confirmed on its chain.
Expose timeout as uncertainty with a next check, not as failure or success by default. Set the deadline from the route’s documented timeliness bound and the destination chain’s expected block production; if no bound is available, keep the operation pending and surface the missing guarantee instead of inventing one.
Reconciliation decides what the user actually received
Reconcile expected and observed amounts from destination-chain events or state, not from a relayer response or a quote. For example, say a user deposits 100 units and the quoted output is 98.4 after route costs. If the destination call succeeds and transfers 97.9 because execution conditions changed within the allowed bounds, record 97.9 as delivered and compare it with the user’s minimum-output constraint. If it falls below that bound and reverts, record no delivery and follow the route’s recovery path.
Run reconciliation after every state transition and periodically for operations stuck beyond the normal delivery window. Check both chains from a healthy RPC source, verify block hashes for events near a reorganization, and make the reconciler safe to run repeatedly. Alert on impossible combinations, such as a destination success event with no accepted source event or two successful destination effects for one intent key.
The design choice that changes the answer is whether your application controls the destination contract. If it does, enforce idempotency on-chain and define explicit refund or recovery states; if it only observes a third-party route, report its documented operation states and avoid promising a recovery action you cannot execute. For users who need the asset movement and exchange handled as one cross-chain flow, Biswap provides a cross-chain bridge and swap service, while an integrator’s responsibility is to represent that flow’s settlement state accurately.
Top comments (0)