Documentation Index Fetch the complete documentation index at: https://docs.o1.exchange/llms.txt
Use this file to discover all available pages before exploring further.
This guide walks you through a complete swap from a TypeScript client. We’ll trade 1,000 USDC for WETH on Base, then receive native ETH back instead of WETH.
Production base URL is currently https://quiet-bloodhound-531.convex.site. If you’re integrating against a vanity domain (e.g. https://api.o1.exchange/dex), substitute that throughout.
Prerequisites
Base RPC URL Any Base mainnet RPC works. We use viem defaults below.
Wallet with USDC Plus a small ETH balance to pay gas (and approve the router if this is your first swap).
1. Set up your client
import { createPublicClient , createWalletClient , http , erc20Abi } from "viem" ;
import { privateKeyToAccount } from "viem/accounts" ;
import { base } from "viem/chains" ;
const API_URL = "https://quiet-bloodhound-531.convex.site" ;
const API_KEY = process . env . O1_API_KEY ! ;
const account = privateKeyToAccount ( process . env . PRIVATE_KEY as `0x ${ string } ` );
const publicClient = createPublicClient ({ chain: base , transport: http () });
const walletClient = createWalletClient ({ chain: base , account , transport: http () });
const USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ;
const WETH = "0x4200000000000000000000000000000000000006" ;
2. Get a quote
Endpoint: POST {API_URL}/quoteconst quote = await fetch ( ` ${ API_URL } /quote` , {
method: "POST" ,
headers: {
"x-api-key" : API_KEY ,
"content-type" : "application/json" ,
},
body: JSON . stringify ({
chainId: 8453 ,
tokenIn: USDC ,
tokenOut: WETH ,
amountIn: "1000000000" , // 1,000 USDC (6 decimals)
slippageBps: 100 , // 1% slippage tolerance
}),
}). then (( r ) => r . json ());
{
"quoteId" : "a1b2c3d4..." ,
"expiresAt" : 1712180000000 ,
"routePlan" : {
"chainId" : 8453 ,
"tokenIn" : "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ,
"tokenOut" : "0x4200000000000000000000000000000000000006" ,
"amountIn" : "1000000000" ,
"expectedAmountOut" : "484446072048780734" ,
"minAmountOut" : "479601611328292926" ,
"slippageBps" : 100 ,
"blockNumber" : 44266656 ,
"gasEstimate" : { "gasUnits" : 300000 },
"routes" : [
{
"amountIn" : "1000000000" ,
"legs" : [
{
"dex" : "UNIV3" ,
"tokenIn" : "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ,
"tokenOut" : "0x4200000000000000000000000000000000000006" ,
"amountIn" : "1000000000" ,
"minOut" : "0" ,
"poolId" : "0xd0b5...f224" ,
"data" : { "kind" : "v3_direct" , "pool" : "0xd0b5...f224" }
}
]
}
]
}
}
The fields you’ll display to the user:
routePlan.expectedAmountOut — the price you quote
routePlan.minAmountOut — the worst-case fill after slippage
routePlan.routes[].legs[].dex — which venues are involved
expiresAt — quote validity window (about 10 seconds; see Quote freshness )
3. Approve the router (first swap only)
Skip this step entirely if tokenIn is native ETH, or if you’re using an EIP-2612 permit (see POST /submit → permit).
const ROUTER = "0xe56e22354DDdc07cF2dfbCFb53a90fB0a56E50D5" ;
const allowance = await publicClient . readContract ({
address: USDC ,
abi: erc20Abi ,
functionName: "allowance" ,
args: [ account . address , ROUTER ],
});
if ( allowance < BigInt ( quote . routePlan . amountIn )) {
await walletClient . writeContract ({
address: USDC ,
abi: erc20Abi ,
functionName: "approve" ,
args: [ ROUTER , BigInt ( quote . routePlan . amountIn )],
});
}
4. Build the transaction
Endpoint: POST {API_URL}/submitconst submit = await fetch ( ` ${ API_URL } /submit` , {
method: "POST" ,
headers: {
"x-api-key" : API_KEY ,
"content-type" : "application/json" ,
},
body: JSON . stringify ({
quoteId: quote . quoteId ,
user: account . address ,
unwrapNativeOut: true , // receive native ETH instead of WETH
}),
}). then (( r ) => r . json ());
{
"quoteId" : "a1b2c3d4..." ,
"chainId" : 8453 ,
"to" : "0xe56e22354DDdc07cF2dfbCFb53a90fB0a56E50D5" ,
"data" : "0x..." ,
"value" : "0"
}
5. Sign and send
const txHash = await walletClient . sendTransaction ({
to: submit . to as `0x ${ string } ` ,
data: submit . data as `0x ${ string } ` ,
value: BigInt ( submit . value ),
});
console . log ( `Submitted: https://basescan.org/tx/ ${ txHash } ` );
That’s it. Wait for the receipt with publicClient.waitForTransactionReceipt({ hash: txHash }) and the swap is complete.
Full example, end to end
Complete TypeScript script
import { createPublicClient , createWalletClient , http , erc20Abi } from "viem" ;
import { privateKeyToAccount } from "viem/accounts" ;
import { base } from "viem/chains" ;
const API_URL = "https://quiet-bloodhound-531.convex.site" ;
const API_KEY = process . env . O1_API_KEY ! ;
const ROUTER = "0xe56e22354DDdc07cF2dfbCFb53a90fB0a56E50D5" ;
const USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" ;
const WETH = "0x4200000000000000000000000000000000000006" ;
async function main () {
const account = privateKeyToAccount ( process . env . PRIVATE_KEY as `0x ${ string } ` );
const publicClient = createPublicClient ({ chain: base , transport: http () });
const walletClient = createWalletClient ({ chain: base , account , transport: http () });
// 1. Quote
const quote = await fetch ( ` ${ API_URL } /quote` , {
method: "POST" ,
headers: { "x-api-key" : API_KEY , "content-type" : "application/json" },
body: JSON . stringify ({
chainId: 8453 ,
tokenIn: USDC ,
tokenOut: WETH ,
amountIn: "1000000000" ,
slippageBps: 100 ,
}),
}). then (( r ) => r . json ());
console . log ( `Expected out: ${ quote . routePlan . expectedAmountOut } WETH (wei)` );
// 2. Approve if needed
const allowance = await publicClient . readContract ({
address: USDC ,
abi: erc20Abi ,
functionName: "allowance" ,
args: [ account . address , ROUTER ],
});
if ( allowance < BigInt ( quote . routePlan . amountIn )) {
const approveHash = await walletClient . writeContract ({
address: USDC ,
abi: erc20Abi ,
functionName: "approve" ,
args: [ ROUTER , BigInt ( quote . routePlan . amountIn )],
});
await publicClient . waitForTransactionReceipt ({ hash: approveHash });
}
// 3. Submit
const submit = await fetch ( ` ${ API_URL } /submit` , {
method: "POST" ,
headers: { "x-api-key" : API_KEY , "content-type" : "application/json" },
body: JSON . stringify ({
quoteId: quote . quoteId ,
user: account . address ,
unwrapNativeOut: true ,
}),
}). then (( r ) => r . json ());
// 4. Send
const txHash = await walletClient . sendTransaction ({
to: submit . to as `0x ${ string } ` ,
data: submit . data as `0x ${ string } ` ,
value: BigInt ( submit . value ),
});
const receipt = await publicClient . waitForTransactionReceipt ({ hash: txHash });
console . log ( `Confirmed in block ${ receipt . blockNumber } ` );
}
main (). catch ( console . error );
What’s next
Two-step vs one-step When to use /quote + /submit vs /execute.
ERC-20 approvals Approve patterns, including one-tx permit-enabled swaps.
Native ETH Sell ETH directly without wrapping, or receive ETH instead of WETH.
Quote freshness How to keep the displayed price live in your UI.