Swap Integration Guide
Step-by-step guide to quoting and executing token swaps from customer wallets.
Prerequisites
- An active Rach API key (
live_sk_…) - At least one customer wallet provisioned via the Wallet-as-a-Service API
- The customer wallet must hold enough of
from_tokenplus gas (MATIC on Polygon, BNB on BSC) to cover the swap and network fees
Base URL
https://api.rach.financeAuthentication
Pass your API key in the header on every request:
X-API-Key: live_sk_YOUR_KEYStep 1 — Get a Quote
Always fetch a quote first. It tells you the expected output amount and the minimum you should accept (to_amount_min). Quotes expire in 30 seconds.
GET /api/v1/swap/quote| Query param | Required | Description |
|---|---|---|
from_chain | Yes | Source chain: POL, BSC, ETH, ARB, etc. |
to_chain | Yes | Destination chain (same as from_chain for DEX swaps) |
from_token | Yes | Token address or "native" for MATIC/BNB |
to_token | Yes | Token address or "native" for MATIC/BNB |
amount_in | Yes | Amount in base units (wei / smallest denomination) |
# Quote: 100 USDC → MATIC on Polygon
# USDC on Polygon: 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
curl "https://api.rach.finance/api/v1/swap/quote\
?from_chain=POL\
&to_chain=POL\
&from_token=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174\
&to_token=native\
&amount_in=100000000" \
-H "X-API-Key: live_sk_YOUR_KEY"# Quote: 100 USDC on Polygon → USDC on Arbitrum
curl "https://api.rach.finance/api/v1/swap/quote\
?from_chain=POL\
&to_chain=ARB\
&from_token=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174\
&to_token=0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8\
&amount_in=100000000" \
-H "X-API-Key: live_sk_YOUR_KEY"async function getSwapQuote({ fromChain, toChain, fromToken, toToken, amountIn }) {
const params = new URLSearchParams({ from_chain: fromChain, to_chain: toChain,
from_token: fromToken, to_token: toToken, amount_in: amountIn.toString() })
const res = await fetch(`https://api.rach.finance/api/v1/swap/quote?${params}`, {
headers: { 'X-API-Key': 'live_sk_YOUR_KEY' }
})
if (!res.ok) throw new Error(await res.text())
return res.json()
}
// 100 USDC (6 decimals) → MATIC on Polygon
const quote = await getSwapQuote({
fromChain: 'POL',
toChain: 'POL',
fromToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
toToken: 'native',
amountIn: 100_000_000n // 100 USDC
})
console.log(`Expected out: ${quote.to_amount_expected}`)
console.log(`Minimum out: ${quote.to_amount_min}`)
console.log(`Platform fee: ${quote.platform_fee}`)
console.log(`Expires at: ${new Date(quote.expires_at * 1000).toISOString()}`)import requests
def get_swap_quote(from_chain, to_chain, from_token, to_token, amount_in):
res = requests.get(
'https://api.rach.finance/api/v1/swap/quote',
params={
'from_chain': from_chain,
'to_chain': to_chain,
'from_token': from_token,
'to_token': to_token,
'amount_in': str(amount_in),
},
headers={'X-API-Key': 'live_sk_YOUR_KEY'}
)
res.raise_for_status()
return res.json()
quote = get_swap_quote(
from_chain='POL',
to_chain='POL',
from_token='0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
to_token='native',
amount_in=100_000_000 # 100 USDC
)
print(f"Expected: {quote['to_amount_expected']}")
print(f"Min out: {quote['to_amount_min']}")Response (DEX — same-chain):
{
"from_chain": "POL",
"to_chain": "POL",
"from_token": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"to_token": "native",
"from_amount": "100000000",
"to_amount_expected": "134521000000000000000",
"to_amount_min": "133948495000000000000",
"platform_fee": "300000",
"estimated_seconds": 30,
"expires_at": 1751673028
}Response (bridge — cross-chain):
{
"from_chain": "POL",
"to_chain": "ARB",
"from_token": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"to_token": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
"from_amount": "100000000",
"to_amount_expected": "99150000",
"to_amount_min": "98656000",
"platform_fee": "720000",
"estimated_seconds": 180,
"expires_at": 1751673028
}Quote expiry
Quotes expire in 30 seconds. Execute the swap immediately after fetching the quote, using to_amount_min as your slippage floor. Fetching a fresh quote at execution time means the output is already slightly different — always use the quote's to_amount_min, not a value calculated yourself.
Step 2 — Execute the Swap
After getting a quote, execute the swap. The API broadcasts the transaction and returns the tx_hash immediately. Settlement is asynchronous.
POST /api/v1/swap/:customerID| Field | Type | Required | Description |
|---|---|---|---|
from_chain | string | Yes | Source chain (POL, BSC, ETH, …) |
to_chain | string | Yes | Destination chain |
from_token | string | Yes | Token address or "native" |
to_token | string | Yes | Token address or "native" |
amount_in | string | Yes | Amount in base units |
amount_out_min | string | No | Slippage floor in base units (use quote's to_amount_min) |
curl -X POST "https://api.rach.finance/api/v1/swap/cust_abc123" \
-H "X-API-Key: live_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_chain": "POL",
"to_chain": "POL",
"from_token": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"to_token": "native",
"amount_in": "100000000",
"amount_out_min": "133948495000000000000"
}'async function executeSwap(customerId, body) {
const res = await fetch(`https://api.rach.finance/api/v1/swap/${customerId}`, {
method: 'POST',
headers: {
'X-API-Key': 'live_sk_YOUR_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
if (!res.ok) throw new Error(await res.text())
return res.json()
}
// Full flow: quote then execute
const quote = await getSwapQuote({ ... })
const result = await executeSwap('cust_abc123', {
from_chain: 'POL',
to_chain: 'POL',
from_token: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
to_token: 'native',
amount_in: '100000000',
amount_out_min: quote.to_amount_min // slippage floor from quote
})
console.log(`Swap submitted: ${result.tx_hash}`)
console.log(`Status: ${result.status}`) // "pending"import requests
def execute_swap(customer_id, payload, api_key):
res = requests.post(
f'https://api.rach.finance/api/v1/swap/{customer_id}',
json=payload,
headers={'X-API-Key': api_key, 'Content-Type': 'application/json'}
)
res.raise_for_status()
return res.json()
result = execute_swap('cust_abc123', {
'from_chain': 'POL',
'to_chain': 'POL',
'from_token': '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
'to_token': 'native',
'amount_in': '100000000',
'amount_out_min': quote['to_amount_min']
}, 'live_sk_YOUR_KEY')
print(f"tx_hash: {result['tx_hash']}")Response:
{
"tx_hash": "0x4a3f2e1b8c9d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3",
"status": "pending"
}Transaction status
status: "pending" means the transaction was broadcast to the network. Use the tx_hash to track confirmation on:
- Polygon: polygonscan.com
- BSC: bscscan.com
- Other chains: the chain's respective explorer
Step 3 — Check Swap History
Get the swap history for a customer to show them past transactions.
GET /api/v1/swap/:customerID/history?page=1&limit=20curl "https://api.rach.finance/api/v1/swap/cust_abc123/history?page=1&limit=20" \
-H "X-API-Key: live_sk_YOUR_KEY"Response:
{
"swaps": [
{
"id": 42,
"customer_id": "cust_abc123",
"type": "dex",
"from_chain": "POL",
"to_chain": "POL",
"dex": "quickswap",
"router_address": "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff",
"token_in": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"token_out": "native",
"amount_in": "100000000",
"amount_out": "134521000000000000000",
"platform_fee": "300000",
"approve_tx_hash": "0xabc...",
"swap_tx_hash": "0x4a3f...",
"status": "confirmed",
"confirmed_at": "2026-07-04T23:55:12Z",
"created_at": "2026-07-04T23:54:40Z"
}
],
"total": 8,
"page": 1,
"limit": 20
}Complete Integration Example
const API_KEY = 'live_sk_YOUR_KEY'
const BASE = 'https://api.rach.finance'
const headers = { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }
async function swapTokens(customerId, { fromChain, toChain, fromToken, toToken, amountIn }) {
// 1. Quote
const qp = new URLSearchParams({
from_chain: fromChain, to_chain: toChain,
from_token: fromToken, to_token: toToken,
amount_in: amountIn.toString()
})
const quoteRes = await fetch(`${BASE}/api/v1/swap/quote?${qp}`, { headers })
if (!quoteRes.ok) throw new Error(`Quote failed: ${await quoteRes.text()}`)
const quote = await quoteRes.json()
console.log(`Swapping ${amountIn} → expected ${quote.to_amount_expected}`)
console.log(`Platform fee: ${quote.platform_fee} | Expires: ${new Date(quote.expires_at * 1000).toISOString()}`)
// 2. Execute immediately (quote expires in 30s)
const execRes = await fetch(`${BASE}/api/v1/swap/${customerId}`, {
method: 'POST',
headers,
body: JSON.stringify({
from_chain: fromChain,
to_chain: toChain,
from_token: fromToken,
to_token: toToken,
amount_in: amountIn.toString(),
amount_out_min: quote.to_amount_min // slippage protection
})
})
if (!execRes.ok) throw new Error(`Swap failed: ${await execRes.text()}`)
const result = await execRes.json()
console.log(`Swap submitted! tx_hash: ${result.tx_hash}`)
return result
}
// DEX swap — 50 USDC → MATIC on Polygon
await swapTokens('cust_abc123', {
fromChain: 'POL',
toChain: 'POL',
fromToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC on Polygon
toToken: 'native', // MATIC
amountIn: 50_000_000n // 50 USDC (6 decimals)
})
// Bridge — 100 USDC from Polygon to Arbitrum
await swapTokens('cust_abc123', {
fromChain: 'POL',
toChain: 'ARB',
fromToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC on Polygon
toToken: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', // USDC on Arbitrum
amountIn: 100_000_000n
})Common Token Addresses
Polygon
| Token | Address |
|---|---|
| MATIC (native) | "native" |
| USDC | 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 |
| USDT | 0xc2132D05D31c914a87C6611C10748AEb04B58e8F |
| DAI | 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063 |
| WBTC | 0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6 |
| WETH | 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619 |
BSC
| Token | Address |
|---|---|
| BNB (native) | "native" |
| USDC | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d |
| USDT | 0x55d398326f99059fF775485246999027B3197955 |
| DAI | 0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3 |
| WBTC | 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c |
| ETH | 0x2170Ed0880ac9A755fd29B2688956BD959F933F8 |
Amount Conversions
All amounts are passed in base units (no decimals). Use the token's decimal count to convert:
| Token | Decimals | 1 token in base units |
|---|---|---|
| MATIC / BNB (native) | 18 | 1000000000000000000 |
| USDC | 6 | 1000000 |
| USDT | 6 (BSC) / 6 (POL) | 1000000 |
| WBTC | 8 | 100000000 |
| WETH | 18 | 1000000000000000000 |
// Helper: convert human amount to base units
function toBaseUnits(amount, decimals) {
return BigInt(Math.round(amount * 10 ** decimals)).toString()
}
toBaseUnits(100, 6) // "100000000" for 100 USDC
toBaseUnits(0.5, 18) // "500000000000000000" for 0.5 MATICError Responses
| HTTP | Error | Cause |
|---|---|---|
| 400 | from_chain, to_chain, from_token, to_token and amount_in are required | Missing query params on quote |
| 400 | amount_in must be a positive integer in base units | Non-numeric or zero amount |
| 400 | tokenIn and tokenOut resolve to the same address | Same token for in/out |
| 400 | swap not supported on network "X" | Chain not supported for DEX swaps |
| 500 | customer wallet not found for … | Customer has no provisioned wallet |
| 500 | approve failed / swap transaction failed | On-chain transaction reverted |
Best Practices
Always use amount_out_min — set it to the quote's to_amount_min. Without it, large price movements between quote and execution can result in a much worse rate.
Execute within 30 seconds — quotes expire quickly because on-chain prices move. Fetch the quote immediately before executing, not minutes earlier.
Convert amounts carefully — off-by-one on decimals is the most common integration mistake. Always verify the decimal count for each token before calculating base units.
Check wallet balance first — the swap will fail on-chain if the customer wallet lacks sufficient token balance or gas. Use the WaaS balance endpoints to verify before initiating.
Next Steps
- Full API Reference →
- Wallet-as-a-Service → — provision customer wallets
- Market Data → — get live prices to display alongside swap UIs
