> ## 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.

# Offramps

> How to test offramp flows in the sandbox environment

> [!NOTE]
>
> **Prerequisite:** You need a KYC-approved wallet with tokens and an active bank account on your organization.
> See [KYC Overview](https://docs.etherfuse.com/guides/onboarding.md) for what verification requires, and [KYC WebSDK](https://docs.etherfuse.com/guides/kyc-websdk.md) for the hosted flow.

> [!NOTE]
>
> **Redemptions are independent of how much was onramped.** A customer can sell (redeem) any amount of a stablebond up to their current balance — it does not need to match the amount they originally bought. There's no paired buy/sell or lot tracking; partial redemptions and redeeming tokens acquired elsewhere both work.

## Offramp Flow

**1. Discover Assets**

**Details**

Either of these endpoints will give you the asset identifiers needed for quotes:

* [GET /ramp/assets](https://docs.etherfuse.com/api-reference/assets/list-assets.md) — Requires auth. Returns all rampable stablecoins and stablebonds for the specified blockchain. The `currency` parameter controls sort priority (matching assets appear first). Best for building integrations where you already know the customer's wallet.

* [GET /lookup/stablebonds](https://docs.etherfuse.com/api-reference/lookup/list-stablebonds.md) — Public, no auth. Returns all stablebonds across all chains with pricing and supply data. Best for exploring what's available before onboarding a customer.

Use the `identifier` from either response as the asset value in your quote.

> [!WARNING]
>
> **Asset identifier format differs by chain:**
>
> * **Solana** — Base58 mint address (e.g. `AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q`)
> * **Stellar** — `CODE:ISSUER` format (e.g. `CETES:GC3CW7...`)
> * **EVM (Base, Polygon, Monad, Avalanche)**: Raw contract address only (e.g. `0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29` on Base sandbox)
>
> Using `SYMBOL:0x...` on EVM chains will return `UnsupportedBlockchain`.

**2. Create a Quote — POST /ramp/quote**

**Details**

All orders require a quote. Quotes expire after **2 minutes**. For offramps, set `type: "offramp"` with the token identifier as `sourceAsset` and the fiat currency as `targetAsset`. See [POST /ramp/quote](https://docs.etherfuse.com/api-reference/quotes/get-quote.md) for the full schema.

```bash Sandbox theme={null}
curl -X POST https://api.sand.etherfuse.com/ramp/quote \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "quoteId": "<uuid>",        # You generate this UUID
    "customerId": "<org_uuid>",  # From onboarding
    "blockchain": "base",
    "quoteAssets": {
      "type": "offramp",
      "sourceAsset": "0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29",
      "targetAsset": "MXN"
    },
    "sourceAmount": "50"
  }'
```

```bash Production theme={null}
curl -X POST https://api.etherfuse.com/ramp/quote \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "quoteId": "<uuid>",        # You generate this UUID
    "customerId": "<org_uuid>",  # From onboarding
    "blockchain": "base",
    "quoteAssets": {
      "type": "offramp",
      "sourceAsset": "0x834df4C1d8f51Be24322E39e4766697BE015512F",
      "targetAsset": "MXN"
    },
    "sourceAmount": "50"
  }'
```

> [!TIP]
>
> **Partner fees:** If your organization has a partner fee configured, it is applied automatically. To override per-quote, add `"partnerFeeBps": 100` (0–500) to the request body. See [Partner Fees](https://docs.etherfuse.com/fees.md#partner-fees) for details.

**3. Create an Order — POST /ramp/order**

**Details**

The `quoteId` carries the direction, blockchain, and amounts from your quote — no need to repeat them. The response includes `orderId` and a `burnTransaction` — a pre-built, unsigned transaction that burns the customer's tokens. See [POST /ramp/order](https://docs.etherfuse.com/api-reference/orders/create-order.md) for the full schema.

> [!WARNING]
>
> **Do not build your own transaction** unless using [anchor mode](#anchor-mode-stellar-only). Always use the `burnTransaction` provided by Etherfuse — it contains the correct parameters, amounts, and contract calls.

> [!NOTE]
>
> **EVM offramps** on Base, Polygon, Monad, and Avalanche require the customer to sign a bridge transaction and hold the chain's native currency for gas and the bridge fee. **Sponsored EVM offramps**, where enabled, use the [approval flow](https://docs.etherfuse.com/guides/chains/base.md#offramps-approve-the-token-first) instead. **Stellar offramps** require the wallet to already hold XLM and the asset's trustline, or no `burnTransaction` is produced; see [Stellar](https://docs.etherfuse.com/guides/chains/stellar.md#offramps-and-swaps-fund-the-wallet-first).

```bash Sandbox theme={null}
curl -X POST https://api.sand.etherfuse.com/ramp/order \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "<uuid>",              # You generate this UUID
    "bankAccountId": "<bank_account_uuid>", # From onboarding
    "cryptoWalletId": "<wallet_uuid>",   # From onboarding
    "quoteId": "<quote_id_from_step_1>"  # From Step 2
  }'
```

```bash Production theme={null}
curl -X POST https://api.etherfuse.com/ramp/order \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "<uuid>",              # You generate this UUID
    "bankAccountId": "<bank_account_uuid>", # From onboarding
    "cryptoWalletId": "<wallet_uuid>",   # From onboarding
    "quoteId": "<quote_id_from_step_1>"  # From Step 2
  }'
```

> [!TIP]
>
> The response includes a `statusPage` URL, for example `https://sandbox.etherfuse.com/ramp/order/<order_uuid>`. Open it in a browser to sign the burn transaction and track progress without building wallet integration into your test harness. You must sign in to Etherfuse with an account in the order's organization or its parent organization to open it.

> [!NOTE]
>
> **Optional order fields:** `memo` attaches your own reference string to the order. `feePayer` applies only to **Stellar contract-wallet (C-address) offramps** — a contract wallet can't be the transaction source, so you provide a funded Stellar G-account public key to pay the network fee.

**4. Sign the Burn Transaction**

**Details**

> [!NOTE]
>
> **Embedded wallets skip this step.** If the order's wallet is an [embedded wallet](https://docs.etherfuse.com/guides/embedded-wallets.md), there is no `burnTransaction` to sign. Wait for `approval` on the order (via webhook, WebSocket, or polling) and submit the owner's signature to [`POST /ramp/order/{orderId}/approvals`](https://docs.etherfuse.com/api-reference/orders/submit-order-approval) instead.

Unlike onramps, offramps require an **on-chain action**: the customer must sign and submit the burn transaction to release their tokens. You can handle this two ways:

1. **In your app** — Deserialize the `burnTransaction` from the order response or `order_updated` webhook, sign it with the customer's wallet, and broadcast it to the network.
2. **Via the status page:** Use the `statusPage` URL from the order response (for example `https://sandbox.etherfuse.com/ramp/order/<order_uuid>`) to review and sign the transaction in the Etherfuse UI. The page requires an Etherfuse sign in with an account in the order's organization or its parent organization.

> [!WARNING]
>
> **Stellar transactions expire in \~1–2 minutes.** If the customer takes too long, submitting the burn tx returns `tx_too_late`. Don't reuse the stale one — call [POST /ramp/order/\{order\_id}/regenerate\_tx](https://docs.etherfuse.com/api-reference/orders/regenerate-tx.md) (a fresh `burnTransaction` arrives over the `order_updated` webhook). Polling it \~once a minute keeps a signable tx in front of the user. See [Stellar](https://docs.etherfuse.com/guides/chains/stellar.md#transaction-expiry--regeneration).

**5. Verify Completion — GET /ramp/order/\{order\_uuid}**

**Details**

Once the burn is confirmed on-chain, the system detects it and initiates the fiat payout automatically. Status progression: `created` → `funded` (burn confirmed) → `completed` (fiat sent) → `finalized` (reversal window passed). See [GET /ramp/order](https://docs.etherfuse.com/api-reference/orders/get-order.md) for the full response schema.

> [!NOTE]
>
> **No fiat simulation needed.** Unlike onramps, there is no `fiat_received` step for offramps. The sandbox processes the fiat payout automatically once the burn transaction is confirmed on-chain.

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

```bash Production theme={null}
curl -H "Authorization: <api_key>" \
  https://api.etherfuse.com/ramp/order/<order_uuid>
```

## Anchor Mode (Stellar Only)

For Stellar offramps, you can use **anchor mode** instead of the default burn transaction flow. In anchor mode, you build and submit the payment transaction yourself — Etherfuse provides the destination address and memo.

This is useful when you want full control over transaction construction, or when integrating with wallets that support [SEP-24](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md)-style withdrawal flows.

**1. Create Order with useAnchor**

**Details**

Add `"useAnchor": true` to your order request. The quote must be for a **Stellar offramp**.

```bash Sandbox theme={null}
curl -X POST https://api.sand.etherfuse.com/ramp/order \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "<uuid>",
    "bankAccountId": "<bank_account_uuid>",
    "cryptoWalletId": "<wallet_uuid>",
    "quoteId": "<quote_id>",
    "useAnchor": true
  }'
```

```bash Production theme={null}
curl -X POST https://api.etherfuse.com/ramp/order \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "<uuid>",
    "bankAccountId": "<bank_account_uuid>",
    "cryptoWalletId": "<wallet_uuid>",
    "quoteId": "<quote_id>",
    "useAnchor": true
  }'
```

The response includes anchor-specific fields instead of a `burnTransaction`:

```json theme={null}
{
  "offramp": {
    "orderId": "...",
    "withdrawAnchorAccount": "GABPM7AXX...",
    "withdrawMemo": "RkFLRU1FTU8xMjM0NTY3ODkw...",
    "withdrawMemoType": "hash"
  }
}
```

**2. Build and Submit Payment**

**Details**

Build a Stellar payment transaction to the `withdrawAnchorAccount` with:

* **Asset & amount** from your quote (e.g., USDC, XLM, or CETES)
* **Memo** — decode `withdrawMemo` from base64, then attach as a `Memo.hash`

```javascript theme={null}
const memoBytes = Buffer.from(withdrawMemo, "base64");
const tx = new StellarSdk.TransactionBuilder(sourceAccount, { fee, networkPassphrase })
  .addOperation(StellarSdk.Operation.payment({
    destination: withdrawAnchorAccount,
    asset: sendAsset,
    amount: quoteAmount,
  }))
  .addMemo(StellarSdk.Memo.hash(memoBytes.toString("hex")))
  .setTimeout(300)
  .build();
```

Sign and submit the transaction to the Stellar network.

**3. Track Order Status**

**Details**

The rest of the flow is identical to standard offramps. Once the payment is detected on-chain, the order progresses through `created` → `funded` → `completed` → `finalized`.

> [!WARNING]
>
> Payments sent **without a memo** or with an **unrecognized memo** will be automatically refunded to the sender address.

**Chain-specific quote examples**

**Solana** — Use the Base58 mint address as `sourceAsset`:

```json theme={null}
{
  "blockchain": "solana",
  "quoteAssets": {
    "type": "offramp",
    "sourceAsset": "AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q",
    "targetAsset": "MXN"
  }
}
```

**Stellar** — Use `CODE:ISSUER` format:

```json theme={null}
{
  "blockchain": "stellar",
  "quoteAssets": {
    "type": "offramp",
    "sourceAsset": "CETES:GC3CW7...",
    "targetAsset": "MXN"
  }
}
```

**EVM (Base, Polygon, Monad, Avalanche)**: Use the raw contract address. This example uses Base; obtain the identifier for your chain and environment from `GET /ramp/assets`:

```json theme={null}
{
  "blockchain": "base",
  "quoteAssets": {
    "type": "offramp",
    "sourceAsset": "0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29",
    "targetAsset": "MXN"
  }
}
```

**Common errors**

| Error | Cause | Fix |
| - | - | - |
| `UnsupportedBlockchain` | Using `SYMBOL:0x...` format on an EVM chain | Use the raw contract address only |
| `Terms and conditions have not been completed` | Wallet not KYC-approved | Complete the [onboarding flow](https://docs.etherfuse.com/guides/onboarding.md) |
| `Quote not found or expired` | Quote TTL is 2 minutes | Create a fresh quote |
| `Bank account not found` | Bank account not owned by the requesting org | Verify the bank account belongs to your organization |
| `quote_id is required` | Missing quote — the quoteless path is deprecated | Create a quote via `POST /ramp/quote` first |
| No `burnTransaction` / no webhook (Stellar) | Wallet isn't funded with XLM or is missing the asset's trustline, so the tx can't be built | Fund the wallet (\~1.5 XLM) and add the trustline first. See [Stellar](https://docs.etherfuse.com/guides/chains/stellar.md#offramps-and-swaps-fund-the-wallet-first) |
| `tx_too_late` (Stellar) | Burn transaction expired (\~1–2 min) before submission | Regenerate via [`regenerate_tx`](https://docs.etherfuse.com/api-reference/orders/regenerate-tx) and resubmit promptly |
| `OrderTooSmall` (EVM) | Offramp too small to cover the bundled gas fee (fee can't exceed 50% of the order) | Offramp a larger amount (≳ 50 CETES). See [Base](https://docs.etherfuse.com/guides/chains/base.md#gasless-offramps) |


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