Skip to navigation

Sell walkthrough

On a SELL, the customer’s own USDT is locked in escrow and the merchant pays fiat to the customer’s account outside Sinbad. The customer’s wallet therefore needs enough USDT, enough POL for network fees, and a sufficient token allowance before the escrow transaction. This walkthrough assumes an ordinary customer wallet. See Wallets and gas for signing and fees.

Before you start

  • A customer session for the customer. See Create a session.
  • A saved payment account where the customer will receive fiat. See Saved payment accounts.
  • A customer wallet with enough USDT for the sale and enough POL for network fees on up to four transactions: two approvals, the escrow creation and the release.

1. Ask for an offer

Call POST /v1/offers/best with side set to SELL. The request is the same as in the Buy walkthrough, with these differences:

  • With amount.mode set to FIAT, value is the market value of the USDT in the selected currency, before merchant margin and fees. Sinbad converts it to USDT at the market rate, then deducts margin and fees from the fiat proceeds. The offer returns the resulting USDT quantity in breakdownInput.amount.
  • With USDT, value is an exact token quantity.
  • Always send savedPaymentMethodId, the account that receives the fiat. If you omit it, Sinbad uses the customer’s first saved account for that payment method.

An amount outside every listing’s limits returns 200 with empty results. If the only match is a listing that uses the customer’s own wallet, the call fails with 400 VALIDATION_ERROR and details.reason: "SELF_TRADE_NOT_ALLOWED". Offer and breakdown requests share a rate limit per customer. See Limits and limitations.

2. Create the breakdown

Call POST /v1/trade-breakdowns with the chosen offer’s breakdownInput, exactly as received, and an Idempotency-Key header. Send every field unchanged, including paymentMethodId and savedPaymentMethodId. A changed selection can return 400 or 409, and a missing selection can return 404. In either case, ask for a fresh offer.

Sinbad responds with 201:

  • breakdownId and status: "AWAITING_FUNDING".
  • expiresAt, 300 seconds (5 minutes) after creation.
  • tradeDetails, including YouReceive.
  • escrowTransaction, the prepared transaction for the customer’s wallet to sign.
  • approvalRequired, with an approval snapshot of the wallet’s current allowance.

Show tradeDetails to the customer and continue only if they agree.

If the wallet holds less USDT than the amount to sell, the call fails with 400 INSUFFICIENT_BALANCE. It does not include details.kind: "NATIVE_GAS", because this is a token shortage, not a fee shortage. The failed call can still hold the customer’s open SELL trade. After the customer tops up, repeat the identical request with the same Idempotency-Key while the breakdown is live, or use a new key to get a fresh quote that replaces it.

See One open trade per side for when a new breakdown replaces the earlier one and when it fails.

3. Approve the token if needed

If approvalRequired is true, call POST /v1/trade-breakdowns/{breakdownId}/approval with an Idempotency-Key header and an empty body. Sinbad returns up to two prepared transactions, in the order to run them:

  • If the wallet already has a non-zero allowance that is too small, the first is an approvalReset (approve 0) and the second is the approval for the exact amount.
  • Otherwise, there is one approval.
  • If no approval is needed, transactions is empty.

Sign and submit each one, and wait until it is CONFIRMED before you sign the next. Sinbad checks the escrow transaction against the current chain state, so it fails until the allowance is confirmed. Approval does not create a trade and does not extend the 300-second deadline.

If an approval fails and you need a new one, call the endpoint again with a new Idempotency-Key. The same key returns the same prepared transactions.

4. Sign and submit the escrow transaction

Sign escrowTransaction and call POST /v1/transactions with its preparationId and an Idempotency-Key. This locks the customer’s USDT.

POST /v1/transactions
Idempotency-Key: tx-<preparationId>
Content-Type: application/json
{ "preparationId": "<escrowTransaction.preparationId>", "signedTransaction": "0x02..." }

Confirm every required approval before you submit the escrow transaction, and make sure the escrow transaction is included in a block before expiresAt. Confirmation tracking can finish after expiresAt, so keep polling the existing transaction.

The wallet needs POL for every transaction. If it is short, the call fails with 400 INSUFFICIENT_BALANCE and details.kind: "NATIVE_GAS". For nonces, retries and unknown outcomes, see Wallets and gas.

5. Wait for the trade

The trade exists only after the escrow is confirmed on chain. Either:

The breakdown’s other statuses are AWAITING_FUNDING, EXPIRED and REVIEW_REQUIRED. You can also use the trade.created and trade.updated webhooks. The new trade starts in CREATED. Trade status can lag the chain, so keep polling until confirmed and reconcile with GET /v1/trades/{tradeId}.

On a funded SELL trade in CREATED, PAID or DISPUTED, paymentInstructions contains the customer’s saved receiving-account snapshot. It becomes null after settlement. Trade lists and webhooks show only a displayLabel, never the account data.

6. The merchant pays the fiat

The merchant sends fiat to the customer’s saved account and marks the trade paid. The trade becomes PAID. The merchant may upload proof of payment, which the customer can read with GET /v1/trades/{tradeId}/proof. The customer cannot upload proof on a SELL. See Payment proof.

7. The customer releases

Release the USDT only after the fiat has actually arrived:

  1. Call POST /v1/trades/{tradeId}/actions/{action}/execution with the release action. The call takes no body and no Idempotency-Key, and every call returns a new prepared transaction.
  2. Have the wallet sign it.
  3. Submit it with POST /v1/transactions, which does need an Idempotency-Key.
POST /v1/trades/{tradeId}/actions/release/execution
-> { "transaction": { "preparationId": "...", "purpose": "release", ... } }
POST /v1/transactions
Idempotency-Key: tx-<preparationId>
{ "preparationId": "<transaction.preparationId>", "signedTransaction": "0x02..." }

The USDT goes to the merchant and the trade becomes RELEASED. The customer’s SELL side is free for a new trade. See release and Trade actions.

What can go wrong

SituationWhat to do
The fiat does not arriveA SELL has no cancel action. While the trade is PAID, the customer can open a dispute at any time with the reason payment_not_received. While it is still CREATED, a dispute is possible only after the dispute window has passed. Before that, preparing the dispute fails with 409 CONFLICT, details.reason: "TOO_EARLY" and details.availableAt. See Disputes.
The trade stays openSinbad does not automatically cancel an unpaid SELL as it does for a BUY. Open a dispute when it is allowed, or contact Sinbad support.
The wallet is short of USDTSee step 2.
The customer needs to talk to the merchantUse Chat and images. It works throughout the trade.

Next steps