# Build and deposit

> Create the order, choose recipients, and send the deposit from the user's wallet.

`POST /v2/build` checks the request against its quote, signs the order, stores it in `pending_deposit`, and returns what the user's wallet must send. Nothing happens on-chain until the user deposits.

## The request

```ts
{
  ...quote.build,        // from GET /v2/quote, unchanged, terms included
  depositor: string,     // EVM address that will send the deposit
  payoutAddr?: string,   // receives the payout; defaults to depositor
  refundAddr?: string    // receives any refund; defaults to depositor
}
```

`depositor` is the address that will sign and send the deposit. It must be lowercase or exactly EIP-55 checksummed.

## Build once

**`/v2/build` is not idempotent.** Every call that succeeds creates a new order with a new `orderId`. Do not retry a build that returned an `orderId`. Persist the `orderId` before you hand anything to the wallet: it keys every later call.

Request validation failures happen before order creation. A `500` or `502`, however, can leave a reserved TRON deposit address or an order record whose tracking did not start. An error response does not give you a usable deposit plan. Do not fund an order recovered from a failed build without confirming it with Khalani. Back off and retry at most once, then contact Khalani with the `x-request-id`; repeated failures can exhaust the deposit-address pool. See [Errors and retries](/guides/errors/#build-errors).

A call that returned no response at all may still have created an order. Look for it with `GET /v2/orders?depositor=…&from=…&to=…`, which lists newest first and includes `pending_deposit` orders. A matching record alone does not prove the build completed or tracking started.

- **EVM source:** the listing confirms the order exists but does not return its transactions, so you cannot deposit into it. Build again. The unfunded order stays at `pending_deposit` and holds nothing.
- **TRON source:** the entry carries `depositAddress` and `srcAmountSpoke`, but confirm the recovered order with Khalani before funding it when the build outcome is unknown.

## Recipients

`payoutAddr` defaults to the depositor's address on the destination chain, and `refundAddr` to the depositor's address on the source chain. Only a regular (EOA) wallet is sure to control the same address on every chain.

- **Smart-contract wallets** must pass `payoutAddr` and `refundAddr` explicitly. Depositing from one does not enable self-service cancellation: the [cancel endpoint](/guides/cancel-and-refunds/#cancel-early) accepts ECDSA signatures, not ERC-1271.
- **TRON as the destination** requires `payoutAddr`. **TRON as the source** requires `refundAddr`. Neither can default to an EVM depositor.
- **Every TRON recipient must be an activated account**: one that has received TRX or a token before. An unactivated address returns `400`. Fund it once before you build.

## EVM source: send the transactions

The response holds an optional `approveTxs` list and one `depositTx`:

```ts
{
  orderId: string,
  nonce: string,
  approveTxs?: [{ to, data, value: "0", chainId }],
  depositTx: { to, from, data, value, chainId, gasLimit }
}
```

The rules for the wallet:

1. **Send each `approveTxs` entry in order and check that its receipt succeeded**, then send the deposit. With viem, require `receipt.status === "success"`: waiting for a receipt alone does not rule out a revert. Usually there is one exact-amount approval. A leading `approve(0)` appears only when a non-zero allowance already exists, because USDT-style tokens reject changing one non-zero allowance to another.
2. **Send `depositTx` from `depositor`, exactly once.** `depositTx.from` equals `depositor`. A deposit sent from any other account does not create an order, and neither does a second deposit for the same order. Those funds are held until Khalani returns them: contact Khalani with the `orderId`. Re-send only if the first deposit never landed.
3. **Send `value` with the deposit.** It is the relay fee, in hex wei. Sending less reverts with `InsufficientValueForFee`.
4. **Mind the formats.** `approveTxs[].value` is the decimal string `"0"`; `depositTx.value` is hex. Both are wei. `gasLimit` is a fixed hex value, not an estimate.

With an EIP-1193 wallet, forward the fields as they are:

```ts
await provider.request({
  method: "eth_sendTransaction",
  params: [{ from: depositTx.from, to: depositTx.to, data: depositTx.data, value: depositTx.value, gas: depositTx.gasLimit }],
});
```

`depositTx` carries no nonce or gas price, so the wallet can sign it again, and a reverted deposit costs only gas and leaves the order depositable. It goes stale in three ways: the order's deadline passes, the relay fee in `value` rises, or the allowance it was built against changes. If any of those happens, build again.

:::caution[The deadline runs from the build]
The order's deadline is the build time plus your `orderTtl`. A deposit that reaches the hub after it does not publish. Ask the user to deposit right after the build.
:::

## Non-EVM source (TRON): transfer to an address

A TRON source has no deposit contract, so the response has no transactions:

```ts
{ orderId: string, nonce: string, depositAddress: string, expectedAmount: string, deadline: number }
```

Transfer **exactly** `expectedAmount` of the source token to `depositAddress`, from any wallet, before `deadline` (unix seconds). A transfer of any other amount is rejected rather than credited. On a TRON source the order skips `deposited` and its `deposit` field stays `null`.

## After the deposit

[Track the order](/guides/track-orders/) until delivery succeeds or fails. See the [`POST /v2/build` reference](/api/operations/buildorder/) for every field and status.
