OctoSwap API
OctoSwap's Quote API lets any dApp integrate directly with the default provider (Universal Router flow). This guide walks through fetching quotes, handling Permit2-based gasless approvals, building permit-backed payloads, and broadcasting swaps.
Configuration
| Constant | Description | Example Value |
|---|---|---|
API_BASE_URL | Base URL for backend calls. | https://api.octo.exchange |
UNIVERSAL_ROUTER_ADDRESS | Contract address targeted by every built transaction. | 0x241BF19641839b249E8174Bd22FACd3d3ac0642A |
PERMIT2_ADDRESS | Uniswap Permit2 contract used for gasless approvals. | 0x000000000022D473030F116dDEE9F6B43aC78BA3 |
NATIVE_TOKEN | Use literal nativecoin in requests and the backend substitutes this value. | nativecoin |
All token amounts must be sent as stringified integers in token base units (wei-style). Slippage values are expressed as whole percentages (e.g. 1 for 1%).
The quote API enforces a rate limit of 3 requests per second per caller. Throttle outbound requests accordingly to avoid 429 responses.
1. Get a Quote (POST /api/getQuote)
Use the quote endpoint to retrieve an executable swap payload for the Universal Router. This is the only call required when the user already granted ERC20 approvals.
Request body
{
"tokenIn": "0xeeee..." | "nativecoin",
"tokenOut": "0x1234...",
"amountIn": "1000000000000000000",
"account": "0xUserAddress",
"slippage": 1,
"useRouterBalance": false
}
| Field | Notes |
|---|---|
tokenIn / tokenOut | Lower-case checksummed addresses. Use nativecoin for the chain's native asset. |
amountIn | Stringified BigNumber in token-in base units. |
account | Recipient address. The zero address is accepted when no wallet is connected. |
slippage | Percentage tolerance enforced when building calldata. |
useRouterBalance | false by default. Set true when sourcing funds already held by the router (e.g. Atlas MEV-protected mode). |
Successful response
{
"success": true,
"data": {
"tokenIn": { "address": "0x...", "symbol": "MON", "decimals": 18 },
"tokenOut": { "address": "0x...", "symbol": "USDC", "decimals": 6 },
"amountIn": "1000000000000000000",
"amountOut": "1234000",
"priceImpactPercent": 0.42,
"marketPriceImpact": 0.38,
"routes": [...],
"paths": [...],
"calldata": "0x3593...",
"value": "0x0",
"gasLimit": "310000",
"allowances": {
"needsERC20Approval": true,
"needsPermit2": false
}
}
}
When success is false, the payload contains error with a human-readable description. Clients can cache the last valid response and treat transient HTTP/network failures with retries.
2. Sign a Permit2 Approval
If allowances.needsPermit2 is true and the user wants a gasless approval, request a Permit2 PermitSingle signature covering the exact input amount. A typical flow:
- Instantiate the Permit2 allowance provider and fetch the nonce.
const allowanceProvider = new AllowanceProvider(provider, PERMIT2_ADDRESS);
const nonce = await allowanceProvider.getNonce(tokenAddress, owner, spender); - Build the permit payload (the example below uses a 30-minute expiry window).
const now = Math.floor(Date.now() / 1000);
const expires = now + 30 * 60;
const permit: PermitSingle = {
details: {
token: tokenAddress.toLowerCase(),
amount: amountInWei.toString(),
expiration: expires.toString(),
nonce: nonce.toString()
},
spender: UNIVERSAL_ROUTER_ADDRESS.toLowerCase(),
sigDeadline: expires.toString()
}; - Generate domain/types via
AllowanceTransfer.getPermitData(permit, PERMIT2_ADDRESS, chainId)and request the wallet to sign typed data.const signature = await walletClient.signTypedData({
account,
domain,
types,
primaryType: "PermitSingle",
message: values
}); - Send the resulting object (
{ details, spender, sigDeadline, signature }) to the build endpoint.
Invalidate cached permit data whenever the input amount or token pair changes to avoid replaying stale signatures.
3. Build With Permit (POST /api/buildQuoteWithPermit)
Embed the permit signature into router calldata by calling the build endpoint.
Request body
{
"account": "0xUserAddress",
"recipient": "0xUserAddress",
"tokenIn": "0x..." | "nativecoin",
"tokenOut": "0x...",
"amountIn": "1000000000000000000",
"permit2permit": {
"details": { "token": "0x...", "amount": "...", "expiration": "...", "nonce": "..." },
"spender": "0x...",
"sigDeadline": "...",
"signature": "0xabc..."
},
"slippage": 1
}
The response structure matches getQuote, but calldata now executes Permit2 before the swap. Track whether a permit-backed quote is active so it can be reused until trade parameters change.
4. Submit the Transaction
Both endpoints return transaction-ready fields that can be sent through any EVM-compatible library. Example using ethers:
import { ethers } from "ethers";
const signer = provider.getSigner(account);
const txResponse = await signer.sendTransaction({
to: UNIVERSAL_ROUTER_ADDRESS,
data: quote.calldata,
value: quote.value ? BigInt(quote.value) : undefined,
gasLimit: quote.gasLimit ? BigInt(quote.gasLimit) : undefined
});
const receipt = await provider.waitForTransaction(txResponse.hash);
Guidelines:
- Always forward the
valuereturned by the API (non-zero only for native swaps). - After submission, poll the chain until the receipt is available and relay success or failure to the user.
- For additional safety, call
eth_estimateGaswith the built transaction before broadcasting and reconcile the result with the providedgasLimit.
5. Error Handling & Retries
success: falseresponses include anerrorstring that should be surfaced to the user.- Rate limits or network hiccups: throttle requests (for example, one request per second) and retry with the same payload when appropriate.
- Permit expirations: choose an expiration window that matches your product requirements; request a fresh signature and rebuild when the permit lapses.
- Token pair or amount changes invalidate cached quotes and permits—clear derived data to prevent mismatches.
Recommended flow: Quote → (optional Permit) → Permit Build → Send to Universal Router.