Skip to content

Order Lifecycle & Refunds ​

What has to be true for a payment to become orders, the states an order moves through, how a multisend aggregates them, and when funds come back.

Payment rules ​

The backend indexes your payment (the Paid event on EVM, the treasury transfer on Solana / TON), loads the quote named by its quoteId, and checks the payment against it. All of these must hold:

RuleDetail
Same payerSent by the wallet you quoted as sender. On Solana that wallet is the fee payer (first signer).
Same tokenThe fromToken you quoted: the native coin, the ERC-20, or the exact SPL mint.
Exact amountThe pay target receives exactly fromTotal base units — not more, not less.
Before the deadlineThe transaction's block time is at or before deadline. On EVM the contract reverts a late call; on Solana / TON nothing stops a late transfer — it lands and becomes a refund case.
One quoteIdEVM: the quoteId argument. Solana: exactly one memo carrying the id, and only one asset credited to the treasury in that transaction. TON: the text comment.
Not a self-transferSolana / TON: a transfer from the treasury to itself is ignored.
OnceEach quote is honoured once.

When a rule is broken:

What went wrongWhat happens
Different payer, token or amountHeld for review. No order, no automatic refund; the payment is flagged to the operator. Contact support with the transaction hash — see Errors.
Landed after deadlineEVM: the contract reverts, nothing is paid. Solana / TON: no order; a refund is recorded with reason expired.
Unknown quoteId, or no readable memo / commentNo order; a refund is recorded with reason unknown_quote (an id the backend never issued) or unattributed (no readable id at all — Solana / TON).
Same quoteId paid twiceThe first payment is honoured; the second is held for review — which payment to return is the operator's call, nothing is refunded automatically.

Order states ​

Once the payment passes the checks, the quote is expanded into one order per recipient:

StatusMeaning
pendingReserved — the current flow never produces it; an order appears directly as processing.
processingThe testnet payout is queued or in flight.
completedThe recipient received their tokens. to.txHash is the payout transaction.
failedThe payout could not be delivered to that recipient.
payment indexed → checked against the quote → order(s) → pending / processing → completed
                                                                            ↘ failed → refund

You observe these on each order object via status, and the payout transaction via to.txHash once completed. Payouts usually land within seconds of the payment being indexed.

Multisend: aggregate status ​

A payment with many recipients produces many orders. The batches view and the order-by-tx lookup collapse them into one aggregate status:

AggregateWhen
processingAny recipient is still pending/processing.
completedEvery recipient was delivered.
failedEvery recipient failed.
partialSome recipients were delivered and some failed.

For a fully-failed batch the whole payment is refunded on the FROM chain. For a partial batch the failed slices are refunded, capped so that the delivered part still cost at least $1 at the quote's locked price (otherwise the multisend minimum could be bypassed). A refund the cap would reduce to zero is not sent automatically — it is held for the operator.

Refunds ​

A refund returns FROM funds to the payer when a payment can't be (fully) honoured:

ReasonCause
failed_orderThe failed recipients of a settled batch — their FROM slice is returned (capped for a partial batch, see above).
unknown_quoteThe payment referenced a quoteId the backend never issued.
unattributedA transfer to the treasury with no readable quoteId at all (Solana / TON), above a small dust threshold — smaller amounts are ignored.
ambiguous_memoOne Solana transaction carrying several different quoteIds — it cannot be split between them.
expiredThe payment landed after the quote's deadline (Solana / TON).
bad_signatureThe stored quote's signature did not verify against a trusted signer key — an operator-side issue, not something a payer can cause.

Refund states are refund_pending (recorded, return not yet sent) and refunded (return tx sent). Refunds are issued by the operator from the receiving account. The record for a refused payment (expired, unknown_quote, unattributed, ambiguous_memo, bad_signature) is written when the payment is indexed; a failed_order record appears within a few minutes of the batch settling. The return transaction follows once it is sent. Track them via the refund field on GET /api/order?from_tx_hash= (EVM tx hash or Solana signature), or in the payer's history (GET /api/orders?view=batches, status=refund for refunds only). On TON use the history: a refused payment never consumed its quote, so from_tx_hash=<quoteId> does not find it:

json
{
  "data": [ /* … the delivered/failed orders … */ ],
  "refund": {
    "status": "refunded",
    "reason": "failed_order",
    "from": { "symbol": "ETH", "amount": "0.0003", "address": "0xpayer", "txHash": "0x<payment>" },
    "refundTxHash": "0x<return tx>",
    "refundedAt": 1784120300
  }
}

Avoid refunds

The avoidable reasons are expired (submit your payment well before the quote deadline), unattributed and ambiguous_memo (always send exactly one quoteId in the memo / comment). Pay the exact fromTotal, from the quoted wallet, before the deadline, and never retry a payment whose transaction already confirmed — track it instead.

Tracking to completion ​

A robust client polls GET /api/order?from_tx_hash= until every order is terminal:

js
const terminal = o => o.status === 'completed' || o.status === 'failed'

for (;;) {
  const { data, refund } = await fetch(`${API}/order?from_tx_hash=${ref}`).then(r => r.json())
  if (data.length && data.every(terminal)) {
    const ok     = data.filter(o => o.status === 'completed').length
    const failed = data.filter(o => o.status === 'failed').length
    console.log(`${ok} delivered, ${failed} failed`)
    if (refund) console.log(`refund ${refund.status}: ${refund.from.amount} ${refund.from.symbol}`)
    break
  }
  await new Promise(r => setTimeout(r, 5000))
}
python
terminal = lambda o: o["status"] in ("completed", "failed")

while True:
    r = requests.get(f"{API}/order", params={"from_tx_hash": ref}, timeout=30).json()
    data, refund = r["data"], r["refund"]
    if data and all(map(terminal, data)):
        ok = sum(o["status"] == "completed" for o in data)
        print(f"{ok} delivered, {len(data) - ok} failed")
        if refund:
            print(f"refund {refund['status']}: {refund['from']['amount']} {refund['from']['symbol']}")
        break
    time.sleep(5)

ref is the EVM tx hash, the Solana signature, or the quoteId on TON. For a wait estimate while orders are still processing, call GET /api/orders/load?ref=<ref> with the tx hash, the signature or an order id — on TON pass data[0].id from the lookup above, /orders/load does not resolve a quoteId; to be notified instead of polling, subscribe to GET /api/orders/stream.