UNPKG

@symmetry-hq/baskets-sdk

Version:

Software Development Kit for interacting with Symmetry Baskets Program

449 lines (448 loc) 25.6 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.BuyState = void 0; const anchor_1 = require("@coral-xyz/anchor"); const web3_js_1 = require("@solana/web3.js"); const config_1 = require("./config"); const utils_1 = require("./utils"); const basketState_1 = require("./basketState"); const splTokenHelpers_1 = require("./splTokenHelpers"); const instructionsBuilder_1 = require("./instructionsBuilder"); class BuyState { constructor(ownAddress, buyStateData, basket) { this.ownAddress = ownAddress; this.data = buyStateData; this.basket = basket; } static loadFromRawData(program, rawData, basket) { return __awaiter(this, void 0, void 0, function* () { let buyStateData = program.coder.accounts.decode("buyState", rawData.account.data); if (!basket) basket = yield basketState_1.Basket.loadFromPubkey(program, buyStateData.fund); return new BuyState(rawData.pubkey, buyStateData, basket); }); } static loadMultiple(program, rawDatas) { return __awaiter(this, void 0, void 0, function* () { let buyStateDatas = []; let baskets = []; for (let i = 0; i < rawDatas.length; i++) { let decoded = program.coder.accounts .decode("buyState", rawDatas[i].account.data); buyStateDatas.push(rawDatas[i]); baskets.push(decoded.fund); } let basketsData = yield program.provider.connection .getMultipleAccountsInfo(baskets, "confirmed"); let buyStates = []; for (let i = 0; i < buyStateDatas.length; i++) { let basket = basketState_1.Basket.loadFromRawData(program, { pubkey: baskets[i], //@ts-ignore account: basketsData[i], }); buyStates.push(yield this.loadFromRawData(program, buyStateDatas[i], basket)); } return buyStates; }); } static loadFromPubkey(program, buyState, basket) { return __awaiter(this, void 0, void 0, function* () { let buyStateData = yield program.account.buyState.fetch(buyState, "confirmed"); if (!basket) basket = yield basketState_1.Basket.loadFromPubkey(program, buyStateData.fund); return new BuyState(buyState, //@ts-ignore buyStateData, basket); }); } static computeMintAmountWithMultipleTokens(tokenList, basket, contribution, oraclePrices) { let contributions = []; let totalContribution = 0; let basketWorth = 0; for (let i = 0; i < basket.data.numOfTokens.toNumber(); i++) { let tokenPrice = oraclePrices[basket.data.currentCompToken[i].toNumber()]; let amount = 0; for (let j = 0; j < contribution.length; j++) if (contribution[j].token.toBase58() == tokenList[basket.data.currentCompToken[i].toNumber()].tokenMint) amount = contribution[j].amount; totalContribution += tokenPrice * amount; contributions.push(tokenPrice * amount); basketWorth += tokenPrice * parseInt(basket.data.currentCompAmount[i].toString()) / 10 ** tokenList[basket.data.currentCompToken[i].toNumber()].decimals; } let valueToRebalance = 0; for (let i = 0; i < basket.data.numOfTokens.toNumber(); i++) { let recommendedContribution = totalContribution * basket.data.targetWeight[i].toNumber() / basket.data.weightSum.toNumber(); if (recommendedContribution > contributions[i]) valueToRebalance += recommendedContribution + contributions[i]; else valueToRebalance += contributions[i] - recommendedContribution; } totalContribution -= valueToRebalance * basket.data.rebalanceSlippage.toNumber() / 10000; if (basket.data.supplyOutstanding.toNumber() == 0) return totalContribution / 100; else return totalContribution * basket.data.supplyOutstanding.toNumber() / 10 ** 6 / basketWorth; } // static async multipleTokensDeposit( // program: Program<BasketsIDL>, // wallet: Wallet, // tokenList: TokenSettings[], // basket: Basket, // contribution: {token: PublicKey, amount: number}[], // lamports: number, // updateOracles: boolean, // NEDD TO IMPLEMENT // ): Promise <TransactionSignature> { // let connection: Connection = program.provider.connection; // let tokenMints = basket.data.currentCompToken.slice(0, basket.data.numOfTokens.toNumber()) // .map(token => new PublicKey(tokenList[token.toNumber()].tokenMint)); // tokenMints.push(basket.data.fundToken); // let buyerAtas = tokenMints.map(token => getAssociatedTokenAddressSync( // token, // wallet.publicKey, // true, // )); // let infobuyerAtas = await connection.getMultipleAccountsInfo(buyerAtas, "confirmed"); // let preTransaction = new Transaction(); // preTransaction.instructions = infobuyerAtas // .map((info, id) => { return {id: id, info: info} }) // .filter(x => x.info == null).map(x => // createAssociatedTokenAccountInstruction( // wallet.publicKey, // buyerAtas[x.id], // wallet.publicKey, // tokenMints[x.id], // ) // ); // let wSolIndex = tokenMints.findIndex(mint => mint.toBase58() == NATIVE_MINT.toBase58()); // if (wSolIndex != -1) { // //@ts-ignore // let info: AccountInfo<Buffer> = infobuyerAtas[wSolIndex]; // let amount = 0; // for (let i = 0; i < contribution.length; i++) { // if (contribution[i].token.toBase58() == NATIVE_MINT.toBase58()) // amount = contribution[i].amount; // } // let toDeposit = Math.floor(amount * 10**9); // if (info) { // let parsedInfo = AccountLayout.decode(info.data); // toDeposit -= parseInt(parsedInfo.amount.toString()); // } // if (toDeposit > 0) { // preTransaction.add( // SystemProgram.transfer({ // fromPubkey: wallet.publicKey, // toPubkey: buyerAtas[wSolIndex], // lamports: toDeposit // }), // ).add( // createSyncNativeInstruction(buyerAtas[wSolIndex], TOKEN_PROGRAM_ID) // ); // } // } // let transaction = new Transaction(); // transaction.instructions = [ // await buildBuyBasketWithMultipleTokensIx(program, tokenList, wallet.publicKey, basket, contribution), // ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}), // ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports}) // ]; // let signedTransactions = await signTransactionsWithWallet( // connection, // wallet, // [ // {transaction: preTransaction, signers: []}, // {transaction: transaction, signers: []} // ] // ); // let txs = await sendSignedTransactions( // connection, // signedTransactions, // 2 // ); // return txs[1]; // } static computeMintAmountWithSingleToken(tokenList, basket, tokenSettings, amount, oraclePrices) { let tokenWorth = []; let basketWorth = 0; for (let i = 0; i < basket.data.numOfTokens.toNumber(); i++) { let tokenPrice = oraclePrices[basket.data.currentCompToken[i].toNumber()]; let usdValue = tokenPrice * parseInt(basket.data.currentCompAmount[i].toString()) / 10 ** tokenList[basket.data.currentCompToken[i].toNumber()].decimals; tokenWorth.push(usdValue); basketWorth += usdValue; } let totalFee = 10; totalFee += basket.data.hostFee.toNumber(); totalFee += basket.data.managerFee.toNumber(); if (basket.ownAddress.toBase58() == config_1.BEYOND_LST_BASKET.toBase58()) totalFee = 0; amount = amount * (10000 - totalFee) / 10000; let totalContribution = amount * oraclePrices[tokenSettings.id]; let valueToRebalance = 0; for (let i = 0; i < basket.data.numOfTokens.toNumber(); i++) { let valueBefore = tokenWorth[i]; let valueAfter = tokenWorth[i]; if (basket.data.currentCompToken[i].toNumber() == tokenSettings.id) valueAfter += totalContribution; let targetValueBefore = basketWorth * basket.data.targetWeight[i].toNumber() / basket.data.weightSum.toNumber(); let targetValueAfter = (basketWorth + totalContribution) * basket.data.targetWeight[i].toNumber() / basket.data.weightSum.toNumber(); if (basket.data.currentCompToken[i].toNumber() != tokenSettings.id) { valueToRebalance += Math.max(targetValueAfter, valueAfter) - Math.max(targetValueBefore, valueAfter); } else { if (valueAfter <= targetValueAfter) { continue; } let overflow = valueAfter - targetValueAfter; if (valueBefore <= targetValueBefore) valueToRebalance += overflow; else valueToRebalance += overflow - (valueBefore - targetValueBefore); } } if (basket.ownAddress.toBase58() == config_1.BEYOND_LST_BASKET.toBase58()) valueToRebalance = 0; totalContribution -= valueToRebalance * 300 / 10000; if (basket.data.supplyOutstanding.toNumber() == 0) return totalContribution / 100; else return totalContribution * basket.data.supplyOutstanding.toNumber() / 10 ** 6 / basketWorth; } static singleTokenDeposit(program, wallet, tokenList, basket, tokenMint, amount, lamports, updateOracles) { return __awaiter(this, void 0, void 0, function* () { let connection = program.provider.connection; let buyerTokenAccount = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(tokenMint, wallet.publicKey, true); let buyerBasketTokenAccount = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(basket.data.fundToken, wallet.publicKey, true); let symmetryFeeAccount = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(tokenMint, config_1.BUY_FEE_WALLET); let hostFeeAccount = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(tokenMint, basket.data.hostPubkey, true); let managerFeeAccount = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(tokenMint, basket.data.feeDelegate.toBase58() == web3_js_1.PublicKey.default.toBase58() ? basket.data.manager : basket.data.feeDelegate, true); let infobuyerAtas = yield connection.getMultipleAccountsInfo([buyerTokenAccount, buyerBasketTokenAccount, symmetryFeeAccount, hostFeeAccount, managerFeeAccount], "confirmed"); let preTransaction = new web3_js_1.Transaction(); if (!infobuyerAtas[0]) preTransaction.add((0, splTokenHelpers_1.createAssociatedTokenAccountInstruction)(wallet.publicKey, buyerTokenAccount, wallet.publicKey, tokenMint)); if (!infobuyerAtas[1]) preTransaction.add((0, splTokenHelpers_1.createAssociatedTokenAccountInstruction)(wallet.publicKey, buyerBasketTokenAccount, wallet.publicKey, basket.data.fundToken)); if (!infobuyerAtas[2]) preTransaction.add((0, splTokenHelpers_1.createAssociatedTokenAccountInstruction)(wallet.publicKey, symmetryFeeAccount, config_1.BUY_FEE_WALLET, tokenMint)); if (tokenMint.toBase58() == splTokenHelpers_1.NATIVE_MINT.toBase58()) { //@ts-ignore let info = infobuyerAtas[0]; let toDeposit = Math.floor(amount * 10 ** 9); if (info) { let parsedInfo = splTokenHelpers_1.AccountLayout.decode(info.data); toDeposit -= parseInt(parsedInfo.amount.toString()); } if (toDeposit > 0) { preTransaction.add(web3_js_1.SystemProgram.transfer({ fromPubkey: wallet.publicKey, toPubkey: buyerTokenAccount, lamports: toDeposit })).add((0, splTokenHelpers_1.createSyncNativeInstruction)(buyerTokenAccount, splTokenHelpers_1.TOKEN_PROGRAM_ID)); } } preTransaction.add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: config_1.ADDITIONAL_UNITS })); preTransaction.add(web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: lamports })); let mainIx = yield (0, instructionsBuilder_1.buildBuyBasketWithSingleTokenIx)(program, tokenList, wallet.publicKey, basket, tokenMint, amount); let transaction = new web3_js_1.Transaction(); transaction.instructions = [ mainIx, web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: config_1.ADDITIONAL_UNITS }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: lamports }) ]; let signedTransactions = yield (0, utils_1.signTransactionsWithWallet)(connection, wallet, [ { transaction: preTransaction, signers: [] }, { transaction: transaction, signers: [] } ]); let txs = yield (0, utils_1.sendSignedTransactions)(connection, signedTransactions, 2); return txs[1]; }); } static createNew(program_1, wallet_1, tokenList_1, basket_1, amount_1) { return __awaiter(this, arguments, void 0, function* (program, wallet, tokenList, basket, amount, lamports = config_1.ADDITIONAL_FEE) { let connection = program.provider.connection; let buyData = yield (0, instructionsBuilder_1.buildBuyBasketIx)(program, tokenList, wallet.publicKey, basket, amount); let transaction = new web3_js_1.Transaction(); transaction.instructions = [ buyData, web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: config_1.ADDITIONAL_UNITS }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: lamports }) ]; let signedTransactions = yield (0, utils_1.signTransactionsWithWallet)(connection, wallet, [{ transaction: transaction, signers: [] }]); yield (0, utils_1.sendSignedTransactions)(connection, signedTransactions, 1); return yield BuyState.loadFromPubkey(program, buyData.keys[11].pubkey, basket); }); } update(program) { return __awaiter(this, void 0, void 0, function* () { //@ts-ignore this.data = yield program.account.buyState.fetch(this.ownAddress, "confirmed"); }); } getBuyStateRebalanceInfo(tokenList) { let currentTokens = Array.from(this.data.token, x => x.toNumber()); let amountToSpend = Array.from(this.data.amountToSpend, x => parseInt(x.toString())); let rebalanceInfos = []; for (let i = 1; i < currentTokens.length; i++) if (amountToSpend[i] > 0) rebalanceInfos.push({ tokenId: currentTokens[i], tokenAccountFrom: tokenList[0].pdaTokenAccount, mintFrom: tokenList[0].tokenMint, oracleFrom: tokenList[0].oracleAccount, tokenAccountTo: tokenList[currentTokens[i]].pdaTokenAccount, mintTo: tokenList[currentTokens[i]].tokenMint, oracleTo: tokenList[currentTokens[i]].oracleAccount, amountFrom: amountToSpend[i], decimals: tokenList[0].decimals, volume: amountToSpend[i] / 10 * tokenList[0].decimals, side: config_1.Side.From }); return rebalanceInfos.filter(x => x.volume > 0.005); } rebalanceBuyState(program, wallet, tokenList, jupSwapDatas, lamports, updateOraclesTxData, lookups) { return __awaiter(this, void 0, void 0, function* () { let basket = this.basket; let transactionsData = updateOraclesTxData; for (let i = 0; i < jupSwapDatas.length; i++) { let rebalanceData = jupSwapDatas[i]; if (!rebalanceData) continue; let tokenId = rebalanceData.toTokenId; let ix = (rebalanceData.type == "Simple") ? yield program.methods .rebalanceBuyState(tokenId, new anchor_1.BN(rebalanceData.fromAmount), rebalanceData.dataLength, Array.from(rebalanceData.data)) .accounts({ fundState: basket.ownAddress, buyState: this.ownAddress, tokenList: config_1.TOKEN_LIST_ADDRESS, oracleSol: new web3_js_1.PublicKey(tokenList[1].oracleAccount), oracleToken: new web3_js_1.PublicKey(tokenList[tokenId].oracleAccount), oracleUsdc: new web3_js_1.PublicKey(tokenList[0].oracleAccount), pdaAccount: config_1.BASKETS_PROGRAM_PDA, pdaTokenAccount: new web3_js_1.PublicKey(tokenList[tokenId].pdaTokenAccount), pdaUsdcAccount: new web3_js_1.PublicKey(tokenList[0].pdaTokenAccount), rebalanceFeeAccount: config_1.REBALANCE_FEE_ACCOUNT, tokenProgram: splTokenHelpers_1.TOKEN_PROGRAM_ID, }) .remainingAccounts(rebalanceData.accounts) .instruction() : yield program.methods .rebalanceBuyStateTransitive(tokenId, new anchor_1.BN(rebalanceData.fromAmount), rebalanceData.firstIxEnd, rebalanceData.dataLength, rebalanceData.firstIxAccounts, Array.from(rebalanceData.data)) .accounts({ fundState: basket.ownAddress, buyState: this.ownAddress, tokenList: config_1.TOKEN_LIST_ADDRESS, oracleSol: new web3_js_1.PublicKey(tokenList[1].oracleAccount), oracleToken: new web3_js_1.PublicKey(tokenList[tokenId].oracleAccount), oracleUsdc: new web3_js_1.PublicKey(tokenList[0].oracleAccount), pdaAccount: config_1.BASKETS_PROGRAM_PDA, pdaTokenAccount: new web3_js_1.PublicKey(tokenList[tokenId].pdaTokenAccount), pdaMidAccount: new web3_js_1.PublicKey(rebalanceData.midTokenPda), pdaUsdcAccount: new web3_js_1.PublicKey(tokenList[0].pdaTokenAccount), rebalanceFeeAccount: config_1.REBALANCE_FEE_ACCOUNT, tokenProgram: splTokenHelpers_1.TOKEN_PROGRAM_ID, }) .remainingAccounts(rebalanceData.accounts) .instruction(); transactionsData.push({ payerKey: wallet.publicKey, instructions: [ ix, web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: config_1.ADDITIONAL_UNITS }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: lamports }) ], lookupTables: [...lookups, ...rebalanceData.lookupTableAccounts] }); } const blockhash = (yield program.provider.connection.getLatestBlockhash("confirmed")).blockhash; let signedTransactions = yield (0, utils_1.signVersionedTransactions)(wallet, transactionsData.map(tx => new web3_js_1.VersionedTransaction(new web3_js_1.TransactionMessage({ payerKey: tx.payerKey, recentBlockhash: blockhash, instructions: tx.instructions, }).compileToV0Message(tx.lookupTables)))); return yield (0, utils_1.sendSignedTransactions)(program.provider.connection, signedTransactions, updateOraclesTxData.length > 0 ? 1 : 0); }); } mint(program, swbProgram, wallet, tokenList, lookups, lamports, updateOracles) { return __awaiter(this, void 0, void 0, function* () { let transactionsData = []; if (updateOracles) transactionsData = yield (0, instructionsBuilder_1.updateOraclesTxs)(swbProgram, wallet.publicKey, this.basket.getSwbFeeds(tokenList), lamports); transactionsData.map(tx => tx.lookupTables = [...lookups, ...tx.lookupTables]); transactionsData.push({ payerKey: wallet.publicKey, instructions: [ yield (0, instructionsBuilder_1.buildMintFromBuyStateIx)(program, tokenList, wallet.publicKey, this.basket, this), web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: config_1.ADDITIONAL_UNITS }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: lamports }) ], lookupTables: lookups }); const blockhash = (yield program.provider.connection.getLatestBlockhash("confirmed")).blockhash; let signedTransactions = yield (0, utils_1.signVersionedTransactions)(wallet, transactionsData.map(tx => new web3_js_1.VersionedTransaction(new web3_js_1.TransactionMessage({ payerKey: tx.payerKey, recentBlockhash: blockhash, instructions: tx.instructions, }).compileToV0Message(tx.lookupTables)))); return yield (0, utils_1.sendSignedTransactions)(program.provider.connection, signedTransactions, signedTransactions.length); }); } claimTokens(program_1, wallet_1, tokenList_1) { return __awaiter(this, arguments, void 0, function* (program, wallet, tokenList, lamports = config_1.ADDITIONAL_FEE) { let buyer = this.data.buyer; let connection = program.provider.connection; let transactions = []; let ixId = 0; let claimIxs = yield (0, instructionsBuilder_1.buildClaimTokensFromBuyStateIxs)(program, tokenList, wallet.publicKey, this.basket, this); for (let i = 0; i < this.data.token.length; i++) { if (parseInt(this.data.amountBought[i].toString()) == 0 && i != 0) continue; let transaction = new web3_js_1.Transaction(); let tokenId = this.data.token[i].toNumber(); let userTokenAccount = (0, splTokenHelpers_1.getAssociatedTokenAddressSync)(new web3_js_1.PublicKey(tokenList[tokenId].tokenMint), buyer, true); let infoAta = yield connection.getAccountInfo(userTokenAccount, "confirmed"); if (!infoAta) transaction.add(yield (0, splTokenHelpers_1.createAssociatedTokenAccountInstruction)(wallet.publicKey, userTokenAccount, buyer, new web3_js_1.PublicKey(tokenList[tokenId].tokenMint))); transaction.instructions = [ ...transaction.instructions, claimIxs[ixId], web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: config_1.ADDITIONAL_UNITS }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: lamports }) ]; transactions.push(transaction); ixId = ixId + 1; } let signedTransactions = yield (0, utils_1.signTransactionsWithWallet)(connection, wallet, transactions.map(transaction => { return { transaction: transaction, signers: [] }; })); return yield (0, utils_1.sendSignedTransactions)(connection, signedTransactions); }); } } exports.BuyState = BuyState;