Complete Swap Flow
{
"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"
}
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 = 'Basic 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: {'Authorization': 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',
'Authorization': 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/quote', {
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',
'Authorization': 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()
Extra notes of the code
Last updated
Was this helpful?
