@symmetry-hq/baskets-sdk
Version:
Software Development Kit for interacting with Symmetry Baskets Program
449 lines (448 loc) • 25.6 kB
JavaScript
"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;