@symmetry-hq/baskets-sdk
Version:
Software Development Kit for interacting with Symmetry Baskets Program
843 lines (794 loc) • 32 kB
text/typescript
import { AddressLookupTableAccount, ComputeBudgetProgram, Keypair, PublicKey, SYSVAR_INSTRUCTIONS_PUBKEY, SYSVAR_RENT_PUBKEY, SystemProgram, TransactionInstruction, VersionedTransaction } from "@solana/web3.js";
import { BUY_FEE_ACCOUNT, BUY_FEE_WALLET, CREATE_FEE_ACCOUNT, CreateBasketParams, BASKETS_PROGRAM_ID, BASKETS_PROGRAM_PDA, FilterTime, FilterType, SortBy, TOKEN_LIST_ADDRESS, TOKEN_STATS_ADDRESS, TokenSettings, WeightTime, WeightType, ADDITIONAL_UNITS, SimpleCreateParams, SimpleEditParams, REBALANCE_FEE_ACCOUNT, REBALANCE_FEE_WALLET, TransactionToSend } from "./config";
import { MD5 } from "crypto-js";
import { BN, Program, Wallet } from "@coral-xyz/anchor";
import { BasketsIDL, IDL } from "./basketsIDL";
import { validateCreateBasketParams } from "./utils";
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from "./splTokenHelpers";
import { Basket } from "./basketState";
import { BuyState } from "./buyState";
import { Metaplex } from "@metaplex-foundation/js";
import { FetchSignaturesMultiResponse, PullFeed } from "@switchboard-xyz/on-demand";
export async function buildCreateBasketIx(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
basketParams: SimpleCreateParams,
): Promise<TransactionInstruction> {
if (basketParams.composition.length == 0)
throw new Error("Empty composition");
if (!basketParams.feeDelegate)
basketParams.feeDelegate = basketParams.manager;
let assetPool = [0];
let rules = [];
let weightSum = 0;
basketParams.composition.map(info => weightSum += info.weight);
for (let i = 0; i < basketParams.composition.length; i++) {
let tokenSettings = tokenList.find(
token => token.tokenMint == basketParams.composition[i].token.toBase58()
);
if (!tokenSettings)
throw new Error("Token not supported.");
if (tokenSettings.id != 0)
assetPool.push(tokenSettings.id);
rules.push({
filterBy: FilterType.Fixed,
filterDays: FilterTime.Day,
sortBy: SortBy.DescendingOrder,
totalWeight: Math.floor((basketParams.composition[i].weight * 1000) / weightSum),
fixedAsset: tokenSettings.id,
numAssets: 1,
weightBy: WeightType.Fixed,
weightDays: WeightTime.Day,
weightExpo: 0,
excludeAssets: [],
ruleAssets: [],
})
}
let validateBasketParams: CreateBasketParams = {
...basketParams,
name: basketParams.name,
symbol: basketParams.symbol,
uri: basketParams.uri,
manager: PublicKey.default,
hostPlatform: PublicKey.default,
hostPlatformFee: 0,
activelyManaged: 1,
assetPool: assetPool,
rules: rules,
refilterInterval: 7 * 24 * 3600,
reweightInterval: 24 * 3600
}
await validateCreateBasketParams(validateBasketParams, tokenList, undefined, true);
let tokens = rules.map(info => info.fixedAsset);
let weights = rules.map(info => new BN(info.totalWeight));
while (tokens.length < 15) tokens.push(0);
while (weights.length < 15) weights.push(new BN(0));
let message = basketParams.name + basketParams.symbol + basketParams.uri;
let md5 = Array.from(Buffer.from(MD5(message).toString(), "hex"));
let seedPubkey = Keypair.generate().publicKey;
let [basketToken] = PublicKey.findProgramAddressSync(
[Buffer.from("mint"), seedPubkey.toBuffer()],
BASKETS_PROGRAM_ID
);
let [basketState] = PublicKey.findProgramAddressSync(
[Buffer.from("basket"), seedPubkey.toBuffer()],
BASKETS_PROGRAM_ID
);
if (basketParams.symbol.length < 3) throw new Error("Wrong symbol format");
if (basketParams.name.length < 3) throw new Error("Wrong name format");
const metaplex = Metaplex.make(program.provider.connection);
const metadataAccount = metaplex.nfts().pdas().metadata({ mint: basketToken });
let createInstruction =
await program.methods
.createBasket(
basketParams.managerFee,
basketParams.hostPlatformFee,
basketParams.activelyManaged,
new BN(basketParams.rebalanceInterval),
basketParams.rebalanceThreshold,
basketParams.rebalanceSlippage,
basketParams.lpOffsetThreshold,
[basketParams.disableRebalance? 1 : 0, basketParams.disableLp? 1 : 0],
basketParams.composition.length,
tokens,
weights.map(x => x.toNumber()),
{
name: basketParams.name,
symbol: basketParams.symbol,
uri: basketParams.uri,
}
)
.accounts({
manager: basketParams.manager,
tokenList: TOKEN_LIST_ADDRESS,
fundState: basketState,
pdaAccount: BASKETS_PROGRAM_PDA,
fundToken: basketToken,
createFeeSweeper: CREATE_FEE_ACCOUNT,
metadataAccount: metadataAccount,
metadataProgram: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: SYSVAR_RENT_PUBKEY,
host: basketParams.hostPlatform,
feeDelegate: basketParams.feeDelegate,
seedPubkey: seedPubkey,
})
.instruction();
return createInstruction;
}
export async function buildEditBasketIx(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
basketObj: Basket,
basketParams: SimpleEditParams,
): Promise<TransactionInstruction> {
if (basketParams.composition.length == 0)
throw new Error("Empty composition");
if (!basketParams.feeDelegate)
basketParams.feeDelegate = basketObj.data.feeDelegate;
if (!basketParams.composition) {
basketParams.composition = [];
for (let i = 0; i < basketObj.data.numRuleTokens.toNumber(); i++)
if (basketObj.data.ruleTokenWeights[i].toNumber() != 0)
basketParams.composition.push({
token: new PublicKey(
tokenList[basketObj.data.ruleTokens[i].toNumber()].tokenMint
),
weight: basketObj.data.ruleTokenWeights[i].toNumber()
})
}
let assetPool = [0];
let rules = [];
let weightSum = 0;
basketParams.composition.map(info => weightSum += info.weight);
for (let i = 0; i < basketParams.composition.length; i++) {
let tokenSettings = tokenList.find( //@ts-ignore
token => token.tokenMint == basketParams.composition[i].token.toBase58()
);
if (!tokenSettings)
throw new Error("Token not supported.");
if (tokenSettings.id != 0)
assetPool.push(tokenSettings.id);
rules.push({
filterBy: FilterType.Fixed,
filterDays: FilterTime.Day,
sortBy: SortBy.DescendingOrder,
totalWeight: Math.floor((basketParams.composition[i].weight * 1000) / weightSum),
fixedAsset: tokenSettings.id,
numAssets: 1,
weightBy: WeightType.Fixed,
weightDays: WeightTime.Day,
weightExpo: 0,
excludeAssets: [],
ruleAssets: [],
})
}
//@ts-ignore
let validateBasketParams: CreateBasketParams = {
...basketParams,
name: "",
symbol: "",
uri: "",
manager: PublicKey.default,
hostPlatform: PublicKey.default,
hostPlatformFee: 0,
activelyManaged: 1,
assetPool: assetPool,
rules: rules,
refilterInterval: 7 * 24 * 3600,
reweightInterval: 24 * 3600
}
await validateCreateBasketParams(validateBasketParams, tokenList, basketObj.ownAddress, true);
let tokens = rules.map(info => info.fixedAsset);
let weights = rules.map(info => new BN(info.totalWeight));
while (tokens.length < 15) tokens.push(0);
while (weights.length < 15) weights.push(new BN(0));
return await program.methods
.editBasket(
basketParams.managerFee,
new BN(basketParams.rebalanceInterval),
basketParams.rebalanceThreshold,
basketParams.rebalanceSlippage,
(basketParams.lpOffsetThreshold),
[basketParams.disableRebalance? 1 : 0, basketParams.disableLp? 1 : 0],
rules.length,
tokens,
weights.map(x => x.toNumber()),
)
.accounts({
fundState: basketObj.ownAddress,
manager: basketObj.data.manager,
tokenList: TOKEN_LIST_ADDRESS,
feeDelegate: basketParams.feeDelegate,
})
.instruction();
}
export async function buildEditManagetIx(
program: Program<BasketsIDL>,
basketState: Basket,
newManager: PublicKey,
): Promise<TransactionInstruction> {
return await program.methods
.editManager(
newManager
)
.accounts({
manager: basketState.data.manager,
fundState: basketState.ownAddress,
})
.instruction();
}
export async function buildCloseBasketIx(
program: Program<BasketsIDL>,
basketState: Basket,
): Promise<TransactionInstruction> {
return await program.methods.closeBasket()
.accounts({
manager: basketState.data.manager,
fundState: basketState.ownAddress,
fundToken: basketState.data.fundToken,
pdaAccount: BASKETS_PROGRAM_PDA,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID
})
.instruction();
}
export async function buildCreateMetadataIx(
program: Program<BasketsIDL>,
creator: PublicKey,
basket: PublicKey,
tokenMint: PublicKey,
metadata: {
symbol: string,
name: string,
uri: string
}
): Promise<TransactionInstruction> {
let {symbol, name, uri} = metadata;
if (symbol.length < 3 || symbol.length > 10) throw new Error("Wrong symbol format");
if (name.length < 3 || name.length > 60) throw new Error("Wrong name format");
const metaplex = Metaplex.make(program.provider.connection);
const metadataAccount = metaplex.nfts().pdas().metadata({ mint: tokenMint });
return await program.methods.createFundTokenMintMetadata({
name: name,
symbol: symbol,
uri: uri,
}).accounts({
manager: creator,
fundToken: tokenMint,
fundState: basket,
updateAuthority: BASKETS_PROGRAM_PDA,
metadataAccount: metadataAccount,
metadataProgram: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
systemProgram: SystemProgram.programId,
rent: SYSVAR_RENT_PUBKEY,
}).instruction();
}
export async function buildUpdateMetadataIx(
program: Program<BasketsIDL>,
creator: PublicKey,
basket: PublicKey,
tokenMint: PublicKey,
metadata: {
symbol: string,
name: string,
uri: string
}
): Promise<TransactionInstruction> {
let {symbol, name, uri} = metadata;
if (symbol.length < 3 || symbol.length > 10) throw new Error("Wrong symbol format");
if (name.length < 3 || name.length > 60) throw new Error("Wrong name format");
const metaplex = Metaplex.make(program.provider.connection);
const metadataAccount = metaplex.nfts().pdas().metadata({ mint: tokenMint });
return await program.methods.updateFundTokenMintMetadata({
name: name,
symbol: symbol,
uri: uri,
}).accounts({
manager: creator,
fundToken: tokenMint,
fundState: basket,
updateAuthority: BASKETS_PROGRAM_PDA,
metadataAccount: metadataAccount,
metadataProgram: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
systemProgram: SystemProgram.programId,
rent: SYSVAR_RENT_PUBKEY,
}).instruction();
}
export async function buildSetMetadataIx(
program: Program<BasketsIDL>,
basket: Basket,
metadata: {
symbol: string,
name: string,
uri: string
}
): Promise<TransactionInstruction> {
if (basket.data.symbolLength == 0)
return await buildCreateMetadataIx(
program, basket.data.manager, basket.ownAddress, basket.data.fundToken, metadata
); else
return await buildUpdateMetadataIx(
program, basket.data.manager, basket.ownAddress, basket.data.fundToken, metadata
);
}
export async function buildBuyBasketIx(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
user: PublicKey,
basketObj: Basket,
amount: number,
): Promise<TransactionInstruction> {
let buyerUsdcTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[0].tokenMint),
user,
true,
);
let managerUsdcTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[0].tokenMint),
basketObj.data.feeDelegate.toBase58() == PublicKey.default.toBase58()
? basketObj.data.manager : basketObj.data.feeDelegate,
true,
);
let hostUsdcTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[0].tokenMint),
basketObj.data.hostPubkey,
true,
);
let buyerBasketTokenAccount = getAssociatedTokenAddressSync(
basketObj.data.fundToken,
user,
true,
);
let seedPubkey = Keypair.generate().publicKey;
let [buyState] = PublicKey.findProgramAddressSync(
[Buffer.from("buy"), seedPubkey.toBuffer()],
BASKETS_PROGRAM_ID
);
return await program.methods
.buyFund(new BN(amount * 10 ** tokenList[0].decimals))
.accounts({
buyer: user,
fundState: basketObj.ownAddress,
fundToken: basketObj.data.fundToken,
tokenList: TOKEN_LIST_ADDRESS,
pdaAccount: BASKETS_PROGRAM_PDA,
pdaUsdcAccount: new PublicKey(tokenList[0].pdaTokenAccount),
buyerUsdcAccount: buyerUsdcTokenAccount,
managerUsdcAccount: managerUsdcTokenAccount,
smfFeeAccount: BUY_FEE_ACCOUNT,
hostUsdcAccount: hostUsdcTokenAccount,
buyerFundTokenAccount: buyerBasketTokenAccount,
buyState: buyState,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: SYSVAR_RENT_PUBKEY,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
seedPubkey: seedPubkey,
})
.instruction()
}
export async function buildClaimTokensFromBuyStateIxs(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
feePayer: PublicKey,
basketObj: Basket,
buyStateObj: BuyState,
): Promise<TransactionInstruction[]> {
let ixs: TransactionInstruction[] = [];
for (let i = 0; i < buyStateObj.data.token.length; i++) {
if (parseInt(buyStateObj.data.amountBought[i].toString()) == 0 && i != 0)
continue;
let tokenId = buyStateObj.data.token[i].toNumber();
let userTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[tokenId].tokenMint),
buyStateObj.data.buyer,
true,
);
ixs.push(
await program.methods
.claimTokenFromBuyState(new BN(tokenId))
.accounts({
signer: feePayer,
buyer: buyStateObj.data.buyer,
fundState: basketObj.ownAddress,
buyState: buyStateObj.ownAddress,
tokenList: TOKEN_LIST_ADDRESS,
buyerTokenAccount: userTokenAccount,
pdaTokenAccount: new PublicKey(tokenList[tokenId].pdaTokenAccount),
pdaAccount: BASKETS_PROGRAM_PDA,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
})
.instruction()
);
}
return ixs;
}
export async function buildMintFromBuyStateIx(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
feePayer: PublicKey,
basketObj: Basket,
buyStateObj: BuyState,
): Promise<TransactionInstruction> {
let accounts = [];
for (let i = 0; i < basketObj.data.numOfTokens.toNumber(); i++)
accounts.push({
pubkey: new PublicKey(
tokenList[basketObj.data.currentCompToken[i].toNumber()].oracleAccount
),
isSigner: false,
isWritable: false,
})
return await program.methods
.mintFund()
.accounts({
signer: feePayer,
buyer: buyStateObj.data.buyer,
fundState: basketObj.ownAddress,
tokenList: TOKEN_LIST_ADDRESS,
buyState: buyStateObj.ownAddress,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
pdaAccount: BASKETS_PROGRAM_PDA,
buyerFundTokenAccount: buyStateObj.data.buyerFundTokenAccount,
fundToken: basketObj.data.fundToken,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID
})
.remainingAccounts(accounts)
.instruction();
}
export async function buildBuyBasketWithMultipleTokensIx(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
user: PublicKey,
basketObj: Basket,
contribution: {token: PublicKey, amount: number}[],
): Promise<TransactionInstruction> {
let tokenMints = basketObj.data.currentCompToken.slice(0, basketObj.data.numOfTokens.toNumber())
.map(token => new PublicKey(tokenList[token.toNumber()].tokenMint));
tokenMints.push(basketObj.data.fundToken);
let buyerAtas = tokenMints.map(token =>
getAssociatedTokenAddressSync(token, user, true)
);
for (let i = 0; i < contribution.length; i++) {
let inBasket = basketObj.data.currentCompToken.find(info =>
tokenList[info.toNumber()].tokenMint == contribution[i].token.toBase58()
);
if (!inBasket)
throw new Error("Token not present in the current compostion.");
}
let amountsBN: BN[] = [];
let remainingAccounts = [];
for (let i = 0; i < basketObj.data.numOfTokens.toNumber(); i++) {
let tokenSettings = tokenList[basketObj.data.currentCompToken[i].toNumber()];
let tokenContribution = contribution.find(info => info.token.toBase58() == tokenSettings.tokenMint);
let amount = (tokenContribution) ? tokenContribution.amount : 0;
amountsBN.push(new BN(Math.floor(amount * 10 ** tokenSettings.decimals)));
remainingAccounts.push({
pubkey: new PublicKey(tokenSettings.oracleAccount),
isSigner: false,
isWritable: false,
});
remainingAccounts.push({
pubkey: buyerAtas[i],
isSigner: false,
isWritable: true,
});
remainingAccounts.push({
pubkey: new PublicKey(tokenSettings.pdaTokenAccount),
isSigner: false,
isWritable: true,
});
}
let buyerBasketTokenAccount = buyerAtas[buyerAtas.length - 1];
while (amountsBN.length < 20) amountsBN.push(new BN(0));
return await program.methods
.instantMint(amountsBN)
.accounts({
authority: user,
buyerFundTokenAccount: buyerBasketTokenAccount,
fundToken: basketObj.data.fundToken,
fundState: basketObj.ownAddress,
tokenList: TOKEN_LIST_ADDRESS,
pdaAccount: BASKETS_PROGRAM_PDA,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(remainingAccounts)
.instruction();
}
export async function buildBuyBasketWithSingleTokenIx(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
user: PublicKey,
basketObj: Basket,
contributionToken: PublicKey,
contributionAmount: number,
): Promise<TransactionInstruction> {
let tokenSettings = tokenList.find(info =>
info.tokenMint == contributionToken.toBase58()
);
if (!tokenSettings || !basketObj.data.currentCompToken.find(x => x.toNumber() == tokenSettings?.id))
throw new Error("Token not present in the current compostion.");
let buyerTokenAccount = getAssociatedTokenAddressSync(contributionToken, user, true);
let buyerBasketTokenAccount = getAssociatedTokenAddressSync(basketObj.data.fundToken, user, true);
let symmetryFeeAccount = getAssociatedTokenAddressSync(contributionToken, BUY_FEE_WALLET);
let hostFeeAccount = getAssociatedTokenAddressSync(contributionToken, basketObj.data.hostPubkey, true);
let managerFeeAccount = getAssociatedTokenAddressSync(contributionToken, basketObj.data.manager, true);
let remainingAccounts = [];
for (let i = 0; i < basketObj.data.numOfTokens.toNumber(); i++) {
let tokenSettings = tokenList[basketObj.data.currentCompToken[i].toNumber()];
remainingAccounts.push({
pubkey: new PublicKey(tokenSettings.oracleAccount),
isSigner: false,
isWritable: false,
});
}
return await program.methods
.singleTokenDeposit(tokenSettings?.id, new BN(contributionAmount * 10 ** tokenSettings.decimals))
.accounts({
authority: user,
fundState: basketObj.ownAddress,
fundToken: basketObj.data.fundToken,
buyerTokenAccount: buyerTokenAccount,
buyerFundTokenAccount: buyerBasketTokenAccount,
pdaAccount: BASKETS_PROGRAM_PDA,
pdaTokenAccount: new PublicKey(tokenSettings.pdaTokenAccount),
symmetryFeeAccount: symmetryFeeAccount,
hostFeeAccount: hostFeeAccount,
managerFeeAccount: managerFeeAccount,
oracleAccount: new PublicKey(tokenSettings.oracleAccount),
oracleSol: new PublicKey(tokenList[1].oracleAccount),
tokenList: TOKEN_LIST_ADDRESS,
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(remainingAccounts)
.instruction();
}
export async function buildSellBasketToSingleTokenIx(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
user: PublicKey,
basketObj: Basket,
withdrawToken: PublicKey,
amount: number,
): Promise<TransactionInstruction> {
let tokenSettings = tokenList.find(info => info.tokenMint == withdrawToken.toBase58());
if (!tokenSettings || !basketObj.data.currentCompToken.find(x => x.toNumber() == tokenSettings?.id))
throw new Error("Token not present in the current compostion.");
let userBasketTokenAccount = getAssociatedTokenAddressSync(basketObj.data.fundToken, user, true);
let userTokenAccount = getAssociatedTokenAddressSync(withdrawToken, user, true);
let remainingAccounts = [];
for (let i = 0; i < basketObj.data.numOfTokens.toNumber(); i++) {
let tokenSettings = tokenList[basketObj.data.currentCompToken[i].toNumber()];
remainingAccounts.push({
pubkey: new PublicKey(tokenSettings.oracleAccount),
isSigner: false,
isWritable: false,
});
}
return await program.methods
.instantBurn(
new BN(amount * 10 ** 6),
tokenSettings.id,
)
.accounts({
seller: user,
pdaAccount: BASKETS_PROGRAM_PDA,
fundState: basketObj.ownAddress,
fundToken: basketObj.data.fundToken,
sellerFundTokenAccount: userBasketTokenAccount,
withdrawTokenMint: withdrawToken,
sellerTokenAccount: userTokenAccount,
pdaTokenAccount: new PublicKey(tokenSettings?.pdaTokenAccount),
tokenList: TOKEN_LIST_ADDRESS,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
})
.remainingAccounts(remainingAccounts)
.instruction()
}
export async function buildSellBasketIx(
program: Program<BasketsIDL>,
user: PublicKey,
basketObj: Basket,
amount: number,
rebalance: number,
): Promise<TransactionInstruction> {
let userBasketTokenAccount = getAssociatedTokenAddressSync(
basketObj.data.fundToken,
user,
true,
);
let seedPubkey = Keypair.generate().publicKey;
let [sellState] = PublicKey.findProgramAddressSync(
[Buffer.from("sell"), seedPubkey.toBuffer()],
BASKETS_PROGRAM_ID
);
return await program.methods
.sellFund(
new BN(amount * 10 ** 6),
new BN(rebalance),
)
.accounts({
seller: user,
fundState: basketObj.ownAddress,
pdaAccount: BASKETS_PROGRAM_PDA,
newFundState: sellState,
sellerFundTokenAccount: userBasketTokenAccount,
fundToken: basketObj.data.fundToken,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: SYSVAR_RENT_PUBKEY,
seedPubkey: seedPubkey,
})
.instruction()
}
export async function buildClaimTokensFromSellStateIxs(
program: Program<BasketsIDL>,
tokenList: TokenSettings[],
feePayer: PublicKey,
basketObj: Basket,
): Promise<TransactionInstruction[]> {
let basketOwner = basketObj.data.manager;
let ixs: TransactionInstruction[] = [];
for (let i = 0; i < basketObj.data.numOfTokens.toNumber(); i++) {
if (parseInt(basketObj.data.currentCompAmount[i].toString()) == 0 && i != 0)
continue;
let tokenId = basketObj.data.currentCompToken[i].toNumber();
let userTokenAccount = getAssociatedTokenAddressSync(
new PublicKey(tokenList[tokenId].tokenMint),
basketOwner,
true,
);
ixs.push(
await program.methods
.claimToken(new BN(tokenId))
.accounts({
signer: feePayer,
manager: basketOwner,
fundState: basketObj.ownAddress,
tokenList: TOKEN_LIST_ADDRESS,
sellerTokenAccount: userTokenAccount,
pdaTokenAccount: new PublicKey(tokenList[tokenId].pdaTokenAccount),
pdaAccount: BASKETS_PROGRAM_PDA,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
})
.instruction()
);
}
return ixs;
}
export async function buildUpdateCurrentWeightsIx(
program: Program<BasketsIDL>,
basket: Basket,
tokenList: TokenSettings[],
): Promise<TransactionInstruction> {
let accounts = [];
for (let i = 0; i < basket.data.numOfTokens.toNumber(); i++)
accounts.push({
pubkey: new PublicKey(
tokenList[basket.data.currentCompToken[i].toNumber()].oracleAccount
),
isSigner: false,
isWritable: false,
})
return await program.methods
.updateCurrentWeights()
.accounts({
fundState: basket.ownAddress,
tokenList: TOKEN_LIST_ADDRESS,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
})
.remainingAccounts(accounts)
.instruction();
}
export async function buildWithdrawBeforeRebalanceIx(
program: Program<BasketsIDL>,
feePayer: PublicKey,
basket: Basket,
tokenList: TokenSettings[],
fromToken: number,
toToken: number,
fromAmount: number,
): Promise<TransactionInstruction> {
return await program.methods.withdrawBeforeRebalance(
fromToken,
toToken,
new BN(fromAmount)
).accounts({
signer: feePayer,
basketState: basket.ownAddress,
tokenList: TOKEN_LIST_ADDRESS,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
oracleTokenFrom: new PublicKey(tokenList[fromToken].oracleAccount),
oracleTokenTo: new PublicKey(tokenList[toToken].oracleAccount),
pdaAccount: BASKETS_PROGRAM_PDA,
pdaTokenFrom: new PublicKey(tokenList[fromToken].pdaTokenAccount),
pdaTokenTo: new PublicKey(tokenList[toToken].pdaTokenAccount),
signerTokenFrom: getAssociatedTokenAddressSync(new PublicKey(tokenList[fromToken].tokenMint), feePayer, true),
signerTokenTo: getAssociatedTokenAddressSync(new PublicKey(tokenList[toToken].tokenMint), feePayer, true),
fromTokenMint: new PublicKey(tokenList[fromToken].tokenMint),
toTokenMint: new PublicKey(tokenList[toToken].tokenMint),
rebalanceState: PublicKey.findProgramAddressSync([Buffer.from("flashrebalance")], BASKETS_PROGRAM_ID)[0],
tokenProgram: TOKEN_PROGRAM_ID,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
rent: SYSVAR_RENT_PUBKEY,
instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
}).instruction();
}
export async function buildDepositAfterRebalanceIx(
program: Program<BasketsIDL>,
feePayer: PublicKey,
basket: Basket,
tokenList: TokenSettings[],
toToken: number,
) {
return await program.methods.depositAfterRebalance().accounts({
signer: feePayer,
basketState: basket.ownAddress,
tokenList: TOKEN_LIST_ADDRESS,
oracleSol: new PublicKey(tokenList[1].oracleAccount),
oracleTokenTo: new PublicKey(tokenList[toToken].oracleAccount),
pdaAccount: BASKETS_PROGRAM_PDA,
pdaTokenTo: new PublicKey(tokenList[toToken].pdaTokenAccount),
signerTokenTo: getAssociatedTokenAddressSync(new PublicKey(tokenList[toToken].tokenMint), feePayer, true),
rebalanceState: PublicKey.findProgramAddressSync([Buffer.from("flashrebalance")], BASKETS_PROGRAM_ID)[0],
rebalanceFeeAccount: getAssociatedTokenAddressSync(new PublicKey(tokenList[toToken].tokenMint), REBALANCE_FEE_WALLET, true),
tokenProgram: TOKEN_PROGRAM_ID,
systemProgram: SystemProgram.programId,
}).instruction();
}
export async function getSwbFeedsUpdateIxs(
program: Program,
payer: PublicKey,
feeds : PublicKey[],
numSignatures: number = 2,
): Promise<[
TransactionInstruction,
AddressLookupTableAccount[],
FetchSignaturesMultiResponse
][]> {
let maxUpdates = Math.floor(18 / numSignatures);
let ixPromises = [];
for (let i = 0; i < feeds.length; i += maxUpdates) {
let promise = PullFeed.fetchUpdateManyIx(program, {
feeds: feeds.slice(i, i + maxUpdates),
numSignatures: numSignatures,
payer: payer,
});
ixPromises.push(promise);
}
return await Promise.all(ixPromises);
}
export async function updateOraclesTxs(
swbProgram: Program,
payer: PublicKey,
feeds: PublicKey[],
lamports: number,
): Promise<TransactionToSend[]> {
let res = await getSwbFeedsUpdateIxs(
swbProgram,
payer,
feeds,
).catch((e) => {
console.log("Couldn't fetch SWB oracle update txs", e.message);
return [];
});
return res.map(update => {
return {
payerKey: payer,
instructions: [
update[0],
ComputeBudgetProgram.setComputeUnitLimit({units: ADDITIONAL_UNITS}),
ComputeBudgetProgram.setComputeUnitPrice({microLamports: lamports})
],
lookupTables: update[1]
}
})
}