import Decimal from 'decimal.js'; import BN from 'bn.js'; import { createAssociatedTokenAccountIdempotentInstruction, createAssociatedTokenAccountInstruction, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, } from '@solana/spl-token'; import { ComputeBudgetProgram, Connection, Keypair, PublicKey, TransactionInstruction, TransactionSignature } from '@solana/web3.js'; import { COMPUTE_UNITS, LUT_EXTEND_BATCH_SIZE, MINTS, PRIORITY_FEE, UPDATE_TOKEN_PRICES_MAX_ACCOUNTS } from './constants'; import { claimBountyIx } from './instructions/automation/claimBounty'; import { flashDepositIx, flashWithdrawIx } from './instructions/automation/flashSwap'; import { updateTokenPricesIx } from './instructions/automation/priceUpdate'; import { cancelRebalanceIx, createRebalanceIntentIx, initRebalanceIntentIx, resizeRebalanceIntentIx } from './instructions/automation/rebalanceIntent'; import { addBountyIx } from './instructions/management/addBounty'; import { createGlobalConfigIx, editGlobalConfigIx } from './instructions/management/admin'; import { withdrawFeesIx, claimFeeTokensFromBasketIx, claimFeeTokensFromBasketIxs } from './instructions/management/claimFees'; import { createBasketIx, createBasketStateAccountIx, resizeBasketStateIx } from './instructions/management/createBasket'; import { cancelIntentIx, createEditBasketIntentIx, executeEditBasketIntentIx } from './instructions/management/edit'; import { createBasketLookupTablesInstruction, extendBasketLookupTablesIx, overwriteBasketLookupTablesIx } from './instructions/management/luts'; import { getAta, getBasketFeesPda, getBasketState, getBasketTokenMintPda, getIntentPda, getLookupTableAccount, getRebalanceIntentPda, getRentPayerPda, getWithdrawBasketFeesPda } from './instructions/pda'; import { depositTokensIx, lockDepositsIx, mintBasketIx } from './instructions/user/deposit'; import { redeemTokensIx } from './instructions/user/withdraw'; import { getJupTokenLedgerAndSwapInstructions } from './jup'; import { Basket, FormattedAccumulatedFees, FormattedAddTokenSettings, FormattedAsset, FormattedAutomationSettings, FormattedBasket, FormattedCreatorSettings, FormattedCustomRebalanceSettings, FormattedDepositsSettings, FormattedFeeSettings, FormattedForceRebalanceSettings, FormattedLookupTables, FormattedLpSettings, FormattedMakeDirectSwapSettings, FormattedManagersSettings, FormattedMetadataSettings, FormattedOracle, FormattedOracleAggregator, FormattedOracleSettings, FormattedScheduleSettings, FormattedUpdateWeightsSettings, WithdrawBasketFees } from './layouts/basket'; import { FormattedGlobalConfig, GlobalConfig } from './layouts/config'; import { decimalToFraction } from './layouts/fraction'; import { AddOrEditTokenInput, EditAddTokenSettings, EditAutomationSettings, EditCreatorSettings, EditCustomRebalanceSettings, EditDepositsSettings, EditFeeSettings, EditForceRebalanceSettings, EditLpSettings, EditMakeDirectSwapSettings, EditManagerSettings, EditMetadataSettings, EditScheduleSettings, EditUpdateWeightsSettings, FormattedBounty, FormattedBountySchedule, FormattedIntent, FormattedIntentStatus, FormattedTaskType, Intent, MakeDirectSwapInput, OracleInput, Settings, TaskContext, TaskType, UpdateWeightsInput } from './layouts/intents/intent'; import { FormattedOraclePrice, FormattedRebalanceAction, FormattedRebalanceIntent, FormattedRebalanceType, FormattedTaskCompletion, FormattedTokenAuction, MintData, PriceUpdatesData, RebalanceIntent, RebalanceType, AuctionData, RedeemData, DepositData, ClaimBountyData, UIRebalanceIntent } from './layouts/intents/rebalanceIntent'; import { FormattedOracleType, OracleType } from './layouts/oracle'; import { BasketFilter, computeTokenMintsHash, fetchBasketsMultiple, fetchBasket, fetchBaskets, loadBasketPrice } from './states/basket'; import { fetchGlobalConfig } from './states/config'; import { fetchIntent, fetchIntents, fetchIntentsMultiple, IntentFilter } from './states/intents/intent'; import { computeRebalanceIntentBountyAmount, fetchRebalanceIntent, fetchRebalanceIntents, fetchRebalanceIntentsMultiple, RebalanceIntentFilter } from './states/intents/rebalanceIntent'; import { fetchWithdrawBasketFees, fetchWithdrawBasketFeesMultiple, fetchWithdrawBasketFeesList, WithdrawBasketFeesFilter } from './states/withdrawBasketFees'; import { buildPythPriceFeedUpdateIxs, fetchFeedIdsFromAccounts } from './states/oracles/pythOracle'; import { BasketCreationTx, prepareTxPayloadBatchSequence, prepareVersionedTxs, sendTxPayloadBatchSequence, sendVersionedTxs, signTxPayloadBatchSequence, signVersionedTxs, TxBatchData, TxPayloadBatchSequence, VersionedTxs, Wallet, wrapWsolIxs } from './txUtils'; export class SymmetryCore { private sdkParams: { connection: Connection, network: "devnet" | "mainnet", priorityFee: number, //TODO: lower comput units for all txs } /** * Initializes the SymmetryCore SDK. * @param params {connection: Connection, network: "devnet" | "mainnet", priorityFee?: number} - The parameters for the SDK. * @param params.connection - The connection to the Solana network. * @param params.network - The network to use (devnet or mainnet). * @param params.priorityFee - Optional. Compute unit price in micro-lamports. Defaults to PRIORITY_FEE (25_000). */ constructor(params: { connection: Connection, network: "devnet" | "mainnet", priorityFee?: number, }) { this.sdkParams = { connection: params.connection, network: params.network, priorityFee: params.priorityFee ?? PRIORITY_FEE, }; } /** * Fetches the global config account. * @returns {Promise} The global config. */ async fetchGlobalConfig(): Promise { return await fetchGlobalConfig(this.sdkParams.connection); } /** * Fetches a basket by its public key. * @param {string} basketPubkey - The public key of the basket. * @returns {Promise} The basket. */ async fetchBasket(basketPubkey: string): Promise { return await fetchBasket(this.sdkParams.connection, new PublicKey(basketPubkey)); } /** * Fetches multiple baskets by their public keys. * @param {string[]} basketPubkeys - The public keys of the baskets. * @returns {Promise>} A map of basket public keys to baskets. */ async fetchMultipleBaskets(basketPubkeys: string[]): Promise> { return await fetchBasketsMultiple(this.sdkParams.connection, basketPubkeys.map(key => new PublicKey(key))); } /** * Fetches all baskets. * @param {BasketFilter} basketFilter - Optional. The filter to apply to the baskets. * @param {"creator" | "host" | "manager"} basketFilter.type - The type of the basket filter ("creator", "host", "manager"). * @param {string} basketFilter.pubkey - The public key of the basket filter. * @returns {Promise} The baskets. */ async fetchAllBaskets(basketFilter?: BasketFilter): Promise { return await fetchBaskets(this.sdkParams.connection, basketFilter); } /** * Fetches all baskets created by a given public key. * @param {string} creatorPubkey - The public key of the creator. * @returns {Promise} The baskets. */ async fetchCreatedBaskets(creatorPubkey: string): Promise { return await fetchBaskets(this.sdkParams.connection, { type: "creator", pubkey: creatorPubkey }); } /** * Fetches all baskets hosted by a given public key. * @param {string} hostPubkey - The public key of the host. * @returns {Promise} The baskets. */ async fetchHostedBaskets(hostPubkey: string): Promise { return await fetchBaskets(this.sdkParams.connection, { type: "host", pubkey: hostPubkey }); } /** * Fetches all baskets managed by a given public key. * @param {string} managerPubkey - The public key of the manager. * @returns {Promise} The baskets. */ async fetchManagedBaskets(managerPubkey: string): Promise { return await fetchBaskets(this.sdkParams.connection, { type: "manager", pubkey: managerPubkey }); } /** * Derives baskets by their mints. * @param {string[]} mints - The mint public keys of the baskets. * @returns {Promise>} A map of basket mint public keys to basket public keys. */ async deriveBasketsByMints(mints: string[]): Promise> { let result: Map = new Map(); for (let basketId = 0; basketId < 50000; basketId++) { let mint = getBasketTokenMintPda(basketId).toBase58(); if (mints.includes(mint)) { result.set(mint, getBasketState(new PublicKey(mint)).toBase58()); } } return result; } /** * Fetches baskets by their mint public keys. * @param {string[]} mints - The mint public keys of the baskets. * @returns {Promise>} A map of basket mint public keys to baskets. */ async fetchBasketsFromMints( mints: string[], ): Promise> { let basketPubkeys: PublicKey[] = mints.map(mint => { let res: PublicKey | null = null; try { res = getBasketState(new PublicKey(mint)) } catch (e) { return null } return res; }).filter(mint => mint !== null); const basketsMap = await fetchBasketsMultiple(this.sdkParams.connection, basketPubkeys); let mintsMap: Map = new Map(); for (let basket of basketsMap.values()) { mintsMap.set(basket.mint.toBase58(), basket); } return mintsMap; } /** * Loads the price of a basket. * @param {Basket} basket - The basket. * @returns {Promise} The basket with the price. */ async loadBasketPrice( basket: Basket, ): Promise { return await loadBasketPrice(basket, this.sdkParams.connection); } /** * Fetches an intent by its public key. * @param {string} intentPubkey - The public key of the intent. * @returns {Promise} The intent. */ async fetchIntent(intentPubkey: string): Promise { return await fetchIntent(this.sdkParams.connection, new PublicKey(intentPubkey)); } /** * Fetches multiple intents by their public keys. * @param {string[]} intentPubkeys - The public keys of the intents. * @returns {Promise>} A map of intent public keys to intents. */ async fetchMultipleIntents(intentPubkeys: string[]): Promise> { return await fetchIntentsMultiple(this.sdkParams.connection, intentPubkeys.map(key => new PublicKey(key))); } /** * Fetches all intents. * @param {IntentFilter} filter - Optional. The filter to apply to the intents. * @param {"creator" | "host" | "manager" | "basket" | "owner"} filter.type - The type of the intent filter ("creator", "host", "manager", "basket", "owner"). * @param {string} filter.pubkey - The public key of the intent filter. * @returns {Promise} The intents. */ async fetchAllIntents(filter?: IntentFilter): Promise { return await fetchIntents(this.sdkParams.connection, filter); } /** * Fetches all intents created by a given public key. * @param {string} creatorPubkey - The public key of the creator. * @returns {Promise} The intents. */ async fetchCreatedIntents(creatorPubkey: string): Promise { return await fetchIntents(this.sdkParams.connection, { type: "manager", pubkey: creatorPubkey }); } /** * Fetches all intents for a given basket. * @param {string} basketPubkey - The public key of the basket. * @returns {Promise} The intents. */ async fetchBasketIntents(basketPubkey: string): Promise { return await fetchIntents(this.sdkParams.connection, { type: "basket", pubkey: basketPubkey }); } /** * Fetches a rebalance intent by its public key. * @param {string} rebalanceIntentPubkey - The public key of the rebalance intent. * @returns {Promise} The rebalance intent. */ async fetchRebalanceIntent(rebalanceIntentPubkey: string): Promise { return await fetchRebalanceIntent(this.sdkParams.connection, new PublicKey(rebalanceIntentPubkey)); } /** * Fetches multiple rebalance intents by their public keys. * @param {string[]} rebalanceIntentPubkeys - The public keys of the rebalance intents. * @returns {Promise>} A map of rebalance intent public keys to rebalance intents. */ async fetchMultipleRebalanceIntents(rebalanceIntentPubkeys: string[]): Promise> { return await fetchRebalanceIntentsMultiple(this.sdkParams.connection, rebalanceIntentPubkeys.map(key => new PublicKey(key))); } /** * Fetches all rebalance intents. * @param {RebalanceIntentFilter} filter - Optional. The filter to apply to the rebalance intents. * @param {"owner" | "basket"} filter.type - The type of the rebalance intent filter ("owner", "basket"). * @param {string} filter.pubkey - The public key of the rebalance intent filter. * @returns {Promise} The rebalance intents. */ async fetchAllRebalanceIntents(filter?: RebalanceIntentFilter): Promise { return await fetchRebalanceIntents(this.sdkParams.connection, filter); } /** * Fetches all rebalance intents for a given owner. * @param {string} ownerPubkey - The public key of the owner. * @returns {Promise} The rebalance intents. */ async fetchOwnerRebalanceIntents(ownerPubkey: string): Promise { return await fetchRebalanceIntents(this.sdkParams.connection, { type: "owner", pubkey: ownerPubkey }); } /** * Fetches all rebalance intents for a given basket. * @param {string} basketPubkey - The public key of the basket. * @returns {Promise} The rebalance intents. */ async fetchBasketRebalanceIntents(basketPubkey: string): Promise { return await fetchRebalanceIntents(this.sdkParams.connection, { type: "basket", pubkey: basketPubkey }); } /** * Fetches a WithdrawBasketFees account by its public key. * @param {string} withdrawBasketFeesPubkey - The public key of the WithdrawBasketFees account. * @returns {Promise} The WithdrawBasketFees account. */ async fetchWithdrawBasketFees(withdrawBasketFeesPubkey: string): Promise { return await fetchWithdrawBasketFees(this.sdkParams.connection, new PublicKey(withdrawBasketFeesPubkey)); } /** * Fetches multiple WithdrawBasketFees accounts by their public keys. * @param {string[]} withdrawBasketFeesPubkeys - The public keys of the WithdrawBasketFees accounts. * @returns {Promise>} A map of WithdrawBasketFees public keys to WithdrawBasketFees accounts. */ async fetchMultipleWithdrawBasketFees(withdrawBasketFeesPubkeys: string[]): Promise> { return await fetchWithdrawBasketFeesMultiple(this.sdkParams.connection, withdrawBasketFeesPubkeys.map(key => new PublicKey(key))); } /** * Fetches all WithdrawBasketFees accounts with optional filter. * @param {WithdrawBasketFeesFilter} filter - Optional. The filter to apply to the WithdrawBasketFees accounts. * @param {"basket" | "manager" | "creator" | "host" | "symmetry"} filter.type - The type of the filter. * @param {string} filter.pubkey - The public key to filter by. * @returns {Promise} The WithdrawBasketFees accounts. */ async fetchAllWithdrawBasketFees(filter?: WithdrawBasketFeesFilter): Promise { return await fetchWithdrawBasketFeesList(this.sdkParams.connection, filter); } /** * Fetches all WithdrawBasketFees accounts for a given basket. * @param {string} basketPubkey - The public key of the basket. * @returns {Promise} The WithdrawBasketFees accounts. */ async fetchBasketWithdrawBasketFees(basketPubkey: string): Promise { return await fetchWithdrawBasketFeesList(this.sdkParams.connection, { type: "basket", pubkey: basketPubkey }); } /** * Fetches all WithdrawBasketFees accounts for a given manager. * @param {string} managerPubkey - The public key of the manager. * @returns {Promise} The WithdrawBasketFees accounts. */ async fetchManagerWithdrawBasketFees(managerPubkey: string): Promise { return await fetchWithdrawBasketFeesList(this.sdkParams.connection, { type: "manager", pubkey: managerPubkey }); } /** * Fetches all WithdrawBasketFees accounts for a given creator. * @param {string} creatorPubkey - The public key of the creator. * @returns {Promise} The WithdrawBasketFees accounts. */ async fetchCreatorWithdrawBasketFees(creatorPubkey: string): Promise { return await fetchWithdrawBasketFeesList(this.sdkParams.connection, { type: "creator", pubkey: creatorPubkey }); } /** * Fetches all WithdrawBasketFees accounts for a given host. * @param {string} hostPubkey - The public key of the host. * @returns {Promise} The WithdrawBasketFees accounts. */ async fetchHostWithdrawBasketFees(hostPubkey: string): Promise { return await fetchWithdrawBasketFeesList(this.sdkParams.connection, { type: "host", pubkey: hostPubkey }); } /** * Fetches all WithdrawBasketFees accounts for a given symmetry fee collector. * @param {string} symmetryPubkey - The public key of the symmetry fee collector. * @returns {Promise} The WithdrawBasketFees accounts. */ async fetchSymmetryWithdrawBasketFees(symmetryPubkey: string): Promise { return await fetchWithdrawBasketFeesList(this.sdkParams.connection, { type: "symmetry", pubkey: symmetryPubkey }); } async createGlobalConfigTx(params: { signer: PublicKey, }): Promise { let ix = createGlobalConfigIx({admin: params.signer}); let txBatchData: TxBatchData = {batches: [[{ payer: params.signer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } async editGlobalConfigTx(params: { signer: PublicKey, config: GlobalConfig, }): Promise { let ix = editGlobalConfigIx(params); let txBatchData: TxBatchData = {batches: [[{ payer: params.signer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Creates a basket. * @param {Object} params - The parameters for the basket creation. * @param {string} params.creator - The public key of the creator. * @param {string} params.start_price - The start price of the basket. * @param {string} params.name - The name of the basket. * @param {string} params.symbol - The symbol of the basket. * @param {string} params.metadata_uri - The metadata URI of the basket. * @param {Object} params.host_platform_params - Optional. The host platform parameters. * @param {string} params.host_platform_params.host_pubkey - The public key of the host. * @param {number} params.host_platform_params.host_deposit_fee_bps - The host deposit fee in basis points. * @param {number} params.host_platform_params.host_withdraw_fee_bps - The host withdrawal fee in basis points. * @param {number} params.host_platform_params.host_management_fee_bps - The host management fee in basis points. * @param {number} params.host_platform_params.host_performance_fee_bps - The host performance fee in basis points. * @returns {Promise} The basket creation transaction. */ async createBasketTx(params: { creator: string, start_price: string, name: string, symbol: string, metadata_uri: string, host_platform_params?: { host_pubkey: string, host_deposit_fee_bps: number, host_withdraw_fee_bps: number, host_management_fee_bps: number, host_performance_fee_bps: number, }, }): Promise { const creator = new PublicKey(params.creator); const host = new PublicKey(params.host_platform_params?.host_pubkey ?? params.creator); const hostFees = { hostDepositFeeBps: params.host_platform_params?.host_deposit_fee_bps ?? 0, hostWithdrawalFeeBps: params.host_platform_params?.host_withdraw_fee_bps ?? 0, hostManagementFeeBps: params.host_platform_params?.host_management_fee_bps ?? 0, hostPerformanceFeeBps: params.host_platform_params?.host_performance_fee_bps ?? 0, }; const metadataParams = { name: params.name, symbol: params.symbol, uri: params.metadata_uri, }; const startPrice = decimalToFraction(new Decimal(parseFloat(params.start_price) / 10 ** 6)); let globalConfig = await this.fetchGlobalConfig(); let bountyWsolAmount = 0; if (globalConfig.bountyMint.equals(MINTS["mainnet"].WSOL)) { let boundBounty = parseInt(globalConfig.bountyBondAmount.toString()); let minBasketAutomationBounty = parseInt(globalConfig.minBountyForBasketAutomation.toString()); bountyWsolAmount = boundBounty + minBasketAutomationBounty; } let wsolIxs = await wrapWsolIxs( this.sdkParams.connection, creator, bountyWsolAmount, ); let basketId = parseInt(globalConfig.totalNumberOfBaskets.toString()); let mint = getBasketTokenMintPda(basketId); let basket = getBasketState(mint); const slot = await this.sdkParams.connection.getSlot(); let ix = createBasketIx({ basket, mint, slot, creator, host, startPrice: startPrice, hostFees, metadataParams, network: this.sdkParams.network, }); let preIx = createBasketStateAccountIx({ creator, basket }); let preIx2 = resizeBasketStateIx({ basket }); let createBasketFeesAccountIx = createAssociatedTokenAccountInstruction( creator, getAta(getBasketFeesPda(basket), mint), getBasketFeesPda(basket), mint, ); let txBatchData: TxBatchData = {batches: [[{ payer: creator, instructions: [ ...wsolIxs, preIx, preIx2, preIx2, ix, createBasketFeesAccountIx, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return { mint: mint.toBase58(), basket: basket.toBase58(), batches: txPayloadBatchSequence.batches, }; } /** * Changes the creator of a basket. * @param {TaskContext} context - The context of the task. * @param {EditCreatorSettings} settings - The settings for the creator edit. * @returns {Promise} The transaction payload batch sequence. */ async editCreatorTx(context: TaskContext, settings: EditCreatorSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditCreator, context, settings }); } /** * Creates an edit manager settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditManagerSettings} settings - The settings for the manager edit. * @returns {Promise} The transaction payload batch sequence. */ async editManagersTx(context: TaskContext, settings: EditManagerSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditManagerSettings, context, settings }); } /** * Creates an edit schedule settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditScheduleSettings} settings - The settings for the schedule edit. * @returns {Promise} The transaction payload batch sequence. */ async editScheduleTx(context: TaskContext, settings: EditScheduleSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditScheduleSettings, context, settings }); } /** * Creates an edit fees settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditFeeSettings} settings - The settings for the fee edit. * @returns {Promise} The transaction payload batch sequence. */ async editFeesTx(context: TaskContext, settings: EditFeeSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditFeeSettings, context, settings }); } /** * Creates an edit automation settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditAutomationSettings} settings - The settings for the automation edit. * @returns {Promise} The transaction payload batch sequence. */ async editAutomationTx(context: TaskContext, settings: EditAutomationSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditAutomationSettings, context, settings }); } /** * Creates an edit LP settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditLpSettings} settings - The settings for the LP edit. * @returns {Promise} The transaction payload batch sequence. */ async editLpTx(context: TaskContext, settings: EditLpSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditLpSettings, context, settings }); } /** * Creates an edit metadata settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditMetadataSettings} settings - The settings for the metadata edit. * @returns {Promise} The transaction payload batch sequence. */ async editMetadataTx(context: TaskContext, settings: EditMetadataSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditMetadataSettings, context, settings }); } /** * Creates an edit deposits settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditDepositsSettings} settings - The settings for the deposits edit. * @returns {Promise} The transaction payload batch sequence. */ async editDepositsTx(context: TaskContext, settings: EditDepositsSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditDepositsSettings, context, settings }); } /** * Creates an edit force rebalance settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditForceRebalanceSettings} settings - The settings for the force rebalance edit. * @returns {Promise} The transaction payload batch sequence. */ async editForceRebalanceTx(context: TaskContext, settings: EditForceRebalanceSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditForceRebalanceSettings, context, settings }); } /** * Creates an edit custom rebalance settings intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditCustomRebalanceSettings} settings - The settings for the custom rebalance edit. * @returns {Promise} The transaction payload batch sequence. */ async editCustomRebalanceTx(context: TaskContext, settings: EditCustomRebalanceSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditCustomRebalanceSettings, context, settings }); } /** * Creates an edit add token delay intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditAddTokenSettings} settings - The settings for the add token delay edit. * @returns {Promise} The transaction payload batch sequence. */ async editAddTokenDelayTx(context: TaskContext, settings: EditAddTokenSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditAddTokenDelay, context, settings }); } /** * Creates an edit update weights delay intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditUpdateWeightsSettings} settings - The settings for the update weights delay edit. * @returns {Promise} The transaction payload batch sequence. */ async editUpdateWeightsDelayTx(context: TaskContext, settings: EditUpdateWeightsSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditUpdateWeightsDelay, context, settings }); } /** * Creates an edit swap delay intent for a basket. * @param {TaskContext} context - The context of the task. * @param {EditMakeDirectSwapSettings} settings - The settings for the make direct swap delay edit. * @returns {Promise} The transaction payload batch sequence. */ async editSwapDelayTx(context: TaskContext, settings: EditMakeDirectSwapSettings): Promise { return this.openBasketIntentTx({ type: TaskType.EditMakeDirectSwapDelay, context, settings }); } /** * Creates an add or edit token intent for a basket. * @param {TaskContext} context - The context of the task. * @param {AddOrEditTokenInput} settings - The settings for the add or edit token. * @returns {Promise} The transaction payload batch sequence. */ async addOrEditTokenTx(context: TaskContext, settings: AddOrEditTokenInput): Promise { return this.openBasketIntentTx({ type: TaskType.AddToken, context, settings }); } /** * Creates a update weights intent for a basket. * @param {TaskContext} context - The context of the task. * @param {UpdateWeightsInput} settings - The settings for the update weights. * @returns {Promise} The transaction payload batch sequence. */ async updateWeightsTx(context: TaskContext, settings: UpdateWeightsInput): Promise { return this.openBasketIntentTx({ type: TaskType.UpdateWeights, context, settings }); } /** * Creates a direct swap intent for a basket. * @param {TaskContext} context - The context of the task. * @param {MakeDirectSwapInput} settings - The settings for the make direct swap. * @param {TransactionInstruction} jup_swap_ix - Optional. The JUP swap instruction. * @returns {Promise} The transaction payload batch sequence. */ async makeDirectSwapTx(context: TaskContext, settings: MakeDirectSwapInput, jup_swap_ix?: TransactionInstruction): Promise { return this.openBasketIntentTx({ type: TaskType.MakeDirectSwap, context, settings, jup_swap_ix: jup_swap_ix }); } /** * Creates basket intent account. * @param {Object} params - The parameters for the basket intent. * @param {TaskContext} params.context - The context of the task. * @param {TaskType} params.type - The type of the task. * @param {Settings} params.settings - The settings for the task. * @param {number} params.min_bounty_per_task - Optional. The minimum bounty per task. * @param {number} params.max_bounty_per_task - Optional. The maximum bounty per task. * @param {TransactionInstruction} params.jup_swap_ix - Optional. The JUP swap instruction (for MakeDirectSwap task). * @returns {Promise} The transaction payload batch sequence. */ async openBasketIntentTx(params: { context: TaskContext, type: TaskType, settings: Settings, jup_swap_ix?: TransactionInstruction, }): Promise { let basket = await this.fetchBasket(params.context.basket); let manager = new PublicKey(params.context.manager); let activationTimestamp = new BN(params.context.activation_timestamp ?? 0); let expirationTimestamp = new BN(params.context.expiration_timestamp ?? 0); let editType = params.type; let editData = params.settings; let modificationDelay = (() => { switch(editType) { case TaskType.EditCreator: return new BN(0); case TaskType.EditManagerSettings: return basket.settings.managers.modificationDelay; case TaskType.EditFeeSettings: return basket.settings.fees.modificationDelay; case TaskType.EditScheduleSettings: return basket.settings.schedule.modificationDelay; case TaskType.EditAutomationSettings: return basket.settings.automation.modificationDelay; case TaskType.EditLpSettings: return basket.settings.lp.modificationDelay; case TaskType.EditMetadataSettings: return basket.settings.metadata.modificationDelay; case TaskType.EditDepositsSettings: return new BN(0); case TaskType.EditForceRebalanceSettings: return basket.settings.forceRebalanceModificationDelay; case TaskType.EditCustomRebalanceSettings: return basket.settings.customRebalanceModificationDelay; case TaskType.EditAddTokenDelay: return basket.settings.addTokenDelay; case TaskType.EditUpdateWeightsDelay: return basket.settings.updateWeightsDelay; case TaskType.EditMakeDirectSwapDelay: return basket.settings.makeDirectSwapDelay; case TaskType.AddToken: return basket.settings.addTokenDelay; case TaskType.UpdateWeights: return basket.settings.updateWeightsDelay; case TaskType.MakeDirectSwap: return basket.settings.makeDirectSwapDelay; default: return new BN(0); } })(); let globalConfig = await this.fetchGlobalConfig(); let minBounty = params.context.min_bounty !== undefined ? new BN(params.context.min_bounty) : globalConfig.bountyPerTask.minBounty; let maxBounty = params.context.max_bounty !== undefined ? new BN(params.context.max_bounty) : globalConfig.bountyPerTask.maxBounty; if (parseInt(minBounty.toString()) > parseInt(maxBounty.toString())) minBounty = maxBounty.div(new BN(2)); let bountyWsolAmount = 0; if (globalConfig.bountyMint.equals(MINTS["mainnet"].WSOL)) { let boundBounty = parseInt(globalConfig.bountyBondAmount.toString()); let maxBountyPerTask = parseInt(maxBounty.toString()); bountyWsolAmount = maxBountyPerTask + boundBounty; } let wsolIxs = await wrapWsolIxs( this.sdkParams.connection, manager, bountyWsolAmount, ); let intentSeedArray = Keypair.generate().publicKey.toBytes(); let tokenProgram: PublicKey | undefined = undefined; if (editType === TaskType.AddToken) { let tokenMint = new PublicKey((editData as AddOrEditTokenInput).token_mint); intentSeedArray = tokenMint.toBytes(); let tokenInfo = await this.sdkParams.connection.getAccountInfo(tokenMint); if (tokenInfo!.owner.equals(TOKEN_PROGRAM_ID) || tokenInfo!.owner.equals(TOKEN_2022_PROGRAM_ID)) { tokenProgram = tokenInfo!.owner; } } let intent = getIntentPda(basket.ownAddress, intentSeedArray, editType); let openBasketIntentIx = createEditBasketIntentIx({ manager: manager, basket: basket, intent: intent, intentSeedArray: intentSeedArray, editType: editType, editData: editData, activationTimestamp: activationTimestamp, expirationTimestamp: expirationTimestamp, minBounty: minBounty, maxBounty: maxBounty, tokenProgram: tokenProgram, }); let txBatchData: TxBatchData = {batches: [[{ payer: manager, instructions: [ ...wsolIxs, openBasketIntentIx, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; // Only execute in the same transaction when there is no delay and no scheduled activation. if (modificationDelay.eq(new BN(0)) && activationTimestamp.eq(new BN(0))) { let executeBasketIntentIx = executeEditBasketIntentIx({ basket: basket.ownAddress, intent: intent, editType: editType, manager: manager, bountyMint: basket.settings.bountyMint, keeper: manager, basketMint: basket.mint, }); if (editType == TaskType.MakeDirectSwap && params.jup_swap_ix) { let flashParams = { keeper: manager, basket: basket.ownAddress, rebalanceIntent: undefined, intent: intent, mintIn: new PublicKey((editData as MakeDirectSwapInput).from_token_mint), mintOut: new PublicKey((editData as MakeDirectSwapInput).to_token_mint), amountIn: new BN((editData as MakeDirectSwapInput).amount_from), amountOut: new BN((editData as MakeDirectSwapInput).amount_to), mode: undefined, } let ixWithdraw = flashWithdrawIx(flashParams); let ixDeposit = flashDepositIx(flashParams); txBatchData.batches.push([{ payer: manager, instructions: [ createAssociatedTokenAccountIdempotentInstruction(manager, getAta(manager, flashParams.mintIn), manager, flashParams.mintIn), createAssociatedTokenAccountIdempotentInstruction(manager, getAta(manager, flashParams.mintOut), manager, flashParams.mintOut), ixWithdraw, params.jup_swap_ix, ixDeposit, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]); txBatchData.batches.push([{ payer: manager, instructions: [ executeBasketIntentIx, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]); } else { txBatchData.batches[0][0].instructions = [ ...wsolIxs, openBasketIntentIx, executeBasketIntentIx, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ]; } } let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Executes a basket intent. Basket intent will be executed immediately. * @param {Object} params - The parameters for the basket intent. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.intent - The public key of the intent. * @returns {Promise} The transaction payload batch sequence. */ async executeBasketIntentTx(params: { keeper: string, intent: string, }): Promise { let keeper = new PublicKey(params.keeper); let intent = await this.fetchIntent(params.intent); let basketMint: PublicKey | undefined; if (intent.taskType === TaskType.EditMetadataSettings) { let basket = await this.fetchBasket(intent.basket.toBase58()); basketMint = basket.mint; } let ix = executeEditBasketIntentIx({ basket: intent.basket, intent: intent.ownAddress!, editType: intent.taskType, manager: intent.manager, bountyMint: intent.bounty.bountyMint, keeper: keeper, basketMint: intent.taskType === TaskType.EditMetadataSettings ? basketMint : undefined, }); let txBatchData: TxBatchData = {batches: [[{ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Cancels a basket intent. Basket intent account will get closed. * @param {Object} params - The parameters for the basket intent. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.intent - The public key of the intent. * @returns {Promise} The transaction payload batch sequence. */ async cancelBasketIntentTx(params: { keeper: string, intent: string, }): Promise { let keeper = new PublicKey(params.keeper); let intent = await this.fetchIntent(params.intent); let ix = cancelIntentIx({ keeper: keeper, basket: intent.basket, intent: intent.ownAddress!, editType: intent.taskType, manager: intent.manager, bountyMint: intent.bounty.bountyMint, }); let txBatchData: TxBatchData = {batches: [[{ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Cancels a rebalance intent. Rebalance intent status will be set to AuctionFinished. * @param {Object} params - The parameters for the rebalance intent. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.rebalance_intent - The public key of the rebalance intent. * @returns {Promise} The transaction payload batch sequence. */ async cancelRebalanceIntentTx(params: { keeper: string, rebalance_intent: string, }): Promise { let keeper = new PublicKey(params.keeper); let intent: RebalanceIntent = (await this.fetchRebalanceIntent(params.rebalance_intent)).chain_data; let ix = cancelRebalanceIx({ keeper: keeper, basket: intent.basket, rebalanceIntent: intent.ownAddress!, }); let txBatchData: TxBatchData = {batches: [[{ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Creates a rebalance intent for a user to deposit tokens into a basket. * * Batch layout: * * batch 0: * * tx0: [create, resize, init rebalance intent], * * batch 1: * * tx0: [deposit tokens ix], * * tx1: [deposit tokens ix], * * ... * * batch 2: * * tx0: [lock deposits ix, start price updates ix], * * @param {Object} params - The parameters for the basket purchase. * @param {string} params.buyer - The public key of the buyer. * @param {string} params.basket_mint - The mint public key of the basket. * @param {{mint: string, amount: number}[]} params.contributions - The contributions to the basket. * @param {string} params.contributions[].mint - The mint public key of the contribution. * @param {number} params.contributions[].amount - The amount of the contribution. * @param {number} params.rebalance_slippage_bps - Optional. The rebalance slippage in basis points. * @param {number} params.per_trade_rebalance_slippage_bps - Optional. The per trade rebalance slippage in basis points. * @param {number} params.execution_start_time - Optional. The execution start time. * @param {number} params.min_bounty_amount - Optional. The minimum bounty amount. * @param {number} params.max_bounty_amount - Optional. The maximum bounty amount. * @returns {Promise} The transaction payload batch sequence. */ async buyBasketTx(params: { buyer: string, basket_mint: string, contributions: {mint: string, amount: number}[], rebalance_slippage_bps?: number, per_trade_rebalance_slippage_bps?: number, execution_start_time?: number, min_bounty_amount?: number, max_bounty_amount?: number, }): Promise { let buyer = new PublicKey(params.buyer); let contributions = params.contributions.map(contribution => ({ mint: new PublicKey(contribution.mint), amount: contribution.amount, })); let rebalanceSlippageBps = params.rebalance_slippage_bps ?? 100; let perTradeRebalanceSlippageBps = params.per_trade_rebalance_slippage_bps ?? 100; let executionStartTime = params.execution_start_time ?? 0; let minBountyAmount = params.min_bounty_amount ?? 0; let maxBountyAmount = params.max_bounty_amount ?? 0; const basket = await this.fetchBasket(getBasketState(new PublicKey(params.basket_mint)).toBase58()); let globalConfig = await this.fetchGlobalConfig(); let bountyWsolAmount = 0; if (globalConfig.bountyMint.equals(MINTS["mainnet"].WSOL)) { let boundBounty = parseInt(globalConfig.bountyBondAmount.toString()); let maxBountyPerTask = parseInt(globalConfig.bountyPerTask.maxBounty.toString()); let bountyPerPriceUpdateTaskDivisor = parseInt(globalConfig.bountyPerPriceUpdateTaskDivisor.toString()); if (maxBountyAmount > maxBountyPerTask) maxBountyPerTask = maxBountyAmount; bountyWsolAmount = computeRebalanceIntentBountyAmount( RebalanceType.Deposit, basket.numTokens, boundBounty, maxBountyPerTask, Math.floor(maxBountyPerTask / bountyPerPriceUpdateTaskDivisor), ); } let wsolContributionAmount = params.contributions.find(contribution => contribution.mint === MINTS["mainnet"].WSOL.toBase58())?.amount ?? 0; let wsolIxs = await wrapWsolIxs( this.sdkParams.connection, buyer, bountyWsolAmount + wsolContributionAmount, ); const rebalanceIntent = getRebalanceIntentPda(basket.ownAddress, buyer); let rentPayer = getRentPayerPda(); let rentPayerAi = await this.sdkParams.connection.getAccountInfo(rentPayer); let rebalanceIntentRentPayer = buyer; // TODO: check actual lamports required for rent if (rentPayerAi?.lamports ?? 0 > 500_000_000) rebalanceIntentRentPayer = rentPayer; let bountyMint = basket.settings.bountyMint; // let lockDepositsIxs: TransactionInstruction[] = [ // lockDepositsIx({ // owner: buyer, // basket: basket.ownAddress, // }) // ]; let txBatchData: TxBatchData = {batches: [ [{ payer: buyer, instructions: [ ...wsolIxs, createRebalanceIntentIx({ signer: buyer, owner: buyer, basket: basket.ownAddress, }), resizeRebalanceIntentIx(rebalanceIntent), initRebalanceIntentIx({ signer: buyer, owner: buyer, basket: basket.ownAddress, basketTokenMint: basket.mint, rebalanceIntentRentPayer: rebalanceIntentRentPayer, bountyMint: bountyMint, rebalanceType: RebalanceType.Deposit, basketRebalanceIntent: undefined, rebalanceSlippageBps: rebalanceSlippageBps, perTradeRebalanceSlippageBps: perTradeRebalanceSlippageBps, executionStartTime: executionStartTime, minBountyAmount: minBountyAmount, maxBountyAmount: maxBountyAmount, }), ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }], depositTokensIx({ owner: buyer, basket: basket.ownAddress, contributions: contributions, }).map(ix => { return { payer: buyer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}) ], lookupTables: [], }; }), // [{ // payer: buyer, // instructions: [ // ...lockDepositsIxs, // ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), // ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), // ], // lookupTables: [], // }] ]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Deposits additional tokens into an existing user deposit rebalance intent. * * This is useful when the user already created a deposit intent (for example via buyBasketTx), * but has not called lockDepositsTx yet and wants to add more contributions. * * @param {Object} params - The parameters for depositing more tokens. * @param {string} params.buyer - The public key of the buyer / deposit owner. * @param {{mint: string, amount: number}[]} params.contributions - Extra token contributions. * @param {string} params.rebalance_intent - Optional. Existing rebalance intent pubkey. * @param {RebalanceIntent} params.rebalance_intent_chain_data - Optional. Existing rebalance intent chain data. * @returns {Promise} The transaction payload batch sequence. */ async depositTokensTx(params: { buyer: string, contributions: {mint: string, amount: number}[], rebalance_intent?: string, rebalance_intent_chain_data?: RebalanceIntent, }): Promise { let buyer = new PublicKey(params.buyer); if (params.contributions.length === 0) { throw new Error("Contributions cannot be empty"); } let contributions = params.contributions.map(contribution => ({ mint: new PublicKey(contribution.mint), amount: contribution.amount, })); let rebalanceIntentState: RebalanceIntent; if (params.rebalance_intent_chain_data) { rebalanceIntentState = params.rebalance_intent_chain_data; } else if (params.rebalance_intent) { rebalanceIntentState = (await this.fetchRebalanceIntent(params.rebalance_intent)).chain_data; } else { throw new Error("Provide one of rebalance_intent_chain_data or rebalance_intent"); } let wsolContributionAmount = params.contributions .filter(contribution => contribution.mint === MINTS["mainnet"].WSOL.toBase58()) .reduce((acc, contribution) => acc + contribution.amount, 0); let wsolIxs = await wrapWsolIxs( this.sdkParams.connection, buyer, wsolContributionAmount, ); let txBatchData: TxBatchData = { batches: [] }; if (wsolIxs.length > 0) { txBatchData.batches.push([{ payer: buyer, instructions: [ ...wsolIxs, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]); } txBatchData.batches.push( depositTokensIx({ owner: buyer, basket: rebalanceIntentState.basket, contributions: contributions, }).map(ix => ({ payer: buyer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })) ); let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Locks deposits for a user and starts auction flow. * @param {Object} params - The parameters for the lock deposits. * @param {string} params.buyer - The public key of the buyer. * @param {string} params.basket_mint - The mint public key of the basket. * @returns {Promise} The transaction payload batch sequence. */ async lockDepositsTx(params: { buyer: string, basket_mint: string, }): Promise { let buyer = new PublicKey(params.buyer); let basket = await this.fetchBasket(getBasketState(new PublicKey(params.basket_mint)).toBase58()); let txBatchData: TxBatchData = {batches: [[{ payer: buyer, instructions: [ lockDepositsIx({ owner: buyer, basket: basket.ownAddress, }), ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Creates a rebalance intent for a user to withdraw tokens from a basket. * @param {{Object}} params - The parameters for the basket sale. * @param {string} params.seller - The public key of the seller. * @param {string} params.basket_mint - The mint public key of the basket. * @param {number} params.withdraw_amount - The amount of the withdrawal. * @param {string[]} params.keep_tokens - The token mints to skip rebalancing (will directly be sent to the user without being rebalanced). * @param {number} params.rebalance_slippage_bps - Optional. The rebalance slippage in basis points. * @param {number} params.per_trade_rebalance_slippage_bps - Optional. The per trade rebalance slippage in basis points. * @param {number} params.execution_start_time - Optional. The execution start time. * @param {number} params.min_bounty_amount - Optional. The minimum bounty amount. * @param {number} params.max_bounty_amount - Optional. The maximum bounty amount. * @returns {Promise} The transaction payload batch sequence. */ async sellBasketTx(params: { seller: string, basket_mint: string, withdraw_amount: number, keep_tokens: string[], rebalance_slippage_bps?: number, per_trade_rebalance_slippage_bps?: number, execution_start_time?: number, min_bounty_amount?: number, max_bounty_amount?: number, }): Promise { let seller = new PublicKey(params.seller); let basketMint = new PublicKey(params.basket_mint); let withdrawAmount = params.withdraw_amount; let keepTokens = params.keep_tokens; let rebalanceSlippageBps = params.rebalance_slippage_bps ?? 100; let perTradeRebalanceSlippageBps = params.per_trade_rebalance_slippage_bps ?? 100; let executionStartTime = params.execution_start_time ?? 0; let minBountyAmount = params.min_bounty_amount ?? 0; let maxBountyAmount = params.max_bounty_amount ?? 0; const basket = await this.fetchBasket(getBasketState(basketMint).toBase58()); let globalConfig = await this.fetchGlobalConfig(); let bountyWsolAmount = 0; if (globalConfig.bountyMint.equals(MINTS["mainnet"].WSOL)) { let boundBounty = parseInt(globalConfig.bountyBondAmount.toString()); let maxBountyPerTask = parseInt(globalConfig.bountyPerTask.maxBounty.toString()); let bountyPerPriceUpdateTaskDivisor = parseInt(globalConfig.bountyPerPriceUpdateTaskDivisor.toString()); if (maxBountyAmount > maxBountyPerTask) maxBountyPerTask = maxBountyAmount; bountyWsolAmount = computeRebalanceIntentBountyAmount( RebalanceType.Deposit, basket.numTokens, boundBounty, maxBountyPerTask, Math.floor(maxBountyPerTask / bountyPerPriceUpdateTaskDivisor), ); } let wsolIxs = await wrapWsolIxs( this.sdkParams.connection, seller, bountyWsolAmount, ); const rebalanceIntent = getRebalanceIntentPda(basket.ownAddress, seller); let rentPayer = getRentPayerPda(); let rentPayerAi = await this.sdkParams.connection.getAccountInfo(rentPayer); let rebalanceIntentRentPayer = seller; // TODO: check actual lamports required for rent if (rentPayerAi?.lamports ?? 0 > 500_000_000) rebalanceIntentRentPayer = rentPayer; let bountyMint = basket.settings.bountyMint; let keepAllTokensFlag = 1; for (let i = 0; i < basket.numTokens; i++) { if (!keepTokens.includes(basket.composition[i].mint.toBase58())) { keepAllTokensFlag = 0; break; } } let tokenMintsHash = computeTokenMintsHash( basket.composition.slice(0, basket.numTokens) .map(asset => asset.mint.toBase58()) ); let keepTokensBitmask = new BN(0); if (keepTokens && keepTokens.length > 0) { for (const mint of keepTokens) { let tokenIndex = -1; for (let i = 0; i < basket.numTokens; i++) { if (basket.composition[i].mint.toBase58() === mint) { tokenIndex = i; break; } } if (tokenIndex === -1) { throw new Error(`Token mint not in basket: ${mint}`); } keepTokensBitmask = keepTokensBitmask.or(new BN(1).shln(tokenIndex)); } } const basketRebalanceIntent = basket.settings.activeRebalance.gt(new BN(0)) ? getRebalanceIntentPda(basket.ownAddress, basket.ownAddress) : undefined; let txBatchData: TxBatchData = {batches: [ [{ payer: seller, instructions: [ ...wsolIxs, createRebalanceIntentIx({ signer: seller, owner: seller, basket: basket.ownAddress, }), resizeRebalanceIntentIx(rebalanceIntent), initRebalanceIntentIx({ signer: seller, owner: seller, basket: basket.ownAddress, basketTokenMint: basketMint, rebalanceIntentRentPayer: rebalanceIntentRentPayer, bountyMint: bountyMint, rebalanceType: RebalanceType.Withdraw, basketRebalanceIntent: basketRebalanceIntent, rebalanceSlippageBps: rebalanceSlippageBps, perTradeRebalanceSlippageBps: perTradeRebalanceSlippageBps, executionStartTime: executionStartTime, minBountyAmount: minBountyAmount, maxBountyAmount: maxBountyAmount, withdrawParamsBurnAmount: withdrawAmount, withdrawParamsTokenMintsHash: tokenMintsHash, withdrawParamsKeepTokensBitmask: keepTokensBitmask, withdrawParamsKeepAllTokens: keepAllTokensFlag, }), ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }], ]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Creates a rebalance intent for a basket rebalance. * @param {Object} params - The parameters for the basket rebalance. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.basket_mint - The basket token mint public key. * @param {number} params.rebalance_slippage_bps - Optional. The rebalance slippage in basis points. * @param {number} params.per_trade_rebalance_slippage_bps - Optional. The per trade rebalance slippage in basis points. * @param {number} params.execution_start_time - Optional. The execution start time. * @param {number} params.min_bounty_amount - Optional. The minimum bounty amount. * @param {number} params.max_bounty_amount - Optional. The maximum bounty amount. * @returns {Promise} The transaction payload batch sequence. */ async rebalanceBasketTx(params: { keeper: string, basket_mint: string, rebalance_slippage_bps?: number, per_trade_rebalance_slippage_bps?: number, execution_start_time?: number, min_bounty_amount?: number, max_bounty_amount?: number, }): Promise { let keeper = new PublicKey(params.keeper); let rebalanceSlippageBps = params.rebalance_slippage_bps ?? 100; let perTradeRebalanceSlippageBps = params.per_trade_rebalance_slippage_bps ?? 100; let executionStartTime = params.execution_start_time ?? 0; let minBountyAmount = params.min_bounty_amount ?? 0; let maxBountyAmount = params.max_bounty_amount ?? 0; const basket = await this.fetchBasket(getBasketState(new PublicKey(params.basket_mint)).toBase58()); let globalConfig = await this.fetchGlobalConfig(); let bountyWsolAmount = 0; if (globalConfig.bountyMint.equals(MINTS["mainnet"].WSOL)) { let boundBounty = parseInt(globalConfig.bountyBondAmount.toString()); let maxBountyPerTask = parseInt(globalConfig.bountyPerTask.maxBounty.toString()); let bountyPerPriceUpdateTaskDivisor = parseInt(globalConfig.bountyPerPriceUpdateTaskDivisor.toString()); if (maxBountyAmount > maxBountyPerTask) maxBountyPerTask = maxBountyAmount; bountyWsolAmount = computeRebalanceIntentBountyAmount( RebalanceType.Basket, basket.numTokens, boundBounty, maxBountyPerTask, Math.floor(maxBountyPerTask / bountyPerPriceUpdateTaskDivisor), ); } let wsolIxs = await wrapWsolIxs( this.sdkParams.connection, keeper, bountyWsolAmount, ); const rebalanceIntent = getRebalanceIntentPda(basket.ownAddress, basket.ownAddress); let rentPayer = getRentPayerPda(); let rentPayerAi = await this.sdkParams.connection.getAccountInfo(rentPayer); let rebalanceIntentRentPayer = keeper; // TODO: check actual lamports required for rent if (rentPayerAi?.lamports ?? 0 > 500_000_000) rebalanceIntentRentPayer = rentPayer; let bountyMint = basket.settings.bountyMint; let txBatchData: TxBatchData = {batches: [[{ payer: keeper, instructions: [ ...wsolIxs, createRebalanceIntentIx({ signer: keeper, owner: basket.ownAddress, basket: basket.ownAddress, }), resizeRebalanceIntentIx(rebalanceIntent), initRebalanceIntentIx({ signer: keeper, owner: basket.ownAddress, basket: basket.ownAddress, basketTokenMint: basket.mint, rebalanceIntentRentPayer: rebalanceIntentRentPayer, bountyMint: bountyMint, rebalanceType: RebalanceType.Basket, basketRebalanceIntent: undefined, rebalanceSlippageBps: rebalanceSlippageBps, perTradeRebalanceSlippageBps: perTradeRebalanceSlippageBps, executionStartTime: executionStartTime, minBountyAmount: minBountyAmount, maxBountyAmount: maxBountyAmount, }), ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Build update token prices transactions. * * For pyth oracle accounts, fetches feed IDs, builds vaa [create init encode], [write verify], * * [update feed], [close vaa] instructions. For all tokens, builds [update token prices] instructions. * * returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * Batch layout: * * batch 0: * * tx0: [vaa0CreateIx, vaa0InitIx, vaa0WriteIx], * * tx1: [vaa1CreateIx, vaa1InitIx, vaa1WriteIx], * * ... * * batch 1: * * tx0: [vaa0WriteVerifyIx, vaa0VerifyIx], * * tx1: [vaa1WriteVerifyIx, vaa1VerifyIx], * * ... * * batch 2: * * tx0: [updateFeed0Ix], * * tx1: [updateFeed1Ix], * * ... * * batch 3: * * tx0: [updateTokenPricesIx(max 10 tokens)], * * tx1: [updateTokenPricesIx(max 10 tokens)], * * ... * * txLast: [closeVaa0Ix, closeVaa1Ix, ...] * * @param {Object} params - The parameters for the update token prices. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.basket - The public key of the basket. * @param {string} params.rebalance_intent - The public key of the rebalance intent. * @returns {Promise} The transaction payload batch sequence. */ async updateTokenPricesTx(params: { keeper: string, basket: string, rebalance_intent: string, }): Promise { let keeper = new PublicKey(params.keeper); let rebalanceIntent = new PublicKey(params.rebalance_intent); let basketRebalanceIntent = getRebalanceIntentPda(new PublicKey(params.basket), new PublicKey(params.basket)); let basket = await this.fetchBasket(params.basket); let pythPriceFeeds: PublicKey[] = []; let updateTokenPricesIxs: TransactionInstruction[] = []; for (let startIndex = 0; startIndex < basket.numTokens; startIndex++) { let allKeys: PublicKey[] = []; let tokenIndices: number[] = []; for (let endIndex = startIndex; endIndex < basket.numTokens; endIndex++) { let numAccounts = basket.composition[endIndex].oracleAggregator.oracles .slice(0, basket.composition[endIndex].oracleAggregator.numOracles) .reduce((acc, oracle) => acc + oracle.oracleSettings.numRequiredAccounts, 0); if (allKeys.length + numAccounts > UPDATE_TOKEN_PRICES_MAX_ACCOUNTS) break; if (tokenIndices.length == 20) break; startIndex = endIndex; tokenIndices.push(endIndex); let agg = basket.composition[endIndex].oracleAggregator; for (let i = 0; i < agg.numOracles; i++) { const oracleData = agg.oracles[i]; for (let j = 0; j < oracleData.oracleSettings.numRequiredAccounts; j++) { const lutId = oracleData.accountsToLoadLutIds[j]; const lutIdx = oracleData.accountsToLoadLutIndices[j]; allKeys.push(basket.lutPubkeys![lutId].state.addresses[lutIdx]); if (oracleData.oracleSettings.oracleType === OracleType.Pyth) pythPriceFeeds.push(basket.lutPubkeys![lutId].state.addresses[lutIdx]); } } } while (tokenIndices.length < 20) tokenIndices.push(0); // TODO: in last instruction we should include performance fee accounts let performanceFees = basket.settings.fees.hostPerformanceFeeBps + basket.settings.fees.creatorPerformanceFeeBps + basket.settings.fees.managersPerformanceFeeBps; updateTokenPricesIxs.push( updateTokenPricesIx({ keeper: keeper, basket: basket.ownAddress, rebalanceIntent: rebalanceIntent, lookupTable0: basket.lookupTables.active[0], lookupTable1: basket.lookupTables.active[1], tokenIndices: tokenIndices, additionalOracleAccounts: allKeys, basketRebalanceIntent: basket.settings.activeRebalance.eq(new BN(0)) ? undefined : basketRebalanceIntent, basketMint: performanceFees > 0 ? basket.mint : undefined, }), ); } const {feedIds }= await fetchFeedIdsFromAccounts(this.sdkParams.connection, pythPriceFeeds); const { vaaCreateInitEncodeIxs, vaaWriteVerifyIxs, updateFeedIxs, closeVaaIxs, } = await buildPythPriceFeedUpdateIxs( keeper, feedIds, ); let txBatchData: TxBatchData = {batches: [ [ ...vaaCreateInitEncodeIxs.map(ixs => ({ payer: keeper, instructions: [ ...ixs.ixs, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), ], [ ...vaaWriteVerifyIxs.map(ixs => ({ payer: keeper, instructions: [ ...ixs, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), ], [ ...updateFeedIxs.map(ix => ({ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), ], [ ...updateTokenPricesIxs.map(ix => ({ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [basket.lookupTables.active[0], basket.lookupTables.active[1]], })), { payer: keeper, instructions: [ ...closeVaaIxs, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], } ], ]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); for (let [txId, vaaCreateInitEncodeIx] of vaaCreateInitEncodeIxs.entries()) { versionedTxs.batches[0][txId].sign([vaaCreateInitEncodeIx.signer]); } let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Build Pyth price feed update transactions from on-chain price account pubkeys. * Fetches feed IDs from accounts, builds update instructions, and returns * TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * Batch layout: * * batch 0: * * tx0: [vaa0CreateIx, vaa0InitIx, vaa0WriteIx], * * tx1: [vaa1CreateIx, vaa1InitIx, vaa1WriteIx], * * ... * * batch 1: * * tx0: [vaa0WriteVerifyIx, vaa0VerifyIx], * * tx1: [vaa1WriteVerifyIx, vaa1VerifyIx], * * ... * * batch 2: * * tx0: [updateFeed0Ix], * * tx1: [updateFeed1Ix], * * ... * * batch 3: * * txLast: [closeVaa0Ix, closeVaa1Ix, ...] * */ async updatePythPriceFeedsTx(params: { keeper: string, accounts: string[], }): Promise { const payer = new PublicKey(params.keeper); const priceAccounts = params.accounts.map(a => new PublicKey(a)); const { feedIds } = await fetchFeedIdsFromAccounts(this.sdkParams.connection, priceAccounts); const { vaaCreateInitEncodeIxs, vaaWriteVerifyIxs, updateFeedIxs, closeVaaIxs, } = await buildPythPriceFeedUpdateIxs( payer, feedIds, ); let txBatchData: TxBatchData = {batches: [ [ ...vaaCreateInitEncodeIxs.map(ixs => ({ payer: payer, instructions: [ ...ixs.ixs, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), ], [ ...vaaWriteVerifyIxs.map(ixs => ({ payer: payer, instructions: [ ...ixs, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), ], [ ...updateFeedIxs.map(ix => ({ payer: payer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), ], [ { payer: payer, instructions: [ ...closeVaaIxs, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], } ], ]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); for (let [txId, vaaCreateInitEncodeIx] of vaaCreateInitEncodeIxs.entries()) { versionedTxs.batches[0][txId].sign([vaaCreateInitEncodeIx.signer]); } let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Build flash swap transactions (Rebalance or MakeDirectSwap). * * Withdraws amount_out tokens(mint_out) from basket, deposits amount_in tokens(mint_in) to basket. * * If jup_swap_ix is provided, uses it to swap amount_out tokens(mint_out) for amount_in tokens(mint_in). * * returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * Batch layout: batch 0: tx: 0: * * create associated token account_mint_out * * create associated token account_mint_in * * jup_token_ledger_ix * * flash withdraw * * jup_swap_ix * * flash deposit * * set compute unit limit * * set compute unit price * * @param {Object} params - The parameters for the flash swap. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.basket - The public key of the basket. * @param {string} params.rebalance_intent - The public key of the rebalance intent (for Rebalance). * @param {string} params.intent - The public key of the intent (for MakeDirectSwap). * @param {string} params.mint_in - The mint of the input token (deposited to basket). * @param {string} params.mint_out - The mint of the output token (withdrawn from basket). * @param {number} params.amount_in - The amount of the input token (deposited to basket). * @param {number} params.amount_out - The amount of the output token (withdrawn from basket). * @param {"exact_in" | "exact_out" | "ioc"} params.mode - The mode of the flash swap. * @param {TransactionInstruction} params.jup_token_ledger_ix - The jup token ledger ix. * @param {TransactionInstruction} params.jup_swap_ix - The jup swap ix. * @param {PublicKey[]} params.jup_address_lookup_table_addresses - The jup address lookup table addresses. * @returns {Promise} The transaction payload batch sequence. */ async flashSwapTx(params: { keeper: string, basket: string, rebalance_intent?: string, intent?: string, mint_in: string, mint_out: string, amount_in: number, amount_out: number, mode?: number, jup_token_ledger_ix?: TransactionInstruction, jup_swap_ix?: TransactionInstruction, jup_address_lookup_table_addresses?: PublicKey[], }): Promise { let keeper = new PublicKey(params.keeper); let basket = new PublicKey(params.basket); let rebalanceIntent = params.rebalance_intent ? new PublicKey(params.rebalance_intent) : undefined; let intent = params.intent ? new PublicKey(params.intent) : undefined; let mintIn = new PublicKey(params.mint_in); let mintOut = new PublicKey(params.mint_out); let amountIn = new BN(params.amount_in); let amountOut = new BN(params.amount_out); let mode = params.mode ? params.mode : 0; let flashParams = { keeper: keeper, basket: basket, rebalanceIntent: rebalanceIntent, intent: intent, mintIn: mintIn, mintOut: mintOut, amountIn: amountIn, amountOut: amountOut, mode: mode, }; let ixWithdraw = flashWithdrawIx(flashParams); let ixDeposit = flashDepositIx(flashParams); let ixs: TransactionInstruction[] = []; ixs = ixs.concat([ createAssociatedTokenAccountIdempotentInstruction(keeper, getAta(keeper, mintIn), keeper, mintIn), createAssociatedTokenAccountIdempotentInstruction(keeper, getAta(keeper, mintOut), keeper, mintOut), ]); if (params.jup_token_ledger_ix) { ixs.push(params.jup_token_ledger_ix); } ixs.push(ixWithdraw); if (params.jup_swap_ix) { ixs.push(params.jup_swap_ix); } ixs.push(ixDeposit); ixs = ixs.concat([ ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ]); let txBatchData: TxBatchData = {batches: [[{ payer: keeper, instructions: ixs, lookupTables: params.jup_address_lookup_table_addresses ?? [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Mints basket tokens for a user. Executable when the rebalance intent has finished its auctions. * * Returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * @param {Object} params - The parameters for the mint. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.rebalance_intent - The public key of the rebalance intent. * @returns {Promise} The transaction payload batch sequence. */ async mintTx(params: { keeper: string, rebalance_intent: string, }): Promise { let keeper = new PublicKey(params.keeper); let rebalanceIntent: RebalanceIntent = (await this.fetchRebalanceIntent(params.rebalance_intent)).chain_data; let basket = await this.fetchBasket(rebalanceIntent.basket.toBase58()); let basketRebalanceIntent = getRebalanceIntentPda(basket.ownAddress, rebalanceIntent.owner); let ix = mintBasketIx({ keeper: keeper, basket: basket.ownAddress, basketTokenMint: basket.mint, buyer: rebalanceIntent.owner, basketRebalanceIntent: basket.settings.activeRebalance.eq(new BN(0)) ? undefined : basketRebalanceIntent, }); let txBatchData: TxBatchData = {batches: [[{ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Redeems tokens from the rebalance intent with Withdraw status. * * Returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * @param {Object} params - The parameters for the redeem tokens. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.rebalance_intent - The public key of the rebalance intent. * @returns {Promise} The transaction payload batch sequence. */ async redeemTokensTx(params: { keeper: string, rebalance_intent: string, }): Promise { let keeper = new PublicKey(params.keeper); let rebalanceIntent: RebalanceIntent = (await this.fetchRebalanceIntent(params.rebalance_intent)).chain_data; let batchSize = 7; let ixs: TransactionInstruction[] = []; for (let batchStart = 0; batchStart < rebalanceIntent.tokens.length; batchStart += batchSize) { let tokenMints: PublicKey[] = []; for (let i = batchStart; i < rebalanceIntent.tokens.length && i < batchStart + batchSize; i++) if (rebalanceIntent.tokens[i].amount.gt(new BN(0))) tokenMints.push(rebalanceIntent.tokens[i].mint); if (tokenMints.length > 0) { ixs.push( redeemTokensIx({ keeper: keeper, basket: rebalanceIntent.basket, owner: rebalanceIntent.owner, tokenMints: tokenMints, }) ); } } let txBatchData: TxBatchData = {batches: [ixs.map(ix => ({ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }))]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Claims the bounty to keepers who have completed tasks for rebalance intent. * * Executebale when the rebalance intent has finished its auctions. * * For Withdraw status rebalance intent, tokens have to be redeemed first. * * Closes the rebalance intent if all keepers have claimed the bounty. * * @param {Object} params - The parameters for the claim bounty. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.rebalance_intent - The public key of the rebalance intent. * @returns {Promise} The transaction payload batch sequence. */ async claimBountyTx(params: { keeper: string, rebalance_intent: string, }): Promise { let keeper = new PublicKey(params.keeper); let rebalanceIntent: RebalanceIntent = (await this.fetchRebalanceIntent(params.rebalance_intent)).chain_data; let keepersArray: string[] = []; if (!rebalanceIntent.auctionCreationTask .completedBy.equals(PublicKey.default)) keepersArray.push(rebalanceIntent.auctionCreationTask.completedBy.toBase58()); if (!rebalanceIntent.cancelRebalanceTask.completedBy.equals(PublicKey.default)) keepersArray.push(rebalanceIntent.cancelRebalanceTask.completedBy.toBase58()); if (!rebalanceIntent.placeholderTask.completedBy.equals(PublicKey.default)) keepersArray.push(rebalanceIntent.placeholderTask.completedBy.toBase58()); if (!rebalanceIntent.finishPriceUpdateTask.completedBy.equals(PublicKey.default)) keepersArray.push(rebalanceIntent.finishPriceUpdateTask.completedBy.toBase58()); if (!rebalanceIntent.mintBasketTask.completedBy.equals(PublicKey.default)) keepersArray.push(rebalanceIntent.mintBasketTask.completedBy.toBase58()); for (let i = 0; i < rebalanceIntent.priceUpdateTasks.length; i++) if (!rebalanceIntent.priceUpdateTasks[i].completedBy.equals(PublicKey.default)) keepersArray.push(rebalanceIntent.priceUpdateTasks[i].completedBy.toBase58()); for (let i = 0; i < rebalanceIntent.tokenSettlementTasks.length; i++) if (!rebalanceIntent.tokenSettlementTasks[i].completedBy.equals(PublicKey.default)) keepersArray.push(rebalanceIntent.tokenSettlementTasks[i].completedBy.toBase58()); keepersArray = Array.from(new Set(keepersArray)); let batchSize = 10; let ixs: TransactionInstruction[] = []; let wsolIxs = await wrapWsolIxs(this.sdkParams.connection, keeper, 0); ixs.push(...wsolIxs); for (let batchStart = 0; batchStart < keepersArray.length; batchStart += batchSize) ixs.push(claimBountyIx({ keeper: keeper, basket: rebalanceIntent.basket, //@ts-ignore intent: rebalanceIntent.ownAddress, bountyMint: rebalanceIntent.bounty.bountyMint, bountyDepositor: rebalanceIntent.bounty.bountyDepositor, rentPayer: rebalanceIntent.rentPayer, keepers: keepersArray.map(keeper => new PublicKey(keeper)).slice(batchStart, batchStart + batchSize), }) ); let txBatchData: TxBatchData = {batches: [ixs.map(ix => ({ payer: keeper, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }))]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Adds bounty for automation to basket. * Returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * @param {Object} params - The parameters for the add bounty. * @param {string} params.keeper - The public key of the keeper. * @param {string} params.basket - The public key of the basket. * @param {number} params.amount - The amount of the bounty. * @returns {Promise} The transaction payload batch sequence. */ async addBountyTx(params: { keeper: string, basket: string, amount: number, }): Promise { let keeper = new PublicKey(params.keeper); let basket = await this.fetchBasket(params.basket); let globalConfig = await this.fetchGlobalConfig(); let bountyWsolAmount = 0; if (globalConfig.bountyMint.equals(MINTS["mainnet"].WSOL)) { bountyWsolAmount = params.amount; } let wsolIxs = await wrapWsolIxs( this.sdkParams.connection, keeper, bountyWsolAmount, ); let ix = addBountyIx({ keeper: keeper, basket: basket.ownAddress, bountyMint: basket.settings.bountyMint, amount: params.amount, }); let txBatchData: TxBatchData = {batches: [[{ payer: keeper, instructions: [ ...wsolIxs, ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], }]]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Withdraws fees from the basket, initializes WithdrawBasketFees accounts, and claims the fee tokens * for all fee types that the claimer is authorized to claim (symmetry, creator, host, or manager fees). * * This function automatically determines which fee types the claimer can claim based on: * - Symmetry fees: if claimer equals globalConfig.symmetryFeeCollector * - Creator fees: if claimer equals basket.settings.creator * - Host fees: if claimer equals basket.settings.host * - Manager fees: if claimer is in basket.settings.managers.managers * * Returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * Batch layout: * - batch 0: One transaction per claimable fee type, each containing a withdrawFeesIx instruction. * All transactions in this batch can run in parallel. These create and populate the WithdrawBasketFees accounts. * - batch 1: Multiple transactions for claiming fee tokens, automatically batched based on account limits. * All transactions in this batch can run in parallel. These transfer tokens from WithdrawBasketFees accounts to claimers. * * Note: If not all tokens are claimed in batch 1 (e.g., due to account limits or errors), * use claimTokenFeesFromBasketTx to claim the remaining tokens. * * @param {Object} params - The parameters for withdrawing and claiming basket fees. * @param {string} params.claimer - The public key of the claimer. * @param {string} params.basket - The public key of the basket. * @returns {Promise} The transaction payload batch sequence. */ async withdrawBasketFeesTx(params: { claimer: string, basket: string, }): Promise { let claimer = new PublicKey(params.claimer); let basket = await this.fetchBasket(params.basket); let globalConfig = await this.fetchGlobalConfig(); let claimableFeeTypes: number[] = []; let owners: PublicKey[][] = []; if (claimer.equals(globalConfig.symmetryFeeCollector)) { claimableFeeTypes.push(0); owners.push([globalConfig.symmetryFeeCollector]); } if (claimer.equals(basket.settings.creator)) { claimableFeeTypes.push(1); owners.push([basket.settings.creator]); } if (claimer.equals(basket.settings.host)) { claimableFeeTypes.push(2); owners.push([basket.settings.host]); } if (basket.settings.managers.managers.find(manager => manager.equals(claimer))) { claimableFeeTypes.push(3); owners.push(basket.settings.managers.managers); } let withdrawFeesIxs: TransactionInstruction[] = []; let claimTokenFeesFromBasketIxs: TransactionInstruction[] = []; claimableFeeTypes.forEach((feeType, index) => { withdrawFeesIxs.push( withdrawFeesIx({ claimer, basketTokenMint: basket.mint, feeType: feeType, }) ); claimTokenFeesFromBasketIxs.push( ...claimFeeTokensFromBasketIxs( claimer, basket.ownAddress, getWithdrawBasketFeesPda(basket.ownAddress, feeType), claimer, owners[index], basket.composition.map(composition => composition.mint), ) ); }); let txBatchData: TxBatchData = {batches: [ withdrawFeesIxs.map(ix => ({ payer: claimer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), claimTokenFeesFromBasketIxs.map(ix => ({ payer: claimer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })), ]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Claims fee tokens from a WithdrawBasketFees account. * * This function transfers accumulated fee tokens from the WithdrawBasketFees account * to the claimers (owners) based on their weights. The tokens are automatically batched * into multiple transactions if needed to respect Solana's account limit (30 accounts per transaction). * * Returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * @param {Object} params - The parameters for claiming basket fees. * @param {string} params.claimer - The public key of the claimer. * @param {string} params.basket - The public key of the basket. * @returns {Promise} The transaction payload batch sequence. */ async claimTokenFeesFromBasketTx(params: { claimer: string, withdrawBasketFees: string, }): Promise { let claimer = new PublicKey(params.claimer); let withdrawBasketFees = await this.fetchWithdrawBasketFees(params.withdrawBasketFees); let claimTokenFeesFromBasketIxs = claimFeeTokensFromBasketIxs( claimer, withdrawBasketFees.basket, withdrawBasketFees.ownAddress!, withdrawBasketFees.rentPayer, withdrawBasketFees.owners, withdrawBasketFees.accumulatedTokens, ); // Create batches: all claim instructions can run in parallel let txBatchData: TxBatchData = {batches: [ claimTokenFeesFromBasketIxs.map(ix => ({ payer: claimer, instructions: [ ix, ComputeBudgetProgram.setComputeUnitLimit({units: COMPUTE_UNITS}), ComputeBudgetProgram.setComputeUnitPrice({microLamports: this.sdkParams.priorityFee}), ], lookupTables: [], })) ]}; let versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); let txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } /** * Rewrites lookup tables for the basket. (Used when the basket lookup tables are full) * * Returns TxPayloadBatchSequence ready for signAndSendTxPayloadBatchSequence. * * Batch layout: * * batch 0: * * tx0: [create temp lookup tables], * * batch 1: * * tx0: [extend temp lookup tables (with oracles of active tokens)], * * tx1: [extend temp lookup tables (with oracles of active tokens)], * * ... * * batch 2: * * tx0: [overwrite lookup tables with temp, deactivate old lookup tables], * * @param {Object} params - The parameters for the rewrite lookup tables. * @param {string} params.signer - The public key of the signer. * @param {string} params.basket_mint - The mint of the basket. * @param {string[]} params.additional_accounts - The additional accounts. * @returns {Promise} The transaction payload batch sequence. */ async rewriteLookupTablesTx(params: { signer: string, basket_mint: string, additional_accounts: string[], }): Promise { const signer = new PublicKey(params.signer); const basketMint = new PublicKey(params.basket_mint); const additionalAccounts = params.additional_accounts.map(a => new PublicKey(a)); const basketAddress = getBasketState(basketMint); const basket = await this.fetchBasket(basketAddress.toBase58()); const activeLut0 = basket.lookupTables.active[0]; const activeLut1 = basket.lookupTables.active[1]; let oldTempLut0 = basket.lookupTables.temp[0]; let oldTempLut1 = basket.lookupTables.temp[1]; const slot = await this.sdkParams.connection.getSlot("finalized"); const newLut0 = getLookupTableAccount(basketAddress, slot); const newLut1 = getLookupTableAccount(basketAddress, slot - 1); // Step 1: create lookup tables const createIx = createBasketLookupTablesInstruction({ signer, basket: basketAddress, oldTempLookupTable0: oldTempLut0, oldTempLookupTable1: oldTempLut1, newTempLookupTable0: newLut0, newTempLookupTable1: newLut1, slot, }); const txBatchData: TxBatchData = { batches: [ [{ payer: signer, instructions: [ createIx, ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.sdkParams.priorityFee }), ], lookupTables: [], }], ]}; // Step 2: extend LUTs in batches of ~20 const BATCH_SIZE = LUT_EXTEND_BATCH_SIZE; for (let i = 0; i < additionalAccounts.length; i += BATCH_SIZE) { const batch = additionalAccounts.slice(i, i + BATCH_SIZE); const extendIx = extendBasketLookupTablesIx({ signer, basket: basketAddress, tempLookupTable0: newLut0, tempLookupTable1: newLut1, additionalAccounts: batch, }); txBatchData.batches.push([{ payer: signer, instructions: [ extendIx, ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.sdkParams.priorityFee }), ], lookupTables: [], }]); } // Step 3: overwrite active LUTs const overwriteIx = overwriteBasketLookupTablesIx({ signer, basket: basketAddress, tempLookupTable0: newLut0, tempLookupTable1: newLut1, activeLookupTable0: activeLut0, activeLookupTable1: activeLut1, }); txBatchData.batches.push([{ payer: signer, instructions: [ overwriteIx, ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: this.sdkParams.priorityFee }), ], lookupTables: [], }]); const versionedTxs = await prepareVersionedTxs(this.sdkParams.connection, txBatchData); const txPayloadBatchSequence = prepareTxPayloadBatchSequence(txBatchData, versionedTxs); return txPayloadBatchSequence; } async signAndSendVersionedTxs(params: { versionedTxs: VersionedTxs, wallet: Wallet, }): Promise { let { versionedTxs, wallet } = params; versionedTxs = await signVersionedTxs(wallet, versionedTxs); return await sendVersionedTxs(this.sdkParams.connection, versionedTxs, false); } async signAndSendTxPayloadBatchSequence(params: { txPayloadBatchSequence: TxPayloadBatchSequence, wallet: Wallet, simulateTransactions?: boolean, }): Promise { let { txPayloadBatchSequence, wallet, simulateTransactions = false } = params; txPayloadBatchSequence = await signTxPayloadBatchSequence(wallet, txPayloadBatchSequence); return await sendTxPayloadBatchSequence(this.sdkParams.connection, txPayloadBatchSequence, simulateTransactions); } } export { BasketCreationTx, TxPayloadBatchSequence, VersionedTxs, } export { GlobalConfig, Basket, BasketFilter, Intent, IntentFilter, RebalanceIntent, RebalanceIntentFilter } export { FormattedGlobalConfig, FormattedIntentStatus, FormattedTaskType, FormattedBounty, FormattedBountySchedule, FormattedIntent, } export { FormattedRebalanceType, FormattedRebalanceAction, FormattedOraclePrice, FormattedTokenAuction, FormattedTaskCompletion, FormattedRebalanceIntent, UIRebalanceIntent, DepositData, PriceUpdatesData, AuctionData, MintData, RedeemData, ClaimBountyData, } export { FormattedCreatorSettings, FormattedManagersSettings, FormattedFeeSettings, FormattedScheduleSettings, FormattedAutomationSettings, FormattedLpSettings, FormattedMetadataSettings, FormattedDepositsSettings, FormattedForceRebalanceSettings, FormattedCustomRebalanceSettings, FormattedAddTokenSettings, FormattedUpdateWeightsSettings, FormattedMakeDirectSwapSettings, FormattedAccumulatedFees, FormattedLookupTables, FormattedAsset, FormattedOracleSettings, FormattedOracleAggregator, FormattedOracle, FormattedOracleType, FormattedBasket, } export { EditCreatorSettings, EditManagerSettings, EditFeeSettings, EditScheduleSettings, EditAutomationSettings, EditLpSettings, EditMetadataSettings, EditDepositsSettings, EditForceRebalanceSettings, EditCustomRebalanceSettings, EditAddTokenSettings, EditUpdateWeightsSettings, EditMakeDirectSwapSettings, AddOrEditTokenInput, OracleInput, UpdateWeightsInput, MakeDirectSwapInput, Settings, TaskContext, TaskType, } export { getJupTokenLedgerAndSwapInstructions, } export { KeeperMonitor, RebalanceHandler, } from './keeperMonitor';