UNPKG

@symmetry-hq/baskets-sdk

Version:

Software Development Kit for interacting with Symmetry Baskets Program

766 lines (765 loc) 41.2 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAddressLookupTableAccounts = void 0; exports.delay = delay; exports.rawOraclePrices = rawOraclePrices; exports.getOraclePrices = getOraclePrices; exports.getFilteredProgramAccounts = getFilteredProgramAccounts; exports.signVersionedTransactions = signVersionedTransactions; exports.signTransactionsWithWallet = signTransactionsWithWallet; exports.sendSignedTransaction = sendSignedTransaction; exports.confirmTransaction = confirmTransaction; exports.sendSignedTransactions = sendSignedTransactions; exports.buildTxFromQuoteResponse = buildTxFromQuoteResponse; exports.findRoute = findRoute; exports.generateJupSwapInstruction = generateJupSwapInstruction; exports.generateJupTxData = generateJupTxData; exports.calculateRebalanceAmounts = calculateRebalanceAmounts; exports.stringToAscii = stringToAscii; exports.asciiToString = asciiToString; exports.fetchTokenList = fetchTokenList; exports.getCurrentComposition = getCurrentComposition; exports.validateCreateBasketParams = validateCreateBasketParams; exports.tryMetadata = tryMetadata; const anchor_1 = require("@coral-xyz/anchor"); const web3_js_1 = require("@solana/web3.js"); const basketsIDL_1 = require("./basketsIDL"); const config_1 = require("./config"); const client_1 = require("@pythnetwork/client"); const axios_1 = __importDefault(require("axios")); const basketState_1 = require("./basketState"); const nodewallet_1 = __importDefault(require("@coral-xyz/anchor/dist/cjs/nodewallet")); const splTokenHelpers_1 = require("./splTokenHelpers"); function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function rawOraclePrices(connection, tokenList) { return __awaiter(this, void 0, void 0, function* () { let oraclePricesData = yield connection .getMultipleAccountsInfo(tokenList.map((x) => x.oracleAccount), "confirmed"); let oraclePrices = []; for (let i = 0; i < oraclePricesData.length; i++) { if (oraclePricesData[i] == null) { oraclePrices.push(0); continue; } if (tokenList[i].oracleType == 0) //@ts-ignore oraclePrices.push((0, client_1.parsePriceData)(oraclePricesData[i].data).aggregate.price); else if (tokenList[i].oracleType == 1) { let price = parseInt(new anchor_1.BN(//@ts-ignore oraclePricesData[i].data.subarray(tokenList[i].oracleIndex * 8 + 9, tokenList[i].oracleIndex * 8 + 17), 10, "le").toString()) / 10 ** 12; price = price * (10000 - tokenList[i].oracleConfidencePct) / 10000; oraclePrices.push(price); } else if (tokenList[i].oracleType == 2) { let total_lamports = parseInt(new anchor_1.BN(//@ts-ignore oraclePricesData[i].data.subarray(1 + 3 * 32 + 1 + 5 * 32, 1 + 3 * 32 + 1 + 5 * 32 + 8), 10, "le").toString()); let pool_token_supply = parseInt(new anchor_1.BN(//@ts-ignore oraclePricesData.data.subarray(1 + 3 * 32 + 1 + 5 * 32 + 8, 1 + 3 * 32 + 1 + 5 * 32 + 16), 10, "le").toString()); oraclePrices.push(total_lamports / pool_token_supply); } else if (tokenList[i].oracleType == 3) { //@ts-ignore let data = oraclePricesData[i].data; let price = data.readBigInt64LE(73); let exp = data.readInt32LE(89); oraclePrices.push(Number(price) * 10 ** exp); } else if (tokenList[i].oracleType == 4) { //@ts-ignore let priceObj = oraclePricesData[i].data.subarray(2264, 2392); let price = parseInt(new anchor_1.BN(priceObj.subarray(32, 48), 10, "le").toString()); oraclePrices.push(price / 10 ** 18); } else { oraclePrices.push(0); } } for (let i = 0; i < oraclePrices.length; i++) if (tokenList[i].oracleType == 2) oraclePrices[i] *= oraclePrices[1]; return oraclePrices; }); } function getOraclePrices(program, tokenList) { return __awaiter(this, void 0, void 0, function* () { let oraclePricesData = (yield program.provider.connection .getMultipleAccountsInfo(tokenList.map(x => new web3_js_1.PublicKey(x.oracleAccount)), "confirmed")); let oraclePrices = []; for (let i = 0; i < oraclePricesData.length; i++) { if (oraclePricesData[i] == null) { oraclePrices.push(0); } else { if (tokenList[i].oracleType == "Pyth") //@ts-ignore oraclePrices.push((0, client_1.parsePriceData)(oraclePricesData[i].data).aggregate.price); else if (tokenList[i].oracleType == "Switchboard") { let price = parseInt(new anchor_1.BN(//@ts-ignore oraclePricesData[i].data.subarray(tokenList[i].oracleIndex * 8 + 9, tokenList[i].oracleIndex * 8 + 17), 10, "le").toString()) / 10 ** 12; price = price * (10000 - tokenList[i].oracleConfidencePct) / 10000; oraclePrices.push(price); } else if (tokenList[i].oracleType == "LST") { let total_lamports = parseInt(new anchor_1.BN(//@ts-ignore oraclePricesData[i].data.subarray(1 + 3 * 32 + 1 + 5 * 32, 1 + 3 * 32 + 1 + 5 * 32 + 8), 10, "le").toString()); let pool_token_supply = parseInt(new anchor_1.BN(//@ts-ignore oraclePricesData[i].data.subarray(1 + 3 * 32 + 1 + 5 * 32 + 8, 1 + 3 * 32 + 1 + 5 * 32 + 16), 10, "le").toString()); oraclePrices.push(total_lamports / pool_token_supply); } else if (tokenList[i].oracleType == "PythSponsored") { //@ts-ignore let data = oraclePricesData[i].data; let price = data.readBigInt64LE(73); let exp = data.readInt32LE(89); oraclePrices.push(Number(price) * 10 ** exp); } else if (tokenList[i].oracleType == "SwbOnDemand") { //@ts-ignore let priceObj = oraclePricesData[i].data.subarray(2264, 2392); let price = parseInt(new anchor_1.BN(priceObj.subarray(32, 48), 10, "le").toString()); oraclePrices.push(price / 10 ** 18); } else { oraclePrices.push(0); } } } for (let i = 0; i < oraclePrices.length; i++) if (tokenList[i].oracleType == "LST") oraclePrices[i] *= oraclePrices[1]; return oraclePrices; }); } function getFilteredProgramAccounts(connection, filters) { return __awaiter(this, void 0, void 0, function* () { return yield connection .getProgramAccounts(config_1.BASKETS_PROGRAM_ID, { commitment: "confirmed", filters, encoding: 'base64' }); }); } function signVersionedTransactions(wallet, transactions) { return __awaiter(this, void 0, void 0, function* () { let txs = []; // @ts-ignore txs = yield wallet.signAllTransactions(transactions).catch((e) => { console.log("Couldn't sign transactions: " + e.message); return null; }); if (!txs) { try { // @ts-ignore transactions.map(tx => tx.sign([wallet.payer])); txs = transactions; } catch (e) { console.log("Couldn't sign transactions: " + e.message); } } return txs; }); } function signTransactionsWithWallet(connection, wallet, transactionsData) { return __awaiter(this, void 0, void 0, function* () { if (transactionsData.length == 0) return []; let { blockhash } = yield connection.getLatestBlockhash("confirmed"); for (let i = 0; i < transactionsData.length; i++) { transactionsData[i].transaction.feePayer = wallet.publicKey; transactionsData[i].transaction.recentBlockhash = blockhash; if (transactionsData[i].signers.length > 0) transactionsData[i].transaction.partialSign(...transactionsData[i].signers); } return yield wallet.signAllTransactions(transactionsData.map(data => data.transaction)).catch((e) => { throw new config_1.BasketError("Couldn't sign transactions: " + e.message); }); }); } function sendSignedTransaction(connection_1, transaction_1) { return __awaiter(this, arguments, void 0, function* (connection, transaction, retries = 2, delayMs = 400) { let serialized = transaction.serialize(); let txId = yield connection.sendRawTransaction(serialized, { skipPreflight: true, preflightCommitment: "processed", maxRetries: 3 }).catch((e) => { throw new config_1.BasketError("Couldn't send transaction: " + e.message); }); connection.sendRawTransaction(serialized, { skipPreflight: false, preflightCommitment: "processed", maxRetries: 3 }).catch((e) => console.log(txId + " : " + e.message)); for (let numDelay = 1; numDelay < retries; numDelay++) delay(delayMs * numDelay).then(() => { connection. sendRawTransaction(serialized, { skipPreflight: true }).catch(() => { }); }); return txId; }); } function confirmTransaction(connection_1, txId_1) { return __awaiter(this, arguments, void 0, function* (connection, txId, timeout = 30) { let result = undefined; for (let _ = 0; _ < timeout && result == undefined; _++) { yield delay(1000); yield connection .getTransaction(txId, { commitment: "confirmed", maxSupportedTransactionVersion: 1 }) .catch((e) => { throw new config_1.BasketError("Couldn't confirm transaction", txId); }) .then(response => { if (!response) return; if (response.meta && response.meta.err) result = false; else result = true; }); } if (result == undefined) return false; return result; }); } function sendSignedTransactions(connection_1, transactions_1) { return __awaiter(this, arguments, void 0, function* (connection, transactions, confirmFirstN = 0) { if (transactions.length == 0) return []; if (!transactions) return ["SignatureError"]; let txs = []; for (let i = 0; i < confirmFirstN; i++) { let txId = yield sendSignedTransaction(connection, transactions[i]); yield confirmTransaction(connection, txId).catch((e) => { console.log(txId, e.message, "Couldn't confirm"); return true; }); txs.push(txId); } let remainingTxs = yield Promise.all(transactions .slice(confirmFirstN, transactions.length) .map(transaction => sendSignedTransaction(connection, transaction).catch((e) => "Error"))); // await Promise.all(remainingTxs.map(tx => confirmTransaction(connection, tx))).catch(() => {}); return [...txs, ...remainingTxs]; }); } const getAddressLookupTableAccounts = (connection, keys) => __awaiter(void 0, void 0, void 0, function* () { const addressLookupTableAccountInfos = yield connection.getMultipleAccountsInfo(keys.map((key) => new web3_js_1.PublicKey(key))); return addressLookupTableAccountInfos.reduce((acc, accountInfo, index) => { const addressLookupTableAddress = keys[index]; if (accountInfo) { let state = web3_js_1.AddressLookupTableAccount.deserialize(accountInfo.data); const addressLookupTableAccount = new web3_js_1.AddressLookupTableAccount({ key: new web3_js_1.PublicKey(addressLookupTableAddress), state: state, }); acc.push(addressLookupTableAccount); } return acc; }, new Array()); }); exports.getAddressLookupTableAccounts = getAddressLookupTableAccounts; function buildTxFromQuoteResponse(quoteResponse, rebalanceInfo, connection, jupAPIkey) { return __awaiter(this, void 0, void 0, function* () { const txData = (yield axios_1.default.post(jupAPIkey + "swap-instructions", JSON.stringify({ quoteResponse, userPublicKey: config_1.BASKETS_PROGRAM_PDA.toBase58(), // feeAccount: REBALANCE_FEE_ACCOUNT.toBase58(), wrapAndUnwrapSol: false, }), { headers: { 'Content-Type': 'application/json' } })).data; let { addressLookupTableAddresses, swapInstruction } = txData; let from = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(new web3_js_1.PublicKey(quoteResponse.inputMint), config_1.BASKETS_PROGRAM_PDA, true); let to = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(new web3_js_1.PublicKey(quoteResponse.outputMint), config_1.BASKETS_PROGRAM_PDA, true); for (let i = 0; i < swapInstruction.accounts.length; i++) { if (swapInstruction.accounts[i].pubkey == config_1.BASKETS_PROGRAM_PDA.toBase58()) swapInstruction.accounts[i].isSigner = false; if (swapInstruction.accounts[i].pubkey == from.toBase58()) swapInstruction.accounts[i].pubkey = rebalanceInfo.tokenAccountFrom; if (swapInstruction.accounts[i].pubkey == to.toBase58()) swapInstruction.accounts[i].pubkey = rebalanceInfo.tokenAccountTo; } swapInstruction.accounts = swapInstruction.accounts.map((x) => { return Object.assign(Object.assign({}, x), { pubkey: new web3_js_1.PublicKey(x.pubkey) }); }); let data = Buffer.from(swapInstruction.data, "base64"); let dataLength = data.length - 19; let fromAmount = parseInt(new anchor_1.BN(data.slice(dataLength, dataLength + 8), "le").toString()); let toAmount = parseInt(new anchor_1.BN(data.slice(dataLength + 8, dataLength + 16), "le").toString()); let slippageBps = new anchor_1.BN(dataLength + 16, dataLength + 18, "le").toNumber(); let feeBps = new anchor_1.BN(data.slice(dataLength + 18, dataLength + 19), "le").toNumber(); data = Buffer.concat([data.slice(0, dataLength), Buffer.alloc(128 - dataLength)]); let lookupTableAccounts = yield (0, exports.getAddressLookupTableAccounts)(connection, addressLookupTableAddresses); return { type: "Simple", programId: new web3_js_1.PublicKey(swapInstruction.programId), accounts: swapInstruction.accounts, firstIxEnd: dataLength, firstIxAccounts: swapInstruction.accounts.length, dataLength: dataLength, data: data, fromTokenId: rebalanceInfo.side == config_1.Side.To ? rebalanceInfo.tokenId : 0, toTokenId: rebalanceInfo.side == config_1.Side.From ? rebalanceInfo.tokenId : 0, midTokenPda: "", fromAmount: fromAmount, toAmount: toAmount, slippageBps: slippageBps, feeBps: feeBps, lookupTableAccounts: lookupTableAccounts, }; }); } function findRoute(rebalanceInfo, jupAPIkey, slippage, midToken, inputAmountOverwrite) { return __awaiter(this, void 0, void 0, function* () { let res = { inputAmount: 0, outputAmount: 0, quotes: [] }; try { let quoteResponse = (yield axios_1.default.get(jupAPIkey + "quote" + "?inputMint=" + rebalanceInfo.mintFrom + "&outputMint=" + rebalanceInfo.mintTo + "&amount=" + inputAmountOverwrite + "&slippageBps=" + slippage + // "&platformFeeBps=" + 5 + "&onlyDirectRoutes=true")).data; res.inputAmount = inputAmountOverwrite; res.outputAmount = parseInt(quoteResponse.outAmount); res.quotes = [quoteResponse]; } catch (_a) { } try { let route1 = (yield axios_1.default.get(jupAPIkey + "quote" + "?inputMint=" + rebalanceInfo.mintFrom + "&outputMint=" + midToken + "&amount=" + inputAmountOverwrite + "&slippageBps=" + slippage + // "&platformFeeBps=" + 5 + "&onlyDirectRoutes=true")).data; let route2 = (yield axios_1.default.get(jupAPIkey + "quote" + "?inputMint=" + midToken + "&outputMint=" + rebalanceInfo.mintTo + "&amount=" + route1.outAmount + "&slippageBps=" + slippage + // "&platformFeeBps=" + 5 + "&onlyDirectRoutes=true")).data; if (res.outputAmount <= parseInt(route2.outAmount)) { res.inputAmount = inputAmountOverwrite; res.outputAmount = parseInt(route2.outAmount); res.quotes = [route1, route2]; } } catch (_b) { } return res; }); } function generateJupSwapInstruction(rebalanceInfo, slippage, connection, fromPriceWithDecimals, toPriceWithDecimals, midTokenMint, midTokenPda, jupAPIkey) { return __awaiter(this, void 0, void 0, function* () { let l = 1, r = rebalanceInfo.amountFrom, m = 0; let res = yield findRoute(rebalanceInfo, jupAPIkey, slippage, midTokenMint, rebalanceInfo.amountFrom); let inputValue = rebalanceInfo.amountFrom * fromPriceWithDecimals; let outputValue = res.outputAmount * toPriceWithDecimals; if (inputValue * (10000 - slippage) / 10000 > outputValue && "https://quote-api.jup.ag/v6/" != jupAPIkey) { for (let it = 0; it < 3; it++) { m = (l + r) >> 1; let check = yield findRoute(rebalanceInfo, jupAPIkey, slippage, midTokenMint, m); let inputValue = m * fromPriceWithDecimals; let outputValue = check.outputAmount * toPriceWithDecimals; if (inputValue * (10000 - slippage) / 10000 >= outputValue) r = m; else { l = m; res = check; } if ((r - l) * fromPriceWithDecimals <= 5) break; // No point in iterating within 2USD } } if (res.outputAmount == 0) throw new Error("Couldn't find quote within slippage"); if (res.quotes.length == 2) { let data = yield Promise.all([ yield buildTxFromQuoteResponse(res.quotes[0], Object.assign(Object.assign({}, rebalanceInfo), { tokenAccountTo: midTokenPda }), connection, jupAPIkey), yield buildTxFromQuoteResponse(res.quotes[1], Object.assign(Object.assign({}, rebalanceInfo), { tokenAccountFrom: midTokenPda }), connection, jupAPIkey) ]); let combined = { type: "Transitive", programId: new web3_js_1.PublicKey(data[0].programId), accounts: [...data[0].accounts, ...data[1].accounts], firstIxEnd: data[0].dataLength, dataLength: data[0].dataLength + data[1].dataLength, firstIxAccounts: data[0].accounts.length, data: Buffer.concat([ data[0].data.slice(0, data[0].dataLength), data[1].data.slice(0, data[1].dataLength), Buffer.alloc(128 - data[0].dataLength - data[1].dataLength) ]), fromTokenId: rebalanceInfo.side == config_1.Side.To ? rebalanceInfo.tokenId : 0, toTokenId: rebalanceInfo.side == config_1.Side.From ? rebalanceInfo.tokenId : 0, midTokenPda: midTokenPda, fromAmount: data[0].fromAmount, toAmount: data[1].toAmount, slippageBps: data[1].slippageBps, feeBps: data[1].feeBps, lookupTableAccounts: [...data[0].lookupTableAccounts, ...data[1].lookupTableAccounts], }; return combined; } let data = yield buildTxFromQuoteResponse(res.quotes[0], rebalanceInfo, connection, jupAPIkey); return data; }); } function generateJupTxData(signer, mintFrom, mintTo, amountFrom, maxAllowedAccounts, slippage, fromPriceWithDecimals, toPriceWithDecimals, jupAPIkey) { return __awaiter(this, void 0, void 0, function* () { // Helper function to get a quote from Jupiter API const getQuote = (amount) => __awaiter(this, void 0, void 0, function* () { let requestURL = `${jupAPIkey}quote?inputMint=${mintFrom}&outputMint=${mintTo}&amount=${amount}&slippageBps=${slippage + 50}`; if (maxAllowedAccounts != 64) requestURL += `&maxAccounts=${maxAllowedAccounts}`; const response = yield axios_1.default.get(requestURL).catch(e => { console.log("Jup quote error"); console.log(e.message); console.log("--------"); return ({ data: null }); }); return response.data; }); let swapValue = 0; // Get initial quote let res = yield getQuote(amountFrom); if (!res) return null; const inputValue = amountFrom * fromPriceWithDecimals; const outputValue = res.outAmount * toPriceWithDecimals; console.log("##### Checking Routes"); console.log(mintFrom, mintTo, (amountFrom * fromPriceWithDecimals).toFixed(2), (res.outAmount * toPriceWithDecimals).toFixed(2), (outputValue / inputValue).toFixed(6)); // Check if the swap meets the slippage requirement if (inputValue * (10000 - slippage) / 10000 > outputValue) { // Binary search for the optimal amount let l = 1, r = amountFrom; for (let it = 0; it < 5; it++) { if ((r - l) * fromPriceWithDecimals <= 1) break; // Stop if the difference is less than $1 const m = Math.floor((l + r) / 2); const check = yield getQuote(m); if (!check) { r = m; continue; } const checkInputValue = m * fromPriceWithDecimals; const checkOutputValue = check.outAmount * toPriceWithDecimals; console.log(checkInputValue.toFixed(2), checkOutputValue.toFixed(2), (checkOutputValue / checkInputValue).toFixed(6)); if (checkInputValue * (10000 - slippage) / 10000 > checkOutputValue) { r = m; } else { l = m; res = check; swapValue = checkInputValue; break; } } } else swapValue = inputValue; if (!res) return null; // Get swap instructions from Jupiter API const txData = yield axios_1.default.post(`${jupAPIkey}swap-instructions`, { quoteResponse: res, userPublicKey: signer.toBase58(), wrapAndUnwrapSol: false, }, { headers: { 'Content-Type': 'application/json' } }).then(response => response.data); return Object.assign(Object.assign({}, txData), { tokenAmount: res.inAmount, swapValue: swapValue, res }); }); } function calculateRebalanceAmounts(program, numTokens, timestamp, lastRebalanceTime, rebalanceInterval, currentCompToken, currentCompAmount, targetWeights, weightSum, tokenList, rebalanceThreshold, oraclePriceData, forceRebalance) { let currentValues = []; let basketWorth = 0; for (let i = 0; i < numTokens; i++) { let price = oraclePriceData[currentCompToken[i]]; let tokenAmount = currentCompAmount[i] / 10 ** tokenList[currentCompToken[i]].decimals; let tokenValue = price * tokenAmount; currentValues.push(tokenValue); basketWorth += tokenValue; } let rebalanceInfos = []; if (basketWorth == 0) return rebalanceInfos; for (let i = 1; i < numTokens; i++) { let currentPercentage = (basketWorth > 0) ? currentValues[i] / basketWorth : 0; let targetPercentage = (weightSum > 0) ? targetWeights[i] / weightSum : 0; if (lastRebalanceTime[i] + rebalanceInterval > timestamp && (!forceRebalance)) continue; if (currentPercentage > targetPercentage * (1 + rebalanceThreshold / 10000) || forceRebalance) rebalanceInfos.push({ tokenId: currentCompToken[i], tokenAccountFrom: tokenList[currentCompToken[i]].pdaTokenAccount, mintFrom: tokenList[currentCompToken[i]].tokenMint, oracleFrom: tokenList[currentCompToken[i]].oracleAccount, tokenAccountTo: tokenList[0].pdaTokenAccount, mintTo: tokenList[0].tokenMint, oracleTo: tokenList[0].oracleAccount, amountFrom: Math.floor(currentCompAmount[i] * (1 - targetPercentage / currentPercentage)), decimals: tokenList[currentCompToken[i]].decimals, volume: currentValues[i] - targetPercentage * basketWorth, side: config_1.Side.To, }); if (currentPercentage < targetPercentage * (1 - rebalanceThreshold / 10000) || forceRebalance) rebalanceInfos.push({ tokenId: currentCompToken[i], tokenAccountTo: tokenList[currentCompToken[i]].pdaTokenAccount, mintTo: tokenList[currentCompToken[i]].tokenMint, oracleTo: tokenList[currentCompToken[i]].oracleAccount, tokenAccountFrom: tokenList[0].pdaTokenAccount, mintFrom: tokenList[0].tokenMint, oracleFrom: tokenList[0].oracleAccount, amountFrom: Math.floor((targetPercentage * basketWorth - currentValues[i]) * 10 ** tokenList[0].decimals), decimals: tokenList[0].decimals, volume: (targetPercentage * basketWorth - currentValues[i]), side: config_1.Side.From }); } return rebalanceInfos.filter(x => x.volume > 0.005); } function stringToAscii(coingeckoId) { let coingeckoIdAscii = []; for (let i = 0; i < coingeckoId.length; i++) coingeckoIdAscii.push(coingeckoId[i].charCodeAt(0)); while (coingeckoIdAscii.length != 30) coingeckoIdAscii.push(0); return coingeckoIdAscii; } function asciiToString(coingeckoIdAscii) { let coingeckoId = ""; for (let i = 0; i < coingeckoIdAscii.length; i++) if (coingeckoIdAscii[i] != 0) coingeckoId += String.fromCharCode(coingeckoIdAscii[i]).toString(); return coingeckoId; } function fetchTokenList(program) { return __awaiter(this, void 0, void 0, function* () { let solanaTokenList = (yield axios_1.default.get("https://cache.symmetry.fi/tokenlist.json")).data; let tokenMap = {}; for (let i = 0; i < solanaTokenList.length; i++) tokenMap[solanaTokenList[i].address] = { symbol: solanaTokenList[i].symbol, name: solanaTokenList[i].name, decimals: solanaTokenList[i].decimals, }; let state = yield program.account.tokenList.fetch(config_1.TOKEN_LIST_ADDRESS, "confirmed"); let numTokens = state.numTokens.toNumber(); let tokens = []; for (let i = 0; i < numTokens; i++) { //@ts-ignore let tokenSettings = state.list[i]; tokens.push({ id: i, symbol: tokenMap[tokenSettings.tokenMint.toBase58()] ? tokenMap[tokenSettings.tokenMint.toBase58()].symbol : undefined, name: tokenMap[tokenSettings.tokenMint.toBase58()] ? tokenMap[tokenSettings.tokenMint.toBase58()].name : undefined, tokenMint: tokenSettings.tokenMint.toBase58(), decimals: tokenSettings.decimals, coingeckoId: asciiToString(tokenSettings.coingeckoId), pdaTokenAccount: tokenSettings.pdaTokenAccount.toBase58(), oracleType: ["Pyth", "Switchboard", "LST", "PythSponsored", "SwbOnDemand"][tokenSettings.oracleType], oracleAccount: tokenSettings.oracleAccount.toBase58(), oracleIndex: tokenSettings.oracleIndex, oracleConfidencePct: tokenSettings.oracleConfidencePct, fixedConfidenceBps: tokenSettings.fixedConfidenceBps, tokenSwapFeeBeforeTwBps: tokenSettings.tokenSwapFeeBeforeTwBps, tokenSwapFeeAfterTwBps: tokenSettings.tokenSwapFeeAfterTwBps, isLive: tokenSettings.isLive == 0 ? false : true, lpOn: tokenSettings.lpOn == 0 ? false : true, useCurveData: tokenSettings.useCurveData == 0 ? false : true, additionalData: tokenSettings.additionalData, }); } return tokens; }); } function getCurrentComposition(basket, tokenList, oraclePriceData) { let basketWorth = 0; let currentComp = []; for (let i = 0; i < basket.data.numOfTokens.toNumber(); i++) { let token = basket.data.currentCompToken[i].toNumber(); let amount = parseInt(basket.data.currentCompAmount[i].toString()) / 10 ** tokenList[token].decimals; basketWorth += amount * oraclePriceData[token]; currentComp.push({ mintAddress: tokenList[token].tokenMint, coingeckoId: tokenList[token].coingeckoId, symbol: tokenList[token].symbol, symmetryTokenId: token, lockedAmount: amount, oraclePrice: oraclePriceData[token], usdValue: amount * oraclePriceData[token], currentWeight: 0, targetWeight: Number(basket.data.targetWeight[i].toNumber() * 100 / basket.data.weightSum.toNumber()), tokenData: tokenList[token], }); } for (let i = 0; i < basket.data.numOfTokens.toNumber(); i++) currentComp[i].currentWeight = currentComp[i].usdValue * 100 / basketWorth; let symbol = asciiToString(basket.data.symbol.slice(0, basket.data.symbolLength)); let name = asciiToString(basket.data.name.slice(0, basket.data.nameLength)); let uri = asciiToString(basket.data.uri.slice(0, basket.data.uriLength)); let basketInfo = { symbol: symbol, name: name, uri: uri, currentSupply: basket.data.supplyOutstanding.toNumber() / 10 ** 6, basketTokenMint: basket.data.fundToken.toBase58(), basketWorth: basketWorth, rawPrice: 10 ** 6 * basketWorth / basket.data.supplyOutstanding.toNumber(), manager: basket.data.manager.toBase58(), managerFee: basket.data.managerFee.toNumber() / 100, activelyManaged: basket.data.activelyManaged.toNumber(), host: basket.data.hostPubkey.toBase58(), hstFee: basket.data.hostFee.toNumber(), currentComposition: currentComp, rules: basket.data.rules.slice(0, basket.data.numOfRules.toNumber()).map(rule => { return { totalWeight: rule.totalWeight.toNumber(), filterBy: rule.filterBy.toNumber(), filterDays: rule.filterDays.toNumber(), sortBy: rule.sortBy.toNumber(), weightBy: rule.weightBy.toNumber(), weightDays: rule.weightDays.toNumber(), weightExpo: rule.weightExpo.toNumber() / 100, fixedAsset: tokenList[rule.fixedAsset.toNumber()], ruleAssets: rule.ruleAssets .slice(0, rule.numAssets.toNumber()) .map(x => tokenList[x.toNumber()]) }; }) }; return basketInfo; } function validateCreateBasketParams(createBasketParams_1, tokenList_1, basketState_2) { return __awaiter(this, arguments, void 0, function* (createBasketParams, tokenList, basketState, skipNameCheck = false) { var _a; if (createBasketParams.name.length > 60) throw new config_1.BasketError("Basket Name should be at most 60 characters"); if (createBasketParams.symbol.length > 10) throw new config_1.BasketError("Basket Symbol should be at most 10 characters"); if (createBasketParams.uri.length > 300) throw new config_1.BasketError("Basket MetadataURI should be at most 300 characters"); if (createBasketParams.refilterInterval < 24 * 60 * 60) throw new config_1.BasketError("Minimum refilter interval should be 24 hours"); if (createBasketParams.reweightInterval < 60 * 60) throw new config_1.BasketError("Minimum reweight interval should be 1 hour"); if (createBasketParams.rebalanceInterval < 60 * 60) throw new config_1.BasketError("Minimum rebalance interval should be 1 hour"); if (createBasketParams.rebalanceThreshold > config_1.BPS_DIVIDER) throw new config_1.BasketError("Maximum rebalance threshold should be 10000 bps"); if (createBasketParams.rebalanceSlippage > config_1.BPS_DIVIDER) throw new config_1.BasketError("Maximum rebalance slippage should be 10000 bps"); if (createBasketParams.lpOffsetThreshold > config_1.BPS_DIVIDER) throw new config_1.BasketError("Maximum lp offset threshold should be 10000 bps"); if (createBasketParams.managerFee > config_1.BPS_DIVIDER) throw new config_1.BasketError("Maximum manager fee should be 10000 bps"); if (createBasketParams.hostPlatformFee > config_1.BPS_DIVIDER) throw new config_1.BasketError("Maximum host platform fee should be 10000 bps"); let totalAssets = 1; for (let i = 0; i < createBasketParams.rules.length; i++) { if (createBasketParams.rules[i].totalWeight <= 0) throw new config_1.BasketError("Total weight of each rule should be positive"); if (createBasketParams.rules[i].totalWeight > 1000) throw new config_1.BasketError("Maximum total weight of each roule should be 1000"); if (createBasketParams.rules[i].weightExpo < 0) throw new config_1.BasketError("Rule weight expo should be positive"); if (createBasketParams.rules[i].weightExpo > 100 && createBasketParams.rules[i].weightBy != config_1.WeightType.Performance) throw new config_1.BasketError("Maxiumum rule weight expo should be 100"); if (createBasketParams.rules[i].weightExpo > 1000) throw new config_1.BasketError("Maxiumum rule weight expo for performance should be 1000"); if (createBasketParams.rules[i].weightBy > 3 || createBasketParams.rules[i].filterBy > 3) throw new config_1.BasketError("FilterBy and WeightBy should be in range 0..3"); if (createBasketParams.rules[i].weightDays > 5 || createBasketParams.rules[i].filterDays > 5) throw new config_1.BasketError("FilterDays and WeightDays should be in range 0..5"); if (createBasketParams.rules[i].sortBy > 1) throw new config_1.BasketError("SortBy should be in range 0..1"); if (createBasketParams.rules[i].filterBy == config_1.FilterType.Fixed) { if (((_a = tokenList.find(token => token.id == createBasketParams.rules[i].fixedAsset)) === null || _a === void 0 ? void 0 : _a.isLive) == false) throw new config_1.BasketError("One of the fixed assets is not currently supported."); if ((createBasketParams.assetPool.find(x => x == createBasketParams.rules[i].fixedAsset)) == undefined) throw new config_1.BasketError("One of the fixed assets is not present in the asset pool."); totalAssets += 1; continue; } else totalAssets += createBasketParams.rules[i].numAssets; } createBasketParams.assetPool = createBasketParams.assetPool.sort(); if (createBasketParams.assetPool[0] != 0 || createBasketParams.assetPool.length != new Set(createBasketParams.assetPool).size) throw new config_1.BasketError("Asset pool should contain USDC and shouldn't contain repeating tokens"); if (createBasketParams.assetPool.find(x => { var _a; return ((_a = tokenList.find(token => token.id == x)) === null || _a === void 0 ? void 0 : _a.isLive) == false; })) throw new config_1.BasketError("One of the tokens in the asset pool is not currently supported."); if (totalAssets > config_1.COMBINED_TOKENS_IN_A_BASKET) throw new config_1.BasketError("Maximum allowed number of assets in a basket is " + config_1.COMBINED_TOKENS_IN_A_BASKET); if (skipNameCheck) return; let check = yield axios_1.default.post("https://api.symmetry.fi/v1/funds-name-setter", JSON.stringify({ command: "check_exists", name: createBasketParams.name, symbol: createBasketParams.symbol, description: createBasketParams.uri, })); if (check.data.status == "fail" && check.data.msg) throw new config_1.BasketError(check.data.msg); if (check.data.status != "ok") throw new config_1.BasketError("Validation failed"); let basketStatePubkey = basketState ? basketState.toBase58() : ""; if ((check.data.name_owner != null && check.data.name_owner != basketStatePubkey) || (check.data.symbol_owner != null && check.data.symbol_owner != basketStatePubkey)) throw new config_1.BasketError("Basket with given name or symbol already exists."); }); } function tryMetadata(parsed) { return __awaiter(this, void 0, void 0, function* () { let metadata = undefined; try { if (parsed.uri) { let req = yield fetch(parsed.uri); let json = yield req.json(); metadata = json; } } catch (_a) { } return metadata; }); } function getBasketTokenMint(connection, basket) { return __awaiter(this, void 0, void 0, function* () { let provider = new anchor_1.AnchorProvider(connection, new nodewallet_1.default(web3_js_1.Keypair.generate()), { commitment: "processed" }); let program = new anchor_1.Program(basketsIDL_1.IDL, provider); let basketObj = yield basketState_1.Basket.loadFromPubkey(program, basket); return basketObj.data.fundToken; }); } function initATAForUserOrWrapSolIxs(connection_1, user_1, tokenMint_1) { return __awaiter(this, arguments, void 0, function* (connection, user, tokenMint, lamports = 0) { let ixs = []; let ata = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(tokenMint, user); let infoAta = yield connection.getMultipleAccountsInfo([ata]); if (!infoAta[0]) ixs.push((0, splTokenHelpers_1.createAssociatedTokenAccountInstruction)(user, ata, user, tokenMint)); if (tokenMint.toBase58() == splTokenHelpers_1.NATIVE_MINT.toBase58()) { //@ts-ignore let info = infoAta[0]; let toDeposit = lamports; if (info) { let parsedInfo = splTokenHelpers_1.AccountLayout.decode(info.data); toDeposit -= parseInt(parsedInfo.amount.toString()); } if (toDeposit > 0) { ixs.push(web3_js_1.SystemProgram.transfer({ fromPubkey: user, toPubkey: ata, lamports: toDeposit })); ixs.push((0, splTokenHelpers_1.createSyncNativeInstruction)(ata, splTokenHelpers_1.TOKEN_PROGRAM_ID)); } } return ixs; }); }