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

# Tokens and rates

This guide explains how to pick tokens, preview rates, and handle ERC-20 approvals.

> **Live source of truth:** always call **`GET /meta/tokens`** for the supported tokens/addresses in your current environment (Sandbox vs Production). The list below is a snapshot for reference.

***

## Supported tokens (snapshot)

| Symbol label        | Contract address                             | Notes            |
| ------------------- | -------------------------------------------- | ---------------- |
| BASE\_USDC          | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | Base mainnet     |
| BASE\_USDC(Testnet) | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | **Sandbox only** |
| USDT                | `0xdAC17F958D2ee523a2206206994597C13D831ec7` | Ethereum mainnet |
| LISK\_USDT          | `0x05D032ac25d322df992303dCa074EE7392C117b9` | Lisk             |
| SCROLL\_USDC        | `0x06efdbff2a14a7c8e15944d1f4a48f9f95f663a4` | Scroll           |
| SCROLL\_USDT        | `0xF55BEC9cAfDBE8730F096AA55Dad6D22D44099df` | Scroll           |
| ARBITRUM\_WXM       | `0xb6093B61544572ab42A0E43af08abAFD41Bf25a6` | Arbitrum         |

> **Note:** addresses can differ by environment. Always use `/meta/tokens` in the **target environment** to get the canonical list (and decimals).

***

## Environment & token mismatch

* **Testnet tokens will not work in Production.** Use testnet tokens strictly in **Sandbox**.
* If a token isn’t configured for the current environment, the server should return a **400** explaining the mismatch instead of failing later.

**Recommended 400 example (orders/create)**

```json theme={null}
{
  "status": "error",
  "message": "Token not supported in env=live",
  "data": {
    "token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "hint": "Call /meta/tokens for supported tokens in this environment"
  }
}
```

**Recommended 400 example (quote)**

```json theme={null}
{
  "status": "error",
  "message": "No USDC configured for env=live",
  "data": { "token": "USDC" }
}
```

**Implementation hint (server-side):** validate token → environment at the **route layer** and map internal lookup errors (e.g., `No Web3 instance found for token`) to a **400** with a clear message.

***

## Preview a quote (no auth)

Use **`GET /quote`** to preview pricing **without** creating an order.

**Parameters**

* `amount_fiat` (KES): positive number
* `token`: `USDC | USDT | WXM`
* `order_type`: `OnRamp|OffRamp` or `0|1` (defaults to `OnRamp`)

**Response**

* `rate`: KES per token **after markup**
* `token_amount`: tokens the user would receive (OnRamp) or must send (OffRamp)
* `fiat_paid`: echo of input
* `symbol`, `decimals`: metadata

**Example — OnRamp**

```bash theme={null}
curl -G "$BASE_URL/quote" \
--data-urlencode "amount_fiat=100" \
--data-urlencode "token=USDC" \
--data-urlencode "order_type=OnRamp"
```

**Example — OffRamp**

```bash theme={null}
curl -G "$BASE_URL/quote" \
--data-urlencode "amount_fiat=1750" \
--data-urlencode "token=USDT" \
--data-urlencode "order_type=OffRamp"
```

**Success example**

```json theme={null}
{
  "status": "success",
  "message": "Quote computed",
  "data": {
    "rate": 132.712345,
    "token_amount": 0.753621,
    "fiat_paid": 100,
    "symbol": "USDC",
    "decimals": 6
  }
}
```

**Errors**

* `400 bad_order_type` → `order_type must be 0|1 or OnRamp|OffRamp`
* `400 unknown_token` / `token_env_mismatch`
* `429` rate limited; respect `Retry-After`
* `502` upstream price provider error

***

## Units & decimals

* Fields like `token_amount` and `amount_sent` are **human units** (e.g., `12.34` USDC).
* ERC-20 **approvals** must be in **base units** (integers), using the token’s decimals.

**Convert human → base units (ethers v6)**

```js theme={null}
import { parseUnits } from "ethers";
const amountHuman = "12.34";   // 12.34 USDC
const decimals = 6;            // from /meta/tokens or the /quote response
const amountBase = parseUnits(amountHuman, decimals); // 12340000
```

***

## Off-ramp approvals (ERC-20)

Before creating an **OffRamp** order, approve the Element Pay spender/contract for the token.

**If allowance is too low**, `POST /orders/create` responds with:

```json theme={null}
{
  "status": "error",
  "message": "Approve the contract to spend your tokens before OffRamp.",
  "data": { "required": 79218430, "current": 0 }
}
```

**Approve with ethers (v6)**

```js theme={null}
import { Contract, parseUnits } from "ethers";
import erc20 from "./erc20.json" assert { type: "json" };

const token = new Contract(TOKEN_ADDRESS, erc20, signer);
const amountBase = parseUnits("50", 6); // e.g., approve 50.0 for USDC (6 decimals)

// Replace with the Element Pay spender for your environment
await token.approve(ELEMENTPAY_SPENDER_ADDRESS, amountBase);
```

**Best practices**

* Approve just-enough (or a reasonable buffer), not unlimited.
* Reuse allowance across multiple off-ramp orders until depleted.
* Handle `insufficient_balance` similarly (you’ll get `required` vs `current` in `data`).

***

## Putting it together

1. Call **`/meta/tokens`** to get supported tokens, addresses, and decimals (for your environment).
2. Call **`/quote`** to preview `rate` and `token_amount` before creating an order.
3. For **OffRamp**, ensure allowance ≥ required amount (approve if needed).
4. Create the order via **`POST /orders/create`** and track via **webhooks** or `GET /orders/tx/{tx_hash}`.

**See also**

* API Reference → **Quote**
* API Reference → **Create Order**
* API Reference → **Webhooks**
