> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getsinbad.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getsinbad.com/_mcp/server.

# Wallets and gas

> Sign customer transactions, pay network fees in POL and recover safely when you do not know whether a submission reached the chain.

This page is for partners whose customers use ordinary wallets. The customer's registered wallet signs every transaction and pays its network fee ("gas") in POL. The [Buy walkthrough](/buy-walkthrough) and [Sell walkthrough](/sell-walkthrough) tell you when each transaction is needed.

> **Note**
>
> Partners that use smart accounts execute prepared transactions through their own wallet infrastructure and arrange network fees or sponsorship themselves. Report the transaction hash of the transaction included in a block through the same transaction API. Do not report a UserOperation identifier, and do not send a UserOperation signature in `signedTransaction`, which accepts only a serialized signed transaction.

## 1. Take the prepared transaction

Every blockchain step comes back from Sinbad as a prepared transaction:

| Field                                    | Meaning                                                                                                                                                         |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `preparationId`                          | The ID you send back when you submit the signed transaction.                                                                                                    |
| `purpose`                                | What the transaction does. One of `approvalReset`, `approval`, `createEscrow`, `markPaid`, `release`, `cancelPrePaid`, `openDispute` or `mutualResolveDispute`. |
| `chainId`, `from`, `to`, `data`, `value` | The transaction to sign. `value` is always `"0"` and `from` is always the customer's registered wallet.                                                         |
| `expiresAt`                              | The signing deadline. For approval and escrow transactions it is the breakdown's deadline. For trade actions it is `null`, so they have no signing deadline.    |

Prepared transactions come from a breakdown (`escrowTransaction`), from [`POST /v1/trade-breakdowns/{breakdownId}/approval`](https://docs.getsinbad.com/api-reference/transactions/prepare-token-approval) (`transactions`) and from [`POST /v1/trades/{tradeId}/actions/{action}/execution`](https://docs.getsinbad.com/api-reference/trade-actions/prepare-partner-customer-action) (`transaction`). Use `to`, `data` and `value` unchanged.

## 2. Build and sign the transaction

Sign an EIP-1559 (type 2) transaction. You choose the nonce, gas limit and fees, and the customer's wallet pays for them.

* Set `chainId`, `to`, `data` and `value` from the prepared transaction, and leave the access list empty.
* Use the wallet's next nonce, counting pending transactions: call `eth_getTransactionCount` with the `pending` tag.
* Set `gasLimit` and `maxFeePerGas` above 0, and keep `maxPriorityFeePerGas` at or below `maxFeePerGas`. Estimate the gas for the call and add a margin.

Before it accepts signed bytes, Sinbad checks that the signer is `from` and that the chain, destination, calldata and value match the prepared transaction. A mismatch returns `400 VALIDATION_ERROR`.

## 3. Submit the signed transaction

Call [`POST /v1/transactions`](https://docs.getsinbad.com/api-reference/transactions/submit-prepared-transaction) with the signed bytes in `signedTransaction` (hex, at most 16,000 characters) and the `preparationId`. The `Idempotency-Key` header is required: 8 to 128 characters from `A-Z a-z 0-9 _ -`.

```http
POST /v1/transactions
Idempotency-Key: tx-<preparationId>
Content-Type: application/json

{ "preparationId": "<preparationId>", "signedTransaction": "0x02..." }
```

Sinbad broadcasts the transaction and tracks it. The answer is `202`, with a `transaction` object (`status`, `transactionHash`, `confirmations`, `requiredConfirmations`) and a `trade` that is `null` until the trade exists. A `202` means tracking has started, not that the step is done.

To broadcast the transaction yourself, send `transactionHash` instead of `signedTransaction`. Send exactly one of the two, and use the hash of the transaction that was included in a block. Two responses are normal when you report a hash:

* A hash report for an escrow transaction can return `202` with `PENDING_CONFIRMATION` before a receipt is visible.
* A hash report for an action that is unavailable or unconfirmed can return `502 UPSTREAM_ERROR`. Retry the same report later, and do not broadcast again.

## 4. Keep POL in the customer's wallet

The wallet pays gas in POL for every transaction: `approvalReset`, `approval`, `createEscrow`, `markPaid`, `cancelPrePaid`, `release`, `openDispute` and `mutualResolveDispute`.

When you submit signed bytes, Sinbad checks the balance against the transaction's maximum fee (`gasLimit` times `maxFeePerGas`), including pending transactions. If the wallet is short, the call fails before anything is broadcast or recorded with `400 INSUFFICIENT_BALANCE` (numeric ID `4001`) and `details.kind: "NATIVE_GAS"`. The details include:

| Field                                              | Meaning                               |
| -------------------------------------------------- | ------------------------------------- |
| `asset`                                            | `"POL"`                               |
| `chainId`                                          | The chain's integer ID.               |
| `requiredBalanceWei`, `balanceWei`, `shortfallWei` | The three amounts as integer strings. |
| `requiredBalancePol`, `balancePol`, `shortfallPol` | The same amounts in POL.              |

Top up at least the shortfall plus a margin, then submit the same signed bytes again. The prepared transaction stays valid until its `expiresAt`. Because a breakdown lives for only 300 seconds, fund customer wallets in advance and do not wait for this error.

If a SELL wallet is short of USDT, the error has no `details.kind: "NATIVE_GAS"`, because that is a token shortage. A report that sends only `transactionHash` is not balance-checked.

> **Note**
>
> Sinbad checks the nonce before the balance. If the wallet's pending nonce no longer equals the one you signed, the call fails with `409 CONFLICT` ("Wallet nonce changed"). Read the nonce again, sign again and submit.

## 5. Track the status

Poll [`GET /v1/transactions/{preparationId}`](https://docs.getsinbad.com/api-reference/transactions/get-prepared-transaction-status) and use webhooks for the trade itself. Read `requiredConfirmations` from the response rather than hard-coding a value. The trade can update after the transaction confirms, so read the trade again instead of submitting again.

| `transaction.status`   | Meaning                                                                                                                       |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `READY_TO_SIGN`        | Prepared. Nothing has been submitted.                                                                                         |
| `BROADCAST_UNKNOWN`    | Submitted. Sinbad has not yet seen the network accept it, and keeps checking and re-sending the same signed bytes.            |
| `PENDING_CONFIRMATION` | The transaction is on chain and waiting for confirmations.                                                                    |
| `CONFIRMED`            | A successful receipt at the required depth.                                                                                   |
| `REVERTED`             | The transaction failed on chain.                                                                                              |
| `REJECTED`             | A check failed before Sinbad broadcast the transaction: a conflict, a disallowed action or no liquidity.                      |
| `REPLACED`             | Another transaction with the same nonce was confirmed instead.                                                                |
| `EXPIRED`              | The signing window ended without a broadcast.                                                                                 |
| `REVIEW_REQUIRED`      | The evidence does not match, or a broadcast stayed unresolved for more than 24 hours. Contact Sinbad and do not submit again. |

## 6. Use one transaction per prepared transaction

Sinbad normally binds one transaction hash to each prepared transaction.

* After a definite failure (`REVERTED`, `REJECTED` or `EXPIRED`), request a new prepared transaction only if the action is still needed and permitted. A trade action returns a new prepared transaction on every call. An approval needs a new `Idempotency-Key`. A failed escrow transaction needs a new breakdown, once the previous breakdown has released the customer's open trade.
* If you reported the wrong hash for an externally sent escrow transaction, report the correct hash for the same prepared transaction with a new `Idempotency-Key`. Sinbad accepts the correction only after a confirmed receipt proves that exact checkout. It cannot replace tracking that is already confirmed.

> **Warning**
>
> A pending, missing or uncertain receipt is not permission to send another transaction. The first one may still execute.

## 7. Retry safely

The `Idempotency-Key` is bound to the `preparationId` and to a hash of the signed bytes.

* Sending the same bytes with the same key is a safe replay. It returns the current state and never creates a second transaction.
* Sending different bytes with the same key returns `409 CONFLICT`.
* After Sinbad has accepted a transaction for a prepared transaction, different bytes for it are refused with `409 CONFLICT`, even with a new key. Re-sign with different fees only while nothing has been accepted.
* A call that failed before acceptance, such as one with `NATIVE_GAS`, recorded nothing. Fix the cause and submit again. Reuse the key only with identical signed bytes. If you sign different fields, use a new key.
* Sinbad keeps submission-key bindings for 30 days.

The verified escrow hash correction in step 6 is the one exception that needs a fresh key. It never broadcasts another transaction.

## 8. When you do not know whether a submission reached the chain

If your submit call times out, or the status is `BROADCAST_UNKNOWN`, assume the transaction may still reach the chain.

1. Do not build a second transaction for the same step, and do not create a new breakdown or action.
2. Either send the identical request again (same key, same `preparationId`, same signed bytes), or call [`GET /v1/transactions/{preparationId}`](https://docs.getsinbad.com/api-reference/transactions/get-prepared-transaction-status). Sinbad keeps tracking and re-sends the same signed bytes if needed.
3. Wait for `PENDING_CONFIRMATION`, `CONFIRMED`, `REVERTED`, `REPLACED`, `EXPIRED` or `REJECTED`.
4. If the result is `EXPIRED`, `REJECTED` or `REVERTED`, prepare the step again as described in step 6.

Sinbad simulates the call before it broadcasts signed bytes. If the simulation fails, the response is a generic `503 UPSTREAM_ERROR`. Check the allowance, the balance and the trade state before you replay the identical bytes. Temporary RPC problems also return `502` or `503`: keep the request and retry with backoff. Unresolved external escrow tracking reaches `REVIEW_REQUIRED` after 24 hours without expiring the breakdown or releasing the customer's open trade.