# Execute a composed route

Route executes supported two- and three-leg paths through wallets controlled by your application or user. A path can combine providers, including NEAR 1Click, SwapKit and LayerZero OFT, to reach an asset that has no eligible direct route. Every leg must have an execution builder, a current provider quote and passing owner policy checks. Exact availability is returned with the quote; the transfers are not atomic.

Use the same authenticated `/v1` API and `Route-Network: mainnet`. Choose a quote candidate with `composition` and `execution.mode: "multi-step-wallet-signature"`. Create the payment with `POST /payments` as usual.

## Prepare the route

Read `/payments/{id}/next-action` first. An initial BTC → Base USDC → Stellar USDC route includes these optional `action.walletRequirements`:

```json
[
  {"field":"sender","network":"btc"},
  {"field":"recipient","network":"stellar"},
  {"field":"intermediate","network":"base"}
]
```

The intermediate is your customer's Base EVM wallet, where the first leg delivers USDC before the second leg sends it to Stellar. It needs ETH for its own approval and transfer fees. It is a separate required address from the Bitcoin sender and Stellar recipient.

Call `POST /payments/{id}/prepare` with a saved UUID `Idempotency-Key`:

```json
{
  "version": 1,
  "sender": "SOURCE_WALLET",
  "recipient": "FINAL_RECIPIENT",
  "intermediate": "YOUR_INTERMEDIATE_EVM_WALLET"
}
```

Optional `refundTo` defaults to `sender`; `minimumReceived` defaults to the selected quote's final minimum and may only be increased.

For three legs, replace `intermediate` with `intermediates: ["FIRST_INTERMEDIATE_WALLET", "SECOND_INTERMEDIATE_WALLET"]` in the order of `composition.intermediates`. The requirements use `field: "intermediates"` with zero-based `index: 0` and `index: 1` and each wallet's network. The response includes `expectedLegs`; two settled children of a three-leg route are not completion.

A missing or invalid Base intermediate address returns `400 INVALID_INTERMEDIATE_WALLETS` with `error.details: {"field":"intermediate","network":"base"}`. Correct that address on the same saved payment while its quote remains valid; do not infer an intermediate wallet from the Bitcoin or Stellar address. An already-expired, unprepared draft needs a fresh quote. For an invalid three-leg address, `field` identifies its position, such as `intermediates[0]`. An incorrectly shaped wallet list is also rejected.

Each intermediate address must be controlled by your user and funded with ETH for approval/transfer fees. Route does not hold these funds or have the wallet key. The saved final minimum cannot be lower than the selected quote.

The response is `object: "composed_payment"` and includes `legs`, `activePaymentId`, `nextAction`, `wallets`, `state` and recovery details. The first leg has its own immutable payment record. Preparing or retrying the route never automatically broadcasts a transaction.

## Sign each transfer

1. Read `/payments/{activePaymentId}` and its preparation.
2. For NEAR, follow [intent approval and transaction signing](payment-transactions.md). For the other released providers, follow [protocol wallet actions](protocol-execution.md).
3. Save each exact transaction signature, then call `/payments/{activePaymentId}/execute` with the returned authorization ID.
4. Call `/payments/{parentId}/refresh` or read the parent payment. Background checks continue verifying the active child; the parent view derives its state from all child records.
5. After an intermediate leg has a verified, finalized destination receipt, the parent reports `state: "intermediate-held"` and `nextAction: "advance"`.
6. Request `POST /payments/{parentId}/advance-preview` with `{}`, review its returned bounds, then call `POST /payments/{parentId}/advance` with `{"review": <accepted review>}` and a saved UUID `Idempotency-Key`. Route re-quotes using the verified intermediate amount, checks the final minimum and combined fee budget, and saves one next-leg payment. Use a new saved request key for each advance. Retrying a previous key returns its existing workflow without advancing another leg.
7. Sign and submit that child’s actions. The parent becomes `completed` only after all expected children have independently verified destination receipts.

The parent's `/wallet-approval`, `/transaction`, `/transaction/signature` and `/execute` endpoints also delegate to the active leg. Use the active child's preparation version and authorization IDs. The parent never advances or spends another wallet’s funds implicitly.

## Review an interrupted route

Read the parent payment first. `workflow.recovery.intermediates` identifies each intermediate network, exact asset, wallet, verified receipt amount and transaction hash. `disposition` distinguishes an available continuation from a saved next transfer, verified onward funding, or a refund requiring review. These are historical receipts, **not current wallet balances**: the wallet may have spent funds elsewhere. Route checks the actual wallet before signing.

When the parent offers `nextAction: "advance"`, preview the next transfer without creating a deposit or child payment:

```js
const preview = await route.previewAdvance(parentPaymentId);
// Display preview.input, preview.candidate, preview.review and the final minimum.
// Obtain the customer's agreement before proceeding; persist these together.
const savedReview = preview.review;
const advanceRequestKey = crypto.randomUUID();
const workflow = await route.advance(parentPaymentId, advanceRequestKey, savedReview);
// Read workflow.activePaymentId and separately review and sign its wallet action.
```

The HTTP equivalents are `POST /payments/{id}/advance-preview` with `{}`, followed by `POST /payments/{id}/advance` with `{"review": <saved review>}` and a UUID `Idempotency-Key`. Preview requires `payments:write`, creates no credit or spending reservation, and is limited with composed preparation to 12 requests per minute per payment. It is available before the next child is allocated, or after a supported finalized intermediate refund or cancellation as described below. Each preview reads fresh quotes; save the accepted review and request key before calling advance.

The review binds the next leg, provider/path, source amount, preceding receipt and policy version. It also contains an indicative final output floor, combined estimated fee ceiling and expiry. Advance re-quotes: worse bounds or changed identity return `ADVANCE_REVIEW_CHANGED` without allocating a new child. Obtain a new preview and customer review. An app can explicitly agree a larger `maxEstimatedTotalFeeUsd` before advancing, but owner policy always applies. The combined estimate includes prior saved execution estimates; it is not final network-fee accounting or a guaranteed on-chain cap. If `costComplete` is false, show that some fees still need wallet verification. Fees and wallet readiness are checked again for the separately authorized transaction.

If an advance response is lost, **retry the same parent, request key and review**. Route resumes the same saved child and preparation. A changed request body cannot substitute another review under that key. Once a child is allocated, a new preview cannot replace its pending deposit or signed action. A finalized intermediate refund or verified source cancellation can qualify for a separately reviewed continuation. Read that child's next action and recovery report; an expired review or missing receipt does not prove that a signature is unusable.

The original final minimum and provider order remain in force. Preview does not lower that minimum, switch providers, return the original source asset, or spend funds. A verified refund from a later leg is on its intermediate network and requires explicit review; it does not complete or refund the whole route. Credits and processed USD totals remain attached to the logical parent, with completion counted only after all expected receipts are verified.

## Refresh an unfunded transfer

If a prepared composed transfer returns `COMPOSED_QUOTE_EXPIRED`, call `POST /payments/{parentId}/refresh-route` with `{}` and a saved UUID `Idempotency-Key` (SDK: `refreshRoute(parentId, key)`). Route refreshes the remaining quotes, final minimum check and combined estimated fees. It preserves the active payment, provider deposit and transaction bytes. The response reports `activePaymentId`, `expiresAt` and `remainingCostUsd`.

If advancing saved a NEAR child but its provider preparation failed before allocating a deposit, the same endpoint can renew its expired quote. Route retains the original child and an audit record of the new review, preserves its assets, wallets, amount and minimum received, and rechecks the remaining route against the owner’s policy. Retry `advance` with the **original idempotency key and body** to prepare that saved child. This renewal is refused once any provider preparation or allocation exists; use the active payment’s review or recovery action instead. Each NEAR leg uses the slippage actually returned in its quote, within the parent’s approved limit.

For a saved, signed NEAR EVM deposit that Route has not submitted, `refresh-route` can update the remaining-route estimates only after checking that the source transaction is absent, the wallet nonce is unchanged and the provider deposit is unfunded. The signature, deposit, transaction bytes and fee reserve stay unchanged. If the payment review has expired, renew it through `transaction/review` and confirm the returned wallet intent challenge before submitting the saved action. A known transaction, wallet handoff or uncertain submission must be tracked or reconciled instead.

Then read the active child's next action. An unsigned EVM protocol funding action can use `/transaction/review` with its `authorizationId` and another saved request key. Its nonce, recipient, value, calldata and fee fields stay fixed; fresh simulation and policy checks must pass. If those exact bytes are no longer usable, the action requires reconciliation. NEAR continues using its existing deposit-review endpoint and original provider deadlines. Stellar and Bitcoin source transaction expiry is not extended by this route-estimate refresh.

`SOURCE_RECONCILIATION_REQUIRED` means a source signature, submission or browser-wallet handoff already exists. Track the original transaction. An expired review or missing network lookup does not authorize a new transfer. Replaying a successful refresh key returns that saved review, even after its expiry; use a new key for a new observation.

## Continue after an intermediate refund

When a NEAR or SwapKit leg refunds its exact source asset to the saved intermediate wallet on Ethereum, Base or Arbitrum, Route requires verified, finalized source and refund receipts before offering `nextAction: "retry-refund"`. The first leg cannot be restarted through this flow.

Use `/advance-preview` and display the returned amount, route, minimum and combined estimated fee. Pass its complete `review` to `/advance` with a new saved request key. **Review is mandatory for refund continuation.** Route allocates a new child attempt for that same leg, using the verified refunded amount, original provider/path and final recipient minimum. Wallet balances and transaction fees are checked again before signing. A partial refund that cannot meet the original minimum remains in the intermediate wallet.

The parent exposes active children in `legs`; each has `attempt`. `recovery.attempts` preserves every prior child ID, preparation, tracking result and refund link. There are at most three attempts per leg. A replay of an older request returns the current workflow without creating another attempt. Refunded attempts contribute to the combined cost estimate but do not multiply the parent credit charge, rolling principal reservation or processed USD total. Historical children cannot authorize another funding transaction after being superseded.

## Close a cancelled first transfer

If the first source transfer on Ethereum, Base or Arbitrum was cancelled at its saved nonce, use the child payment ID and authorization ID with `POST /payments/{id}/transaction/broadcast-recover`. Supply the cancellation `transactionHash` when available. Route verifies a finalized, zero-value self-transfer with no call data at the exact saved account, network and nonce. Saved signatures and browser-wallet handoffs are supported.

After recovery returns `canClose: true`, explicitly call `POST /payments/{id}/transaction/broadcast-cancel` with the same authorization ID and cancellation hash. For a protocol transfer, Route first searches the wallet's provider events for the same payment sent at another nonce, as for [recovery cancellations](recovery.md#rejected-prompts-and-confirmed-cancellation); a match, recorded evidence that the wallet sent the payment itself, or a provider that cannot be searched by sender keeps the payment for review. Route closes both the first child and the unfunded parent, releases unused payment-credit and rolling API-limit holds once, and keeps the original action, provider allocation and consumed nonce in history. Quote charges and network fees are not refunded. Duplicate closure requests return the saved result.

The parent workflow returns `state: "cancelled"` and `nextAction: "cancelled"`; its recovery report returns `outcome: "closed"`. There is no intermediate balance to continue. Obtain a fresh quote and create a new payment with a new request key if the sender still wants to transfer. Never reuse the cancelled action, deposit instructions or consumed nonce.

A missing receipt, pending cancellation, expired review or wallet balance alone does not allow closure. Transfers with verified source execution, a later allocated leg, committed spending limits or settlement evidence remain in reconciliation. Destination mint cancellation is outside this flow.

## Continue after an intermediate cancellation

For the active second or third source transfer on Ethereum, Base or Arbitrum, Route can verify an on-chain cancellation and preserve the completed route legs. This supports both saved signatures and browser-wallet handoffs. It does not apply to destination mint actions, the first composed leg, or a transfer with verified execution evidence.

1. Keep the parent payment ID, active child payment ID and source action `authorizationId`. A wallet owner may authorize a zero-value native self-transfer with empty data at the saved source nonce. Route does not sign or send this cancellation.
2. Call `POST /payments/{childId}/transaction/broadcast-recover` with `authorizationId` and the cancellation `transactionHash`. Wait for `cancellation-verified`; a pending transaction, missing receipt or expired review is insufficient.
3. Call `POST /payments/{childId}/transaction/broadcast-cancel` with those same identifiers. Route rechecks canonical finality, the exact action identity and cancellation fee, and for a protocol transfer searches for the same payment sent from the wallet at another nonce, as for the first transfer. `paymentClosed: true` closes that child attempt; `parentPaymentId` identifies the funded parent, which remains open. Repeating the request with the same evidence is idempotent.
4. Call `/payments/{parentId}/advance-preview`. Review the same intermediate asset, amount, wallet and provider path, the original recipient minimum, and updated total costs. The returned `review.cancellationHash` binds acceptance to this specific cancellation.
5. Pass the complete `review` to `/payments/{parentId}/advance` with a new idempotency key. Review and sign the new child action separately. There are at most three attempts per intermediate leg, including the original.

The original signature, hash, provider allocation and consumed nonce remain in history. Do not reuse old payment instructions or resend the first leg. A cancellation invalidates the saved source action; it does not revoke standing token approvals. Wallet balance and fee checks run again before another action is signed.

Cancellation does not create a refund receipt or processed payment volume. Parent credit and rolling-limit reservations remain associated with the original funded payment. Verified final delivery settles and charges that parent once. Continuation costs conservatively retain prior attempt estimates and add the verified cancellation network fee, valued using a fresh ETH price at recovery; they are not a claim that every prior estimated fee was spent.

If Route cannot establish the cancellation or account for its fee, it retains the current attempt for reconciliation. For unsupported chains and action types, keep tracking the original transaction; expiry alone never authorizes an automatic replacement.

## Limits and interrupted transfers

Owner and API-key restrictions apply to every involved provider/network/asset. Recipient allowlists must allow both the intermediate and final recipients. The aggregate cost is checked again before signing/submitting a child, including when the child endpoint is called directly. All steps reuse the first payment’s rolling USD principal reservation; the same funds are not counted as a second new payment. Use the original API key for continuation when it has a rolling limit.

`INTERMEDIATE_NOT_SETTLED` means receipt verification is still pending. `SECOND_LEG_UNAVAILABLE` identifies a remaining leg that failed refreshed checks: its error details include the leg number, provider, failed policy checks, fee completeness and recipient minimum. Before the first transfer, obtain a fresh comparison; after funding, preserve the current payment and follow its recovery guidance. `COMPOSED_COST_UNRESOLVED` means fresh cost or USD price evidence is missing; its details identify the active leg and unresolved checks. Retry the existing payment when that evidence is available. `COMPOSED_FEE_LIMIT` means the known combined estimate exceeds the saved policy limit. `FINAL_MINIMUM_NOT_MET`, `COMPOSED_MINIMUM_NOT_MET`, `COMPOSED_COST_UNRESOLVED`, `COMPOSED_FEE_LIMIT` or `LEG_QUOTE_UNAVAILABLE` leaves the intermediate token in the customer-controlled wallet. You may retry `/advance` before that next leg has been allocated. Once a later provider preparation or signed transaction exists, reconcile that saved attempt. Only a supported finalized refund or verified source cancellation permits a reviewed new attempt; Route never automatically resends funds. This release does not provide an automatic reverse swap.

Parent webhooks include `payment.intermediate_received`, `payment.second_leg_submitted`, `payment.requires_attention` and `payment.completed`. Each contains the parent payment ID and triggering child/leg IDs. Three-leg routes additionally emit `payment.second_intermediate_received` and `payment.third_leg_submitted`. Child payment events remain available too.

Production retains the finalized intermediate-receipt gate. Provider availability and pricing remain live checks. No combined guaranteed ETA is inferred from two partial provider estimates.

The NEAR leg inherits its saved quote’s slippage, including when a leg is refreshed. Optional `slippageBps` on preparation must match that quote. To use a different tolerance, request a new comparison with `slippageBps` first. A rejected child preparation remains attached to this workflow; the direct-payment `/reconcile` replacement guidance does not apply to composed children.

## Auto-route paths

[Auto-route](auto-route.md) can discover additional two- and three-leg combinations. Prepare the first transfer, verify its intermediate receipt, then advance and separately authorize each remaining transfer. Each leg must expose released execution support, and an executable intermediate wallet must be on Ethereum, Base or Arbitrum. Supported SwapKit simple deposits can execute as legs; Garden legs are excluded while the provider is paused. Auto-route is priced once at the logical root (40 completion credits), across all its legs.

## Composed quotes to and from Stellar USDT0

With both `near` and `layerzero` enabled and allowed by policy, the same `/quotes` request considers two ordered paths:

- To Stellar USDT0: NEAR conversion into verified Ethereum USDT or Arbitrum USDT0, then the verified OFT bridge to Stellar.
- From Stellar USDT0: the verified OFT bridge into one of those intermediate tokens, then NEAR conversion to the requested destination asset and network, including native BTC or ETH where quoted.

Exact direct USDT paths remain direct. The current composer considers cross-network conversion legs only; it does not yet integrate same-network DEX swaps or arbitrary multihop paths. Both legs are quoted in the requested direction, with chain-native reference addresses. Destination precision is retained, including eight-decimal BTC and eighteen-decimal ETH. Any OFT dust retained at the original source is identified separately.

These candidates have `composition: { atomic: false, requiresRequote: true, outputBasis: "minimum-intermediate-output", intermediate, legs }`. `legs` contains two normalized observations with exact asset identities, inputs, outputs, fees and timing scopes. `adapter` is the first provider; every leg's provider is enforced against policy and connection versions. The parent fees add `leg` and `network`; their currencies must be preserved. Intermediate ETH is not priced using the origin network's gas asset.

The second leg is quoted for the first leg's minimum intermediate output. The combined `received` and `minimumReceived` are **indicative calculations, not an end-to-end minimum guarantee**. The quote expires with its earliest leg or referenced price. Overall ETA stays unknown until timing across both transfers is covered; partial timings are displayed per leg. Gas evidence from both legs is retained, and missing source or intermediate charges keep the total cost incomplete.

Use the [composed payment execution flow](composed-payments.md). It needs a customer-controlled intermediate wallet with ETH for gas and any token approval, plus two separate authorizations. After the first transfer settles, Route re-quotes the next transfer and remaining preview for the actual intermediate receipt and recheck full-route policy. If the second step cannot proceed, funds remain as the intermediate token in that wallet; there is no automatic refund to the original asset. Saved drafts preserve provider order, both connection versions, the actual final asset/network and the second provider's authorization and delivery steps.

## Review a rejected NEAR continuation

A NEAR allocation can return a recipient minimum below the preceding dry quote. Route retains that allocation and blocks funding. For an unfunded EVM continuation, a preparation with `quoteReviewAvailable: true` offers review through the **active child**:

1. Read `GET /payments/{activePaymentId}/quote-review`.
2. Review `minimumReceived` for this leg, `finalMinimumReceived` for the original final recipient, and `estimatedTotalFeeUsd` for the complete route. The original final minimum is never reduced.
3. Accept with `POST /payments/{activePaymentId}/quote-review`, a saved UUID `Idempotency-Key`, and the exact returned `reviewHash` and `minimumReceived`, plus your `maxEstimatedTotalFeeUsd`.
4. Read the child's preparation. Its `intent` contains the accepted terms; `originalIntent` and `preparationReviewId` preserve their history. Refresh the parent's remaining-route estimates when needed, then approve and sign the child's wallet actions separately.

```json
{
  "reviewHash": "HASH_FROM_THE_REVIEW",
  "minimumReceived": "MINIMUM_FROM_THE_REVIEW",
  "maxEstimatedTotalFeeUsd": "YOUR_TOTAL_USD_COST_LIMIT"
}
```

Acceptance reuses the same captured provider response and deposit. It requires fresh evidence that the deposit is unfunded, no saved wallet authorization or transaction, and passing wallet, cost and policy checks. Route quotes the remaining legs using the accepted minimum and includes costs from previous attempts. The accepted total cost ceiling also applies to later signing and submission checks.

The review window is at most five minutes and cannot extend provider deadlines. If the provider reports activity, the allocation has expired, or the remaining route cannot meet your original bounds, Route refuses acceptance. Track existing funds or review recovery guidance; do not create or fund a replacement automatically.

A captured SwapKit quote review must also preserve the parent payment’s final minimum. Route checks this again before the final transfer is authorized or submitted. `COMPOSED_MINIMUM_NOT_MET` leaves the intermediate funds in your wallet; it never authorizes a lower final receipt.

Auto-route comparisons can opt into an additional route-level quote-movement allowance using `autoRoute.requoteSlippageBps` (default 0). The adjusted final minimum is displayed in the quote and saved at preparation; refresh and advance never apply the allowance again. See [Auto-route](auto-route.md).
