> For the complete documentation index, see [llms.txt](https://listed-exchange.gitbook.io/listed/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://listed-exchange.gitbook.io/listed/developers/build-swap.md).

# Build a swap

```
POST https://listed.exchange/api/v1/swap/build
```

Re-quotes the best venue against the live pool, simulates the settlement transaction, falls through to the next venue if it would revert, and returns a transaction ready for the user to sign. Pass the same `partnerFeeBps` / `partnerFeeRecipient` you used on the quote.

## Request

JSON body:

| Field                          | Required           | Description                                                                                                                                          |
| ------------------------------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from`                         | yes                | Input token symbol.                                                                                                                                  |
| `to`                           | yes                | Output token symbol.                                                                                                                                 |
| `amount`                       | yes                | Amount of `from` to sell, human units.                                                                                                               |
| `sender`                       | **yes**            | The address that will sign and swap. Must be a valid `0x…` address.                                                                                  |
| `fromAddress` / `fromDecimals` | for unknown tokens | As in [`/quote`](/listed/developers/quote.md#request).                                                                                               |
| `toAddress` / `toDecimals`     | for unknown tokens | As in `/quote`.                                                                                                                                      |
| `slippageBps`                  | no                 | Integer `0`–`5000`. Default `50`.                                                                                                                    |
| `partnerFeeBps`                | no                 | Your take, integer `0`–`50`. Default `0`. Requires `partnerFeeRecipient` when > 0.                                                                   |
| `partnerFeeRecipient`          | if fee > 0         | Wallet that should receive your share. Must be a `0x` address.                                                                                       |
| `preferEngine`                 | no                 | Force a specific venue: `kyberswap` / `rialto` / `xpath` / `nordstern` / `de1` / `ekubo`. Must still have a live quote. (Also accepted as `engine`.) |
| `fromPriceUsd` / `toPriceUsd`  | no                 | Live USD marks.                                                                                                                                      |

## Example

```bash
curl -X POST "https://listed.exchange/api/v1/swap/build" \
  -H "Authorization: Bearer $LISTED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "ETH",
    "to": "USDG",
    "amount": 1,
    "slippageBps": 50,
    "sender": "0x9CDb231cd70b7522C2b43ad18240649F9599f4bE",
    "partnerFeeBps": 10,
    "partnerFeeRecipient": "0xYourTreasury"
  }'
```

## Response

```json
{
  "route": { "...": "same shape as /quote's route" },
  "engine": "nordstern",
  "tx": {
    "to": "0x…",
    "data": "0x…",
    "value": "1000000000000000000",
    "allowanceTarget": null,
    "permit2": false,
    "chainId": 4663
  },
  "feeTransfer": null,
  "listedFeeBps": 0,
  "integratorFeeBps": 0,
  "partnerFeeBps": 10,
  "partnerFeeRecipient": "0xYourTreasury",
  "totalFeeBps": 10
}
```

### `tx`

The swap transaction. Submit it from `sender`'s wallet.

| Field             | Meaning                                                                                                                                |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `to`              | Contract to call.                                                                                                                      |
| `data`            | Calldata.                                                                                                                              |
| `value`           | Native ETH to send, in wei, as a **string**. `"0"` for ERC-20 input.                                                                   |
| `allowanceTarget` | If non-null and the input is an ERC-20: the spender your user must `approve` (or Permit2-sign) before the swap. Null for native input. |
| `permit2`         | `true` if the approval flow is a Permit2 signature rather than an ERC-20 `approve` (venue-dependent).                                  |
| `chainId`         | `4663`. Reject anything else.                                                                                                          |

The calldata already encodes a **minimum-output** at your `slippageBps`. If the pool can't deliver it, the transaction reverts — the user doesn't get a bad fill.

### The `feeTransfer` field

For **xPath** and **Nordstern** routes, the combined `totalFeeBps` can't be skimmed inside the swap transaction. The API returns:

```json
"feeTransfer": {
  "token": "native",
  "to": "0x65DbA8896387EF19F1A9eCA399B856D2E3B35D1B",
  "amountRaw": "2000000000000000"
}
```

Your client must execute this transfer **right before** the swap. The amount has already been carved out of what the venue swaps, so the sum still equals the user's `amount`.

* `token` is `"native"` or an ERC-20 address.
* `amountRaw` is in base units, as a string.
* If `feeTransfer` is `null`, do nothing — the fee is inside `tx`.

The recipient on `feeTransfer` is the LISTED fee wallet, which forwards your share to `partnerFeeRecipient` (or the venue attaches a transfer when it can). LISTED itself takes 0 bps — the skimmed amount is your take. This is not yet a second on-chain payee on every engine. See [Your fee](/listed/developers/your-fee.md).

{% hint style="warning" %}
Skipping `feeTransfer` doesn't break the swap, but your partner fee is never collected on that route. Handle it.
{% endhint %}

## Errors

| Status | Body                                 | Meaning                                                                                                                   |
| ------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `{ "error": "…" }`                   | Bad parameter (invalid address, non-integer slippage, partner fee without a recipient, missing `sender`, malformed JSON). |
| `422`  | `{ "error": "no executable route" }` | No venue can build this trade right now.                                                                                  |
| `502`  | `{ "error": "build failed" }`        | Upstream failure. Retry with backoff.                                                                                     |

## Notes

* Never cached. Always build immediately before the user signs.
* A quote and a build a few seconds apart can differ — that's the pool moving. The build's minimum-output is the real guarantee.

## Attribute a completed partner swap

Build responses now include `attribution: { buildId, validUntil, confirmations: 12 }` for individual partner keys and single-transaction routes. Save it alongside the returned transaction. `attribution: null` means attribution is unsupported (legacy shared key). The build endpoint now has a 30-second server budget.

After submitting the exact built transaction, report its hash using **the same API key**:

```bash
curl -X POST https://listed.exchange/api/v1/swap/confirm \
  -H "Authorization: Bearer $LISTED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"buildId":"UUID_FROM_BUILD","txHash":"0xTRANSACTION_HASH"}'
```

* `200`: `{ "recorded": true, "alreadyRecorded": false }`. Repeating the same report is safe.
* `202`: pending/not found, fewer than 12 confirmations, or a changed block. Respect `Retry-After`. Persist the hash and retry with backoff; stop and investigate if it remains pending.
* `400`: invalid JSON, build ID or transaction hash.
* `401` / `429`: authentication or the shared partner request budget.
* `404`: no build belonging to this active key.
* `409`: the build or transaction is already attributed elsewhere.
* `422`: reverted, mismatched, or executed outside the build's 20-minute attribution window.
* `503`: verification or storage temporarily unavailable. Retry with backoff.

The on-chain transaction must match the saved chain, sender, target, calldata and native value. It must execute within `validUntil`; reporting can happen later. Gas price and nonce may change. Approval and fee-transfer transactions are not the swap: report the swap hash. Account-abstraction/batched outer transactions are not supported. A replacement transaction can be reported if it still exactly matches the build. Twelve confirmations are a depth check, not a guarantee against every future reorg.

Partner admin shows confirmed swap count, last confirmation, and **estimated USD input notional** for confirmed builds. Prices come from a server-side market lookup at build time, never the caller's price marks. Missing prices remain unpriced and are counted separately. This is not actual received volume, collected fees, or guaranteed fair-market valuation; DEX prices can move or be manipulated and native refunds can reduce actual spend. Only reported transactions appear. Old builds have no attribution and cannot be backfilled. Each key has its own totals; replacing a key starts a separate set of metrics.
