Zap SOR API Guide

This guide will help you get started with using the Odos API for zapping into and out of LP positions for Uniswap V2 and Solidly style DEXs. It will cover getting zap quotes with (/sor/quote/v2/zap) and assembling a transaction (/sor/assemble).

This guide is meant to highlight a more advanced use case for the Odos API, if you are looking for a simple getting started guide for normal swaps, check out the SOR Quick Start Guide.

For more in depth details on zaps and their use-case, use the detailed guide here.

Setup

Firstly, you’ll need to setup your project. This involves importing the required libraries for making HTTP requests.

Recommended HTTP libraries (examples below):

  • Python: requests (Requires installation from here)
  • Javascript: fetch (natively supported in most environments)

Types Of Zap Quotes Supported by Odos

The following request bodies can be sent to the /sor/quote/v2/zap endpoint

Zap into a single pool using a single asset

1 WETH -> Uniswap V2 LP Position WETH/USDC Uniswap V2 Pool

{
  "chainId": 1,
  "inputTokens": [
    {
      "amount": "1000000000000000000",
      "tokenAddress": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
    }
  ],
  "outputTokens": [
    {
      "proportion": 1,
      "tokenAddress": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
    }
  ],
  "referralCode": 0,
  "slippageLimitPercent": 0.3,
  "sourceBlacklist": [],
  "sourceWhitelist": [],
  "userAddr": "0x...",
  "compact": true
}

Zap into a single pool using multiple assets

100 DAI + 250 USDC -> Uniswap V2 LP Position WETH/USDC Uniswap V2 Pool

{
  "chainId": 1,
  "inputTokens": [
    {
      "amount": "100000000000000000000",
      "tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
    },
    {
      "amount": "250000000",
      "tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
    }
  ],
  "outputTokens": [
    {
      "proportion": 1,
      "tokenAddress": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
    }
  ],
  "referralCode": 0,
  "slippageLimitPercent": 0.3,
  "sourceBlacklist": [],
  "sourceWhitelist": [],
  "userAddr": "0x...",
  "compact": true
}

Zap into a single pool using a mix of assets and other LP positions

1 WETH + 1 Uniswap V2 LP Position WETH/USDC Uniswap V2 Pool -> SushiSwap V2 LP Position WETH/USDT SushiSwap V2 Pool

{
  "chainId": 1,
  "inputTokens": [
    {
      "amount": "1000000000000000000",
      "tokenAddress": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
    },
    {
      "amount": "1000000000000000000",
      "tokenAddress": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
    }
  ],
  "outputTokens": [
    {
      "proportion": 1,
      "tokenAddress": "0x06da0fd433C1A5d7a4faa01111c044910A184553"
    }
  ],
  "referralCode": 0,
  "slippageLimitPercent": 0.3,
  "sourceBlacklist": [],
  "sourceWhitelist": [],
  "userAddr": "0x...",
  "compact": true
}

Zap out of a single pool into a single asset

1 Uniswap V2 LP Position WETH/USDC Uniswap V2 Pool -> LINK

{
  "chainId": 1,
  "inputTokens": [
    {
      "amount": "1000000000000000000",
      "tokenAddress": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
    }
  ],
  "outputTokens": [
    {
      "proportion": 1,
      "tokenAddress": "0x514910771AF9Ca656af840dff83E8264EcF986CA"
    }
  ],
  "referralCode": 0,
  "slippageLimitPercent": 0.3,
  "sourceBlacklist": [],
  "sourceWhitelist": [],
  "userAddr": "0x...",
  "compact": true
}

Zap out of a single pool into multiple assets

1 Uniswap V2 LP Position WETH/USDC Uniswap V2 Pool -> 80% WETH + 20% WBTC

{
  "chainId": 1,
  "inputTokens": [
    {
      "amount": "1000000000000000000",
      "tokenAddress": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
    }
  ],
  "outputTokens": [
    {
      "proportion": 0.8,
      "tokenAddress": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
    },
    {
      "proportion": 0.2,
      "tokenAddress": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
    }
  ],
  "referralCode": 0,
  "slippageLimitPercent": 0.3,
  "sourceBlacklist": [],
  "sourceWhitelist": [],
  "userAddr": "0x...",
  "compact": true
}

Zap out of multiple pools into multiple assets

1 Uniswap V2 LP Position WETH/USDC Uniswap V2 Pool + 1 SushiSwap V2 LP Position WETH/USDT SushiSwap V2 Pool -> 50% USDC + 50% USDT

{
  "chainId": 1,
  "inputTokens": [
    {
      "amount": "1000000000000000000",
      "tokenAddress": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
    },
    {
      "amount": "1000000000000000000",
      "tokenAddress": "0x06da0fd433C1A5d7a4faa01111c044910A184553"
    }
  ],
  "outputTokens": [
    {
      "proportion": 0.5,
      "tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
    },
    {
      "proportion": 0.5,
      "tokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
    }
  ],
  "referralCode": 0,
  "slippageLimitPercent": 0.3,
  "sourceBlacklist": [],
  "sourceWhitelist": [],
  "userAddr": "0x...",
  "compact": true
}

Step 0: Check Zap Pool/Token is Routable

Before requesting a quote, the Odos Pricing service can be used to check if an address is routable for zapping using the GET /pricing/token/{chain_id}/{zap_token_addr} endpoint. If the request returns a price, then the token can be routed using the /sor/quote/v2/zap.

Below are some example requests that can be made programatically to check the price for an address.

import json

import requests

chain_id = 1
zap_token_addr = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
zap_price_url = f"https://api.odos.xyz/pricing/token/{chain_id}/{zap_token_addr}"

response = requests.get(zap_price_url)

price_res = response.json()

if response.status_code == 200 and "price" in price_res:
  print(price_res["price"])
  # pool/token price is available and it can be used in routing
else:
  print(f"Routing unavailable for {zap_token_addr}")
  # handle pool/token routing unavailable
const chainId = 1;
const zapTokenAddr = '0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc';
const zapPriceUrl = `https://api.odos.xyz/pricing/token/${chainId}/${zapTokenAddr}`;

const response = await fetch(zapPriceUrl);

const priceRes = await response.json();

if (response.status === 200 && priceRes.price) {
  console.log(priceRes.price)
  // pool/token price is available and it can be used in routing
} else {
  console.error('Error in Quote:', response);
  // handle pool/token routing unavailable
}

Step 1: Generate a Quote

The first step to execute a zap using the Odos SOR API is generating a quote. This can be achieved by sending a POST request to the /sor/quote/v2/zap endpoint, using the same request body format as the /sor/quote/v2 endpoint. It is recommended to start with one of the example request bodies above and customize as necessary.

For more in-depth explanation of specific request parameters, please check out the SOR Quick Start Guide.

import json

import requests

quote_url = "https://api.odos.xyz/sor/quote/v2/zap"

quote_request_body = {
    "chainId": 1,
    "inputTokens": [
      {
        "tokenAddress": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH token address
        "amount": "1000000000000000000" # amount of WETH to use for zap-in
      }
    ],
    "outputTokens": [
      {
        "tokenAddress": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc", # address of Uniswap V2 pool to zap in to
        "proportion": 1 # proportion of input to use for Uniswap V2 zap
      }
    ],
    "slippageLimitPercent": 0.3, # set your slippage limit percentage (1 = 1%)
    "userAddr": "0x...", # checksummed user address
    "referralCode": 0, # referral code (recommended)
    "compact": True,
}

response = requests.post(
  quote_url,
  headers={"Content-Type": "application/json"},
  json=quote_request_body
)

if response.status_code == 200:
  quote = response.json()
  # handle quote response data
else:
  print(f"Error in Quote: {response.json()}")
  # handle quote failure cases
const quoteUrl = 'https://api.odos.xyz/sor/quote/v2/zap';

const quoteRequestBody = {
  chainId: 1, // Replace with desired chainId
  inputTokens: [
    {
        tokenAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH token address address
        amount: '1000000000000000000', // amount of WETH to use for zap-in
    }
  ],
  outputTokens: [
    {
        tokenAddress: '0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc', // address of Uniswap V2 pool to zap in to
        proportion: 1 // proportion of input to use for Uniswap V2 zap
    }
  ],
  userAddr: '0x...', // checksummed user address
  slippageLimitPercent: 0.3, // set your slippage limit percentage (1 = 1%),
  referralCode: 0, // referral code (recommended)
  compact: true,
};

const response = await fetch(
  quoteUrl,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(quoteRequestBody),
  });

if (response.status === 200) {
  const quote = await response.json();
  // handle quote response data
} else {
  console.error('Error in Quote:', response);
  // handle quote failure cases
}

Replace the placeholder values in the request body with actual values for your desired quote. The userAddr should be the checksummed address of the user who may eventually assemble and execute the transaction.

You’ll receive a response with the quote details. The pathId response field will be necessary for assembling your transactions in the next step. Paths are valid for 60 seconds after the quote is received, after which a new quote will have to be requested.

Step 2: Assemble the Transaction

In order to execute an Odos quote on chain, the transaction must be assembled. The Odos API will completely assemble the transaction for you, manual assembly is discouraged and not supported. Once you have the pathId from the quote, you can send a POST request to the /sor/assemble endpoint to get the on-chain transaction data. This is the same endpoint that is used to assemble quotes from /sor/quote/v2.

assemble_url = "https://api.odos.xyz/sor/assemble"

assemble_request_body = {
    "userAddr": "0x...", # the checksummed address used to generate the quote
    "pathId": quote["pathId"], # Replace with the pathId from quote response in step 1
    "simulate": False, # this can be set to true if the user isn't doing their own estimate gas call for the transaction
}

response = requests.post(
  assemble_url,
  headers={"Content-Type": "application/json"},
  json=assemble_request_body
)

if response.status_code == 200:
  assembled_transaction = response.json()
  # handle Transaction Assembly response data
else:
  print(f"Error in Transaction Assembly: {response.json()}")
  # handle Transaction Assembly failure cases
const assembleUrl = 'https://api.odos.xyz/sor/assemble';

const assembleRequestBody = {
  userAddr: '0x...', // the checksummed address used to generate the quote
  pathId: quote.pathId, // Replace with the pathId from quote response in step 1
  simulate: true, // this can be set to true if the user isn't doing their own estimate gas call for the transaction
};

const response = await fetch(
  assembleUrl,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(assembleRequestBody),
  });

if (response.status === 200) {
  const assembledTransaction = await response.json();
  // handle Transaction Assembly response data
} else {
  console.error('Error in Transaction Assembly:', response);
  // handle quote failure cases
}

Replace the placeholder values in the request body with actual values. The userAddr should be the checksummed address of the user who requested the quote and will be executing the transaction.

The response will contain all of the necessary parameters for transaction execution in the transaction field which can be used to sign and execute the swap on-chain.

Step 3: Execute the Transaction

Once you have the transaction details from the /sor/assemble response, you can use the transaction object to sign and send directly with an EOA wallet or conduct a low level call from a smart contract with the transaction.data field. Reminder that modifying transaction.data or assembling any Odos router function call from scratch is not officially supported and 100% at your own risk.

Recommended Web3 libraries (examples below):

from web3 import Web3

# 1. create web3 provider
w3 = Web3(Web3.HTTPProvider("node http endpoint for your network"))

# 2. Extract transaction object from assemble API response
transaction = assembled_transaction["transaction"]

# 3. Sign tx with a private key
pk = "Your EOA private key"

# add the chainId to the transaction object from the API if signing raw transaction
transaction["chainId"] = 1

# web3py requires the value to be an integer
transaction["value"] = int(transaction["value"])

signed_tx = w3.eth.account.sign_transaction(transaction, pk)

# 4. Send the signed transaction
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
const Web3 = require('web3');

// 1. create web3 provider
const web3 = new Web3('node http endpoint for your network or wallet provider');

// 2. Extract transaction object from assemble API response
const transaction = assembledTransaction.transaction;

let txHash;
// 3a. Sign transaction with a web3 provider / wallet
txHash = await web3.eth.accounts.signTransaction(transaction);

// 3b. sign transaction with private key
const pk = 'Your EOA private key';
const signedTx = web3.eth.accounts.signTransaction(transaction, pk);
txHash = await web3.eth.send_raw_transaction(signedTx.rawTransaction);