Skip to main content

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

ConstantDescriptionExample Value
API_BASE_URLBase URL for backend calls.https://api.octo.exchange
UNIVERSAL_ROUTER_ADDRESSContract address targeted by every built transaction.0x241BF19641839b249E8174Bd22FACd3d3ac0642A
PERMIT2_ADDRESSUniswap Permit2 contract used for gasless approvals.0x000000000022D473030F116dDEE9F6B43aC78BA3
NATIVE_TOKENUse 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
}
FieldNotes
tokenIn / tokenOutLower-case checksummed addresses. Use nativecoin for the chain's native asset.
amountInStringified BigNumber in token-in base units.
accountRecipient address. The zero address is accepted when no wallet is connected.
slippagePercentage tolerance enforced when building calldata.
useRouterBalancefalse 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:

  1. Instantiate the Permit2 allowance provider and fetch the nonce.
    const allowanceProvider = new AllowanceProvider(provider, PERMIT2_ADDRESS);
    const nonce = await allowanceProvider.getNonce(tokenAddress, owner, spender);
  2. 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()
    };
  3. 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
    });
  4. 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 value returned 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_estimateGas with the built transaction before broadcasting and reconcile the result with the provided gasLimit.

5. Error Handling & Retries

  • success: false responses include an error string 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.