UNPKG

navi-sdk

Version:

The NAVI SDK Client provides tools for interacting with the Sui blockchain networks, designed for handling transactions, accounts, and smart contracts efficiently. This documentation covers the setup, account management, and transaction handling within th

537 lines (491 loc) 14.3 kB
import BigNumber from "bignumber.js"; import { AggregatorConfig } from "./config"; import { Dex, Quote, SwapOptions } from "../../types"; import { returnMergedCoins } from "../PTB/commonFunctions"; import { Transaction, TransactionResult } from "@mysten/sui/transactions"; import { SuiClient } from "@mysten/sui/client"; import { makeCETUSPTB } from "./Dex/cetus"; import { makeTurbosPTB } from "./Dex/turbos"; import { makeKriyaV3PTB } from "./Dex/kriyaV3"; import { makeAftermathPTB } from "./Dex/aftermath"; import { makeKriyaV2PTB } from "./Dex/KriyaV2"; import { makeDeepbookPTB } from "./Dex/deepbook"; import { getQuote } from "./getQuote"; import { generateRefId } from "./utils"; import { makeBluefinPTB } from "./Dex/bluefin"; import { makeVSUIPTB } from "./Dex/vSui"; import { makeHASUIPTB } from "./Dex/haSui"; import { makeMAGMAPTB } from "./Dex/magma"; import { makeMomentumPTB } from "./Dex/momentum"; export async function getCoins( client: SuiClient, address: string, coinType: any = "0x2::sui::SUI" ) { const coinAddress = coinType.address ? coinType.address : coinType; const coinDetails = await client.getCoins({ owner: address, coinType: coinAddress, }); return coinDetails; } export async function getCoinPTB( address: string, coin: string, amountIn: number | string | bigint, txb: Transaction, client: SuiClient ) { let coinA: TransactionResult; if (coin === "0x2::sui::SUI") { coinA = txb.splitCoins(txb.gas, [txb.pure.u64(amountIn)]); } else { const coinInfo = await getCoins(client, address, coin); // Check if user has enough balance for tokenA if (!coinInfo.data[0]) { throw new Error("Insufficient balance for this coin"); } // Merge coins if necessary, to cover the amount needed const mergedCoin = returnMergedCoins(txb, coinInfo); coinA = txb.splitCoins(mergedCoin, [txb.pure.u64(amountIn)]); } return coinA; } export async function buildSwapPTBFromQuote( userAddress: string, txb: Transaction, minAmountOut: number, coinIn: TransactionResult, quote: Quote, referral: number = 0, ifPrint: boolean = true, // Set ifPrint to be optional with a default value apiKey?: string, swapOptions?: SwapOptions ): Promise<TransactionResult> { if (!quote.routes || quote.routes.length === 0) { throw new Error("No routes found in data"); } const tokenA = quote.from; const tokenB = quote.target; const allPaths = JSON.parse(JSON.stringify(quote.routes)); if (ifPrint) { console.log(`tokenA: ${tokenA}, tokenB: ${tokenB}`); } if ( Number(quote.amount_in) !== quote.routes.reduce( (sum: number, route: any) => sum + Number(route.amount_in), 0 ) ) { throw new Error( "Outer amount_in does not match the sum of route amount_in values" ); } const serviceFee = swapOptions?.serviceFee || swapOptions?.feeOption; // Calculate fee amounts if options provided if ( serviceFee && serviceFee.fee > 0 && serviceFee.receiverAddress && serviceFee.receiverAddress !== "0x0" ) { const totalAmount = quote.amount_in; const serviceFeeAmount = new BigNumber(totalAmount) .multipliedBy(serviceFee.fee) .toFixed(0); const newAmountIn = new BigNumber(totalAmount) .minus(serviceFeeAmount) .toFixed(0); // split coins const serviceFeeCoinIn = txb.splitCoins(coinIn, [serviceFeeAmount]); // get router const [router, serviceFeeRouter] = await Promise.all([ getQuote(tokenA, tokenB, newAmountIn, apiKey, swapOptions), new Promise<Quote | null>((resolve, reject) => { if (serviceFeeAmount === "0") { resolve(null); return; } if ( tokenA === "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT" ) { resolve({ routes: [], amount_in: serviceFeeAmount, amount_out: serviceFeeAmount, from: tokenA, target: tokenA, dexList: [], }); return; } getQuote( tokenA, "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT", serviceFeeAmount, apiKey, swapOptions ) .then((router: Quote) => { resolve(router); }) .catch(reject); }), ]); if (!router.amount_out) { router.amount_out = "0"; } if (serviceFeeRouter && !serviceFeeRouter.amount_out) { serviceFeeRouter.amount_out = "0"; } const [coinOut, feeCoinOut] = await Promise.all([ buildSwapPTBFromQuote( userAddress, txb, minAmountOut, coinIn as any, router as any, referral ), !!serviceFeeRouter && serviceFeeCoinIn ? serviceFeeRouter.from === serviceFeeRouter.target ? serviceFeeCoinIn : buildSwapPTBFromQuote( userAddress, txb, 0, serviceFeeCoinIn as any, serviceFeeRouter as any, referral ) : new Promise((resolve) => { resolve(null); }), ]); if (feeCoinOut) { txb.moveCall({ package: AggregatorConfig.aggregatorContract, module: "slippage", function: "emit_referral_event", arguments: [ coinOut, feeCoinOut as any, txb.pure.address(serviceFee.receiverAddress), txb.pure.u8(router.from_token?.decimals || 9), txb.pure.u8(router.to_token?.decimals || 9), txb.pure.u8(9), txb.pure.u64(router.amount_in), txb.pure.u64(Math.floor((router.from_token?.price || 0) * 1e9)), txb.pure.u64(Math.floor((router.to_token?.price || 0) * 1e9)), txb.pure.u64( BigNumber(serviceFee.fee).multipliedBy(1e4).toFixed(0) ), txb.pure.u64(referral), ], typeArguments: [ router.from, router.target, "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT", ], }); txb.transferObjects([feeCoinOut as any], serviceFee.receiverAddress); } return coinOut; } const finalCoinB = txb.moveCall({ target: "0x2::coin::zero", typeArguments: [tokenB], }); for (let i = 0; i < allPaths.length; i++) { const path = allPaths[i]; const pathCoinAmountIn = Math.floor(path.amount_in); const pathCoinAmountOut = path.amount_out; if (ifPrint) { console.log( `Path Index: `, i, `Amount In: `, pathCoinAmountIn, `Expected Amount Out: `, pathCoinAmountOut ); } let pathTempCoin: any = txb.splitCoins(coinIn, [pathCoinAmountIn]); for (let j = 0; j < path.path.length; j++) { const route = path.path[j]; const poolId = route.id; const provider = route.provider; const tempTokenA = route.from; const tempTokenB = route.target; const a2b = route.a2b; const typeArguments = route.info_for_ptb.typeArguments; let amountInPTB; let tuborsVersion; if (provider === "turbos") { tuborsVersion = route.info_for_ptb.contractVersionId; } if (ifPrint) { console.log( `Route Index: `, i, "-", j, `provider: `, provider, `from: `, tempTokenA, `to: `, tempTokenB ); } amountInPTB = txb.moveCall({ target: "0x2::coin::value", arguments: [pathTempCoin], typeArguments: [tempTokenA], }); switch (provider) { case Dex.CETUS: { const coinA = a2b ? pathTempCoin : txb.moveCall({ target: "0x2::coin::zero", typeArguments: [tempTokenB], }); const coinB = a2b ? txb.moveCall({ target: "0x2::coin::zero", typeArguments: [tempTokenB], }) : pathTempCoin; const coinABs = await makeCETUSPTB( txb, poolId, true, coinA, coinB, amountInPTB, a2b, typeArguments, ); if (a2b) { txb.transferObjects([coinABs[0]], userAddress); pathTempCoin = coinABs[1]; } else { txb.transferObjects([coinABs[1]], userAddress); pathTempCoin = coinABs[0]; } break; } case Dex.TURBOS: { pathTempCoin = txb.makeMoveVec({ elements: [pathTempCoin!], }); const { turbosCoinB, turbosCoinA } = await makeTurbosPTB( txb, poolId, true, pathTempCoin, amountInPTB, a2b, typeArguments, userAddress, tuborsVersion ); txb.transferObjects([turbosCoinA], userAddress); pathTempCoin = turbosCoinB; break; } case Dex.KRIYA_V2: { pathTempCoin = await makeKriyaV2PTB( txb, poolId, true, pathTempCoin, amountInPTB, a2b, typeArguments ); break; } case Dex.KRIYA_V3: { pathTempCoin = await makeKriyaV3PTB( txb, poolId, true, pathTempCoin, amountInPTB, a2b, typeArguments ); break; } case Dex.AFTERMATH: { const amountLimit = route.info_for_ptb.amountLimit; pathTempCoin = await makeAftermathPTB( txb, poolId, pathTempCoin, amountLimit, a2b, typeArguments ); break; } case Dex.DEEPBOOK: { const amountLimit = route.info_for_ptb.amountLimit; const { baseCoinOut, quoteCoinOut } = await makeDeepbookPTB( txb, poolId, pathTempCoin, amountLimit, a2b, typeArguments ); if (a2b) { pathTempCoin = quoteCoinOut; txb.transferObjects([baseCoinOut], userAddress); } else { pathTempCoin = baseCoinOut; txb.transferObjects([quoteCoinOut], userAddress); } break; } case Dex.BLUEFIN: { const { coinAOut, coinBOut } = await makeBluefinPTB( txb, poolId, pathTempCoin, amountInPTB, a2b, typeArguments ); if (a2b) { txb.transferObjects([coinAOut], userAddress); pathTempCoin = coinBOut; } else { txb.transferObjects([coinBOut], userAddress); pathTempCoin = coinAOut; } break; } case Dex.MAGMA: { const coinA = a2b ? pathTempCoin : txb.moveCall({ target: "0x2::coin::zero", typeArguments: [tempTokenB], }); const coinB = a2b ? txb.moveCall({ target: "0x2::coin::zero", typeArguments: [tempTokenB], }) : pathTempCoin; const coinABs = await makeMAGMAPTB( txb, poolId, true, coinA, coinB, amountInPTB, a2b, typeArguments, ); if (a2b) { txb.transferObjects([coinABs[0]], userAddress); pathTempCoin = coinABs[1]; } else { txb.transferObjects([coinABs[1]], userAddress); pathTempCoin = coinABs[0]; } break; } case Dex.VSUI: { pathTempCoin = await makeVSUIPTB(txb, pathTempCoin, a2b); break; } case Dex.HASUI: { pathTempCoin = await makeHASUIPTB(txb, pathTempCoin, a2b); break; } case Dex.MOMENTUM: { const outputCoin = await makeMomentumPTB(txb, poolId, pathTempCoin, amountInPTB, a2b, typeArguments); txb.transferObjects([pathTempCoin], userAddress); pathTempCoin = outputCoin; break; } default: { break; } } } txb.mergeCoins(finalCoinB, [pathTempCoin]); } txb.transferObjects([coinIn], userAddress); txb.moveCall({ target: `${AggregatorConfig.aggregatorContract}::slippage::check_slippage_v2`, arguments: [ finalCoinB, // output coin object txb.pure.u64(Math.floor(minAmountOut)), // min amount out txb.pure.u64(quote.amount_in), // amount in txb.pure.u64(referral), // refferal id ], typeArguments: [tokenA, tokenB], }); return finalCoinB; } export async function swapPTB( address: string, txb: Transaction, fromCoinAddress: string, toCoinAddress: string, coin: TransactionResult, amountIn: number | string | bigint, minAmountOut: number, apiKey?: string, swapOptions: SwapOptions = { baseUrl: undefined, dexList: [], byAmountIn: true, depth: 3, ifPrint: true, } ): Promise<TransactionResult> { const refId = apiKey ? generateRefId(apiKey) : 0; // Get the output coin from the swap route and transfer it to the user const quote = await getQuote( fromCoinAddress, toCoinAddress, amountIn, apiKey, swapOptions ); const finalCoinB = await buildSwapPTBFromQuote( address, txb, minAmountOut, coin, quote, refId, swapOptions.ifPrint, apiKey, swapOptions ); return finalCoinB; } export async function checkIfNAVIIntegrated( digest: string, client: SuiClient ): Promise<boolean> { const results = await client.getTransactionBlock({ digest, options: { showEvents: true }, }); return ( results.events?.some((event) => event.type.includes(`${AggregatorConfig.aggregatorContract}::slippage`) ) ?? false ); }