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 labelContract addressNotes
BASE_USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913Base mainnet
BASE_USDC(Testnet)0x036CbD53842c5426634e7929541eC2318f3dCF7eSandbox only
USDT0xdAC17F958D2ee523a2206206994597C13D831ec7Ethereum mainnet
LISK_USDT0x05D032ac25d322df992303dCa074EE7392C117b9Lisk
SCROLL_USDC0x06efdbff2a14a7c8e15944d1f4a48f9f95f663a4Scroll
SCROLL_USDT0xF55BEC9cAfDBE8730F096AA55Dad6D22D44099dfScroll
ARBITRUM_WXM0xb6093B61544572ab42A0E43af08abAFD41Bf25a6Arbitrum
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)
{
  "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)
{
  "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
curl -G "$BASE_URL/quote" \
--data-urlencode "amount_fiat=100" \
--data-urlencode "token=USDC" \
--data-urlencode "order_type=OnRamp"
Example — OffRamp
curl -G "$BASE_URL/quote" \
--data-urlencode "amount_fiat=1750" \
--data-urlencode "token=USDT" \
--data-urlencode "order_type=OffRamp"
Success example
{
  "status": "success",
  "message": "Quote computed",
  "data": {
    "rate": 132.712345,
    "token_amount": 0.753621,
    "fiat_paid": 100,
    "symbol": "USDC",
    "decimals": 6
  }
}
Errors
  • 400 bad_order_typeorder_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)
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:
{
  "status": "error",
  "message": "Approve the contract to spend your tokens before OffRamp.",
  "data": { "required": 79218430, "current": 0 }
}
Approve with ethers (v6)
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