The DEX Aggregator API supports two integration shapes. They produce equivalent on-chain transactions; the difference is when the routing engine runs.
Two-step /quote → human reviews price → /submit → broadcast.Best for swap UIs.
One-step /execute → broadcast.Best for one-click flows and bots.
Two-step (recommended for swap UIs)
Use this flow when a user reviews the quoted price before they commit.
// 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 ());
// 2. Display to user
showQuote ({
output: formatUnits ( BigInt ( quote . routePlan . expectedAmountOut ), 18 ),
minOutput: formatUnits ( BigInt ( quote . routePlan . minAmountOut ), 18 ),
route: quote . routePlan . routes
. map (( r ) => r . legs . map (( l ) => l . dex ). join ( " → " ))
. join ( " | " ),
fee: ` ${ ( quote . routePlan . feeBps ?? 0 ) / 100 } %` ,
});
// 3. User clicks "Swap" → build tx
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: walletAddress ,
}),
}). then (( r ) => r . json ());
// 4. Send to wallet
const txHash = await walletClient . sendTransaction ({
to: submit . to as `0x ${ string } ` ,
data: submit . data as `0x ${ string } ` ,
value: BigInt ( submit . value ),
});
Pros
User confirms the price they see; no last-second surprise.
You can run validation between quote and submit (e.g. “is this trade > $X? require a confirm dialog”).
Clean separation between read (quote) and write (submit) for caching, analytics, and rate limiting.
Cons
Quotes have a TTL (~10 seconds). If the user takes too long, /submit returns 404 and you must re-quote. See Quote freshness .
Two API calls per swap.
One-step (instant swap)
Use this flow when there’s no preview step.
const exec = await fetch ( ` ${ API_URL } /execute` , {
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 ,
user: walletAddress ,
}),
}). then (( r ) => r . json ());
const txHash = await walletClient . sendTransaction ({
to: exec . to as `0x ${ string } ` ,
data: exec . data as `0x ${ string } ` ,
value: BigInt ( exec . value ),
});
Pros
Single round trip.
No quoteId to track, no expiry to handle.
Cons
The user (or bot) doesn’t see the price before signing. If you still want to show it, log exec.routePlan.expectedAmountOut after the fact.
Less flexibility for caching and analytics, since each call rebuilds the full route plan server-side.
Choosing the right pattern
Use case Pattern Standard swap UI with confirm dialog Two-step One-click “swap now” button One-step Server-side market-maker / bot One-step Limit-order style flow with delayed execution Two-step (re-quote on each tick) Mobile UI with poor connectivity One-step (fewer round trips)
Even when you use /execute for the user-facing call, you can still hit /quote separately for live price display in the modal. Just be aware they consume separate rate-limit budget.
Always: handle expired quotes
Whichever pattern you pick, treat 404 quote not found or expired from /submit as recoverable: re-fetch /quote with the same parameters and retry. Don’t surface this as a user-facing error unless it happens repeatedly.
See Quote freshness for the full pattern.