LogoLogo
  • ☠️Introduction
    • What is Clipper?
    • How Clipper Makes Money for LPs
    • How LPs Earn from Arbitrage
    • Clipper's Benchmark: No Impermanent Loss
      • Clipper vs. CPMMs vs. HODLing
      • Appendix: Math
    • Why Clipper Has Better Trading Prices
    • DAO Protocol Fees
    • DAO Governance
  • ⚔️How to Use Clipper
    • Liquidity Pools
      • Depositing & Withdrawing
      • Farming Pools
    • Trading
    • Community Adventures
    • FAQs
  • 🪙Governance Token
    • Community Governance
    • ⛵SAIL Primer
      • Clipper Fundamentals
      • DEX Market Structure
      • SAIL Supply & Circulation
      • SAIL Farming
      • veSAIL
      • Token Listings
  • 🏴‍☠️Disclaimers & Technical
    • Audits
    • Smart Contracts
      • Subgraph
        • Entities
        • Queries
    • Integrating with Clipper RFQ
      • Introduction
      • Guides
        • How to use clipper RFQ API?
        • Estimate Clipper Prices
        • Interacting with the Clipper Exchange contracts
        • Integration Examples
          • Swap Native token → Shorttail
          • Swap Shorttail → Native token
          • Swap Shorttail → Shorttail
          • Complete Swap Flow
      • API Reference
        • API v2
          • Overview
          • Pool v2
          • Quote v2
        • API v1
          • Overview
          • Pool
          • Quote
          • Sign
      • Troubleshooting & FAQs
    • Terms of Service
    • Privacy Policy
  • ⛵Come Aboard
    • Discord
    • Twitter
    • Github
Powered by GitBook
On this page

Was this helpful?

  1. Disclaimers & Technical
  2. Integrating with Clipper RFQ
  3. Guides
  4. Integration Examples

Complete Swap Flow

We’ve created some demonstration integration code in NodeJS to show how you can replicate Clipper quotes locally and send the transaction using Clipper RFQ API. The code has demonstration solvers for both the closed form and root finding modalities.

Add package.json file with this code:

{
  "name": "clipper",
  "version": "1.0.0",
  "main": "main.js",
  "license": "MIT",
  "dependencies": {
    "bignumber.js": "^9.0.2",
    "node-fetch": "^3.2.0",
    "root-finding": "^1.0.2",
    "web3": "^1.7.0"
  },
  "type": "module"
}

Add main.js file with this code

import fetch from 'node-fetch'
import BN from 'bignumber.js'
import nr from 'root-finding'
import Web3 from 'web3'
import fs from 'fs'
import ethers from "ethers";
import exchangeAbi from "./exchangeAbi.json";

const nodeUrl = process.env.NODE_URL

const rawdata = fs.readFileSync('ERC20.abi.json');
const ERC20ABI = JSON.parse(rawdata);
const authorizationHeader = 'YXBpLXVzZXI6dXNlci1wYXNz';  // This is a placeholder 

const web3 = new Web3(nodeUrl)


async function main() {
    // 1. GET POOL INFO AND ESTIMATE CLIPPER PRICES
    const prices = await fetch('https://api.clipper.exchange/rfq/pool?chain_id=137&fieldset=offchain-data', {
        method: 'GET',
        headers: {'x-api-key': authorizationHeader}
    }).then((x) => x.json());

    const poolAddress = prices.pool.address

    const asset_one = 'MATIC'
    const asset_two = 'USDC'
    const decimalsX = 18
    const decimalsY = 6
    const humanInX = 10000


    let fee_in_basis_points = 0
    for (const pair of prices.pairs) {
        if(((pair.assets[0] === asset_one) && (pair.assets[1] === asset_two)) ||
           ((pair.assets[1] === asset_one) && (pair.assets[0] === asset_two))){
            fee_in_basis_points = pair.fee_in_basis_points
            break
        }
    }

    const assetOneObj = prices.assets.find((x) => x.name === asset_one)
    const assetTwoObj = prices.assets.find((x) => x.name === asset_two)

    const assetOneContract = new web3.eth.Contract(ERC20ABI, assetOneObj.address)
    const assetTwoContract = new web3.eth.Contract(ERC20ABI, assetTwoObj.address)

    const assetOneBalance = await assetOneContract.methods.balanceOf(poolAddress).call()
    const assetTwoBalance = await assetTwoContract.methods.balanceOf(poolAddress).call()

    const M = (10000-fee_in_basis_points)/10000

    const scaledDecimalsX = new BN(10).exponentiatedBy(decimalsX)
    const scaledDecimalsY = new BN(10).exponentiatedBy(decimalsY)
    
    const adjustedInX = humanInX*M
    const inX = new BN(humanInX).multipliedBy(scaledDecimalsX)

    // Pull in additional onchain state. This is from a snapshot of USDC and USDT, respectively.
    const qX = new BN(assetOneBalance).dividedBy(scaledDecimalsX).toNumber()
    const qY = new BN(assetTwoBalance).dividedBy(scaledDecimalsY).toNumber()
    // Pull additional offchain state
    const k = prices.pool.k
    // format variables according to formula
    const pX = assetOneObj.price_in_usd
    const wX = assetOneObj.listing_weight

    const pY = assetTwoObj.price_in_usd
    const wY = assetTwoObj.listing_weight

    const initialGuess = (adjustedInX*pX)/pY

    const initialUtility = Math.pow(pX*qX, 1-k)/Math.pow(wX, k) + Math.pow(pY*qY, 1-k)/Math.pow(wY, k) 
    const newXUtility =  Math.pow(pX*(qX+adjustedInX), 1-k)/Math.pow(wX, k)

    let outY;

    // CLOSED FORM SOLUTION
    const outYClosedForm = qY-Math.pow((initialUtility - newXUtility)*Math.pow(wY,k), (1/(1-k)))/pY

    // ROOT FINDING SOLUTION
    function newYUtility(outY) {
        return Math.pow(pY*(qY-outY), 1-k)/Math.pow(wY, k) 
    }

    function zeroMe(outY) {
        return initialUtility - newXUtility - newYUtility(outY)
    }
    // nr.newtonRaphson(guess, increment, iteration, eps, f);
    // These increment, iteration, and eps values may need to be changed
    const outYRootFinding = nr.newtonRaphson(initialGuess, 1e-6, 50, 1e-8, zeroMe)
    console.log('Closed Form: ', outYClosedForm);
    console.log('Root Finding: ', outYRootFinding);
    outY = outYClosedForm;
    // Adjust for FMV restriction, input >= output (without respect to fees!)
    outY = Math.min(outY, (humanInX*pX)/pY).toFixed(decimalsY)

    // 2. CREATE A QUOTE
    const quote = await fetch('https://api.clipper.exchange/rfq/quote', {
        method: 'POST',
        body: JSON.stringify({
            "input_asset_symbol": asset_one,
            "output_asset_symbol": asset_two,
            "chain_id": 137,
            "input_amount": inX.toFixed(),
            "time_in_seconds": prices.pool.default_time_in_seconds
        }),
        headers: {
            'Content-Type': 'application/json',
            'x-api-key': authorizationHeader
        }
    })
        .then((x) => x.json())

    console.log({
        fromFormula: outY,
        fromQuote: new BN(quote.output_amount).dividedBy(scaledDecimalsY).toFixed(decimalsY)
    })
    
    // 3. SIGN THE QUOTE
    const sign = await fetch('https://api.clipper.exchange/rfq/sign', {
        method: 'POST',
        body: JSON.stringify({
            quote_id: quote.id,  // Use the quote_id from the previous /quote call
            destination_address: "0x5901920A7b8cb1Bba39220FAC138Ffb3800dD212",
            sender_address: "0x5901920A7b8cb1Bba39220FAC138Ffb3800dD212", // Optional: it is for DEX aggregator partners that are using their own smart contract
        }),
        headers: {
            'Content-Type': 'application/json',
            'x-api-key': authorizationHeader
        }
    }).then((x) => x.json());
    
    // 4. SEND THE TRANSACTION
    const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
    const exchangeContract = new ethers.Contract(
        sign.clipper_exchange_address,
        exchangeAbi,
        provider
    );
    
    // Any 32 bytes identifier
    const auxData = "0x00000000000000000000000000000000000000000000000000";

    const result = await exchangeContract.swap(
        sign.input_asset_address,
        sign.output_asset_address,
        sign.input_amount,
        sign.output_amount,
        sign.good_until,
        sign.destination_address,
        [sign.signature.v, sign.signature.r, sign.signature.s],
        auxData
    );
    console.log("Swap Result:", result);
}
main()

Add ERC20.abi.json

Add exchangeAbi.json

Install packages

npm install

Run the code

NODE_URL="https://polygon-rpc.com/" node main.js

Extra notes of the code

The code compares the output from a root-finding implementation in our code (”fromFormula”) to the output from the Clipper servers (”fromQuote”) on Polygon. Note that sometimes these values may diverge slightly because of Polygon block updates.

You can change the tokens and amounts by changing these values in main.js

const asset_one = 'USDT'
const asset_two = 'USDC'
const decimalsX = 6
const decimalsY = 6
const humanInX = 100

The asset_ values are token names, decimals values should be the decimals for the first and second tokens, respectively, and humanInX should be the sold amount of asset_one in human terms (i.e., 1000 USDC ≈ $1,000 rather than “1000000000” USDC).

PreviousSwap Shorttail → ShorttailNextAPI Reference

Last updated 2 months ago

Was this helpful?

🏴‍☠️
5KB
ERC20.abi.json
19KB
exchangeAbi.json