@symmetry-hq/baskets-v2-sdk
Version:
Symmetry Baskets V2 SDK
304 lines (303 loc) • 14.8 kB
JavaScript
;
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],
});
});
}