@symmetry-hq/baskets-sdk
Version:
Software Development Kit for interacting with Symmetry Baskets Program
1,160 lines (1,109 loc) • 46.4 kB
text/typescript
import { AnchorProvider, BN, Instruction, Program, Wallet } from "@coral-xyz/anchor";
import {
AccountInfo,
AddressLookupTableAccount,
ComputeBudgetProgram,
Connection,
Keypair,
MessageV0,
PublicKey,
SystemProgram,
SYSVAR_RENT_PUBKEY,
Transaction,
TransactionInstruction,
TransactionMessage,
TransactionSignature,
VersionedTransaction
} from "@solana/web3.js";
import { BasketsIDL, IDL } from "./basketsIDL";
import {
ADDITIONAL_FEE,
ADDITIONAL_UNITS,
CreateBasketParams,
CREATE_FEE_ACCOUNT,
BasketStateChainData,
BASKETS_PROGRAM_PDA,
CURVE_DATA_ADDRESS,
RebalanceInfo,
REBALANCE_FEE_ACCOUNT,
Side,
SWAP_FEE_ACCOUNT,
TokenSettings,
TOKEN_LIST_ADDRESS,
BASKETS_PROGRAM_ID,
JupSwapData,
JUP_AGGREGATOR,
SimpleEditParams,
FilterType,
FilterTime,
SortBy,
WeightType,
WeightTime,
SimpleCreateParams,
TransactionToSend,
} from "./config";
import {
calculateRebalanceAmounts,
getOraclePrices,
rawOraclePrices,
sendSignedTransactions,
signTransactionsWithWallet,
signVersionedTransactions,
validateCreateBasketParams
} from "./utils";
import { TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, getAssociatedTokenAddressSync } from "./splTokenHelpers";
import axios from "axios";
import { MD5 } from "crypto-js";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { Metaplex } from "@metaplex-foundation/js";
import { buildClaimTokensFromSellStateIxs, buildCloseBasketIx, buildCreateBasketIx, buildEditBasketIx, buildEditManagetIx, buildSellBasketIx, buildSellBasketToSingleTokenIx, buildSetMetadataIx, buildUpdateCurrentWeightsIx, getSwbFeedsUpdateIxs } from "./instructionsBuilder";
export class Basket {
public ownAddress: PublicKey;
public data: BasketStateChainData;
constructor(
ownAddress: PublicKey,
basketData: BasketStateChainData,
) {
this.ownAddress = ownAddress;
this.data = basketData;
}
static loadFromRawData(
program: Program<BasketsIDL>,
rawData: {
pubkey: PublicKey;
account: AccountInfo<Buffer>;
}
): Basket {
return new Basket(
rawData.pubkey,
program.coder.accounts.decode("fundState", rawData.account.data)
)
}
static async loadFromPubkey(
program: Program<BasketsIDL>,
basketState: PublicKey
): Promise<Basket> {
let basketData = await program.account.fundState.fetch(basketState, "confirmed");
return new Basket(
basketState,
//@ts-ignore
basketData,
);
}
static async getCompositionAndPrice(
connection: Connection,
pubkey: PublicKey,
getPrice: boolean = false,
): Promise<any> {
let provider = new AnchorProvider(
connection,
new NodeWallet(Keypair.generate()),
{
skipPreflight: true,
preflightCommitment: "recent",
commitment: "processed",
}
);
let program = new Program<BasketsIDL>(IDL, provider);
let accounts = [pubkey, TOKEN_LIST_ADDRESS];
let accountsInfo = await connection.getMultipleAccountsInfo(accounts, "confirmed");
//@ts-ignore
let basketData = await program.coder.accounts.decode("fundState", accountsInfo[0]?.data);
//@ts-ignore
let tokenList = await program.coder.accounts.decode("tokenList", accountsInfo[1]?.data);
let supply = parseInt(basketData.supplyOutstanding.toString()) / 10 ** 6;
let mint = basketData.fundToken.toBase58();
let tokenIds = basketData.currentCompToken
.slice(0, parseInt(basketData.numOfTokens.toString())).map((x: any) => parseInt(x.toString()));
let tokenAmounts = basketData.currentCompAmount
.slice(0, parseInt(basketData.numOfTokens.toString()))
.map((x: any, id: any) =>
parseInt(x.toString()) / 10 ** tokenList.list[tokenIds[id]].decimals
);
let tokenMints = tokenIds.map((x: any) => tokenList.list[x].tokenMint.toBase58());
let price = undefined;
let tvl = undefined;
if (getPrice) {
let oraclePrices = await rawOraclePrices(connection, tokenList.list.slice(0, parseInt(tokenList.numTokens.toString())));
tvl = 0;
for (let i=0; i<tokenAmounts.length; i++)
tvl += oraclePrices[tokenIds[i]] * tokenAmounts[i];
price = tvl / supply;
}
let result = {
price: price,
supply: supply,
tvl: tvl,
mint: mint,
composition: tokenAmounts.map((_: any, id: any) => {
return {
mint: tokenMints[id],
amount: tokenAmounts[id],
}
})
}
return result;
}
static async create(
program: Program<BasketsIDL>,
connection: Connection,
wallet: Wallet,
tokenList: TokenSettings[],
lookups: AddressLookupTableAccount[],
basketParams: SimpleCreateParams,
lamports: number = ADDITIONAL_FEE,
): Promise<Basket> {
let createData = await buildCreateBasketIx(program, tokenList, basketParams);
let blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
let V0Create = new VersionedTransaction(
new TransactionMessage({
payerKey: wallet.publicKey,
recentBlockhash: blockhash,
instructions: [
createData,
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
]
}).compileToV0Message(lookups)
);
let signedTransactions = await signVersionedTransactions(
wallet,
[V0Create]
);
let resultCreate = await sendSignedTransactions(
connection,
signedTransactions,
1
);
console.log("Create Tx:", resultCreate[0]);
return await Basket
.loadFromPubkey(
program,
createData.keys[2].pubkey,
);
}
async update(program: Program<BasketsIDL>) {
//@ts-ignore
this.data = await program.account.fundState.fetch(this.ownAddress, "confirmed");
}
async editManager(
program: Program<BasketsIDL>,
connection: Connection,
wallet: Wallet,
newManager: PublicKey,
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionSignature> {
let editManager = new Transaction();
editManager.instructions = [
await buildEditManagetIx(program, this, newManager),
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
];
let signedTransactions = await signTransactionsWithWallet(
connection,
wallet,
[{ transaction: editManager, signers: [] }]
);
let resultEdit = await sendSignedTransactions(
connection,
[signedTransactions[0]],
1
);
return resultEdit[0];
}
async edit(
program: Program<BasketsIDL>,
connection: Connection,
wallet: Wallet,
tokenList: TokenSettings[],
lookups: AddressLookupTableAccount[],
basketParams: SimpleEditParams,
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionSignature> {
let blockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
let V0Create = new VersionedTransaction(
new TransactionMessage({
payerKey: wallet.publicKey,
recentBlockhash: blockhash,
instructions: [
await buildEditBasketIx(program, tokenList, this, basketParams),
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
]
}).compileToV0Message(lookups)
);
let signedTransactions = await signVersionedTransactions(
wallet,
[V0Create]
);
let resultEdit = await sendSignedTransactions(
connection,
signedTransactions,
1
);
return resultEdit[0];
}
async setMetaData(
program: Program<BasketsIDL>,
wallet: Wallet,
symbol: string,
name: string,
uri: string,
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionSignature> {
let transaction = new Transaction();
transaction.instructions = [
await buildSetMetadataIx(program, this, {symbol: symbol, name: name, uri: uri}),
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
]
let signedTransactions = await signTransactionsWithWallet(
program.provider.connection,
wallet,
[{transaction: transaction, signers: []}]
);
return (await sendSignedTransactions(
program.provider.connection,
signedTransactions,
1
))[0];
}
async close(
program: Program<BasketsIDL>,
wallet: Wallet,
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionSignature> {
let transaction = new Transaction();
transaction.instructions = [
await buildCloseBasketIx(program, this),
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
];
let signedTransactions = await signTransactionsWithWallet(
program.provider.connection,
wallet,
[{transaction: transaction, signers: []}]
);
return (await sendSignedTransactions(
program.provider.connection,
signedTransactions,
1
))[0];
}
getSwbFeeds(
tokenList: TokenSettings[],
): PublicKey[] {
let oracles: PublicKey[] = [];
for (let i = 0; i < this.data.numOfTokens.toNumber(); i++)
if (tokenList[this.data.currentCompToken[i].toNumber()].oracleType == "SwbOnDemand")
oracles.push(new PublicKey(tokenList[this.data.currentCompToken[i].toNumber()].oracleAccount))
return oracles;
}
async rebalanceFromUsdcTransactionData(
program: Program<BasketsIDL>,
wallet: Wallet,
pda: PublicKey,
basketState: PublicKey,
tokenList: TokenSettings[],
rebalanceFeeAccount: PublicKey,
jupSwapData: JupSwapData,
lamports: number = ADDITIONAL_FEE,
): Promise<{
payerKey: PublicKey,
instructions: TransactionInstruction[],
lookupTables: AddressLookupTableAccount[]
}> {
let tokenId = jupSwapData.toTokenId;
let ix = (jupSwapData.type == "Simple") ?
await program.methods
.rebalanceBuy(
jupSwapData.toTokenId,
new BN(jupSwapData.fromAmount),
jupSwapData.dataLength,
Array.from(jupSwapData.data),
)
.accounts({
signer: wallet.publicKey,
fundState: basketState,
tokenList: TOKEN_LIST_ADDRESS,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
oracleToken: new PublicKey(tokenList[tokenId].oracleAccount),
oracleUsdc: new PublicKey(tokenList[0].oracleAccount),
pdaAccount: pda,
pdaTokenAccount: new PublicKey(tokenList[tokenId].pdaTokenAccount),
pdaUsdcAccount: new PublicKey(tokenList[0].pdaTokenAccount),
rebalanceFeeAccount: rebalanceFeeAccount,
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(jupSwapData.accounts)
.instruction() :
await program.methods
.rebalanceBuyTransitive(
jupSwapData.toTokenId,
new BN(jupSwapData.fromAmount),
jupSwapData.firstIxEnd,
jupSwapData.dataLength,
jupSwapData.firstIxAccounts,
Array.from(jupSwapData.data),
)
.accounts({
signer: wallet.publicKey,
fundState: basketState,
tokenList: TOKEN_LIST_ADDRESS,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
oracleToken: new PublicKey(tokenList[tokenId].oracleAccount),
oracleUsdc: new PublicKey(tokenList[0].oracleAccount),
pdaAccount: pda,
pdaTokenAccount: new PublicKey(tokenList[tokenId].pdaTokenAccount),
pdaMidAccount: new PublicKey(jupSwapData.midTokenPda),
pdaUsdcAccount: new PublicKey(tokenList[0].pdaTokenAccount),
rebalanceFeeAccount: rebalanceFeeAccount,
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(jupSwapData.accounts)
.instruction();
let rawData = {
payerKey: wallet.publicKey,
instructions: [
ix,
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
],
lookupTables: jupSwapData.lookupTableAccounts
};
return rawData;
}
async rebalanceToUsdcTransactionData(
program: Program<BasketsIDL>,
wallet: Wallet,
pda: PublicKey,
basketState: PublicKey,
tokenList: TokenSettings[],
rebalanceFeeAccount: PublicKey,
jupSwapData: JupSwapData,
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionToSend> {
let tokenId = jupSwapData.fromTokenId;
let ix = (jupSwapData.type == "Simple") ?
await program.methods
.rebalanceSell(
tokenId,
new BN(jupSwapData.fromAmount),
jupSwapData.dataLength,
Array.from(jupSwapData.data),
)
.accounts({
signer: wallet.publicKey,
fundState: basketState,
tokenList: TOKEN_LIST_ADDRESS,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
oracleToken: new PublicKey(tokenList[tokenId].oracleAccount),
oracleUsdc: new PublicKey(tokenList[0].oracleAccount),
pdaAccount: pda,
pdaTokenAccount: new PublicKey(tokenList[tokenId].pdaTokenAccount),
pdaUsdcAccount: new PublicKey(tokenList[0].pdaTokenAccount),
rebalanceFeeAccount: rebalanceFeeAccount,
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(jupSwapData.accounts)
.instruction() :
await program.methods
.rebalanceSellTransitive(
tokenId,
new BN(jupSwapData.fromAmount),
jupSwapData.firstIxEnd,
jupSwapData.dataLength,
jupSwapData.firstIxAccounts,
Array.from(jupSwapData.data),
)
.accounts({
signer: wallet.publicKey,
fundState: basketState,
tokenList: TOKEN_LIST_ADDRESS,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
oracleToken: new PublicKey(tokenList[tokenId].oracleAccount),
oracleUsdc: new PublicKey(tokenList[0].oracleAccount),
pdaAccount: pda,
pdaTokenAccount: new PublicKey(tokenList[tokenId].pdaTokenAccount),
pdaMidAccount: new PublicKey(jupSwapData.midTokenPda),
pdaUsdcAccount: new PublicKey(tokenList[0].pdaTokenAccount),
rebalanceFeeAccount: rebalanceFeeAccount,
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(jupSwapData.accounts)
.instruction();
let rawData = {
payerKey: wallet.publicKey,
instructions: [
ix,
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
],
lookupTables: jupSwapData.lookupTableAccounts
};
return rawData;
}
getRebalanceInfo(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
oraclePriceData: number[],
timestamp: number,
forceRebalance: boolean = false,
): RebalanceInfo[] {
let basketStateData = this.data;
let currentCompToken: BN[] = basketStateData.currentCompToken;
let currentCompAmount: BN[] = basketStateData.currentCompAmount;
let targetWeight: BN[] = basketStateData.targetWeight;
let numTokens = parseInt(basketStateData.numOfTokens.toString());
let rebalanceThreshold = basketStateData.rebalanceThreshold;
let weightSum = basketStateData.weightSum;
let rebalanceInterval = parseInt(basketStateData.rebalanceInterval.toString());
let lastRebalanceTime = basketStateData.lastRebalanceTime;
return calculateRebalanceAmounts(
program,
numTokens,
timestamp,
Array.from(lastRebalanceTime, x => parseInt(x.toString())),
rebalanceInterval,
Array.from(currentCompToken, x => parseInt(x.toString())),
Array.from(currentCompAmount, x => parseInt(x.toString())),
Array.from(targetWeight, x => parseInt(x.toString())),
parseInt(weightSum.toString()),
tokenList,
parseInt(rebalanceThreshold.toString()),
oraclePriceData,
forceRebalance,
);
}
async rebalanceFrom(
program: Program<BasketsIDL>,
wallet: Wallet,
tokenList: TokenSettings[],
jupSwapDatas: JupSwapData[],
rebalanceInfos: RebalanceInfo[],
lookups: AddressLookupTableAccount[],
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionToSend[]> {
let ix = await buildUpdateCurrentWeightsIx(program, this, tokenList);
let transactionsData: {
payerKey: PublicKey,
instructions: TransactionInstruction[],
lookupTables: AddressLookupTableAccount[]
}[] = [];
for (let i = rebalanceInfos.length - 1; i >= 0; i--) {
if (rebalanceInfos[i].side == Side.To)
continue;
let rebalanceData = jupSwapDatas[i];
if (!rebalanceData)
continue;
let txData = await this.rebalanceFromUsdcTransactionData(
program,
wallet,
BASKETS_PROGRAM_PDA,
this.ownAddress,
tokenList,
REBALANCE_FEE_ACCOUNT,
rebalanceData,
lamports,
).catch((e) => { console.log(e.message); return null;});
if (!txData)
continue;
transactionsData.push(txData);
}
for (let i = 0; i < transactionsData.length; i++) {
transactionsData[i].instructions = [ix, ...transactionsData[i].instructions];
transactionsData[i].lookupTables = [...lookups, ...transactionsData[i].lookupTables];
}
return transactionsData;
}
async rebalanceTo(
program: Program<BasketsIDL>,
wallet: Wallet,
tokenList: TokenSettings[],
jupSwapDatas: JupSwapData[],
rebalanceInfos: RebalanceInfo[],
lookups: AddressLookupTableAccount[],
lamports: number = ADDITIONAL_FEE,
): Promise<{
payerKey: PublicKey,
instructions: TransactionInstruction[],
lookupTables: AddressLookupTableAccount[]
}[]> {
let ix = await buildUpdateCurrentWeightsIx(program, this, tokenList);
let transactionsData: {
payerKey: PublicKey,
instructions: TransactionInstruction[],
lookupTables: AddressLookupTableAccount[]
}[] = [];
for (let i = rebalanceInfos.length - 1; i >= 0; i--) {
if (rebalanceInfos[i].side == Side.From)
continue;
let rebalanceData = jupSwapDatas[i];
if (!rebalanceData)
continue;
let txData = await this.rebalanceToUsdcTransactionData(
program,
wallet,
BASKETS_PROGRAM_PDA,
this.ownAddress,
tokenList,
REBALANCE_FEE_ACCOUNT,
rebalanceData,
lamports,
).catch((e) => { console.log(e.message); return null;});
if (!txData)
continue;
transactionsData.push(txData);
}
for (let i = 0; i < transactionsData.length; i++) {
transactionsData[i].instructions = [ix, ...transactionsData[i].instructions];
transactionsData[i].lookupTables = [...lookups, ...transactionsData[i].lookupTables];
}
return transactionsData;
}
// async rebalanceSingle(
// program: Program<BasketsIDL>,
// wallet: Wallet,
// tokenList: TokenSettings[],
// jupSwapData: JupSwapData,
// rebalanceInfo: RebalanceInfo,
// lamports: number,
// updateOracles: boolean, /// NEED to implement
// ): Promise<TransactionSignature> {
// let prepareIx = await buildUpdateCurrentWeightsIx(program, this, tokenList);
// let txData = (rebalanceInfo.side == Side.To) ?
// await this.rebalanceToUsdcTransactionData(
// program,
// wallet,
// BASKETS_PROGRAM_PDA,
// this.ownAddress,
// tokenList,
// REBALANCE_FEE_ACCOUNT,
// jupSwapData,
// lamports,
// ) :
// await this.rebalanceFromUsdcTransactionData(
// program,
// wallet,
// BASKETS_PROGRAM_PDA,
// this.ownAddress,
// tokenList,
// REBALANCE_FEE_ACCOUNT,
// jupSwapData,
// lamports,
// );
// txData.instructions = [prepareIx, ...txData.instructions];
// const lookupTableAccount1 = (
// await program.provider.connection.getAddressLookupTable(BASKETS_LOOKUP_TABLE_1)
// ).value;
// const lookupTableAccount2 = (
// await program.provider.connection.getAddressLookupTable(BASKETS_LOOKUP_TABLE_2)
// ).value;
// //@ts-ignore
// txData.lookupTables.push(lookupTableAccount1);
// //@ts-ignore
// txData.lookupTables.push(lookupTableAccount2);
// let blockhash = (await program.provider.connection.getLatestBlockhash()).blockhash;
// let signedTransactions = await signVersionedTransactions(
// wallet,
// [new VersionedTransaction(
// new TransactionMessage({
// payerKey: txData.payerKey,
// recentBlockhash: blockhash,
// instructions: txData.instructions,
// }).compileToV0Message(txData.lookupTables)
// )]
// ).catch(e => { console.log(e); });
// let txs = await sendSignedTransactions(
// program.provider.connection, //@ts-ignore
// signedTransactions,
// 1
// ).catch((e) => {console.log(e); return [""]});
// return txs[0];
// }
// async sellBasketToSingleToken(
// program: Program<BasketsIDL>,
// wallet: Wallet,
// tokenList: TokenSettings[],
// withdrawToken: PublicKey,
// amount: number,
// lamports: number,
// updateOracles: boolean, /// NEED to implement
// ): Promise<TransactionSignature> {
// let transaction = new Transaction();
// transaction.instructions = [
// await buildSellBasketToSingleTokenIx(program, tokenList, wallet.publicKey, this, withdrawToken, amount),
// ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
// ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
// ];
// let signedTransactions = await signTransactionsWithWallet(
// program.provider.connection,
// wallet,
// [{transaction: transaction, signers:[]}]
// );
// let txs = await sendSignedTransactions(
// program.provider.connection,
// signedTransactions,
// 1,
// );
// return txs[0];
// }
async computeOutputAmountWithSingleToken(
oraclePrices: number[],
tokenList: TokenSettings[],
withdrawToken: TokenSettings,
burnAmount: number,
): Promise<number> {
let tokenIndex = -1;
for (let i = 0; i < this.data.currentCompToken.length; i++)
if (parseInt(this.data.currentCompToken[i].toString()) == withdrawToken.id) {
tokenIndex = i; break;
}
if (tokenIndex == -1)
throw new Error("Token not in composition");
let withdrawTokenPrice = oraclePrices[withdrawToken.id];
let withdrawWorth = 0;
let basketWorth = 0;
for (let i = 0; i < parseInt(this.data.numOfTokens.toString()); i++) {
let tokenPrice = oraclePrices[parseInt(this.data.currentCompToken[i].toString())];
basketWorth += tokenPrice * parseInt(this.data.currentCompAmount[i].toString())
/ 10 ** tokenList[parseInt(this.data.currentCompToken[i].toString())].decimals;
let allocation = (
parseInt(this.data.currentCompAmount[i].toString()) * burnAmount * 10 ** 6 /
parseInt(this.data.supplyOutstanding.toString())
);
withdrawWorth += tokenPrice * allocation
/ 10 ** tokenList[parseInt(this.data.currentCompToken[i].toString())].decimals;
}
let targetValueBefore = basketWorth * parseInt(this.data.targetWeight[tokenIndex].toString())
/ parseInt(this.data.weightSum.toString());
let valueBefore = withdrawTokenPrice * parseInt(this.data.currentCompAmount[tokenIndex].toString())
/ 10 ** withdrawToken.decimals
let valueToRebalance = 0;
if (valueBefore <= targetValueBefore)
valueToRebalance = withdrawWorth; else {
let valueToTgtWeight = (valueBefore - targetValueBefore) *
parseInt(this.data.weightSum.toString()) /
(parseInt(this.data.weightSum.toString()) - parseInt(this.data.targetWeight[tokenIndex].toString()))
if (valueToTgtWeight < withdrawWorth) {
valueToRebalance = withdrawWorth - valueToTgtWeight;
}
}
let tokenAmount = 0;
tokenAmount += (withdrawWorth - valueToRebalance) / withdrawTokenPrice;
let penalty = valueToRebalance * 300 / 10000;
tokenAmount += (valueToRebalance - penalty) / withdrawTokenPrice;
if (tokenAmount > parseInt(this.data.currentCompAmount[tokenIndex].toString()) / 10 ** withdrawToken.decimals)
tokenAmount = parseInt(this.data.currentCompAmount[tokenIndex].toString()) / 10 ** withdrawToken.decimals;
return tokenAmount;
}
async sell(
program: Program<BasketsIDL>,
wallet: Wallet,
amount: number,
rebalance: number,
lamports: number = ADDITIONAL_FEE,
): Promise<Basket> {
let sellBasketData = await buildSellBasketIx(program, wallet.publicKey, this, amount, rebalance);
let transaction = new Transaction();
transaction.instructions = [
sellBasketData,
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
];
let signedTransactions = await signTransactionsWithWallet(
program.provider.connection,
wallet,
[{transaction: transaction, signers: []}]
);
await sendSignedTransactions(
program.provider.connection,
signedTransactions,
1,
);
return await Basket.loadFromPubkey(
program,
sellBasketData.keys[3].pubkey,
);
}
async claimTokens(
program: Program<BasketsIDL>,
wallet: Wallet,
tokenList: TokenSettings[],
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionSignature[]> {
await this.update(program);
let basketOwner = this.data.manager;
let connection = program.provider.connection;
let transactions: Transaction[] = [];
let claimTokensIxs = await buildClaimTokensFromSellStateIxs(program, tokenList, wallet.publicKey, this);
let ixId = 0;
for (let i = 0; i < parseInt(this.data.numOfTokens.toString()); i++) {
if (parseInt(this.data.currentCompAmount[i].toString()) == 0 && i != 0)
continue;
let transaction = new Transaction();
let tokenId = parseInt(this.data.currentCompToken[i].toString());
let userTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[tokenId].tokenMint),
basketOwner,
true,
);
let infoAta = await connection.getAccountInfo(userTokenAccount, "confirmed");
if (!infoAta)
transaction.add(
await createAssociatedTokenAccountInstruction(
wallet.publicKey,
userTokenAccount,
basketOwner,
new PublicKey(tokenList[tokenId].tokenMint),
)
);
transaction.instructions = [
...transaction.instructions,
claimTokensIxs[ixId],
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
]
transactions.push(transaction);
ixId = ixId + 1;
}
let signedTransactions = await signTransactionsWithWallet(
connection,
wallet,
transactions.map(transaction => {
return { transaction: transaction, signers: []}
})
);
return await sendSignedTransactions(
connection,
signedTransactions,
);
}
async removeDust(
program: Program<BasketsIDL>,
wallet: Wallet,
tokenList: TokenSettings[],
oraclePriceData: number[],
lamports: number,
updateOracles: boolean, /// NEED to implement
): Promise<TransactionSignature[]> {
let preTransactions: Transaction[] = [];
let swapTransactions: Transaction[] = [];
for (let j = parseInt(this.data.numOfTokens.toString()) - 1; j > 0; j--) {
let token = parseInt(this.data.currentCompToken[j].toString());
let decimals = tokenList[token].decimals;
let amount = parseInt(this.data.currentCompAmount[j].toString());
let weight = parseInt(this.data.targetWeight[j].toString());
let price = oraclePriceData[token];
let value = (amount / 10 ** decimals) * price;
if (value > 0.005 || weight != 0)
continue;
let transaction = new Transaction();
let instructions = [];
let fromTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[0].tokenMint),
wallet.publicKey,
true,
);
let toTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[token].tokenMint),
wallet.publicKey,
true,
);
const info = await program.provider.connection.getAccountInfo(toTokenAccount, "confirmed");
if (!info) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
toTokenAccount,
wallet.publicKey,
new PublicKey(tokenList[token].tokenMint),
)
);
}
let swapFeeAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[token].tokenMint),
SWAP_FEE_ACCOUNT,
);
const info2 = await program.provider.connection.getAccountInfo(swapFeeAccount, "confirmed");
if (!info2) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
swapFeeAccount,
SWAP_FEE_ACCOUNT,
new PublicKey(tokenList[token].tokenMint),
)
);
}
let hostFeeAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[token].tokenMint),
this.data.hostPubkey,
true,
);
const info3 = await program.provider.connection.getAccountInfo(hostFeeAccount, "confirmed");
if (!info3) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
hostFeeAccount,
this.data.hostPubkey,
new PublicKey(tokenList[token].tokenMint),
)
);
}
let managerFeeAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[token].tokenMint),
this.data.manager,
true,
);
const info4 = await program.provider.connection.getAccountInfo(managerFeeAccount, "confirmed");
if (!info4 && managerFeeAccount.toBase58() != hostFeeAccount.toBase58()) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
managerFeeAccount,
this.data.manager,
new PublicKey(tokenList[token].tokenMint),
)
);
}
if (instructions.length > 0) {
transaction.add(...instructions);
transaction = transaction.add(
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS})
).add(
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
);
preTransactions.push(transaction);
}
let usdcAmount = Math.max(Math.floor(1.1 * value * 10 ** 6), 50)
swapTransactions.push(
new Transaction()
.add(await program.methods.liquidityProvision(
new BN(0),
new BN(token),
new BN(usdcAmount),
new BN(amount),
)
.accounts({
buyer: program.provider.publicKey,
fundState: this.ownAddress,
pdaAccount: BASKETS_PROGRAM_PDA,
pdaFromTokenAccount: new PublicKey(tokenList[0].pdaTokenAccount),
buyerFromTokenAccount: fromTokenAccount,
pdaToTokenAccount: new PublicKey(tokenList[token].pdaTokenAccount),
buyerToTokenAccount: toTokenAccount,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
swapFeeAccount: getAssociatedTokenAddressSync(
new PublicKey(tokenList[token].tokenMint),
SWAP_FEE_ACCOUNT,
),
hostFeeAccount: getAssociatedTokenAddressSync(
new PublicKey(tokenList[token].tokenMint),
this.data.hostPubkey,
true,
),
managerFeeAccount: getAssociatedTokenAddressSync(
new PublicKey(tokenList[token].tokenMint),
this.data.manager,
true,
),
tokenList: TOKEN_LIST_ADDRESS,
curveData: CURVE_DATA_ADDRESS,
tokenProgram: TOKEN_PROGRAM_ID
})
.remainingAccounts(this.data.currentCompToken.map(x => {
return {
pubkey: new PublicKey(tokenList[parseInt(x.toString())].oracleAccount),
isSigner: false,
isWritable: false,
}
}))
.instruction()
)
.add(
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS})
).add(
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
)
);
}
let transactions = [...preTransactions, ...swapTransactions];
let signedTransactions = await signTransactionsWithWallet(
program.provider.connection,
wallet,
transactions.map(transaction => {
return { transaction: transaction, signers: []}
})
);
let _ = await sendSignedTransactions(
program.provider.connection,
signedTransactions.slice(0, preTransactions.length),
);
return await sendSignedTransactions(
program.provider.connection,
signedTransactions.slice(preTransactions.length, signedTransactions.length),
swapTransactions.length,
);
}
async liquidityProvision(
program: Program<BasketsIDL>,
wallet: Wallet,
tokenList: TokenSettings[],
fromToken: number,
toToken: number,
fromAmount: number,
lamports: number = ADDITIONAL_FEE,
): Promise<TransactionSignature[]> {
let preTransactions: Transaction[] = [];
let swapTransactions: Transaction[] = [];
let transaction = new Transaction();
let instructions = [];
let fromTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[fromToken].tokenMint),
wallet.publicKey,
true,
);
let toTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[toToken].tokenMint),
wallet.publicKey,
true,
);
const info = await program.provider.connection.getAccountInfo(toTokenAccount, "confirmed");
if (!info) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
toTokenAccount,
wallet.publicKey,
new PublicKey(tokenList[toToken].tokenMint),
)
);
}
let swapFeeAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[toToken].tokenMint),
SWAP_FEE_ACCOUNT,
);
const info2 = await program.provider.connection.getAccountInfo(swapFeeAccount, "confirmed");
if (!info2) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
swapFeeAccount,
SWAP_FEE_ACCOUNT,
new PublicKey(tokenList[toToken].tokenMint),
)
);
}
let hostFeeAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[toToken].tokenMint),
this.data.hostPubkey,
true,
);
const info3 = await program.provider.connection.getAccountInfo(hostFeeAccount, "confirmed");
if (!info3) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
hostFeeAccount,
this.data.hostPubkey,
new PublicKey(tokenList[toToken].tokenMint),
)
);
}
let managerFeeAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[toToken].tokenMint),
this.data.manager,
true,
);
const info4 = await program.provider.connection.getAccountInfo(managerFeeAccount, "confirmed");
if (!info4 && managerFeeAccount.toBase58() != hostFeeAccount.toBase58()) {
instructions.push(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
managerFeeAccount,
this.data.manager,
new PublicKey(tokenList[toToken].tokenMint),
)
);
}
if (instructions.length > 0) {
transaction.add(...instructions);
transaction = transaction.add(
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS})
).add(
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
);
preTransactions.push(transaction);
}
swapTransactions.push(
new Transaction()
.add(await program.methods.liquidityProvision(
new BN(fromToken),
new BN(toToken),
new BN(Math.floor(fromAmount * 10 ** tokenList[fromToken].decimals)),
new BN(0),
)
.accounts({
buyer: program.provider.publicKey,
fundState: this.ownAddress,
pdaAccount: BASKETS_PROGRAM_PDA,
pdaFromTokenAccount: new PublicKey(tokenList[fromToken].pdaTokenAccount),
buyerFromTokenAccount: fromTokenAccount,
pdaToTokenAccount: new PublicKey(tokenList[toToken].pdaTokenAccount),
buyerToTokenAccount: toTokenAccount,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
swapFeeAccount: getAssociatedTokenAddressSync(
new PublicKey(tokenList[toToken].tokenMint),
SWAP_FEE_ACCOUNT,
),
hostFeeAccount: getAssociatedTokenAddressSync(
new PublicKey(tokenList[toToken].tokenMint),
this.data.hostPubkey,
true,
),
managerFeeAccount: getAssociatedTokenAddressSync(
new PublicKey(tokenList[toToken].tokenMint),
this.data.manager,
true,
),
tokenList: TOKEN_LIST_ADDRESS,
curveData: CURVE_DATA_ADDRESS,
tokenProgram: TOKEN_PROGRAM_ID
})
.remainingAccounts(this.data.currentCompToken
.slice(0, parseInt(this.data.numOfTokens.toString())).map(x => {
return {
pubkey: new PublicKey(tokenList[parseInt(x.toString())].oracleAccount),
isSigner: false,
isWritable: false,
}
}))
.instruction()
)
.add(
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS})
).add(
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
)
);
let transactions = [...preTransactions, ...swapTransactions];
let signedTransactions = await signTransactionsWithWallet(
program.provider.connection,
wallet,
transactions.map(transaction => {
return { transaction: transaction, signers: []}
})
);
let _ = await sendSignedTransactions(
program.provider.connection,
signedTransactions.slice(0, preTransactions.length),
);
return await sendSignedTransactions(
program.provider.connection,
signedTransactions.slice(preTransactions.length, signedTransactions.length),
swapTransactions.length,
);
}
}