continuation.settle.
Prerequisites
-
The Composer SDK and a server-side API key in
LIFI_API_KEY; see Before you start. This guide also uses viem: - A destination chain from the served chains. The escrow lives on the bridge’s destination chain, and the outcome must land on that same chain. Here both are Base.
1. Write the destination flow
The destination flow runs later, on Base, from the escrow. Build it like any other flow, on the destination chain, with exactly one input: the token the bridge delivers. Then passdestFlow.build() to the settle node.
- Exactly one input, a resource input in the funding token on the destination chain. The settlement service binds the escrow’s balance to it.
- Leave every outcome in the destination flow’s proxy. In a destination flow,
destFlow.context.executionAddressis the escrow’s execution context, not the user’s proxy. Where a node needs a receiver, binddestFlow.context.executionAddress. Never binddestFlow.context.sender: in a destination flow it is LI.FI’s executor, and the compile fails withleg_binds_sender. - The settle node decides where outcomes go. You set the outcome’s
destinationand therefundaddress in the settle node’sconfig. The compiler then appends transfers to the destination flow (an epilogue): the outcome goes to the settlement contract, which checks the minimum received and forwards it todestination, and every leftover goes torefund. Do not send todestinationfrom the destination flow, and do not give it an epilogue of its own. - No nested
continuation.settle. - An empty destination flow is valid. One with no nodes delivers its funding unchanged: set the outcome token to the funding token.
continuation leg of '<id>' failed at the funding floor.
2. Bridge into the escrow
The source flow runs on Arbitrum. It bridges the USDC to Base and binds the bridge’s output to the settle node. The bridge has nodestination, so it delivers into the settle node’s escrow on Base. The compiler infers this funding from the binding, so the settle node sets no funding.
outcomes[0].minAmount when the destination flow proves a minimum, as the zap’s slippage guard does here; the op reference explains how the minimum received is set.
3. Compile and read the escrow
settlements.settle.escrow, where the funds go, and outputs['settle.output'], the minimum and expected outcome. Flow wire format: settlements describes every field.
escrow. Every compile returns a new escrow address; never reuse one. Trustescrow.expiresAtMs, not the expiry you authored: the settlement service may shorten it.escrow.flooris the minimum funding, derived from the bridge’s quote.amount.minimumis the minimum received, the only guaranteed amount: the settlement contract reverts a run that would deliver less.amount.estimateis the expected outcome; nothing enforces it. Show users the minimum as the guarantee and the estimate as the likely result.actionsholds the approval of Arbitrum USDC, if the proxy needs one, andexecute. Both run on Arbitrum. There is nofundEscrowaction, because the bridge funds the escrow.outputs['bridge.amountOut']reportsrole: 'consumed'andconsumedBy: 'settle': the bridged USDC goes into the escrow, not to the user.result.fill.validUntilMs, when present, is the latest time to submit the user’s transaction. It is never later than the escrow’s expiry and is not enforced on chain.
simulationPolicy: 'allow-revert', a simulated revert returns status: 'partial' (HTTP 206) instead of throwing. The compile registers the escrow before it simulates the source transaction, so a partial response can carry settlements and actions. Never fund an escrow from a partial response; compile again instead.
4. Submit
Send every action in order, each on its owntransactionRequest.chainId. This is the same loop as in Build a same-chain flow, with a check that every action runs on Arbitrum. It uses viem with a private key; use your own wallet stack in production.
owner must be account.address. After a failure, compile again and use the new response. Never fund an escrow from an earlier response.
5. Track the bridge and the escrow
Two services report progress after theexecute transaction is mined:
-
The bridge. Poll the LI.FI API’s
GET /v1/statuswithtxHash=<sourceTxHash>&fromChain=42161&toChain=8453and your API key. It reportsPENDING, thenDONEorFAILED. -
The escrow. Poll
GET /intent-factory/statuswith the escrow’schainIdandaddress:
COMPLETED means the aBasUSDC reached the user. An escrow at READY or later means the bridge has delivered, even if LI.FI still reports PENDING. If the bridge transfer FAILED, the escrow never receives funds and stays EXPIRED after its expiry. A bridge refund on Arbitrum goes to the user’s execution proxy, not the wallet; Track the delivery shows how to recover it. If the USDC lands but the destination flow doesn’t run, the USDC is refunded to refund automatically. Escrow status maps every status to what to show your user, and has a polling function.
Other ways to fund the escrow
A bridge isn’t the only way to fund an escrow. The two other modes fund an escrow on the flow’s own chain, so the destination flow runs later on that same chain. Same-chain continuations explains when to pick each. Each example below is a separate flow, not a change to the running example.- Pay by transfer. Set
funding: 'external'and hand the returnedfundEscrowtransfer to whoever pays. - Exact payment on one chain. Set
exact: true, and bind the settle node’sinputto a declared input or an upstream output instead of a bridge’samountOut. The user’s transaction then funds an escrow on the same chain, and the escrow runs only the destination flow’s compiled bytes. The cross-chain version of this pattern is Pay cross-chain.
Pay by transfer
Withfunding: 'external', the source flow does not touch the funds. The response carries a fundEscrow action: a plain transfer to the escrow that any wallet can submit, for example a payer who scans a QR code. This flow escrows 100 USDC on Base for an empty destination flow, which delivers the USDC to owner unchanged:
depositResult.transactionRequest is absent and actions holds only the fundEscrow transaction. Track the escrow as in step 5.
Optional: verify the binding before funding
An exact destination flow addssettlements[id].binding, which discloses what the escrow will run; Flow wire format: settlements describes each field. The compile already checks that payload matches payloadHash, so this step is not mandatory. We recommend recomputing the hash yourself before anyone funds the escrow. Run this on the result of a compile with exact: true, such as the one in Pay cross-chain:
- The compile checks that
payloadwraps the compiled destination flow and that its hash matches. It cannot prove that the settlement service put that hash in the escrow’s constraint. You also cannot check yet how the escrow address is derived; a way to check it is planned. - The bytes are fixed at compile time. An on-chain assumption that stops holding before the run (a cap that fills up, an epoch that ends) makes the run revert, and the escrow is refunded.
- No outcome, no minimum. With
outcomes: [], only the executor, the payload hash and the spending cap constrain the run on chain. Nothing checks what the destination flow achieves. To check it yourself, decodecalldatabefore funding; for an on-chain check of the effect, add aninvariant.*op to the destination flow. A zero-outcome exact destination flow has nosettle.outputentry.
Errors you might see
source.compile throws a ComposeError. Settle-node authoring errors carry a fix field that names the one edit that clears the error.
delivery_error with floor_drift (compile again) or eta_exceeds_expiry (set a later expiry), and validation_error with forbidden_operation (replace the destination flow’s op). continuation.settle errors lists every settle-node error, its cause and its fix.
Next steps
Bridge and deposit
This guide as one runnable file, with polling.
Pay cross-chain
An exact destination flow that pays two recipients.
Escrow status
Status values, what to show your user, and a polling function.
continuation.settle
Every config field, rule and error.

