What is a refund?
The escrow contract returns the user’s deposit.refund(order) on the settler is
permissionless — anyone can call it, but only the order’s own user is paid — and whoever
sends it pays the gas. The hub never sends it, and there is no automatic refund: the hub can
tell you when an order is refundable and hand you the tuple to call with, but the user (or anyone)
has to call refund(order).
When does an order become refundable?
After the escrow’s on-chainexpires (or a later refundableAfter a refund extender set) has
passed, for as long as the order is neither settled nor refunded. The escrow doesn’t check fills:
offering a withdrawal only when no fill was observed is the client’s withdraw rule
(below), not the escrow’s. On the proxy lanes, a refused
registration doesn’t touch the deposit: the user’s own transaction already opened the order in
escrow, the solver still fills it as usual, and if nothing fills it the user waits out that same
deadline like any other unfilled order — it doesn’t refund early. Never re-quote meanwhile: a new
quote means a second deposit (known issue). The exact
expires / refundableAfter semantics are on
List orders — Response (not restated here).
What is refunded, and to whom?
Whatever token the escrow actually holds — not necessarily the token the user thinks they paid with. On a native-coin lane (oif-wrap-native), the user paid in the chain’s native coin, but the
escrow holds (and a refund returns) the wrapped token named by the catalog’s wrappedKey —
never the native coin, and never the token paid with on a token-in order either. It’s paid to the
order’s user, as recorded on
List orders — Response (chain.user); the settler itself
pays only that user (Get escrow order — Behavior, Get on-chain fill).
How do I know it was not filled?
The dating rule that says whether an absent fill is trustworthy lives on Get on-chain fill — Behavior — read it there. The client-side withdraw rule built on top of it: offer a withdrawal only if all three hold —filledis absent fromGET /onchain/{onchainOrderId}/fill, and- the order’s deadline has passed, and
- the destination chain’s watermark
updatedAtis at or after that deadline.
updatedAt is the server’s publish time, not a block timestamp, so it trails chain time by the
observers’ finality depth — if it’s close to the deadline, wait one more poll before deciding.
EIN0051 on this route means “cannot tell”, never “not filled” — never offer a withdrawal on
the strength of an error. Pass onchainOrderId exactly as the hub serves it (0x + 64 lowercase
hex): the route doesn’t validate it, and a miscased or unprefixed id answers with no facts, which
reads as “not filled” (known issue).
How do I withdraw?
These steps are for EVM-origin orders only: for a non-EVM origin (Solana),GET /onchain/{onchainOrderId}/order answers EIN0009 permanently, even
once its Open, fill and settle are observed, so there is no tuple to send
(known issue). A Solana-origin order refunds
through GET /onchain/{onchainOrderId}/refund instead: the hub builds the unsigned refund
transaction, whose fee payer and only signer is the order’s user, for their wallet to sign and send
(unwrap=true also turns wrapped SOL back into SOL). The SDK checks that transaction before the
wallet sees it (SDK — Refunds).
- Take
onchainOrderIdfromGET /orders— or, for a proxy-lane deposit the hub never recorded, fromGET /onchain/tx/{chainDomain}/{txHash}/orderwith the send transaction’s hash. GET /onchain/{onchainOrderId}/fill— apply the withdraw rule above.GET /onchain/{onchainOrderId}/orderfor the tuple and the settler; verify the settler is one of the catalog’sinputSettlersfor that chain, and check the live contract’s own deadline (not just the cached projection).- The user sends
refund(order)to the settler, from whichever device holds the wallet — the on-chain views aren’t scoped to the caller, precisely so a refund can be sent from a second device.
GET /orders needs the key: a page takes onchainOrderId from the order it submitted instead (on
the escrow lanes, the openFor ack’s orderId), or from GET /onchain/tx/…, which is open to it.
Is refundEligible enough?
No. GET /orders’ chain.refundEligible is an advisory estimate from
refundEligibilitySource: "stored_projection" — undated stored observations, not live contract
state — and refundRequiresOnchainCheck is always true. Verify the live contract (step 3
above) before offering, or sending, a refund.
How does a refund show up?
Three places agree once a refund is observed:OnchainFill.refunded (an EventFact),
GET /orders’ chain.status: "REFUNDED", and the upstream status’s state: "refunded".
Can I force a refund for testing?
There is no way to force a refund for testing: an order becomes refundable only once its on-chain deadline has passed, and the hub has no test-refund path.See also
- Get on-chain fill, Get escrow order, List orders
- SDK: Refunds — the withdraw rule, checked on chain, and the refund sent from the user’s wallet