UNPKG

@symmetry-hq/baskets-v2-sdk

Version:

Symmetry Baskets V2 SDK

304 lines (303 loc) 14.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMaxRebalanceAmount = getMaxRebalanceAmount; exports.generateRebalanceInstructionsForTokenPair = generateRebalanceInstructionsForTokenPair; exports.swapTokensHandler = swapTokensHandler; exports.rebalanceBasketTokensHandler = rebalanceBasketTokensHandler; const web3_js_1 = require("@solana/web3.js"); const txUtils_1 = require("./utils/txUtils"); const updateTokenPrices_1 = require("./instructions/update/updateTokenPrices"); const withdrawBeforeRebalance_1 = require("./instructions/rebalance/withdrawBeforeRebalance"); const basket_1 = require("./state/basket"); const depositAfterRebalance_1 = require("./instructions/rebalance/depositAfterRebalance"); const jup_1 = require("./instructions/jup"); const constants_1 = require("./utils/constants"); const createAtas_1 = require("./utils/createAtas"); function getMaxRebalanceAmount(params) { const { fromInfo, toInfo } = params; const valueLimit = Math.min(-fromInfo.valueDiff, toInfo.valueDiff); const amount = Math.min(Math.max(fromInfo.maxSpendAmount, 0), Math.floor(valueLimit * 10 ** fromInfo.tokenDecimals / fromInfo.tokenPrice)); const value = amount * fromInfo.tokenPrice / 10 ** fromInfo.tokenDecimals; return { amount, value }; } function generateRebalanceInstructionsForTokenPair(sdkParams, params) { return __awaiter(this, void 0, void 0, function* () { const { basketState, fromInfo, toInfo, slippageBps, minSwapValue } = params; const { amount, value } = getMaxRebalanceAmount({ fromInfo, toInfo, }); if (amount === 0 || value < minSwapValue) return { amount: 0, value: 0, ixs: [], luts: [], }; const quoteResponse = yield (0, jup_1.getQuoteResponseHandler)({ jupiterApiKey: sdkParams.jupiterApiKey, maxAllowedAccounts: sdkParams.maxAllowedAccounts, fromToken: fromInfo.token, toToken: toInfo.token, amount: amount, slippageBps, }); const withdrawIx = yield (0, withdrawBeforeRebalance_1.withdrawBeforeRebalanceIx)({ program: sdkParams.program, basketState: basketState, payer: sdkParams.payer, fromTokenMint: fromInfo.token, toTokenMint: toInfo.token, amountToWithdraw: amount, checkWeights: true, fromTokenWeight: fromInfo.targetWeight, toTokenWeight: toInfo.targetWeight, }); const jupIxAndLuts = yield (0, jup_1.generateSwapInstruction)({ payer: sdkParams.payer, jupiterApiKey: sdkParams.jupiterApiKey, quoteResponse: quoteResponse, }).catch(() => null); if (!jupIxAndLuts) { return { amount: 0, value: 0, ixs: [], luts: [], }; } const depositIx = yield (0, depositAfterRebalance_1.depositAfterRebalanceIx)({ program: sdkParams.program, basketState: basketState, payer: sdkParams.payer, fromTokenMint: fromInfo.token, toTokenMint: toInfo.token, checkWeights: true, }); return { amount: amount, value: value, ixs: [withdrawIx, jupIxAndLuts.ix, depositIx], luts: jupIxAndLuts.luts, }; }); } function swapTokensHandler(sdkParams, params) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const basketState = yield (0, basket_1.fetchBasketState)(sdkParams.program, params.basket); const fromTokenIndex = basketState.compositionMints.findIndex(mint => mint.toBase58() === params.fromToken.toBase58()); const toTokenIndex = basketState.compositionMints.findIndex(mint => mint.toBase58() === params.toToken.toBase58()); const withdrawIx = yield (0, withdrawBeforeRebalance_1.withdrawBeforeRebalanceIx)({ program: sdkParams.program, basketState: basketState, payer: sdkParams.payer, fromTokenMint: params.fromToken, toTokenMint: params.toToken, amountToWithdraw: params.fromAmount, checkWeights: false, fromTokenWeight: (_a = params.fromTokenWeight) !== null && _a !== void 0 ? _a : basketState.compositionTargetWeights[fromTokenIndex], toTokenWeight: (_b = params.toTokenWeight) !== null && _b !== void 0 ? _b : basketState.compositionTargetWeights[toTokenIndex], }); const jupIxAndLuts = yield (0, jup_1.generateSwapInstruction)({ payer: sdkParams.payer, jupiterApiKey: sdkParams.jupiterApiKey, quoteResponse: params.quoteResponse, }); const depositIx = yield (0, depositAfterRebalance_1.depositAfterRebalanceIx)({ program: sdkParams.program, basketState: basketState, payer: sdkParams.payer, fromTokenMint: params.fromToken, toTokenMint: params.toToken, checkWeights: false, }); const preIxs = yield (0, createAtas_1.createAtasIxs)(sdkParams.connection, { payer: sdkParams.payer, mints: [params.fromToken, params.toToken], }); return yield (0, txUtils_1.prepareV0Transactions)({ connection: sdkParams.connection, payer: sdkParams.payer, priorityFee: sdkParams.priorityFee, multipleIxs: [...preIxs, [withdrawIx, jupIxAndLuts.ix, depositIx]], multipleLookupTableAddresses: [...new Array(preIxs.length).fill([]), jupIxAndLuts.luts], signers: [...new Array(preIxs.length).fill([]), []], batches: [preIxs.length, 1], }); }); } function rebalanceBasketTokensHandler(sdkParams, params) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; // Extract and set default params const basket = params.basket; const fromToken = (_a = params.fromToken) !== null && _a !== void 0 ? _a : web3_js_1.PublicKey.default; const toToken = (_b = params.toToken) !== null && _b !== void 0 ? _b : web3_js_1.PublicKey.default; const minSwapValue = (_c = params.minSwapValue) !== null && _c !== void 0 ? _c : constants_1.MIN_SWAP_VALUE; const maxSellValue = (_d = params.maxSellValuePerToken) !== null && _d !== void 0 ? _d : constants_1.MAX_SELL_VALUE_PER_TOKEN; const maxSwaps = (_e = params.maxNumberOfSwaps) !== null && _e !== void 0 ? _e : constants_1.MAX_NUMBER_OF_SWAPS; // Get price update instructions const { ixs: updatePricesIxs, luts: updatePricesLuts } = yield (0, updateTokenPrices_1.updateTokenPricesIxs)({ program: sdkParams.program, basket: basket, }); // Initialize batch arrays const tokenMints = []; const firstBatchIxs = updatePricesIxs.map(ix => [ix]); const firstBatchLuts = new Array(updatePricesIxs.length).fill(updatePricesLuts); const firstBatchSigners = new Array(updatePricesIxs.length).fill([]); const secondBatchIxs = []; const secondBatchLuts = []; const secondBatchSigners = []; // Get basket state and calculate values const basketState = yield (0, basket_1.fetchBasketState)(sdkParams.program, basket); const slippageBps = basketState.rebalanceSlippageBps; const oraclePrices = yield (0, basket_1.getBasketTokenPrices)(sdkParams.program, basketState); const { rebalanceInfos } = (0, basket_1.computeRebalanceInfos)({ basketState, oraclePrices, }); // Sort rebalance infos by value difference and find relevant tokens const sortedRebalanceInfos = rebalanceInfos.sort((a, b) => a.valueDiff - b.valueDiff); let posIndex = 0; while (posIndex < sortedRebalanceInfos.length && sortedRebalanceInfos[posIndex].valueDiff < 0) posIndex++; let negIndex = sortedRebalanceInfos.length - 1; while (negIndex >= 0 && sortedRebalanceInfos[negIndex].valueDiff > 0) negIndex--; for (let i = 0; i < sortedRebalanceInfos.length; i++) if (sortedRebalanceInfos[i].valueDiff <= 0) sortedRebalanceInfos[i].valueDiff = Math.max(sortedRebalanceInfos[i].valueDiff, -maxSellValue); const fromInfo = sortedRebalanceInfos.find(info => info.token.equals(fromToken)); const toInfo = sortedRebalanceInfos.find(info => info.token.equals(toToken)); // Handle direct swap between fromToken and toToken if (fromInfo && toInfo) { const { ixs, luts, amount } = yield generateRebalanceInstructionsForTokenPair(sdkParams, { basketState, fromInfo, toInfo, slippageBps, minSwapValue, }); if (amount > 0) { secondBatchIxs.push(ixs); secondBatchLuts.push(luts); secondBatchSigners.push([]); tokenMints.push(fromInfo.token, toInfo.token); } } // Handle selling fromToken to multiple tokens else if (fromInfo) { // Iterate through tokens that need value added for (let i = sortedRebalanceInfos.length - 1; i > negIndex; i--) { const toInfo = sortedRebalanceInfos[i]; if (fromInfo.valueDiff >= 0 || toInfo.valueDiff <= 0 || toInfo.token.equals(fromToken)) continue; const { amount, value, ixs, luts } = yield generateRebalanceInstructionsForTokenPair(sdkParams, { basketState, fromInfo, toInfo, slippageBps, minSwapValue, }); if (amount === 0) continue; secondBatchIxs.push(ixs); secondBatchLuts.push(luts); secondBatchSigners.push([]); tokenMints.push(fromInfo.token, toInfo.token); fromInfo.maxSpendAmount -= amount; fromInfo.valueDiff += value; toInfo.valueDiff -= value; if (secondBatchIxs.length === maxSwaps) break; } } // Handle buying toToken from multiple tokens else if (toInfo) { // Iterate through tokens that need value removed for (let i = 0; i < posIndex; i++) { const fromInfo = sortedRebalanceInfos[i]; if (fromInfo.valueDiff >= 0 || toInfo.valueDiff <= 0 || toInfo.token.equals(fromToken)) continue; const { amount, value, ixs, luts } = yield generateRebalanceInstructionsForTokenPair(sdkParams, { basketState, fromInfo, toInfo, slippageBps, minSwapValue, }); if (amount === 0) continue; secondBatchIxs.push(ixs); secondBatchLuts.push(luts); secondBatchSigners.push([]); tokenMints.push(fromInfo.token, toInfo.token); fromInfo.maxSpendAmount -= amount; fromInfo.valueDiff += value; toInfo.valueDiff -= value; if (secondBatchIxs.length === maxSwaps) break; } } else { for (let i = 0; i < posIndex; i++) for (let j = sortedRebalanceInfos.length - 1; j > negIndex; j--) { const fromInfo = sortedRebalanceInfos[i]; const toInfo = sortedRebalanceInfos[j]; if (fromInfo.valueDiff >= 0 || toInfo.valueDiff <= 0 || toInfo.token.equals(fromToken)) continue; if (fromInfo.token.toBase58() === "8888xhvqnTWZ6tNbfAyP8mLLfaKsqfiW33tgA8RZ8888") continue; const { amount, value, ixs, luts } = yield generateRebalanceInstructionsForTokenPair(sdkParams, { basketState, fromInfo, toInfo, slippageBps, minSwapValue, }); if (amount === 0) continue; secondBatchIxs.push(ixs); secondBatchLuts.push(luts); secondBatchSigners.push([]); tokenMints.push(fromInfo.token, toInfo.token); fromInfo.maxSpendAmount -= amount; fromInfo.valueDiff += value; toInfo.valueDiff -= value; if (secondBatchIxs.length === maxSwaps) break; } } if (secondBatchIxs.length === 0) throw new Error("No swaps to perform"); // Create token accounts if needed const preIxs = yield (0, createAtas_1.createAtasIxs)(sdkParams.connection, { payer: sdkParams.payer, mints: tokenMints, }); firstBatchIxs.push(...preIxs); firstBatchLuts.push(...new Array(preIxs.length).fill([])); firstBatchSigners.push(...new Array(preIxs.length).fill([])); // Prepare and return transactions return yield (0, txUtils_1.prepareV0Transactions)({ connection: sdkParams.connection, payer: sdkParams.payer, priorityFee: sdkParams.priorityFee, multipleIxs: [...firstBatchIxs, ...secondBatchIxs], multipleLookupTableAddresses: [...firstBatchLuts, ...secondBatchLuts], signers: [...firstBatchSigners, ...secondBatchSigners], batches: [firstBatchIxs.length, secondBatchIxs.length], }); }); }