import { Connection, PublicKey, GetProgramAccountsFilter, GetProgramAccountsResponse } from '@solana/web3.js'; import { BASKETS_V3_PROGRAM_ID, MAX_MANAGERS_PER_BASKET } from '../../constants'; import { AddOrEditTokenInput, EditAddTokenSettings, EditAutomationSettings, EditCreatorSettings, EditCustomRebalanceSettings, EditDepositsSettings, EditFeeSettings, EditForceRebalanceSettings, EditLpSettings, EditMakeDirectSwapSettings, EditManagerSettings, EditMetadataSettings, EditScheduleSettings, EditUpdateWeightsSettings, FormattedIntent, Intent, INTENT_STATUS_STRINGS, IntentLayout, IntentStatus, MakeDirectSwapInput, Settings, TASK_TYPE_STRINGS, TaskType, taskTypeFromU8, UpdateWeightsInput } from '../../layouts/intents/intent'; import { getMultipleAccountsInfoBatched } from '../../txUtils'; import { AuthorityBitmasks, AuthorityBitmasksLayout, AutomationSettings, AutomationSettingsLayout, EditAddTokenDelay, EditAddTokenDelayLayout, EditCreator, EditCreatorLayout, EditMakeDirectSwapDelay, EditMakeDirectSwapDelayLayout, EditUpdateWeightsDelay, EditUpdateWeightsDelayLayout, FeeSettings, FeeSettingsLayout, LpSettings, LpSettingsLayout, MakeDirectSwap, MakeDirectSwapLayout, ManagerSettings, ManagerSettingsLayout, MetadataSettings, MetadataSettingsLayout, ScheduleSettings, ScheduleSettingsLayout, UpdateWeights, UpdateWeightsLayout } from '../../layouts/config'; import BN from 'bn.js'; import { ORACLE_TYPES_STRINGS, OracleAggregator, OracleAggregatorLayout } from '../../layouts/oracle'; import { fractionToDecimal } from '../../layouts/fraction'; function decodeTaskDataForType(intent: Intent): Settings { let taskType = taskTypeFromU8(intent.taskType); let taskData = intent.taskData; switch (taskType) { case TaskType.EditCreator: let editCreator: EditCreator = EditCreatorLayout.decode(Buffer.from(taskData.slice(0, EditCreatorLayout.getSpan()))); let creator_settings: EditCreatorSettings = { creator: editCreator.creator.toBase58(), } return creator_settings; case TaskType.EditManagerSettings: let managerSettings: ManagerSettings = ManagerSettingsLayout.decode(Buffer.from(taskData.slice(0, ManagerSettingsLayout.getSpan()))); let authorityBitmasks: AuthorityBitmasks = AuthorityBitmasksLayout.decode(Buffer.from(taskData.slice(ManagerSettingsLayout.getSpan(), ManagerSettingsLayout.getSpan() + AuthorityBitmasksLayout.getSpan()))); let totalManagers = MAX_MANAGERS_PER_BASKET; while (totalManagers > 0 && managerSettings.managers[totalManagers - 1].equals(PublicKey.default)) { totalManagers--; } managerSettings.managers = managerSettings.managers.slice(0, totalManagers); managerSettings.managersWeightBps = managerSettings.managersWeightBps.slice(0, totalManagers); let manager_settings: EditManagerSettings = { managers: managerSettings.managers.map((manager, i) => ({ pubkey: manager.toBase58(), fee_split_weight_bps: managerSettings.managersWeightBps[i], authorities: { managers: (((2 ** i) & authorityBitmasks.managersAuthorityBitmask) != 0), fees: (((2 ** i) & authorityBitmasks.feesAuthorityBitmask) != 0), schedule: (((2 ** i) & authorityBitmasks.scheduleAuthorityBitmask) != 0), automation: (((2 ** i) & authorityBitmasks.automationAuthorityBitmask) != 0), lp: (((2 ** i) & authorityBitmasks.lpAuthorityBitmask) != 0), metadata: (((2 ** i) & authorityBitmasks.metadataAuthorityBitmask) != 0), deposits: (((2 ** i) & authorityBitmasks.depositsAuthorityBitmask) != 0), force_rebalance: (((2 ** i) & authorityBitmasks.forceRebalanceAuthorityBitmask) != 0), custom_rebalance: (((2 ** i) & authorityBitmasks.customRebalanceAuthorityBitmask) != 0), add_token: (((2 ** i) & authorityBitmasks.addTokenIntentAuthorityBitmask) != 0), update_weights: (((2 ** i) & authorityBitmasks.updateWeightsIntentAuthorityBitmask) != 0), make_direct_swap: (((2 ** i) & authorityBitmasks.makeDirectSwapIntentAuthorityBitmask) != 0), }, })), modification_delay: parseInt(managerSettings.modificationDelay.toString()), } return manager_settings; case TaskType.EditFeeSettings: let editFeeSettings: FeeSettings = FeeSettingsLayout.decode(Buffer.from(taskData.slice(0, FeeSettingsLayout.getSpan()))); let fee_settings: EditFeeSettings = { creator_deposit_fee_bps: editFeeSettings.creatorDepositFeeBps, creator_withdraw_fee_bps: editFeeSettings.creatorWithdrawFeeBps, creator_management_fee_bps: editFeeSettings.creatorManagementFeeBps, creator_performance_fee_bps: editFeeSettings.creatorPerformanceFeeBps, managers_deposit_fee_bps: editFeeSettings.managersDepositFeeBps, managers_withdraw_fee_bps: editFeeSettings.managersWithdrawFeeBps, managers_management_fee_bps: editFeeSettings.managersManagementFeeBps, managers_performance_fee_bps: editFeeSettings.managersPerformanceFeeBps, basket_deposit_fee_bps: editFeeSettings.basketDepositFeeBps, basket_withdraw_fee_bps: editFeeSettings.basketWithdrawFeeBps, modification_delay: parseInt(editFeeSettings.modificationDelay.toString()), } return fee_settings; case TaskType.EditScheduleSettings: let editScheduleSettings: ScheduleSettings = ScheduleSettingsLayout.decode(Buffer.from(taskData.slice(0, ScheduleSettingsLayout.getSpan()))); let schedule_settings: EditScheduleSettings = { cycle_start_time: parseInt(editScheduleSettings.cycleStartTime.toString()), cycle_duration: parseInt(editScheduleSettings.cycleDuration.toString()), deposits_start: parseInt(editScheduleSettings.depositsStart.toString()), deposits_end: parseInt(editScheduleSettings.depositsEnd.toString()), automation_start: parseInt(editScheduleSettings.automationStart.toString()), automation_end: parseInt(editScheduleSettings.automationEnd.toString()), management_start: parseInt(editScheduleSettings.managementStart.toString()), management_end: parseInt(editScheduleSettings.managementEnd.toString()), modification_delay: parseInt(editScheduleSettings.modificationDelay.toString()), } return schedule_settings; case TaskType.EditAutomationSettings: let editAutomationSettings: AutomationSettings = AutomationSettingsLayout.decode(Buffer.from(taskData.slice(0, AutomationSettingsLayout.getSpan()))); let automation_settings: EditAutomationSettings = { enabled: editAutomationSettings.allowAutomation == 1 ? true : false, rebalance_slippage_threshold_bps: editAutomationSettings.rebalanceSlippageThresholdBps, per_trade_rebalance_slippage_threshold_bps: editAutomationSettings.perTradeRebalanceSlippageThresholdBps, rebalance_activation_threshold_abs_bps: editAutomationSettings.rebalanceActivationThresholdAbsBps, rebalance_activation_threshold_rel_bps: editAutomationSettings.rebalanceActivationThresholdRelBps, rebalance_activation_cooldown: parseInt(editAutomationSettings.rebalanceActivationCooldown.toString()), modification_delay: parseInt(editAutomationSettings.modificationDelay.toString()), } return automation_settings; case TaskType.EditLpSettings: let editLpSettings: LpSettings = LpSettingsLayout.decode(Buffer.from(taskData.slice(0, LpSettingsLayout.getSpan()))); let lp_settings: EditLpSettings = { enabled: editLpSettings.allowLp == 1 ? true : false, lp_threshold_bps: editLpSettings.lpThresholdBps, modification_delay: parseInt(editLpSettings.modificationDelay.toString()), } return lp_settings; case TaskType.EditMetadataSettings: let editMetadataSettings: MetadataSettings = MetadataSettingsLayout.decode(Buffer.from(taskData.slice(0, MetadataSettingsLayout.getSpan()))); let metadata = { symbol: Buffer.from(editMetadataSettings.symbol.slice(0, editMetadataSettings.symbolLength)).toString(), name: Buffer.from(editMetadataSettings.name.slice(0, editMetadataSettings.nameLength)).toString(), uri: Buffer.from(editMetadataSettings.uri.slice(0, editMetadataSettings.uriLength)).toString() } let metadata_settings: EditMetadataSettings = { symbol: metadata.symbol, name: metadata.name, uri: metadata.uri, modification_delay: parseInt(editMetadataSettings.modificationDelay.toString()), } return metadata_settings; case TaskType.EditDepositsSettings: let deposits_settings: EditDepositsSettings = { enabled: taskData[0] == 1 ? true : false, } return deposits_settings; case TaskType.EditForceRebalanceSettings: let force_rebalance_settings: EditForceRebalanceSettings = { enabled: taskData[0] == 1 ? true : false, modification_delay: parseInt(new BN(Buffer.from(taskData.slice(1, 9)), "le").toString()), } return force_rebalance_settings; case TaskType.EditCustomRebalanceSettings: let custom_rebalance_settings: EditCustomRebalanceSettings = { enabled: taskData[0] == 1 ? true : false, modification_delay: parseInt(new BN(Buffer.from(taskData.slice(1, 9)), "le").toString()), } return custom_rebalance_settings; case TaskType.EditAddTokenDelay: let editAddTokenDelay: EditAddTokenDelay = EditAddTokenDelayLayout.decode(Buffer.from(taskData.slice(0, EditAddTokenDelayLayout.getSpan()))); let add_token_settings: EditAddTokenSettings = { modification_delay: parseInt(editAddTokenDelay.modificationDelay.toString()), } return add_token_settings; case TaskType.EditUpdateWeightsDelay: let editUpdateWeightsDelay: EditUpdateWeightsDelay = EditUpdateWeightsDelayLayout.decode(Buffer.from(taskData.slice(0, EditUpdateWeightsDelayLayout.getSpan()))); let update_weights_settings: EditUpdateWeightsSettings = { modification_delay: parseInt(editUpdateWeightsDelay.modificationDelay.toString()), } return update_weights_settings; case TaskType.EditMakeDirectSwapDelay: let editMakeDirectSwapDelay: EditMakeDirectSwapDelay = EditMakeDirectSwapDelayLayout.decode(Buffer.from(taskData.slice(0, EditMakeDirectSwapDelayLayout.getSpan()))); let make_direct_swap_settings: EditMakeDirectSwapSettings = { modification_delay: parseInt(editMakeDirectSwapDelay.modificationDelay.toString()), } return make_direct_swap_settings; case TaskType.AddToken: let oracleAggregator: OracleAggregator = OracleAggregatorLayout.decode(Buffer.from(taskData.slice(0, OracleAggregatorLayout.getSpan()))); let active = taskData[OracleAggregatorLayout.getSpan()] == 1 ? true : false; let token_mint = new PublicKey(taskData.slice(OracleAggregatorLayout.getSpan() + 1, OracleAggregatorLayout.getSpan() + 33)); let add_token_input: AddOrEditTokenInput = { token_mint: token_mint.toBase58(), active: active, min_oracles_thresh: oracleAggregator.minOraclesThresh, min_conf_bps: oracleAggregator.minConfBps, conf_thresh_bps: oracleAggregator.confThreshBps, conf_multiplier: fractionToDecimal(oracleAggregator.confMultiplier).toNumber(), oracles: oracleAggregator.oracles.slice(0, oracleAggregator.numOracles).map(oracle => ({ oracle_type: ORACLE_TYPES_STRINGS.get(oracle.oracleSettings.oracleType) ?? "example", account_lut_id: oracle.accountsToLoadLutIds[0], account_lut_index: oracle.accountsToLoadLutIndices[0], account: "", num_required_accounts: oracle.oracleSettings.numRequiredAccounts, weight: oracle.oracleSettings.weight, is_required: oracle.oracleSettings.isRequired == 1 ? true : false, conf_thresh_bps: oracle.oracleSettings.confThreshBps, volatility_thresh_bps: oracle.oracleSettings.volatilityThreshBps, max_slippage_bps: oracle.oracleSettings.maxSlippageBps, min_liquidity: parseInt(oracle.oracleSettings.minLiquidity.toString()), staleness_thresh: parseInt(oracle.oracleSettings.stalenessThresh.toString()), staleness_conf_rate_bps: oracle.oracleSettings.stalenessConfRateBps, token_decimals: oracle.oracleSettings.tokenDecimals, twap_seconds_ago: parseInt(oracle.oracleSettings.twapSecondsAgo.toString()), twap_secondary_seconds_ago: parseInt(oracle.oracleSettings.twapSecondarySecondsAgo.toString()), })), } return add_token_input; case TaskType.UpdateWeights: let updateWeights: UpdateWeights = UpdateWeightsLayout.decode(Buffer.from(taskData.slice(0, UpdateWeightsLayout.getSpan()))); let update_weights_input: UpdateWeightsInput = { token_weights: updateWeights.tokenWeights.map(weight => ({ mint: "", weight_bps: weight, })), token_mints_hash: updateWeights.tokenMintsHash, } return update_weights_input; case TaskType.MakeDirectSwap: let makeDirectSwap: MakeDirectSwap = MakeDirectSwapLayout.decode(Buffer.from(taskData.slice(0, MakeDirectSwapLayout.getSpan()))); let make_direct_swap_input: MakeDirectSwapInput = { from_token_mint: makeDirectSwap.fromTokenMint.toBase58(), to_token_mint: makeDirectSwap.toTokenMint.toBase58(), amount_from: parseInt(makeDirectSwap.amountFrom.toString()), amount_to: parseInt(makeDirectSwap.amountTo.toString()), } return make_direct_swap_input; default: throw new Error(`Unknown task type: ${taskType}`); } }; export function addFieldsToIntent(intent: Intent): Intent { let formatted: FormattedIntent = { pubkey: intent.ownAddress!.toBase58(), manager: intent.manager.toBase58(), status: INTENT_STATUS_STRINGS.get(intent.status) ?? "not_active", activation_timestamp: parseInt(intent.activationTimestamp.toString()), expiration_timestamp: parseInt(intent.expirationTimestamp.toString()), basket: intent.basket.toBase58(), bounty: { bounty_depositor: intent.bounty.bountyDepositor.toBase58(), bounty_mint: intent.bounty.bountyMint.toBase58(), bounty_per_price_update_task: { min_bounty: parseInt(intent.bounty.bountyPerPriceUpdateTask.minBounty.toString()), max_bounty: parseInt(intent.bounty.bountyPerPriceUpdateTask.maxBounty.toString()), min_bounty_until: parseInt(intent.bounty.bountyPerPriceUpdateTask.minBountyUntil.toString()), max_bounty_after: parseInt(intent.bounty.bountyPerPriceUpdateTask.maxBountyAfter.toString()), }, bounty_per_task: { min_bounty: parseInt(intent.bounty.bountyPerTask.minBounty.toString()), max_bounty: parseInt(intent.bounty.bountyPerTask.maxBounty.toString()), min_bounty_until: parseInt(intent.bounty.bountyPerTask.minBountyUntil.toString()), max_bounty_after: parseInt(intent.bounty.bountyPerTask.maxBountyAfter.toString()), }, bounty_total: parseInt(intent.bounty.bountyTotal.toString()), bounty_left: parseInt(intent.bounty.bountyLeft.toString()), }, task_type: TASK_TYPE_STRINGS.get(intent.taskType) ?? "unknown", task_data: decodeTaskDataForType(intent), } intent.formatted = formatted; return intent; } export async function fetchIntent( connection: Connection, intentAddress: PublicKey, ): Promise{ const intentAi = await connection.getAccountInfo(intentAddress); if (!intentAi) throw new Error('Basket intent not found'); let intent = IntentLayout.decode(intentAi.data.slice(8)); intent.ownAddress = intentAddress; intent = addFieldsToIntent(intent); return intent; } export async function fetchIntentsMultiple( connection: Connection, intentAddresses: PublicKey[], ): Promise> { let multipleAccountsInfo = await getMultipleAccountsInfoBatched(connection, intentAddresses); let intents: Intent[] = intentAddresses.map(address => { let ai = multipleAccountsInfo.get(address.toBase58()); if (!ai) return null; return { ...IntentLayout.decode(ai.data.slice(8)), ownAddress: address } }).filter(intent => intent !== null); intents = intents.map(intent => addFieldsToIntent(intent)); let intentsMap: Map = new Map(); for (let intent of intents) intentsMap.set(intent.ownAddress!.toBase58(), intent); return intentsMap; } export interface IntentFilter { type: "basket" | "manager"; pubkey: string; } export async function fetchIntents( connection: Connection, filter?: IntentFilter, ): Promise { let accountFilters: GetProgramAccountsFilter[] = [ { dataSize: 8 + IntentLayout.getSpan() }, ]; if (filter?.type === "basket") { accountFilters.push({ memcmp: { offset: 8 + 32 + 1 + 8 + 8, bytes: filter.pubkey } }); } else if (filter?.type === "manager") { accountFilters.push({ memcmp: { offset: 8, bytes: filter.pubkey } }); } const accounts: GetProgramAccountsResponse = await connection .getProgramAccounts( BASKETS_V3_PROGRAM_ID, { commitment: "confirmed", filters: accountFilters, encoding: 'base64' } ); let intents: Intent[] = accounts.map(account => { let intent = IntentLayout.decode(account.account.data.slice(8)); intent.ownAddress = account.pubkey; return intent; }); intents = intents.map(intent => addFieldsToIntent(intent)); return intents; }