> ## Documentation Index
> Fetch the complete documentation index at: https://docs.etherfuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Swaps

> How to test token-to-token swap flows in the sandbox environment

<Info>
  **Prerequisite:** Your organization must be approved before it can quote. Swaps do not need a bank account. For onboarding your customers, see [KYC Overview](/v2/guides/onboarding).
</Info>

A swap exchanges one asset for another on the same chain. In v2 a swap is a quote and an order between two wallet payment accounts, the same way an onramp or offramp is. The destination can be the same wallet as the source, or another wallet on the same chain.

Every call below runs for one organization, set as `$ORGANIZATION_ID` in the examples. Use your own organization's id from `GET /me`, or a customer organization's id when you swap for that customer. Register the wallets, quote and order under the same id. See [Organizations in the route](/v2/overview#organizations-in-the-route).

```bash theme={null}
export ORGANIZATION_ID=<organization_id>
```

<Warning>
  Swaps run on **Stellar**, **Solana** and **Monad**. The source and destination wallets must be on the **same chain**. To move the same token to a wallet on another chain, quote a [cross-chain transfer](/v2/overview) instead. A quote between different tokens on different chains returns `400` `invalid-request`. Base, Polygon and Avalanche support onramps and offramps, not swaps.
</Warning>

<Info>
  **Stellar swaps need a funded, trustlined wallet.** The wallet must hold XLM for the network fee and a trustline for the asset it receives. See the [Stellar](/v2/guides/chains/stellar#offramps-and-swaps-fund-the-wallet-first), [Solana](/v2/guides/chains/solana) and [Monad](/v2/guides/chains/monad) chain guides for per-chain setup.
</Info>

## Swap flow

<Steps>
  <Step title="Discover assets">
    <Expandable title="Details">
      Either of these endpoints gives you the asset identifiers for a quote:

      * [GET /supported\_assets](/v2/api-reference/assets/list-supported-assets) requires auth. Pass `paymentAccountId` with a wallet's id to list the tokens on that wallet's chain.
      * [GET /lookup/stablebonds](/v2/api-reference/lookup/list-stablebonds) is public. It returns every stablebond on every chain, with pricing and supply data.

      Use a token's `asset` from the first, or the `identifier` from the second, as `sourceAsset` and `destinationAsset`.

      **Swap rules:**

      * At least one side must be a stablebond. The other side can be a stablecoin such as USDC, so `USDC → CETES` and `CETES → USDC` both work.
      * Both assets must be stablebonds or supported stablecoins. Non-stable tokens (SOL, XLM) return `400` `invalid-request`.
      * Stablecoin-to-stablecoin swaps (`USDC → USDT`) return `400` `invalid-request`.
      * The two assets must differ.

      <Warning>
        **Asset identifier format differs by chain:**

        * **Solana:** Base58 mint address (for example `AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q`)
        * **Stellar:** `CODE:ISSUER` (for example `CETES:GC3CW7...`)
        * **Monad:** the raw token contract address only (for example `0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29`)
      </Warning>

      <Tip>
        **USDC identifiers:**

        * **Stellar sandbox:** `USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5`
        * **Stellar production:** `USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN`
        * **Solana sandbox:** `4D4r1KuS9WxbPCzzAJnWtYUJSSzDEZzrF1CbwajWGVjg`
        * **Solana production:** `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`
      </Tip>
    </Expandable>
  </Step>

  <Step title="Register the wallets: POST /organization/{organizationId}/payment_account">
    <Expandable title="Details">
      Register the wallet that sends the source asset. To deliver to a different wallet, register that one too. See [Register payment account](/v2/api-reference/payment-accounts/register-payment-account) for the full schema.

      ```bash Sandbox theme={null}
      curl -X POST https://api.sand.etherfuse.com/organization/$ORGANIZATION_ID/payment_account \
        -H "Authorization: <api_key>" \
        -H "Content-Type: application/json" \
        -d '{
          "kind": "wallet",
          "label": "Treasury wallet",
          "accountDetails": {
            "kind": "address",
            "address": "GDUKMGUGD3V6VXTU2RLAUM7A2FABLMHCPWTMDHKP7HHJ6FCZKEY4PVWL",
            "chain": "stellar"
          }
        }'
      ```

      The source wallet needs `source` ready on its chain rail, and the destination wallet needs `destination` ready. Read the account again with [GET /organization/\{organizationId}/payment\_account/\{id}](/v2/api-reference/payment-accounts/get-payment-account) to check.
    </Expandable>
  </Step>

  <Step title="Create a quote: POST /organization/{organizationId}/quote">
    <Expandable title="Details">
      Quote between the two wallet accounts. Both accounts must belong to the organization in the route. Quotes expire **2 minutes** after they are created (`expiresAt`). The response includes `rateBeforeFees`, `rateAfterFees` and `midMarketRate`. See [Create quote](/v2/api-reference/quotes/create-quote) for the full schema.

      <CodeGroup>
        ```bash Sandbox theme={null}
        curl -X POST https://api.sand.etherfuse.com/organization/$ORGANIZATION_ID/quote \
          -H "Authorization: <api_key>" \
          -H "Content-Type: application/json" \
          -d '{
            "source": { "type": "stored", "id": "<wallet_payment_account_id>" },
            "destination": { "type": "stored", "id": "<wallet_payment_account_id>" },
            "sourceAmount": "10",
            "sourceAsset": "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
            "destinationAsset": "CETES:GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4"
          }'
        ```

        ```bash Production theme={null}
        curl -X POST https://api.etherfuse.com/organization/$ORGANIZATION_ID/quote \
          -H "Authorization: <api_key>" \
          -H "Content-Type: application/json" \
          -d '{
            "source": { "type": "stored", "id": "<wallet_payment_account_id>" },
            "destination": { "type": "stored", "id": "<wallet_payment_account_id>" },
            "sourceAmount": "10",
            "sourceAsset": "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
            "destinationAsset": "CETES:GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4"
          }'
        ```
      </CodeGroup>

      <Tip>
        **Partner fees:** We apply your organization's partner fee automatically. To set it for one quote, add `"partnerFeeBpsOverride": 100`. We cap values above 500. See [Partner fees](/v2/fees#partner-fees).
      </Tip>
    </Expandable>
  </Step>

  <Step title="Create the order: POST /organization/{organizationId}/order">
    <Expandable title="Details">
      Send the `quoteId`. The response is `201` with the full order, and the order's `id` equals the `quoteId`. See [Create order](/v2/api-reference/orders/create-order) for the full schema.

      ```bash Sandbox theme={null}
      curl -X POST https://api.sand.etherfuse.com/organization/$ORGANIZATION_ID/order \
        -H "Authorization: <api_key>" \
        -H "Content-Type: application/json" \
        -d '{ "quoteId": "<quote_id>" }'
      ```

      As on an offramp, `fundingInstructions.rail` is the chain, and `fundingInstructions.destination` gives the `address` to send to, a `memo` when one is needed, and a `preparedTransaction` when we built one.
    </Expandable>
  </Step>

  <Step title="Send the source asset">
    <Expandable title="Details">
      The swap does not start until the customer's wallet sends the source asset. Fund the order before `fundingDeadline` in one of two ways:

      1. **Sign the prepared transaction.** When `preparedTransaction` is present, sign `transaction` with the customer's wallet and submit it to the network.
      2. **Send a transfer yourself.** Send the order's `source.amount` of `source.asset` to `address`, with `memo` when it is present.

      <Warning>
        **Prepared transactions go stale.** A Solana or Stellar transaction goes stale after `refreshAfter`. A Stellar or EVM transaction also goes stale when the wallet sends another transaction first. In either case, call [POST /organization/\{organizationId}/order/\{id}/funding\_instructions/refresh](/v2/api-reference/orders/refresh-funding-instructions). The new funding instructions arrive a moment later in an `orderUpdated` webhook and on GET.
      </Warning>
    </Expandable>
  </Step>

  <Step title="Track the order: GET /organization/{organizationId}/order/{id}">
    <Expandable title="Details">
      We receive the source asset, run the swap on the chain's venue, and deliver the result to the destination wallet. The status moves `created` → `funded` → `completed`. Read the order with [Get order](/v2/api-reference/orders/get-order), or subscribe to [`orderUpdated`](/v2/api-reference/webhook-events/orderupdated) with [POST /webhook\_subscription](/v2/api-reference/webhook-subscriptions/create-webhook-subscription).

      ```bash Sandbox theme={null}
      curl -H "Authorization: <api_key>" \
        https://api.sand.etherfuse.com/organization/$ORGANIZATION_ID/order/<order_id>
      ```

      Swaps are orders, so [List orders](/v2/api-reference/orders/list-orders) returns them with your onramps and offramps.
    </Expandable>
  </Step>
</Steps>

<Expandable title="Chain-specific swap examples">
  **Stellar:** USDC to CETES:

  ```json theme={null}
  {
    "source": { "type": "stored", "id": "<stellar_wallet_payment_account_id>" },
    "destination": { "type": "stored", "id": "<stellar_wallet_payment_account_id>" },
    "sourceAmount": "10",
    "sourceAsset": "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
    "destinationAsset": "CETES:GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4"
  }
  ```

  **Solana:** USDC to CETES:

  ```json theme={null}
  {
    "source": { "type": "stored", "id": "<solana_wallet_payment_account_id>" },
    "destination": { "type": "stored", "id": "<solana_wallet_payment_account_id>" },
    "sourceAmount": "10",
    "sourceAsset": "4D4r1KuS9WxbPCzzAJnWtYUJSSzDEZzrF1CbwajWGVjg",
    "destinationAsset": "AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q"
  }
  ```
</Expandable>

<Expandable title="Common errors">
  | Error | Cause | Fix |
  | - | - | - |
  | `400` `invalid-request`, no stablebond | Neither side is a stablebond (for example a stablecoin-to-stablecoin swap) | Make one side a stablebond. Use identifiers from `/lookup/stablebonds` |
  | `400` `invalid-request`, non-stable asset | A side is a non-stable token (SOL, XLM, ETH) | Use a stablebond or a supported stablecoin on both sides |
  | `400` `invalid-request`, cross-chain | The two wallets are on different chains and hold different tokens | Swap on one chain, or send the same token across chains |
  | `400` `invalid-request`, same asset | `sourceAsset` equals `destinationAsset` | Pick two different assets |
  | `400` `quote-expired` | The quote is older than 2 minutes, or does not exist | Create a new quote |
  | `409` `payment-account-not-ready` | A wallet is not `ready` in the needed direction | Check the wallet's `rails` |
  | `422` `quote-unavailable` | We cannot quote this path right now, for example thin sandbox liquidity, or the chain has no swap venue | Retry with backoff. Swaps run only on Stellar, Solana and Monad |

  See [Errors](/v2/errors) for every problem type.
</Expandable>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.