diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 65968818a9..64a0cbb748 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -74,7 +74,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: "22.14.x" + node-version: "24.x.x" registry-url: "https://registry.npmjs.org" - name: Install yarn run: yarn @@ -89,13 +89,14 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: "22.14.x" + node-version: "24.x.x" registry-url: "https://registry.npmjs.org" - name: Install yarn run: yarn - name: Run lint run: yarn lint anchor-tests: + name: Anchor tests runs-on: ubicloud timeout-minutes: 60 steps: @@ -128,7 +129,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: "22.14.x" + node-version: "24.x.x" registry-url: "https://registry.npmjs.org" - name: Setup yarn @@ -161,7 +162,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v2 with: - node-version: "22.14.x" + node-version: "24.x.x" registry-url: 'https://registry.npmjs.org' - name: Install dependencies @@ -224,7 +225,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: "22.14.x" + node-version: "24.x.x" registry-url: "https://registry.npmjs.org" - name: Build sdk run: yarn @@ -249,7 +250,14 @@ jobs: } echo "version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT - name: Publish to npm - run: npm publish --access=public + run: | + if [[ "$PACKAGE_VERSION" == *beta* ]]; then + npm publish --access=public --tag latest + elif [[ "$PACKAGE_VERSION" == *alpha* ]]; then + npm publish --access=public --tag alpha + else + npm publish --access=public + fi env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Build sdk for browser @@ -259,7 +267,14 @@ jobs: # Update package name for browser version while keeping the same version node -e "const pkg = require('./package.json'); pkg.name = pkg.name + '-browser'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" - name: Publish browser version to npm - run: npm publish --access=public + run: | + if [[ "$PACKAGE_VERSION" == *beta* ]]; then + npm publish --access=public --tag beta + elif [[ "$PACKAGE_VERSION" == *alpha* ]]; then + npm publish --access=public --tag alpha + else + npm publish --access=public + fi env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Notify Slack on failure diff --git a/CHANGELOG.md b/CHANGELOG.md index 70089b8f8d..2dcc9843b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +### Fixes + +### Breaking + +## [2.156.0] - 2026-01-26 + +### Features + +### Fixes + +### Breaking + +## [2.155.0] - 2026-01-20 + +### Features + +- program: additional logging for amm fills [#2078](https://github.com/drift-labs/protocol-v2/pull/2078) +- program: allow delegate to transfer isolated pos deposit in sub account [#2079](https://github.com/drift-labs/protocol-v2/pull/2079) +- program: use load_maps in update_amms [#2081](https://github.com/drift-labs/protocol-v2/pull/2081) + +### Fixes + +### Breaking + +## [2.154.0] - 2026-01-08 + +### Features + - program: isolated positions [#1757](https://github.com/drift-labs/protocol-v2/pull/1757) - program: delete serum/openbook configs [#2066](https://github.com/drift-labs/protocol-v2/pull/2066) @@ -16,6 +44,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking +## [2.153.0] - 2025-12-30 + +### Features + +- ui: save titan tx when quoted and reuse on swap by @cha-kos in [#2055](https://github.com/drift-labs/protocol-v2/pull/2055) +- feat: minified with esbuild by @LukasDeco in [#2056](https://github.com/drift-labs/protocol-v2/pull/2056) +- ui: fix falsely failing quotes from titan by @cha-kos in [#2058](https://github.com/drift-labs/protocol-v2/pull/2058) + +### Fixes + +- security patch: check feed id after pyth pull atomic update [84b5011](https://github.com/drift-labs/protocol-v2/commit/84b50116c15050c7d19608cd01745a8f7fc39b92) + +### Breaking + ## [2.152.0] - 2025-12-12 ### Features diff --git a/Cargo.lock b/Cargo.lock index ff6fb03c43..c3c1af5abc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -956,7 +956,7 @@ dependencies = [ [[package]] name = "drift" -version = "2.152.0" +version = "2.156.0" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/package.json b/package.json index 4b30b26fde..61b1b4ed67 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ }, "dependencies": { "@ellipsis-labs/phoenix-sdk": "1.4.2", - "@pythnetwork/pyth-solana-receiver": "0.8.0", "@switchboard-xyz/common": "3.0.14", "@switchboard-xyz/on-demand": "2.4.1", "anchor-bankrun": "0.3.0", diff --git a/programs/drift/Cargo.toml b/programs/drift/Cargo.toml index a677d4b74c..d9308c75f2 100644 --- a/programs/drift/Cargo.toml +++ b/programs/drift/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "drift" -version = "2.152.0" +version = "2.156.0" description = "Created with Anchor" edition = "2018" diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 1e461a5855..4bb569f72f 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1803,6 +1803,7 @@ fn fulfill_perp_order( }; if fulfillment_methods.is_empty() { + msg!("no fulfillment methods found"); return Ok((0, 0)); } diff --git a/programs/drift/src/error.rs b/programs/drift/src/error.rs index ea7cc04d87..f62258f271 100644 --- a/programs/drift/src/error.rs +++ b/programs/drift/src/error.rs @@ -696,6 +696,10 @@ pub enum ErrorCode { MarketIndexNotFoundAmmCache, #[msg("Invalid Isolated Perp Market")] InvalidIsolatedPerpMarket, + #[msg("Invalid scale order count - must be between 2 and 10")] + InvalidOrderScaleOrderCount, + #[msg("Invalid scale order price range")] + InvalidOrderScalePriceRange, } #[macro_export] diff --git a/programs/drift/src/ids.rs b/programs/drift/src/ids.rs index 80505bc83f..d2c9adcf5a 100644 --- a/programs/drift/src/ids.rs +++ b/programs/drift/src/ids.rs @@ -140,5 +140,7 @@ pub const WHITELISTED_SWAP_PROGRAMS: &[solana_program::pubkey::Pubkey] = &[ titan_mainnet_argos_v1::id(), ]; -pub const WHITELISTED_EXTERNAL_DEPOSITORS: [Pubkey; 1] = - [pubkey!("zApVWDs3nSychNnUXSS2czhY78Ycopa15zELrK2gAdM")]; +pub const WHITELISTED_EXTERNAL_DEPOSITORS: [Pubkey; 2] = [ + pubkey!("zApVWDs3nSychNnUXSS2czhY78Ycopa15zELrK2gAdM"), + pubkey!("4B62MS5gxpRZ2hwkGCNAAayA5f7LYZRW4z1ASSfU3SXo"), +]; diff --git a/programs/drift/src/instructions/keeper.rs b/programs/drift/src/instructions/keeper.rs index 6019a45199..052d56e62c 100644 --- a/programs/drift/src/instructions/keeper.rs +++ b/programs/drift/src/instructions/keeper.rs @@ -2905,13 +2905,19 @@ pub fn handle_update_amms<'c: 'info, 'info>( let state = &ctx.accounts.state; let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable(); - let oracle_map = &mut OracleMap::load(remaining_accounts_iter, clock.slot, None)?; - let market_map = &mut PerpMarketMap::load( - &get_market_set_from_list(market_indexes), + let AccountMaps { + mut perp_market_map, + mut oracle_map, + .. + } = load_maps( remaining_accounts_iter, + &get_market_set_from_list(market_indexes), + &MarketSet::new(), + clock.slot, + Some(state.oracle_guard_rails), )?; - controller::repeg::update_amms(market_map, oracle_map, state, &clock)?; + controller::repeg::update_amms(&mut perp_market_map, &mut oracle_map, state, &clock)?; Ok(()) } diff --git a/programs/drift/src/instructions/user.rs b/programs/drift/src/instructions/user.rs index 3c50d3a25d..d0ae756f61 100644 --- a/programs/drift/src/instructions/user.rs +++ b/programs/drift/src/instructions/user.rs @@ -81,6 +81,7 @@ use crate::state::order_params::{ PlaceOrderOptions, PostOnlyParam, }; use crate::state::paused_operations::{PerpOperation, SpotOperation}; +use crate::state::scale_order_params::ScaleOrderParams; use crate::state::perp_market::MarketStatus; use crate::state::perp_market_map::{get_writable_perp_market_set, MarketSet}; use crate::state::protected_maker_mode_config::ProtectedMakerModeConfig; @@ -2606,6 +2607,31 @@ pub fn handle_modify_order_by_user_order_id<'c: 'info, 'info>( pub fn handle_place_orders<'c: 'info, 'info>( ctx: Context<'_, '_, 'c, 'info, PlaceOrder>, params: Vec, +) -> Result<()> { + place_orders(&ctx, PlaceOrdersInput::Orders(params)) +} + +#[access_control( + exchange_not_paused(&ctx.accounts.state) +)] +pub fn handle_place_scale_orders<'c: 'info, 'info>( + ctx: Context<'_, '_, 'c, 'info, PlaceOrder>, + params: ScaleOrderParams, +) -> Result<()> { + place_orders(&ctx, PlaceOrdersInput::ScaleOrders(params)) +} + +/// Input for place_orders - either direct OrderParams or ScaleOrderParams to expand +enum PlaceOrdersInput { + Orders(Vec), + ScaleOrders(ScaleOrderParams), +} + +/// Internal implementation for placing multiple orders. +/// Used by both handle_place_orders and handle_place_scale_orders. +fn place_orders<'c: 'info, 'info>( + ctx: &Context<'_, '_, 'c, 'info, PlaceOrder>, + input: PlaceOrdersInput, ) -> Result<()> { let clock = &Clock::get()?; let state = &ctx.accounts.state; @@ -2625,8 +2651,31 @@ pub fn handle_place_orders<'c: 'info, 'info>( let high_leverage_mode_config = get_high_leverage_mode_config(&mut remaining_accounts)?; + // Convert input to order params, expanding scale orders if needed + let order_params = match input { + PlaceOrdersInput::Orders(params) => params, + PlaceOrdersInput::ScaleOrders(scale_params) => { + let order_step_size = match scale_params.market_type { + MarketType::Perp => { + let market = perp_market_map.get_ref(&scale_params.market_index)?; + market.amm.order_step_size + } + MarketType::Spot => { + let market = spot_market_map.get_ref(&scale_params.market_index)?; + market.order_step_size + } + }; + + scale_params.expand_to_order_params(order_step_size) + .map_err(|e| { + msg!("Failed to expand scale order params: {:?}", e); + ErrorCode::InvalidOrder + })? + } + }; + validate!( - params.len() <= 32, + order_params.len() <= 32, ErrorCode::DefaultError, "max 32 order params" )?; @@ -2634,8 +2683,8 @@ pub fn handle_place_orders<'c: 'info, 'info>( let user_key = ctx.accounts.user.key(); let mut user = load_mut!(ctx.accounts.user)?; - let num_orders = params.len(); - for (i, params) in params.iter().enumerate() { + let num_orders = order_params.len(); + for (i, params) in order_params.iter().enumerate() { validate!( !params.is_immediate_or_cancel(), ErrorCode::InvalidOrderIOC, @@ -2654,7 +2703,7 @@ pub fn handle_place_orders<'c: 'info, 'info>( if params.market_type == MarketType::Perp { controller::orders::place_perp_order( - &ctx.accounts.state, + state, &mut user, user_key, &perp_market_map, @@ -2668,7 +2717,7 @@ pub fn handle_place_orders<'c: 'info, 'info>( )?; } else { controller::orders::place_spot_order( - &ctx.accounts.state, + state, &mut user, user_key, &perp_market_map, @@ -4842,7 +4891,7 @@ pub struct TransferIsolatedPerpPositionDeposit<'info> { pub user: AccountLoader<'info, User>, #[account( mut, - has_one = authority + constraint = is_stats_for_user(&user, &user_stats)? )] pub user_stats: AccountLoader<'info, UserStats>, pub authority: Signer<'info>, diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index fe0b403e4b..3ecd0ef296 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -13,6 +13,7 @@ use crate::controller::position::PositionDirection; use crate::state::if_rebalance_config::IfRebalanceConfigParams; use crate::state::oracle::PrelaunchOracleParams; use crate::state::order_params::{ModifyOrderParams, OrderParams}; +use crate::state::scale_order_params::ScaleOrderParams; use crate::state::perp_market::{ContractTier, MarketStatus}; use crate::state::settle_pnl_mode::SettlePnlMode; use crate::state::spot_market::AssetTier; @@ -367,6 +368,13 @@ pub mod drift { handle_place_orders(ctx, params) } + pub fn place_scale_orders<'c: 'info, 'info>( + ctx: Context<'_, '_, 'c, 'info, PlaceOrder>, + params: ScaleOrderParams, + ) -> Result<()> { + handle_place_scale_orders(ctx, params) + } + pub fn begin_swap<'c: 'info, 'info>( ctx: Context<'_, '_, 'c, 'info, Swap<'info>>, in_market_index: u16, diff --git a/programs/drift/src/math/fulfillment.rs b/programs/drift/src/math/fulfillment.rs index 15103bddcd..13352d54ef 100644 --- a/programs/drift/src/math/fulfillment.rs +++ b/programs/drift/src/math/fulfillment.rs @@ -3,6 +3,7 @@ use crate::error::DriftResult; use crate::math::casting::Cast; use crate::math::matching::do_orders_cross; use crate::math::safe_unwrap::SafeUnwrap; +use crate::msg; use crate::state::fulfillment::{PerpFulfillmentMethod, SpotFulfillmentMethod}; use crate::state::perp_market::AMM; use crate::state::user::Order; @@ -73,7 +74,18 @@ pub fn determine_perp_fulfillment_methods( if amm_is_available { let taker_crosses_amm = match limit_price { - Some(taker_price) => do_orders_cross(maker_direction, amm_price, taker_price), + Some(taker_price) => { + let crosses = do_orders_cross(maker_direction, amm_price, taker_price); + if !crosses && fulfillment_methods.is_empty() { + msg!( + "taker does not cross amm: taker price {} amm price {}", + taker_price, + amm_price + ); + } + crosses + } + None => true, }; diff --git a/programs/drift/src/state/mod.rs b/programs/drift/src/state/mod.rs index 73b57392f4..aecc0d1686 100644 --- a/programs/drift/src/state/mod.rs +++ b/programs/drift/src/state/mod.rs @@ -15,6 +15,7 @@ pub mod oracle; pub mod oracle_map; pub mod order_params; pub mod paused_operations; +pub mod scale_order_params; pub mod perp_market; pub mod perp_market_map; pub mod protected_maker_mode_config; diff --git a/programs/drift/src/state/order_params.rs b/programs/drift/src/state/order_params.rs index a5b81b8bb6..97e2619232 100644 --- a/programs/drift/src/state/order_params.rs +++ b/programs/drift/src/state/order_params.rs @@ -1027,3 +1027,4 @@ pub fn parse_optional_params(optional_params: Option) -> (u8, u8) { None => (0, 100), } } + diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index bafb04fc5b..d52c092358 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -911,10 +911,12 @@ impl PerpMarket { mm_oracle_price_data: &MMOraclePriceData, ) -> DriftResult { if self.is_operation_paused(PerpOperation::AmmFill) { + msg!("AMM cannot fill order: AMM fill operation is paused"); return Ok(false); } if self.has_too_much_drawdown()? { + msg!("AMM cannot fill order: has too much drawdown"); return Ok(false); } @@ -964,15 +966,29 @@ impl PerpMarket { return Ok(false); } let amm_wants_to_jit_make = self.amm.amm_wants_to_jit_make(order.direction)?; + if !amm_wants_to_jit_make { + msg!("AMM cannot fill order: AMM does not want to JIT make"); + return Ok(false); + } + let amm_has_low_enough_inventory = self .amm .amm_has_low_enough_inventory(amm_wants_to_jit_make)?; + + if !amm_has_low_enough_inventory { + msg!("AMM cannot fill order: AMM has too much inventory"); + return Ok(false); + } + let amm_can_skip_duration = self.can_skip_auction_duration(&state, amm_has_low_enough_inventory)?; - amm_can_skip_duration - && oracle_valid_for_can_fill_immediately - && user_can_skip_auction_duration + if !amm_can_skip_duration { + msg!("AMM cannot fill order: AMM cannot skip duration"); + return Ok(false); + } + + true }; Ok(can_fill_order) diff --git a/programs/drift/src/state/scale_order_params.rs b/programs/drift/src/state/scale_order_params.rs new file mode 100644 index 0000000000..5b06df86c6 --- /dev/null +++ b/programs/drift/src/state/scale_order_params.rs @@ -0,0 +1,270 @@ +use crate::controller::position::PositionDirection; +use crate::error::{DriftResult, ErrorCode}; +use crate::math::constants::MAX_OPEN_ORDERS; +use crate::math::safe_math::SafeMath; +use crate::state::order_params::{OrderParams, PostOnlyParam}; +use crate::state::user::{MarketType, OrderTriggerCondition, OrderType}; +use crate::validate; +use anchor_lang::prelude::*; + +#[cfg(test)] +mod tests; + +/// Minimum number of orders required for a scale order +pub const MIN_SCALE_ORDER_COUNT: u8 = 2; +/// Maximum number of orders allowed in a single scale order instruction +pub const MAX_SCALE_ORDER_COUNT: u8 = MAX_OPEN_ORDERS; + +/// How to distribute order sizes across scale orders +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Default, Eq, PartialEq, Debug)] +pub enum SizeDistribution { + /// Equal size for all orders + #[default] + Flat, + /// Smallest orders at start price, largest at end price + Ascending, + /// Largest orders at start price, smallest at end price + Descending, +} + +/// Parameters for placing scale orders - multiple limit orders distributed across a price range +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Eq, PartialEq, Debug)] +pub struct ScaleOrderParams { + pub market_type: MarketType, + pub direction: PositionDirection, + pub market_index: u16, + /// Total base asset amount to distribute across all orders + pub total_base_asset_amount: u64, + /// Starting price for the scale (in PRICE_PRECISION) + pub start_price: u64, + /// Ending price for the scale (in PRICE_PRECISION) + pub end_price: u64, + /// Number of orders to place (min 2, max 32) + pub order_count: u8, + /// How to distribute sizes across orders + pub size_distribution: SizeDistribution, + /// Whether orders should be reduce-only + pub reduce_only: bool, + /// Post-only setting for all orders + pub post_only: PostOnlyParam, + /// Bit flags (e.g., for high leverage mode) + pub bit_flags: u8, + /// Maximum timestamp for orders to be valid + pub max_ts: Option, +} + +impl ScaleOrderParams { + /// Validates the scale order parameters + pub fn validate(&self, order_step_size: u64) -> DriftResult<()> { + validate!( + self.order_count >= MIN_SCALE_ORDER_COUNT, + ErrorCode::InvalidOrderScaleOrderCount, + "order_count must be at least {}", + MIN_SCALE_ORDER_COUNT + )?; + + validate!( + self.order_count <= MAX_SCALE_ORDER_COUNT, + ErrorCode::InvalidOrderScaleOrderCount, + "order_count must be at most {}", + MAX_SCALE_ORDER_COUNT + )?; + + validate!( + self.start_price != self.end_price, + ErrorCode::InvalidOrderScalePriceRange, + "start_price and end_price cannot be equal" + )?; + + // For long orders, start price is higher (first buy) and end price is lower (DCA down) + // For short orders, start price is lower (first sell) and end price is higher (scale out up) + match self.direction { + PositionDirection::Long => { + validate!( + self.start_price > self.end_price, + ErrorCode::InvalidOrderScalePriceRange, + "for long scale orders, start_price must be greater than end_price (scaling down)" + )?; + } + PositionDirection::Short => { + validate!( + self.start_price < self.end_price, + ErrorCode::InvalidOrderScalePriceRange, + "for short scale orders, start_price must be less than end_price (scaling up)" + )?; + } + } + + // Validate that total size can be distributed among all orders meeting minimum step size + let min_total_size = order_step_size.safe_mul(self.order_count as u64)?; + validate!( + self.total_base_asset_amount >= min_total_size, + ErrorCode::OrderAmountTooSmall, + "total_base_asset_amount must be at least {} (order_step_size * order_count)", + min_total_size + )?; + + Ok(()) + } + + /// Calculate evenly distributed prices between start and end price + pub fn calculate_price_distribution(&self) -> DriftResult> { + let order_count = self.order_count as usize; + + if order_count == 1 { + return Ok(vec![self.start_price]); + } + + if order_count == 2 { + return Ok(vec![self.start_price, self.end_price]); + } + + let (min_price, max_price) = if self.start_price < self.end_price { + (self.start_price, self.end_price) + } else { + (self.end_price, self.start_price) + }; + + let price_range = max_price.safe_sub(min_price)?; + let num_steps = (order_count - 1) as u64; + let price_step = price_range.safe_div(num_steps)?; + + let mut prices = Vec::with_capacity(order_count); + for i in 0..order_count { + // Use exact end_price for the last order to avoid rounding errors + let price = if i == order_count - 1 { + self.end_price + } else if self.start_price < self.end_price { + self.start_price.safe_add(price_step.safe_mul(i as u64)?)? + } else { + self.start_price.safe_sub(price_step.safe_mul(i as u64)?)? + }; + prices.push(price); + } + + Ok(prices) + } + + /// Calculate order sizes based on size distribution strategy + pub fn calculate_size_distribution(&self, order_step_size: u64) -> DriftResult> { + match self.size_distribution { + SizeDistribution::Flat => self.calculate_flat_sizes(order_step_size), + SizeDistribution::Ascending => self.calculate_scaled_sizes(order_step_size, false), + SizeDistribution::Descending => self.calculate_scaled_sizes(order_step_size, true), + } + } + + /// Calculate flat (equal) distribution of sizes + fn calculate_flat_sizes(&self, order_step_size: u64) -> DriftResult> { + let order_count = self.order_count as u64; + let base_size = self.total_base_asset_amount.safe_div(order_count)?; + // Round down to step size + let rounded_size = base_size + .safe_div(order_step_size)? + .safe_mul(order_step_size)?; + + let mut sizes = vec![rounded_size; self.order_count as usize]; + + // Add remainder to the last order + let total_distributed: u64 = sizes.iter().sum(); + let remainder = self.total_base_asset_amount.safe_sub(total_distributed)?; + if remainder > 0 { + if let Some(last) = sizes.last_mut() { + *last = last.safe_add(remainder)?; + } + } + + Ok(sizes) + } + + /// Calculate scaled (ascending/descending) distribution of sizes + /// Uses multipliers: 1x, 1.5x, 2x, 2.5x, ... for ascending + fn calculate_scaled_sizes( + &self, + order_step_size: u64, + descending: bool, + ) -> DriftResult> { + let order_count = self.order_count as usize; + + // Calculate multipliers: 1.0, 1.5, 2.0, 2.5, ... (using 0.5 step) + // Sum of multipliers = n/2 * (first + last) = n/2 * (1 + (1 + 0.5*(n-1))) + // For precision, multiply everything by 2: multipliers become 2, 3, 4, 5, ... + // Sum = n/2 * (2 + (2 + (n-1))) = n/2 * (3 + n) = n*(n+3)/2 + let multiplier_sum = (order_count * (order_count + 3)) / 2; + + // Base unit size (multiplied by 2 for precision) + let base_unit = self + .total_base_asset_amount + .safe_mul(2)? + .safe_div(multiplier_sum as u64)?; + + let mut sizes = Vec::with_capacity(order_count); + let mut total = 0u64; + + for i in 0..order_count { + // Multiplier for position i is (2 + i) when using 0.5 step scaled by 2 + let multiplier = (2 + i) as u64; + let raw_size = base_unit.safe_mul(multiplier)?.safe_div(2)?; + // Round to step size + let rounded_size = raw_size + .safe_div(order_step_size)? + .safe_mul(order_step_size)? + .max(order_step_size); // Ensure at least step size + sizes.push(rounded_size); + total = total.safe_add(rounded_size)?; + } + + // Adjust last order to account for rounding + if total != self.total_base_asset_amount { + if let Some(last) = sizes.last_mut() { + if total > self.total_base_asset_amount { + let diff = total.safe_sub(self.total_base_asset_amount)?; + *last = last.saturating_sub(diff).max(order_step_size); + } else { + let diff = self.total_base_asset_amount.safe_sub(total)?; + *last = last.safe_add(diff)?; + } + } + } + + if descending { + sizes.reverse(); + } + + Ok(sizes) + } + + /// Expand scale order params into individual OrderParams + pub fn expand_to_order_params(&self, order_step_size: u64) -> DriftResult> { + self.validate(order_step_size)?; + + let prices = self.calculate_price_distribution()?; + let sizes = self.calculate_size_distribution(order_step_size)?; + + let mut order_params = Vec::with_capacity(self.order_count as usize); + + for (i, (price, size)) in prices.iter().zip(sizes.iter()).enumerate() { + order_params.push(OrderParams { + order_type: OrderType::Limit, + market_type: self.market_type, + direction: self.direction, + user_order_id: 0, + base_asset_amount: *size, + price: *price, + market_index: self.market_index, + reduce_only: self.reduce_only, + post_only: self.post_only, + bit_flags: if i == 0 { self.bit_flags } else { 0 }, + max_ts: self.max_ts, + trigger_price: None, + trigger_condition: OrderTriggerCondition::Above, + oracle_price_offset: None, + auction_duration: None, + auction_start_price: None, + auction_end_price: None, + }); + } + + Ok(order_params) + } +} diff --git a/programs/drift/src/state/scale_order_params/tests.rs b/programs/drift/src/state/scale_order_params/tests.rs new file mode 100644 index 0000000000..4877d3d119 --- /dev/null +++ b/programs/drift/src/state/scale_order_params/tests.rs @@ -0,0 +1,568 @@ +use crate::state::order_params::PostOnlyParam; +use crate::state::scale_order_params::{ScaleOrderParams, SizeDistribution}; +use crate::state::user::MarketType; +use crate::{PositionDirection, BASE_PRECISION_U64, PRICE_PRECISION_U64}; + +#[test] +fn test_validate_order_count_bounds() { + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + // Test minimum order count + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 1, // Below minimum + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + assert!(params.validate(step_size).is_err()); + + // Test maximum order count + let params = ScaleOrderParams { + order_count: 33, // Above maximum (MAX_OPEN_ORDERS = 32) + ..params + }; + assert!(params.validate(step_size).is_err()); + + // Test valid order count + let params = ScaleOrderParams { + order_count: 5, + ..params + }; + assert!(params.validate(step_size).is_ok()); +} + +#[test] +fn test_validate_price_range() { + let step_size = BASE_PRECISION_U64 / 1000; + + // Long orders: start_price must be > end_price (scaling down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, + start_price: 100 * PRICE_PRECISION_U64, // Wrong: lower than end + end_price: 110 * PRICE_PRECISION_U64, + order_count: 5, + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + assert!(params.validate(step_size).is_err()); + + // Short orders: start_price must be < end_price (scaling up) + let params = ScaleOrderParams { + direction: PositionDirection::Short, + start_price: 110 * PRICE_PRECISION_U64, // Wrong: higher than end + end_price: 100 * PRICE_PRECISION_U64, + ..params + }; + assert!(params.validate(step_size).is_err()); + + // Valid long order (start high, end low - DCA down) + let params = ScaleOrderParams { + direction: PositionDirection::Long, + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + ..params + }; + assert!(params.validate(step_size).is_ok()); + + // Valid short order (start low, end high - scale out up) + let params = ScaleOrderParams { + direction: PositionDirection::Short, + start_price: 100 * PRICE_PRECISION_U64, + end_price: 110 * PRICE_PRECISION_U64, + ..params + }; + assert!(params.validate(step_size).is_ok()); +} + +#[test] +fn test_price_distribution_long() { + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 5, + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let prices = params.calculate_price_distribution().unwrap(); + assert_eq!(prices.len(), 5); + assert_eq!(prices[0], 110 * PRICE_PRECISION_U64); + assert_eq!(prices[1], 107500000); // 107.5 + assert_eq!(prices[2], 105 * PRICE_PRECISION_U64); + assert_eq!(prices[3], 102500000); // 102.5 + assert_eq!(prices[4], 100 * PRICE_PRECISION_U64); +} + +#[test] +fn test_price_distribution_short() { + // Short: start low, end high (scale out up) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Short, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, + start_price: 100 * PRICE_PRECISION_U64, + end_price: 110 * PRICE_PRECISION_U64, + order_count: 5, + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let prices = params.calculate_price_distribution().unwrap(); + assert_eq!(prices.len(), 5); + assert_eq!(prices[0], 100 * PRICE_PRECISION_U64); + assert_eq!(prices[1], 102500000); // 102.5 + assert_eq!(prices[2], 105 * PRICE_PRECISION_U64); + assert_eq!(prices[3], 107500000); // 107.5 + assert_eq!(prices[4], 110 * PRICE_PRECISION_U64); +} + +#[test] +fn test_flat_size_distribution() { + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 5, + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let sizes = params.calculate_size_distribution(step_size).unwrap(); + assert_eq!(sizes.len(), 5); + + // Total must equal the requested amount + let total: u64 = sizes.iter().sum(); + assert_eq!(total, BASE_PRECISION_U64); + + // Flat distribution: each order should be 1/5 = 20% of total + // Expected: 200_000_000 each (0.2 BASE) + // First 4 orders are exactly 0.2, last order gets any remainder + assert_eq!(sizes[0], 200_000_000); // 20% + assert_eq!(sizes[1], 200_000_000); // 20% + assert_eq!(sizes[2], 200_000_000); // 20% + assert_eq!(sizes[3], 200_000_000); // 20% + assert_eq!(sizes[4], 200_000_000); // 20% (remainder goes here if any) + + // Verify each order is exactly 20% of total + for size in &sizes { + let pct = (*size as f64) / (BASE_PRECISION_U64 as f64) * 100.0; + assert!((pct - 20.0).abs() < 0.1, "Expected ~20%, got {}%", pct); + } +} + +#[test] +fn test_ascending_size_distribution() { + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 5, + size_distribution: SizeDistribution::Ascending, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let sizes = params.calculate_size_distribution(step_size).unwrap(); + assert_eq!(sizes.len(), 5); + + // Total must equal the requested amount + let total: u64 = sizes.iter().sum(); + assert_eq!(total, BASE_PRECISION_U64); + + // Ascending distribution uses multipliers: 1x, 1.5x, 2x, 2.5x, 3x + // Scaled by 2 for precision: 2, 3, 4, 5, 6 (sum = 20) + // Expected proportions: 10%, 15%, 20%, 25%, 30% + // For 1_000_000_000 total: 100M, 150M, 200M, 250M, 300M + assert_eq!(sizes[0], 100_000_000); // 10% - smallest + assert_eq!(sizes[1], 150_000_000); // 15% + assert_eq!(sizes[2], 200_000_000); // 20% + assert_eq!(sizes[3], 250_000_000); // 25% + assert_eq!(sizes[4], 300_000_000); // 30% - largest + + // Verify ascending order: each subsequent order is larger + assert!(sizes[0] < sizes[1]); + assert!(sizes[1] < sizes[2]); + assert!(sizes[2] < sizes[3]); + assert!(sizes[3] < sizes[4]); + + // Verify the proportions are correct (within 1% tolerance for rounding) + let expected_pcts = [10.0, 15.0, 20.0, 25.0, 30.0]; + for (i, (size, expected_pct)) in sizes.iter().zip(expected_pcts.iter()).enumerate() { + let actual_pct = (*size as f64) / (BASE_PRECISION_U64 as f64) * 100.0; + assert!( + (actual_pct - expected_pct).abs() < 1.0, + "Order {}: expected ~{}%, got {}%", + i, + expected_pct, + actual_pct + ); + } +} + +#[test] +fn test_descending_size_distribution() { + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 5, + size_distribution: SizeDistribution::Descending, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let sizes = params.calculate_size_distribution(step_size).unwrap(); + assert_eq!(sizes.len(), 5); + + // Total must equal the requested amount + let total: u64 = sizes.iter().sum(); + assert_eq!(total, BASE_PRECISION_U64); + + // Descending distribution is reverse of ascending + // Multipliers (reversed): 3x, 2.5x, 2x, 1.5x, 1x + // Expected proportions: 30%, 25%, 20%, 15%, 10% + // For 1_000_000_000 total: 300M, 250M, 200M, 150M, 100M + assert_eq!(sizes[0], 300_000_000); // 30% - largest + assert_eq!(sizes[1], 250_000_000); // 25% + assert_eq!(sizes[2], 200_000_000); // 20% + assert_eq!(sizes[3], 150_000_000); // 15% + assert_eq!(sizes[4], 100_000_000); // 10% - smallest + + // Verify descending order: each subsequent order is smaller + assert!(sizes[0] > sizes[1]); + assert!(sizes[1] > sizes[2]); + assert!(sizes[2] > sizes[3]); + assert!(sizes[3] > sizes[4]); + + // Verify the proportions are correct (within 1% tolerance for rounding) + let expected_pcts = [30.0, 25.0, 20.0, 15.0, 10.0]; + for (i, (size, expected_pct)) in sizes.iter().zip(expected_pcts.iter()).enumerate() { + let actual_pct = (*size as f64) / (BASE_PRECISION_U64 as f64) * 100.0; + assert!( + (actual_pct - expected_pct).abs() < 1.0, + "Order {}: expected ~{}%, got {}%", + i, + expected_pct, + actual_pct + ); + } +} + +#[test] +fn test_ascending_size_distribution_3_orders() { + // Test with different order count to verify formula works correctly + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 3, + size_distribution: SizeDistribution::Ascending, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let sizes = params.calculate_size_distribution(step_size).unwrap(); + assert_eq!(sizes.len(), 3); + + // Total must equal the requested amount + let total: u64 = sizes.iter().sum(); + assert_eq!(total, BASE_PRECISION_U64); + + // For 3 orders: multiplier_sum = n*(n+3)/2 = 3*6/2 = 9 + // Multipliers (scaled by 2): 2, 3, 4 + // Expected proportions: 2/9 ≈ 22.2%, 3/9 ≈ 33.3%, 4/9 ≈ 44.4% + let expected_pcts = [22.22, 33.33, 44.44]; + for (i, (size, expected_pct)) in sizes.iter().zip(expected_pcts.iter()).enumerate() { + let actual_pct = (*size as f64) / (BASE_PRECISION_U64 as f64) * 100.0; + assert!( + (actual_pct - expected_pct).abs() < 1.0, + "Order {}: expected ~{}%, got {}%", + i, + expected_pct, + actual_pct + ); + } + + // Verify ascending order + assert!(sizes[0] < sizes[1]); + assert!(sizes[1] < sizes[2]); +} + +#[test] +fn test_flat_distribution_with_remainder() { + // Test flat distribution where total doesn't divide evenly + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 3, // 1.0 / 3 doesn't divide evenly + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let sizes = params.calculate_size_distribution(step_size).unwrap(); + assert_eq!(sizes.len(), 3); + + // Total must still equal exactly the requested amount + let total: u64 = sizes.iter().sum(); + assert_eq!(total, BASE_PRECISION_U64); + + // Each order should be ~33.3%, with remainder going to last order + // step_size = 1_000_000 (0.001) + // base_size = 1_000_000_000 / 3 = 333_333_333 + // rounded_size = (333_333_333 / 1_000_000) * 1_000_000 = 333_000_000 + // First two orders: 333_000_000 each + // Last order: 1_000_000_000 - 2*333_000_000 = 334_000_000 + assert_eq!(sizes[0], 333_000_000); + assert_eq!(sizes[1], 333_000_000); + assert_eq!(sizes[2], 334_000_000); // Gets the remainder +} + +#[test] +fn test_expand_to_order_params_perp() { + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 1, + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 3, + size_distribution: SizeDistribution::Flat, + reduce_only: true, + post_only: PostOnlyParam::MustPostOnly, + bit_flags: 2, // High leverage mode + max_ts: Some(12345), + }; + + let order_params = params.expand_to_order_params(step_size).unwrap(); + assert_eq!(order_params.len(), 3); + + // Check first order has bit flags + assert_eq!(order_params[0].bit_flags, 2); + // Other orders should have 0 bit flags + assert_eq!(order_params[1].bit_flags, 0); + assert_eq!(order_params[2].bit_flags, 0); + + // Check common properties + for op in &order_params { + assert_eq!(op.market_type, MarketType::Perp); + assert_eq!(op.market_index, 1); + assert_eq!(op.reduce_only, true); + assert_eq!(op.post_only, PostOnlyParam::MustPostOnly); + assert_eq!(op.max_ts, Some(12345)); + assert!(matches!(op.direction, PositionDirection::Long)); + } + + // Check prices are distributed (high to low for long) + assert_eq!(order_params[0].price, 110 * PRICE_PRECISION_U64); + assert_eq!(order_params[1].price, 105 * PRICE_PRECISION_U64); + assert_eq!(order_params[2].price, 100 * PRICE_PRECISION_U64); + + // Check total size + let total: u64 = order_params.iter().map(|op| op.base_asset_amount).sum(); + assert_eq!(total, BASE_PRECISION_U64); +} + +#[test] +fn test_expand_to_order_params_spot() { + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + // Spot Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Spot, + direction: PositionDirection::Long, + market_index: 1, // SOL spot market + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 3, + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let order_params = params.expand_to_order_params(step_size).unwrap(); + assert_eq!(order_params.len(), 3); + + // Check all orders are Spot market type + for op in &order_params { + assert_eq!(op.market_type, MarketType::Spot); + assert_eq!(op.market_index, 1); + assert!(matches!(op.direction, PositionDirection::Long)); + } + + // Check prices are distributed (high to low for long) + assert_eq!(order_params[0].price, 110 * PRICE_PRECISION_U64); + assert_eq!(order_params[1].price, 105 * PRICE_PRECISION_U64); + assert_eq!(order_params[2].price, 100 * PRICE_PRECISION_U64); + + // Check total size + let total: u64 = order_params.iter().map(|op| op.base_asset_amount).sum(); + assert_eq!(total, BASE_PRECISION_U64); +} + +#[test] +fn test_spot_short_scale_orders() { + let step_size = BASE_PRECISION_U64 / 1000; // 0.001 + + // Spot Short: start low, end high (scale out up) + let params = ScaleOrderParams { + market_type: MarketType::Spot, + direction: PositionDirection::Short, + market_index: 1, // SOL spot market + total_base_asset_amount: BASE_PRECISION_U64, // 1.0 + start_price: 100 * PRICE_PRECISION_U64, + end_price: 110 * PRICE_PRECISION_U64, + order_count: 4, + size_distribution: SizeDistribution::Ascending, + reduce_only: false, + post_only: PostOnlyParam::MustPostOnly, + bit_flags: 0, + max_ts: Some(99999), + }; + + let order_params = params.expand_to_order_params(step_size).unwrap(); + assert_eq!(order_params.len(), 4); + + // Check all orders are Spot market type and Short direction + for op in &order_params { + assert_eq!(op.market_type, MarketType::Spot); + assert_eq!(op.market_index, 1); + assert!(matches!(op.direction, PositionDirection::Short)); + assert_eq!(op.post_only, PostOnlyParam::MustPostOnly); + assert_eq!(op.max_ts, Some(99999)); + } + + // Check prices are distributed (low to high for short) + assert_eq!(order_params[0].price, 100 * PRICE_PRECISION_U64); + // Middle prices + assert_eq!(order_params[3].price, 110 * PRICE_PRECISION_U64); + + // Ascending: sizes should increase + assert!(order_params[0].base_asset_amount < order_params[3].base_asset_amount); + + // Check total size + let total: u64 = order_params.iter().map(|op| op.base_asset_amount).sum(); + assert_eq!(total, BASE_PRECISION_U64); +} + +#[test] +fn test_two_orders_price_distribution() { + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64, + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 2, + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + let prices = params.calculate_price_distribution().unwrap(); + assert_eq!(prices.len(), 2); + assert_eq!(prices[0], 110 * PRICE_PRECISION_U64); + assert_eq!(prices[1], 100 * PRICE_PRECISION_U64); +} + +#[test] +fn test_validate_min_total_size() { + let step_size = BASE_PRECISION_U64 / 10; // 0.1 + + // Total size is too small for 5 orders with this step size + // Long: start high, end low (DCA down) + let params = ScaleOrderParams { + market_type: MarketType::Perp, + direction: PositionDirection::Long, + market_index: 0, + total_base_asset_amount: BASE_PRECISION_U64 / 20, // 0.05 - not enough + start_price: 110 * PRICE_PRECISION_U64, + end_price: 100 * PRICE_PRECISION_U64, + order_count: 5, + size_distribution: SizeDistribution::Flat, + reduce_only: false, + post_only: PostOnlyParam::None, + bit_flags: 0, + max_ts: None, + }; + + assert!(params.validate(step_size).is_err()); +} diff --git a/sdk/VERSION b/sdk/VERSION index fc701a3a82..b164179dec 100644 --- a/sdk/VERSION +++ b/sdk/VERSION @@ -1 +1 @@ -2.153.0-beta.1 \ No newline at end of file +2.157.0-beta.0 \ No newline at end of file diff --git a/sdk/package.json b/sdk/package.json index 0160365875..1c4dc0516f 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@drift-labs/sdk", - "version": "2.153.0-beta.1", + "version": "2.157.0-beta.0", "main": "lib/node/index.js", "types": "lib/node/index.d.ts", "module": "./lib/browser/index.js", @@ -51,7 +51,7 @@ "@project-serum/serum": "0.13.65", "@pythnetwork/client": "2.5.3", "@pythnetwork/price-service-sdk": "1.7.1", - "@pythnetwork/pyth-solana-receiver": "0.7.0", + "@pythnetwork/pyth-lazer-sdk": "5.2.1", "@solana/spl-token": "0.4.13", "@solana/web3.js": "1.98.0", "@switchboard-xyz/common": "3.0.14", @@ -62,7 +62,6 @@ "helius-laserstream": "0.1.8", "nanoid": "3.3.4", "node-cache": "5.1.2", - "rpc-websockets": "7.5.1", "solana-bankrun": "0.3.1", "strict-event-emitter-types": "2.0.0", "tweetnacl": "1.0.3", @@ -104,7 +103,7 @@ }, "description": "SDK for Drift Protocol", "engines": { - "node": ">=22.14.0" + "node": "^24.0.0" }, "resolutions": { "@solana/web3.js": "1.98.0", @@ -150,9 +149,9 @@ "has-ansi": "<6.0.1" }, "browser": { - "helius-laserstream": false, - "@triton-one/yellowstone-grpc": false, "@grpc/grpc-js": false, + "@triton-one/yellowstone-grpc": false, + "helius-laserstream": false, "zstddec": false } } diff --git a/sdk/scripts/deposit-isolated-positions.ts b/sdk/scripts/deposit-isolated-positions.ts new file mode 100644 index 0000000000..6bff228d26 --- /dev/null +++ b/sdk/scripts/deposit-isolated-positions.ts @@ -0,0 +1,110 @@ +import { Connection, Keypair, PublicKey } from '@solana/web3.js'; +import dotenv from 'dotenv'; +import { AnchorProvider, Idl, Program, ProgramAccount, BN } from '@coral-xyz/anchor'; +import driftIDL from '../src/idl/drift.json'; +import { + DRIFT_PROGRAM_ID, + PerpMarketAccount, + SpotMarketAccount, + OracleInfo, + Wallet, + numberToSafeBN, +} from '../src'; +import { DriftClient } from '../src/driftClient'; +import { DriftClientConfig } from '../src/driftClientConfig'; + +async function main() { + dotenv.config({ path: '../' }); + + const RPC_ENDPOINT = process.env.RPC_ENDPOINT; + if (!RPC_ENDPOINT) throw new Error('RPC_ENDPOINT env var required'); + + let keypair: Keypair; + const pk = process.env.PRIVATE_KEY; + if (pk) { + const secret = Uint8Array.from(JSON.parse(pk)); + keypair = Keypair.fromSecretKey(secret); + } else { + keypair = new Keypair(); + console.warn('Using ephemeral keypair. Provide PRIVATE_KEY to use a real wallet.'); + } + const wallet = new Wallet(keypair); + + const connection = new Connection(RPC_ENDPOINT); + const provider = new AnchorProvider(connection, wallet as any, { + commitment: 'processed', + }); + const programId = new PublicKey(DRIFT_PROGRAM_ID); + const program = new Program(driftIDL as Idl, programId, provider); + + const allPerpMarketProgramAccounts = + (await program.account.perpMarket.all()) as ProgramAccount[]; + const perpMarketIndexes = allPerpMarketProgramAccounts.map((val) => val.account.marketIndex); + const allSpotMarketProgramAccounts = + (await program.account.spotMarket.all()) as ProgramAccount[]; + const spotMarketIndexes = allSpotMarketProgramAccounts.map((val) => val.account.marketIndex); + + const seen = new Set(); + const oracleInfos: OracleInfo[] = []; + for (const acct of allPerpMarketProgramAccounts) { + const key = `${acct.account.amm.oracle.toBase58()}-${Object.keys(acct.account.amm.oracleSource)[0]}`; + if (!seen.has(key)) { + seen.add(key); + oracleInfos.push({ publicKey: acct.account.amm.oracle, source: acct.account.amm.oracleSource }); + } + } + for (const acct of allSpotMarketProgramAccounts) { + const key = `${acct.account.oracle.toBase58()}-${Object.keys(acct.account.oracleSource)[0]}`; + if (!seen.has(key)) { + seen.add(key); + oracleInfos.push({ publicKey: acct.account.oracle, source: acct.account.oracleSource }); + } + } + + const clientConfig: DriftClientConfig = { + connection, + wallet, + programID: programId, + accountSubscription: { type: 'websocket', commitment: 'processed' }, + perpMarketIndexes, + spotMarketIndexes, + oracleInfos, + env: 'devnet', + }; + const client = new DriftClient(clientConfig); + await client.subscribe(); + + const candidates = perpMarketIndexes.filter((i) => i >= 0 && i <= 5); + const targetMarketIndex = candidates.length + ? candidates[Math.floor(Math.random() * candidates.length)] + : perpMarketIndexes[0]; + + const perpMarketAccount = client.getPerpMarketAccount(targetMarketIndex); + const quoteSpotMarketIndex = perpMarketAccount.quoteSpotMarketIndex; + const spotMarketAccount = client.getSpotMarketAccount(quoteSpotMarketIndex); + + const precision = new BN(10).pow(new BN(spotMarketAccount.decimals)); + const amount = numberToSafeBN(0.01, precision); + + const userTokenAccount = await client.getAssociatedTokenAccount(quoteSpotMarketIndex); + const ix = await client.getDepositIntoIsolatedPerpPositionIx( + amount, + targetMarketIndex, + userTokenAccount, + 0 + ); + + const tx = await client.buildTransaction([ix]); + const { txSig } = await client.sendTransaction(tx); + console.log(`Deposited into isolated perp market ${targetMarketIndex}: ${txSig}`); + + await client.getUser().unsubscribe(); + await client.unsubscribe(); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); + + diff --git a/sdk/scripts/find-flagged-users.ts b/sdk/scripts/find-flagged-users.ts new file mode 100644 index 0000000000..9cb32981d0 --- /dev/null +++ b/sdk/scripts/find-flagged-users.ts @@ -0,0 +1,216 @@ +import { Connection, Keypair, PublicKey } from '@solana/web3.js'; +import dotenv from 'dotenv'; +import { + DriftClient, + DriftClientConfig, + Wallet, + UserMap, + DRIFT_PROGRAM_ID, + getMarketsAndOraclesForSubscription, + BulkAccountLoader, + BN, + PerpPosition, +} from '../src'; +import { TransactionSignature } from '@solana/web3.js'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +async function main() { + dotenv.config({ path: '../' }); + // Simple CLI parsing + interface CliOptions { + mode: 'list' | 'one' | 'all'; + targetUser?: string; + } + + function parseCliArgs(): CliOptions { + const args = process.argv.slice(2); + let mode: CliOptions['mode'] = 'list'; + let targetUser: string | undefined = undefined; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === '--mode' && i + 1 < args.length) { + const next = args[i + 1] as CliOptions['mode']; + if (next === 'list' || next === 'one' || next === 'all') { + mode = next; + } + i++; + } else if ((arg === '--user' || arg === '--target') && i + 1 < args.length) { + targetUser = args[i + 1]; + i++; + } + } + return { mode, targetUser }; + } + + const { mode, targetUser } = parseCliArgs(); + + const RPC_ENDPOINT = + process.env.RPC_ENDPOINT ?? 'https://api.mainnet-beta.solana.com'; + + const connection = new Connection(RPC_ENDPOINT); + const keypairPath = + process.env.SOLANA_KEYPAIR ?? + path.join(os.homedir(), '.config', 'solana', 'id.json'); + const secret = JSON.parse(fs.readFileSync(keypairPath, 'utf-8')) as number[]; + const wallet = new Wallet(Keypair.fromSecretKey(Uint8Array.from(secret))); + + const { perpMarketIndexes, spotMarketIndexes, oracleInfos } = + getMarketsAndOraclesForSubscription('mainnet-beta'); + + const accountLoader = new BulkAccountLoader(connection, 'confirmed', 60_000); + + const clientConfig: DriftClientConfig = { + connection, + wallet, + programID: new PublicKey(DRIFT_PROGRAM_ID), + accountSubscription: { + type: 'polling', + accountLoader, + }, + perpMarketIndexes, + spotMarketIndexes, + oracleInfos, + env: 'mainnet-beta', + }; + + const client = new DriftClient(clientConfig); + await client.subscribe(); + + const userMap = new UserMap({ + driftClient: client, + subscriptionConfig: { + type: 'polling', + frequency: 60_000, + commitment: 'confirmed', + }, + includeIdle: false, + syncConfig: { type: 'paginated' }, + throwOnFailedSync: false, + }); + await userMap.subscribe(); + + + const flaggedUsers: Array<{ + userPubkey: string; + authority: string; + flags: Array<{ marketIndex: number; flag: number; isolatedPositionScaledBalance: BN }>; + }> = []; + + console.log(`User map size: ${Array.from(userMap.entries()).length}`); + + for (const [userPubkey, user] of userMap.entries()) { + const userAccount = user.getUserAccount(); + const flaggedPositions = userAccount.perpPositions + .filter((p) => p.positionFlag >= 1 || p.isolatedPositionScaledBalance.toString() !== '0') + .map((p) => ({ marketIndex: p.marketIndex, flag: p.positionFlag, isolatedPositionScaledBalance: p.isolatedPositionScaledBalance, fullPosition: p })); + + if (flaggedPositions.length > 0) { + if(mode === 'one' && userPubkey === targetUser) { + console.log(`flagged positions on user ${userPubkey}`); + console.log(flaggedPositions.map((p) => `mkt=${p.marketIndex}, flag=${p.flag}, isolatedPositionScaledBalance=${p.isolatedPositionScaledBalance.toString()}, fullPosition=${fullLogPerpPosition(p.fullPosition)}`).join('\n\n; ')); + } + flaggedUsers.push({ + userPubkey, + authority: userAccount.authority.toBase58(), + flags: flaggedPositions, + }); + } + } + + // Mode 1: list flagged users (default) + if (mode === 'list') { + console.log(`Flagged users (positionFlag >= 1 || isolatedPositionScaledBalance > 0): ${flaggedUsers.length}`); + for (const u of flaggedUsers) { + const flagsStr = u.flags + .map((f) => `mkt=${f.marketIndex}, flag=${f.flag}, isolatedPositionScaledBalance=${f.isolatedPositionScaledBalance.toString()}`) + .join('; '); + console.log( + `- authority=${u.authority} userAccount=${u.userPubkey} -> [${flagsStr}]` + ); + } + } + + // Helper to invoke updateUserIdle + async function updateUserIdleFor(userAccountPubkeyStr: string): Promise { + const userObj = userMap.get(userAccountPubkeyStr); + if (!userObj) { + console.warn(`User ${userAccountPubkeyStr} not found in userMap`); + return undefined; + } + try { + const sig = await client.updateUserIdle( + new PublicKey(userAccountPubkeyStr), + userObj.getUserAccount() + ); + console.log(`updateUserIdle sent for userAccount=${userAccountPubkeyStr} -> tx=${sig}`); + return sig; + } catch (e) { + console.error(`Failed updateUserIdle for userAccount=${userAccountPubkeyStr}`, e); + return undefined; + } + } + + // Mode 2: updateUserIdle on a single flagged user + if (mode === 'one') { + if (flaggedUsers.length === 0) { + console.log('No flagged users to update.'); + } else { + const chosen = + (targetUser && flaggedUsers.find((u) => u.userPubkey === targetUser)) || + flaggedUsers[0]; + console.log( + `Updating single flagged userAccount=${chosen.userPubkey} authority=${chosen.authority}` + ); + await updateUserIdleFor(chosen.userPubkey); + } + } + + // Mode 3: updateUserIdle on all flagged users + if (mode === 'all') { + if (flaggedUsers.length === 0) { + console.log('No flagged users to update.'); + } else { + console.log(`Updating all ${flaggedUsers.length} flagged users...`); + for (const u of flaggedUsers) { + await updateUserIdleFor(u.userPubkey); + } + console.log('Finished updating all flagged users.'); + } + } + + await userMap.unsubscribe(); + await client.unsubscribe(); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); + + +function fullLogPerpPosition(position: PerpPosition) { + + return ` + [PERP POSITION] + baseAssetAmount=${position.baseAssetAmount.toString()} + quoteAssetAmount=${position.quoteAssetAmount.toString()} + quoteBreakEvenAmount=${position.quoteBreakEvenAmount.toString()} + quoteEntryAmount=${position.quoteEntryAmount.toString()} + openBids=${position.openBids.toString()} + openAsks=${position.openAsks.toString()} + settledPnl=${position.settledPnl.toString()} + lpShares=${position.lpShares.toString()} + remainderBaseAssetAmount=${position.remainderBaseAssetAmount} + lastQuoteAssetAmountPerLp=${position.lastQuoteAssetAmountPerLp.toString()} + perLpBase=${position.perLpBase} + maxMarginRatio=${position.maxMarginRatio} + marketIndex=${position.marketIndex} + openOrders=${position.openOrders} + positionFlag=${position.positionFlag} + isolatedPositionScaledBalance=${position.isolatedPositionScaledBalance.toString()} + `; + +} + diff --git a/sdk/scripts/single-grpc-client-test.ts b/sdk/scripts/single-grpc-client-test.ts index e6724e6615..60de5bd39b 100644 --- a/sdk/scripts/single-grpc-client-test.ts +++ b/sdk/scripts/single-grpc-client-test.ts @@ -48,9 +48,7 @@ async function initializeSingleGrpcClient() { const allPerpMarketProgramAccounts = (await program.account.perpMarket.all()) as ProgramAccount[]; const perpMarketProgramAccounts = allPerpMarketProgramAccounts.filter((val) => - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].includes( - val.account.marketIndex - ) + [46].includes(val.account.marketIndex) ); const perpMarketIndexes = perpMarketProgramAccounts.map( (val) => val.account.marketIndex @@ -60,7 +58,7 @@ async function initializeSingleGrpcClient() { const allSpotMarketProgramAccounts = (await program.account.spotMarket.all()) as ProgramAccount[]; const spotMarketProgramAccounts = allSpotMarketProgramAccounts.filter((val) => - [0, 1, 2, 3, 4, 5].includes(val.account.marketIndex) + [0].includes(val.account.marketIndex) ); const spotMarketIndexes = spotMarketProgramAccounts.map( (val) => val.account.marketIndex @@ -94,7 +92,9 @@ async function initializeSingleGrpcClient() { } } - console.log(`📊 Markets: ${perpMarketIndexes.length} perp, ${spotMarketIndexes.length} spot`); + console.log( + `📊 Markets: ${perpMarketIndexes.length} perp, ${spotMarketIndexes.length} spot` + ); console.log(`🔮 Oracles: ${oracleInfos.length}`); @@ -171,7 +171,9 @@ async function initializeSingleGrpcClient() { await client.subscribe(); console.log('✅ Client subscribed successfully!'); - console.log('🚀 Starting high-load testing (50 reads/sec per perp market)...'); + console.log( + '🚀 Starting high-load testing (50 reads/sec per perp market)...' + ); // High-frequency load testing - 50 reads per second per perp market const loadTestInterval = setInterval(async () => { @@ -179,29 +181,71 @@ async function initializeSingleGrpcClient() { // Test getPerpMarketAccount for each perp market (50 times per second per market) for (const marketIndex of perpMarketIndexes) { const perpMarketAccount = client.getPerpMarketAccount(marketIndex); - console.log("perpMarketAccount name: ", decodeName(perpMarketAccount.name)); - console.log("perpMarketAccount data: ", JSON.stringify({ - marketIndex: perpMarketAccount.marketIndex, - name: decodeName(perpMarketAccount.name), - baseAssetReserve: perpMarketAccount.amm.baseAssetReserve.toString(), - quoteAssetReserve: perpMarketAccount.amm.quoteAssetReserve.toString() - })); + if (!perpMarketAccount) { + console.log(`Perp market ${marketIndex} not found`); + continue; + } + console.log( + 'perpMarketAccount name: ', + decodeName(perpMarketAccount.name) + ); + console.log( + 'perpMarketAccount data: ', + JSON.stringify({ + marketIndex: perpMarketAccount.marketIndex, + name: decodeName(perpMarketAccount.name), + baseAssetReserve: perpMarketAccount.amm.baseAssetReserve.toString(), + quoteAssetReserve: + perpMarketAccount.amm.quoteAssetReserve.toString(), + }) + ); } // Test getMMOracleDataForPerpMarket for each perp market (50 times per second per market) for (const marketIndex of perpMarketIndexes) { try { const oracleData = client.getMMOracleDataForPerpMarket(marketIndex); - console.log("oracleData price: ", oracleData.price.toString()); - console.log("oracleData: ", JSON.stringify({ - price: oracleData.price.toString(), - confidence: oracleData.confidence?.toString(), - slot: oracleData.slot?.toString() - })); + console.log('oracleData price: ', oracleData.price.toString()); + console.log( + 'oracleData: ', + JSON.stringify({ + price: oracleData.price.toString(), + confidence: oracleData.confidence?.toString(), + slot: oracleData.slot?.toString(), + }) + ); } catch (error) { // Ignore errors for load testing } } + + for (const marketIndex of perpMarketIndexes) { + try { + const { data, slot } = + client.accountSubscriber.getMarketAccountAndSlot(marketIndex); + if (!data) { + console.log( + `Perp market getMarketAccountAndSlot ${marketIndex} not found` + ); + continue; + } + console.log( + 'marketAccountAndSlot: ', + JSON.stringify({ + marketIndex: data.marketIndex, + name: decodeName(data.name), + slot: slot?.toString(), + baseAssetReserve: data.amm.baseAssetReserve.toString(), + quoteAssetReserve: data.amm.quoteAssetReserve.toString(), + }) + ); + } catch (error) { + console.error( + `Error getting market account and slot for market ${marketIndex}:`, + error + ); + } + } } catch (error) { console.error('Load test error:', error); } @@ -211,8 +255,14 @@ async function initializeSingleGrpcClient() { const statsInterval = setInterval(() => { console.log('\n📈 Event Counts:', eventCounts); console.log(`⏱️ Client subscribed: ${client.isSubscribed}`); - console.log(`🔗 Account subscriber subscribed: ${client.accountSubscriber.isSubscribed}`); - console.log(`🔥 Load: ${perpMarketIndexes.length * 50 * 2} reads/sec (${perpMarketIndexes.length} markets × 50 getPerpMarketAccount + 50 getMMOracleDataForPerpMarket)`); + console.log( + `🔗 Account subscriber subscribed: ${client.accountSubscriber.isSubscribed}` + ); + console.log( + `🔥 Load: ${perpMarketIndexes.length * 50 * 2} reads/sec (${ + perpMarketIndexes.length + } markets × 50 getPerpMarketAccount + 50 getMMOracleDataForPerpMarket)` + ); }, 5000); // Handle shutdown signals - just exit without cleanup since they never unsubscribe diff --git a/sdk/scripts/withdraw-isolated-positions.ts b/sdk/scripts/withdraw-isolated-positions.ts new file mode 100644 index 0000000000..51f9a74c34 --- /dev/null +++ b/sdk/scripts/withdraw-isolated-positions.ts @@ -0,0 +1,174 @@ +import { Connection, Keypair, PublicKey } from '@solana/web3.js'; +import dotenv from 'dotenv'; +import { + AnchorProvider, + Idl, + Program, + ProgramAccount, +} from '@coral-xyz/anchor'; +import driftIDL from '../src/idl/drift.json'; +import { + DRIFT_PROGRAM_ID, + PerpMarketAccount, + SpotMarketAccount, + OracleInfo, + Wallet, + ZERO, +} from '../src'; +import { DriftClient } from '../src/driftClient'; +import { DriftClientConfig } from '../src/driftClientConfig'; + +function isStatusOpen(status: any) { + return !!status && 'open' in status; +} + +function isPerpMarketType(marketType: any) { + return !!marketType && 'perp' in marketType; +} + +async function main() { + dotenv.config({ path: '../' }); + + const RPC_ENDPOINT = process.env.RPC_ENDPOINT; + if (!RPC_ENDPOINT) throw new Error('RPC_ENDPOINT env var required'); + + // Load wallet + // For safety this creates a new ephemeral wallet unless PRIVATE_KEY is provided (base58 array) + let keypair: Keypair; + const pk = process.env.PRIVATE_KEY; + if (pk) { + const secret = Uint8Array.from(JSON.parse(pk)); + keypair = Keypair.fromSecretKey(secret); + } else { + keypair = new Keypair(); + console.warn( + 'Using ephemeral keypair. Provide PRIVATE_KEY for real withdrawals.' + ); + } + const wallet = new Wallet(keypair); + + // Connection and program for market discovery + const connection = new Connection(RPC_ENDPOINT); + const provider = new AnchorProvider(connection, wallet as any, { + commitment: 'processed', + }); + const programId = new PublicKey(DRIFT_PROGRAM_ID); + const program = new Program(driftIDL as Idl, programId, provider); + + // Discover markets and oracles (like the example test script) + const allPerpMarketProgramAccounts = + (await program.account.perpMarket.all()) as ProgramAccount[]; + const perpMarketIndexes = allPerpMarketProgramAccounts.map( + (val) => val.account.marketIndex + ); + const allSpotMarketProgramAccounts = + (await program.account.spotMarket.all()) as ProgramAccount[]; + const spotMarketIndexes = allSpotMarketProgramAccounts.map( + (val) => val.account.marketIndex + ); + + const seen = new Set(); + const oracleInfos: OracleInfo[] = []; + for (const acct of allPerpMarketProgramAccounts) { + const key = `${acct.account.amm.oracle.toBase58()}-${ + Object.keys(acct.account.amm.oracleSource)[0] + }`; + if (!seen.has(key)) { + seen.add(key); + oracleInfos.push({ + publicKey: acct.account.amm.oracle, + source: acct.account.amm.oracleSource, + }); + } + } + for (const acct of allSpotMarketProgramAccounts) { + const key = `${acct.account.oracle.toBase58()}-${ + Object.keys(acct.account.oracleSource)[0] + }`; + if (!seen.has(key)) { + seen.add(key); + oracleInfos.push({ + publicKey: acct.account.oracle, + source: acct.account.oracleSource, + }); + } + } + + // Build DriftClient with websocket subscription (lightweight) + const clientConfig: DriftClientConfig = { + connection, + wallet, + programID: programId, + accountSubscription: { + type: 'websocket', + commitment: 'processed', + }, + perpMarketIndexes, + spotMarketIndexes, + oracleInfos, + env: 'devnet', + }; + const client = new DriftClient(clientConfig); + await client.subscribe(); + + // Ensure user exists and is subscribed + const user = client.getUser(); + await user.subscribe(); + + const userAccount = user.getUserAccount(); + const openOrders = user.getOpenOrders(); + + const marketsWithOpenOrders = new Set(); + for (const o of openOrders ?? []) { + if (isStatusOpen(o.status) && isPerpMarketType(o.marketType)) { + marketsWithOpenOrders.add(o.marketIndex); + } + } + + const withdrawTargets = userAccount.perpPositions.filter((pos) => { + const isZeroBase = pos.baseAssetAmount.eq(ZERO); + const hasIso = pos.isolatedPositionScaledBalance.gt(ZERO); + const hasOpenOrders = marketsWithOpenOrders.has(pos.marketIndex); + return isZeroBase && hasIso && !hasOpenOrders; + }); + + console.log( + `Found ${withdrawTargets.length} isolated perp positions to withdraw` + ); + + for (const pos of withdrawTargets) { + try { + const amount = client.getIsolatedPerpPositionTokenAmount(pos.marketIndex); + if (amount.lte(ZERO)) continue; + + const perpMarketAccount = client.getPerpMarketAccount(pos.marketIndex); + const quoteAta = await client.getAssociatedTokenAccount( + perpMarketAccount.quoteSpotMarketIndex + ); + + const ixs = await client.getWithdrawFromIsolatedPerpPositionIxsBundle( + amount, + pos.marketIndex, + 0, + quoteAta, + true + ); + + const tx = await client.buildTransaction(ixs); + const { txSig } = await client.sendTransaction(tx); + console.log( + `Withdrew isolated deposit for perp market ${pos.marketIndex}: ${txSig}` + ); + } catch (e) { + console.error(`Failed to withdraw for market ${pos.marketIndex}:`, e); + } + } + + await user.unsubscribe(); + await client.unsubscribe(); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/sdk/src/constants/spotMarkets.ts b/sdk/src/constants/spotMarkets.ts index 243e6ae2b9..ce963d1158 100644 --- a/sdk/src/constants/spotMarkets.ts +++ b/sdk/src/constants/spotMarkets.ts @@ -223,8 +223,8 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ symbol: 'wETH', marketIndex: 4, poolId: 0, - oracle: new PublicKey('6bEp2MiyoiiiDxcVqE8rUHQWwHirXUXtKfAEATTVqNzT'), - oracleSource: OracleSource.PYTH_PULL, + oracle: new PublicKey('93FG52TzNKCnMiasV14Ba34BYcHDb9p4zK4GjZnLwqWR'), + oracleSource: OracleSource.PYTH_LAZER, mint: new PublicKey('7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs'), precision: new BN(10).pow(EIGHT), precisionExp: EIGHT, @@ -237,6 +237,7 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ ), pythFeedId: '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace', + pythLazerId: 2, }, { symbol: 'USDT', @@ -290,14 +291,15 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ symbol: 'bSOL', marketIndex: 8, poolId: 0, - oracle: new PublicKey('BmDWPMsytWmYkh9n6o7m79eVshVYf2B5GVaqQ2EWKnGH'), - oracleSource: OracleSource.PYTH_PULL, + oracle: new PublicKey('6YEQjxkbhfrWV2VdR9zxBJxWYshcMYRs6bpuX1ng2CbP'), + oracleSource: OracleSource.PYTH_LAZER, mint: new PublicKey('bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1'), precision: new BN(10).pow(NINE), precisionExp: NINE, serumMarket: new PublicKey('ARjaHVxGCQfTvvKjLd7U7srvk6orthZSE6uqWchCczZc'), pythFeedId: '0x89875379e70f8fbadc17aef315adf3a8d5d160b811435537e03c97e8aac97d9c', + pythLazerId: 389, }, { symbol: 'JTO', @@ -423,27 +425,29 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ symbol: 'INF', marketIndex: 16, poolId: 0, - oracle: new PublicKey('B7RUYg2zF6UdUSHv2RmpnriPVJccYWojgFydNS1NY5F8'), - oracleSource: OracleSource.PYTH_PULL, + oracle: new PublicKey('7SAxf2SCJe5c72rZNo4etpGjz5TaEAqi8QkbfpRbouuT'), + oracleSource: OracleSource.PYTH_LAZER, mint: new PublicKey('5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm'), precision: new BN(10).pow(NINE), precisionExp: NINE, launchTs: 1716595200000, pythFeedId: '0xf51570985c642c49c2d6e50156390fdba80bb6d5f7fa389d2f012ced4f7d208f', + pythLazerId: 455, }, { symbol: 'dSOL', marketIndex: 17, poolId: 0, - oracle: new PublicKey('4YstsHafLyDbYFxmJbgoEr33iJJEp6rNPgLTQRgXDkG2'), - oracleSource: OracleSource.PYTH_PULL, + oracle: new PublicKey('EopUQMXT56Lbyg1DKDbc7VAQFcx4QGBKBepZxeKe1sJf'), + oracleSource: OracleSource.PYTH_LAZER, mint: new PublicKey('Dso1bDeDjCQxTrWHqUUi63oBvV7Mdm6WaobLbQ7gnPQ'), precision: new BN(10).pow(NINE), precisionExp: NINE, launchTs: 1716595200000, pythFeedId: '0x41f858bae36e7ee3f4a3a6d4f176f0893d4a261460a52763350d00f8648195ee', + pythLazerId: 415, }, { symbol: 'USDY', @@ -573,8 +577,8 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ symbol: 'cbBTC', marketIndex: 27, poolId: 0, - oracle: new PublicKey('9jPy6EHpLkXaMdvfkoVnRnSdJoQysQDKKj3bW5Amz4Ci'), - oracleSource: OracleSource.PYTH_PULL, + oracle: new PublicKey('D1QgWnMoPzB4wJ79Egrc6MGYYM3HHVNhpff1QQHDSoCq'), + oracleSource: OracleSource.PYTH_LAZER, mint: new PublicKey('cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij'), precision: new BN(10).pow(EIGHT), precisionExp: EIGHT, @@ -583,6 +587,7 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ ), pythFeedId: '0x2817d7bfe5c64b8ea956e9a26f573ef64e72e4d7891f2d6af9bcc93f7aff9a97', + pythLazerId: 397, }, { symbol: 'USDS', @@ -654,13 +659,14 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ symbol: 'JLP-1', marketIndex: 33, poolId: 1, - oracle: new PublicKey('3ZLn5XDgSLWhTk2NjqAU44cPkSeC5JAhW5o6w5Nz4p8R'), - oracleSource: OracleSource.PYTH_PULL, + oracle: new PublicKey('DtmeBbyWat6p2vSpRhuZ4MGRndXr2cdpd4GV8izCFvLb'), + oracleSource: OracleSource.PYTH_LAZER, mint: new PublicKey('27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4'), precision: new BN(10).pow(SIX), precisionExp: SIX, pythFeedId: '0x6704952e00b6a088b6dcdb8170dcd591eaf64cff9e996ca75ae0ca55bfb96687', + pythLazerId: 694, launchTs: 1735255852000, }, { @@ -956,14 +962,15 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [ symbol: 'LBTC', marketIndex: 58, poolId: 0, - oracle: new PublicKey('Fa3VKWbdb9yQ89vA9JfYnR6micY9LwGneoQ1So9JgXHT'), - oracleSource: OracleSource.PYTH_PULL, + oracle: new PublicKey('BmU3Hp9SZn77dUhWuED3ySuV3HenBgCoPZZP58M2ZMCr'), + oracleSource: OracleSource.PYTH_LAZER, mint: new PublicKey('LBTCgU4b3wsFKsPwBn1rRZDx5DoFutM6RPiEt1TPDsY'), precision: new BN(10).pow(EIGHT), precisionExp: EIGHT, pythFeedId: '0x8f257aab6e7698bb92b15511915e593d6f8eae914452f781874754b03d0c612b', launchTs: 1756392947000, + pythLazerId: 468, }, { symbol: '2Z', diff --git a/sdk/src/decode/user.ts b/sdk/src/decode/user.ts index 31e365df2e..d068653714 100644 --- a/sdk/src/decode/user.ts +++ b/sdk/src/decode/user.ts @@ -8,6 +8,7 @@ import { OrderType, PerpPosition, PositionDirection, + PositionFlag, SpotBalanceType, SpotPosition, UserAccount, @@ -83,6 +84,10 @@ export function decodeUser(buffer: Buffer): UserAccount { const baseAssetAmount = readSignedBigInt64LE(buffer, offset + 8); const quoteAssetAmount = readSignedBigInt64LE(buffer, offset + 16); const lpShares = readUnsignedBigInt64LE(buffer, offset + 64); + const isolatedPositionScaledBalance = readSignedBigInt64LE( + buffer, + offset + 72 + ); const openOrders = buffer.readUInt8(offset + 94); const positionFlag = buffer.readUInt8(offset + 95); @@ -90,7 +95,13 @@ export function decodeUser(buffer: Buffer): UserAccount { baseAssetAmount.eq(ZERO) && openOrders === 0 && quoteAssetAmount.eq(ZERO) && - lpShares.eq(ZERO) + lpShares.eq(ZERO) && + isolatedPositionScaledBalance.eq(ZERO) && + !( + (positionFlag & + (PositionFlag.BeingLiquidated | PositionFlag.Bankruptcy)) > + 0 + ) ) { offset += 96; continue; @@ -107,9 +118,7 @@ export function decodeUser(buffer: Buffer): UserAccount { const openAsks = readSignedBigInt64LE(buffer, offset); offset += 8; const settledPnl = readSignedBigInt64LE(buffer, offset); - offset += 16; - const isolatedPositionScaledBalance = readSignedBigInt64LE(buffer, offset); - offset += 8; + offset += 24; const lastQuoteAssetAmountPerLp = readSignedBigInt64LE(buffer, offset); offset += 8; const maxMarginRatio = buffer.readUInt16LE(offset); @@ -118,7 +127,6 @@ export function decodeUser(buffer: Buffer): UserAccount { offset += 3; const perLpBase = buffer.readUInt8(offset); offset += 1; - perpPositions.push({ lastCumulativeFundingRate, baseAssetAmount, @@ -135,8 +143,8 @@ export function decodeUser(buffer: Buffer): UserAccount { openOrders, perLpBase, maxMarginRatio, - isolatedPositionScaledBalance, positionFlag, + isolatedPositionScaledBalance, }); } diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 8b0ef4cade..08c372664d 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -48,6 +48,7 @@ import { PositionDirection, ReferrerInfo, ReferrerNameAccount, + ScaleOrderParams, SerumV3FulfillmentConfigAccount, SettlePnlMode, SignedTxData, @@ -57,7 +58,6 @@ import { StateAccount, SwapReduceOnly, SignedMsgOrderParamsMessage, - TakerInfo, TxParams, UserAccount, UserStatsAccount, @@ -140,6 +140,7 @@ import { BASE_PRECISION, GOV_SPOT_MARKET_INDEX, MARGIN_PRECISION, + MIN_I64, ONE, PERCENTAGE_PRECISION, PRICE_PRECISION, @@ -147,7 +148,11 @@ import { QUOTE_SPOT_MARKET_INDEX, ZERO, } from './constants/numericConstants'; -import { findDirectionToClose, positionIsAvailable } from './math/position'; +import { + calculateClaimablePnl, + findDirectionToClose, + positionIsAvailable, +} from './math/position'; import { getSignedTokenAmount, getTokenAmount } from './math/spotBalance'; import { decodeName, DEFAULT_USER_NAME, encodeName } from './userName'; import { MMOraclePriceData, OraclePriceData } from './oracles/types'; @@ -185,17 +190,7 @@ import { trimVaaSignatures, } from './math/oracles'; import { TxHandler } from './tx/txHandler'; -import { - DEFAULT_RECEIVER_PROGRAM_ID, - wormholeCoreBridgeIdl, -} from '@pythnetwork/pyth-solana-receiver'; import { parseAccumulatorUpdateData } from '@pythnetwork/price-service-sdk'; -import { - DEFAULT_WORMHOLE_PROGRAM_ID, - getGuardianSetPda, -} from '@pythnetwork/pyth-solana-receiver/lib/address'; -import { WormholeCoreBridgeSolana } from '@pythnetwork/pyth-solana-receiver/lib/idl/wormhole_core_bridge_solana'; -import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver/lib/idl/pyth_solana_receiver'; import { getFeedIdUint8Array, trimFeedId } from './util/pythOracleUtils'; import { createMinimalEd25519VerifyIx } from './util/ed25519Utils'; import { @@ -210,6 +205,8 @@ import nacl from 'tweetnacl'; import { Slothash } from './slot/SlothashSubscriber'; import { getOracleId } from './oracles/oracleId'; import { SignedMsgOrderParams } from './types'; +import { TakerInfo } from './types'; +// BN is already imported globally in this file via other imports import { sha256 } from '@noble/hashes/sha256'; import { getOracleConfidenceFromMMOracleData } from './oracles/utils'; import { ConstituentMap } from './constituentMap/constituentMap'; @@ -221,6 +218,14 @@ import { } from './math/builder'; import { TitanClient, SwapMode as TitanSwapMode } from './titan/titanClient'; import { UnifiedSwapClient } from './swap/UnifiedSwapClient'; +import { + DEFAULT_RECEIVER_PROGRAM_ID, + WORMHOLE_CORE_BRIDGE_SOLANA_IDL, + DEFAULT_WORMHOLE_PROGRAM_ID, + getGuardianSetPda, + WormholeCoreBridgeSolana, + PythSolanaReceiver, +} from './pyth'; /** * Union type for swap clients (Titan and Jupiter) - Legacy type @@ -295,6 +300,46 @@ export class DriftClient { return this._isSubscribed && this.accountSubscriber.isSubscribed; } + private async getPrePlaceOrderIxs( + orderParams: OptionalOrderParams, + userAccount: UserAccount, + options?: { positionMaxLev?: number; isolatedPositionDepositAmount?: BN } + ): Promise { + const preIxs: TransactionInstruction[] = []; + + if (isVariant(orderParams.marketType, 'perp')) { + const { positionMaxLev, isolatedPositionDepositAmount } = options ?? {}; + + if ( + isolatedPositionDepositAmount?.gt?.(ZERO) && + this.isOrderIncreasingPosition(orderParams, userAccount.subAccountId) + ) { + preIxs.push( + await this.getTransferIsolatedPerpPositionDepositIx( + isolatedPositionDepositAmount as BN, + orderParams.marketIndex, + userAccount.subAccountId + ) + ); + } + + if (positionMaxLev) { + const marginRatio = Math.floor( + (1 / positionMaxLev) * MARGIN_PRECISION.toNumber() + ); + preIxs.push( + await this.getUpdateUserPerpPositionCustomMarginRatioIx( + orderParams.marketIndex, + marginRatio, + userAccount.subAccountId + ) + ); + } + } + + return preIxs; + } + public set isSubscribed(val: boolean) { this._isSubscribed = val; } @@ -768,7 +813,6 @@ export class DriftClient { return lookupTableAccount; } - public async fetchAllLookupTableAccounts(): Promise< AddressLookupTableAccount[] > { @@ -776,7 +820,7 @@ export class DriftClient { if (!this.marketLookupTables) { console.log('Market lookup table address not set'); - return; + return []; } const lookupTableAccountResults = await Promise.all( @@ -785,9 +829,12 @@ export class DriftClient { ) ); - const lookupTableAccounts = lookupTableAccountResults.map( - (result) => result.value - ); + // Filter out null values - lookup tables may not exist on-chain + const lookupTableAccounts = lookupTableAccountResults + .map((result) => result.value) + .filter( + (account): account is AddressLookupTableAccount => account !== null + ); this.lookupTableAccounts = lookupTableAccounts; return lookupTableAccounts; @@ -1129,15 +1176,23 @@ export class DriftClient { return [txSig, userAccountPublicKey]; } - async getInitializeUserStatsIx(): Promise { + async getInitializeUserStatsIx(overrides?: { + /** + * Optional external wallet to use as payer. If provided, this wallet will pay + * for the account creation instead of the default wallet. + */ + externalWallet?: PublicKey; + }): Promise { + const payer = overrides?.externalWallet ?? this.wallet.publicKey; + const authority = this.authority; return await this.program.instruction.initializeUserStats({ accounts: { userStats: getUserStatsAccountPublicKey( this.program.programId, - this.wallet.publicKey // only allow payer to initialize own user stats account + authority ), - authority: this.wallet.publicKey, - payer: this.wallet.publicKey, + authority, + payer, rent: anchor.web3.SYSVAR_RENT_PUBKEY, systemProgram: anchor.web3.SystemProgram.programId, state: await this.getStatePublicKey(), @@ -1166,8 +1221,16 @@ export class DriftClient { async getInitializeSignedMsgUserOrdersAccountIx( authority: PublicKey, - numOrders: number + numOrders: number, + overrides?: { + /** + * Optional external wallet to use as payer. If provided, this wallet will pay + * for the account creation instead of the default wallet. + */ + externalWallet?: PublicKey; + } ): Promise<[PublicKey, TransactionInstruction]> { + const payer = overrides?.externalWallet ?? this.wallet.publicKey; const signedMsgUserAccountPublicKey = getSignedMsgUserAccountPublicKey( this.program.programId, authority @@ -1177,7 +1240,7 @@ export class DriftClient { accounts: { signedMsgUserOrders: signedMsgUserAccountPublicKey, authority, - payer: this.wallet.publicKey, + payer, rent: anchor.web3.SYSVAR_RENT_PUBKEY, systemProgram: anchor.web3.SystemProgram.programId, }, @@ -1592,11 +1655,19 @@ export class DriftClient { private async getInitializeUserInstructions( subAccountId = 0, name?: string, - referrerInfo?: ReferrerInfo + referrerInfo?: ReferrerInfo, + overrides?: { + externalWallet?: PublicKey; + } ): Promise<[PublicKey, TransactionInstruction]> { + // Use external wallet as payer if provided, otherwise use the wallet + const payer = overrides?.externalWallet ?? this.wallet.publicKey; + // The authority is the account owner (this.authority), not the payer + const accountAuthority = this.authority; + const userAccountPublicKey = await getUserAccountPublicKey( this.program.programId, - this.wallet.publicKey, + accountAuthority, subAccountId ); @@ -1618,7 +1689,7 @@ export class DriftClient { if (!state.whitelistMint.equals(PublicKey.default)) { const associatedTokenPublicKey = await getAssociatedTokenAddress( state.whitelistMint, - this.wallet.publicKey + payer ); remainingAccounts.push({ pubkey: associatedTokenPublicKey, @@ -1641,8 +1712,8 @@ export class DriftClient { accounts: { user: userAccountPublicKey, userStats: this.getUserStatsAccountPublicKey(), - authority: this.wallet.publicKey, - payer: this.wallet.publicKey, + authority: accountAuthority, + payer: payer, rent: anchor.web3.SYSVAR_RENT_PUBKEY, systemProgram: anchor.web3.SystemProgram.programId, state: await this.getStatePublicKey(), @@ -1746,7 +1817,6 @@ export class DriftClient { const { txSig } = await this.sendTransaction(tx, [], this.opts); return txSig; } - public async getUpdateUserCustomMarginRatioIx( marginRatio: number, subAccountId = 0 @@ -2555,7 +2625,6 @@ export class DriftClient { this.mustIncludeSpotMarketIndexes.add(spotMarketIndex); }); } - getRemainingAccounts(params: RemainingAccountParams): AccountMeta[] { const { oracleAccountMap, spotMarketAccountMap, perpMarketAccountMap } = this.getRemainingAccountMapsForUsers(params.userAccounts); @@ -3315,7 +3384,14 @@ export class DriftClient { referrerInfo?: ReferrerInfo, donateAmount?: BN, customMaxMarginRatio?: number, - poolId?: number + poolId?: number, + overrides?: { + /** + * Optional external wallet to deposit from. If provided, the deposit will be made + * from this wallet instead of the user's authority wallet. + */ + externalWallet?: PublicKey; + } ): Promise<{ ixs: TransactionInstruction[]; userAccountPublicKey: PublicKey; @@ -3326,17 +3402,20 @@ export class DriftClient { await this.getInitializeUserInstructions( subAccountId, name, - referrerInfo + referrerInfo, + overrides ); + // Check signed message orders account for the actual authority (account owner) const isSignedMsgUserOrdersAccountInitialized = - await this.isSignedMsgUserOrdersAccountInitialized(this.wallet.publicKey); + await this.isSignedMsgUserOrdersAccountInitialized(this.authority); if (!isSignedMsgUserOrdersAccountInitialized) { const [, initializeSignedMsgUserOrdersAccountIx] = await this.getInitializeSignedMsgUserOrdersAccountIx( - this.wallet.publicKey, - 8 + this.authority, + 8, + overrides ); ixs.push(initializeSignedMsgUserOrdersAccountIx); } @@ -3345,7 +3424,8 @@ export class DriftClient { const isSolMarket = spotMarket.mint.equals(WRAPPED_SOL_MINT); - const authority = this.wallet.publicKey; + // Use external wallet for deposit source if provided, otherwise use the wallet + const depositSource = overrides?.externalWallet ?? this.wallet.publicKey; const isFromSubaccount = fromSubAccountId !== null && @@ -3356,7 +3436,7 @@ export class DriftClient { const createWSOLTokenAccount = (isSolMarket && - userTokenAccount.equals(authority) && + userTokenAccount.equals(depositSource) && !isFromSubaccount) || !donateAmount.eq(ZERO); @@ -3365,7 +3445,13 @@ export class DriftClient { let wsolTokenAccount: PublicKey; if (createWSOLTokenAccount) { const { ixs: startIxs, pubkey } = - await this.getWrappedSolAccountCreationIxs(wSolAmount, true); + await this.getWrappedSolAccountCreationIxs( + wSolAmount, + true, + overrides?.externalWallet + ? { authority: overrides.externalWallet } + : undefined + ); wsolTokenAccount = pubkey; @@ -3408,14 +3494,17 @@ export class DriftClient { userTokenAccount, subAccountId, false, - false + false, + overrides?.externalWallet + ? { authority: overrides.externalWallet } + : undefined ); if (subAccountId === 0) { if ( !(await this.checkIfAccountExists(this.getUserStatsAccountPublicKey())) ) { - ixs.push(await this.getInitializeUserStatsIx()); + ixs.push(await this.getInitializeUserStatsIx(overrides)); } } ixs.push(initializeUserAccountIx); @@ -3446,12 +3535,13 @@ export class DriftClient { } // Close the wrapped sol account at the end of the transaction + // Return funds to the deposit source (external wallet if provided) if (createWSOLTokenAccount) { ixs.push( createCloseAccountInstruction( wsolTokenAccount, - authority, - authority, + depositSource, + depositSource, [] ) ); @@ -3462,7 +3552,6 @@ export class DriftClient { userAccountPublicKey, }; } - public async createInitializeUserAccountAndDepositCollateral( amount: BN, userTokenAccount: PublicKey, @@ -3474,7 +3563,10 @@ export class DriftClient { donateAmount?: BN, txParams?: TxParams, customMaxMarginRatio?: number, - poolId?: number + poolId?: number, + overrides?: { + externalWallet?: PublicKey; + } ): Promise<[Transaction | VersionedTransaction, PublicKey]> { const { ixs, userAccountPublicKey } = await this.createInitializeUserAccountAndDepositCollateralIxs( @@ -3487,7 +3579,8 @@ export class DriftClient { referrerInfo, donateAmount, customMaxMarginRatio, - poolId + poolId, + overrides ); const tx = await this.buildTransaction(ixs, txParams); @@ -3506,6 +3599,9 @@ export class DriftClient { * @param referrerInfo * @param donateAmount * @param txParams + * @param customMaxMarginRatio + * @param poolId + * @param overrides - Optional overrides including externalWallet for depositing from a different wallet * @returns */ public async initializeUserAccountAndDepositCollateral( @@ -3519,7 +3615,10 @@ export class DriftClient { donateAmount?: BN, txParams?: TxParams, customMaxMarginRatio?: number, - poolId?: number + poolId?: number, + overrides?: { + externalWallet?: PublicKey; + } ): Promise<[TransactionSignature, PublicKey]> { const [tx, userAccountPublicKey] = await this.createInitializeUserAccountAndDepositCollateral( @@ -3533,7 +3632,8 @@ export class DriftClient { donateAmount, txParams, customMaxMarginRatio, - poolId + poolId, + overrides ); const additionalSigners: Array = []; @@ -4179,27 +4279,52 @@ export class DriftClient { amount: BN, perpMarketIndex: number, subAccountId?: number, - txParams?: TxParams + txParams?: TxParams, + trySettle?: boolean, + noBuffer?: boolean ): Promise { - const { txSig } = await this.sendTransaction( - await this.buildTransaction( - await this.getTransferIsolatedPerpPositionDepositIx( - amount, - perpMarketIndex, - subAccountId - ), - txParams - ), - [], - this.opts + const ixs = []; + const tokenAmountDeposited = + this.getIsolatedPerpPositionTokenAmount(perpMarketIndex); + const transferIx = await this.getTransferIsolatedPerpPositionDepositIx( + amount, + perpMarketIndex, + subAccountId, + noBuffer ); + + const needsToSettle = + amount.lt(tokenAmountDeposited.neg()) || amount.eq(MIN_I64) || trySettle; + if (needsToSettle) { + const settleIx = await this.settleMultiplePNLsIx( + await getUserAccountPublicKey( + this.program.programId, + this.authority, + subAccountId ?? this.activeSubAccountId + ), + this.getUserAccount(subAccountId), + [perpMarketIndex], + SettlePnlMode.TRY_SETTLE + ); + ixs.push(settleIx); + } + + ixs.push(transferIx); + + const tx = await this.buildTransaction(ixs, txParams); + const { txSig } = await this.sendTransaction(tx, [], { + ...this.opts, + skipPreflight: true, + }); return txSig; } public async getTransferIsolatedPerpPositionDepositIx( amount: BN, perpMarketIndex: number, - subAccountId?: number + subAccountId?: number, + noAmountBuffer?: boolean, + signingAuthority?: PublicKey ): Promise { const userAccountPublicKey = await getUserAccountPublicKey( this.program.programId, @@ -4217,17 +4342,22 @@ export class DriftClient { readablePerpMarketIndex: [perpMarketIndex], }); + const amountWithBuffer = + noAmountBuffer || amount.eq(MIN_I64) + ? amount + : amount.add(amount.div(new BN(200))); // .5% buffer + return await this.program.instruction.transferIsolatedPerpPositionDeposit( spotMarketIndex, perpMarketIndex, - amount, + amountWithBuffer, { accounts: { state: await this.getStatePublicKey(), spotMarketVault: spotMarketAccount.vault, user: userAccountPublicKey, userStats: this.getUserStatsAccountPublicKey(), - authority: this.wallet.publicKey, + authority: signingAuthority ?? this.wallet.publicKey, }, remainingAccounts, } @@ -4241,20 +4371,83 @@ export class DriftClient { subAccountId?: number, txParams?: TxParams ): Promise { + const instructions = + await this.getWithdrawFromIsolatedPerpPositionIxsBundle( + amount, + perpMarketIndex, + subAccountId, + userTokenAccount + ); const { txSig } = await this.sendTransaction( - await this.buildTransaction( - await this.getWithdrawFromIsolatedPerpPositionIx( - amount, - perpMarketIndex, - userTokenAccount, - subAccountId - ), - txParams - ) + await this.buildTransaction(instructions, txParams) ); return txSig; } + public async getWithdrawFromIsolatedPerpPositionIxsBundle( + amount: BN, + perpMarketIndex: number, + subAccountId?: number, + userTokenAccount?: PublicKey + ): Promise { + const userAccountPublicKey = await getUserAccountPublicKey( + this.program.programId, + this.authority, + subAccountId ?? this.activeSubAccountId + ); + const userAccount = this.getUserAccount(subAccountId); + + const tokenAmountDeposited = + this.getIsolatedPerpPositionTokenAmount(perpMarketIndex); + const isolatedPositionUnrealizedPnl = calculateClaimablePnl( + this.getPerpMarketAccount(perpMarketIndex), + this.getSpotMarketAccount( + this.getPerpMarketAccount(perpMarketIndex).quoteSpotMarketIndex + ), + userAccount.perpPositions.find((p) => p.marketIndex === perpMarketIndex), + this.getOracleDataForSpotMarket( + this.getPerpMarketAccount(perpMarketIndex).quoteSpotMarketIndex + ) + ); + + const depositAmountPlusUnrealizedPnl = tokenAmountDeposited.add( + isolatedPositionUnrealizedPnl + ); + + const amountToWithdraw = amount.gt(depositAmountPlusUnrealizedPnl) + ? MIN_I64 // min i64 + : amount; + let associatedTokenAccount = userTokenAccount; + if (!associatedTokenAccount) { + const perpMarketAccount = this.getPerpMarketAccount(perpMarketIndex); + const quoteSpotMarketIndex = perpMarketAccount.quoteSpotMarketIndex; + associatedTokenAccount = await this.getAssociatedTokenAccount( + quoteSpotMarketIndex + ); + } + + const withdrawIx = await this.getWithdrawFromIsolatedPerpPositionIx( + amountToWithdraw, + perpMarketIndex, + associatedTokenAccount, + subAccountId + ); + const ixs = [withdrawIx]; + + const needsToSettle = + amount.gt(tokenAmountDeposited) && isolatedPositionUnrealizedPnl.gt(ZERO); + if (needsToSettle) { + const settleIx = await this.settleMultiplePNLsIx( + userAccountPublicKey, + userAccount, + [perpMarketIndex], + SettlePnlMode.TRY_SETTLE + ); + ixs.push(settleIx); + } + return ixs; + } + public async getWithdrawFromIsolatedPerpPositionIx( amount: BN, perpMarketIndex: number, @@ -4438,7 +4631,6 @@ export class DriftClient { } ); } - public async getRemovePerpLpSharesIx( marketIndex: number, sharesToBurn?: BN, @@ -4595,7 +4787,8 @@ export class DriftClient { referrerInfo?: ReferrerInfo, cancelExistingOrders?: boolean, settlePnl?: boolean, - positionMaxLev?: number + positionMaxLev?: number, + isolatedPositionDepositAmount?: BN ): Promise<{ cancelExistingOrdersTx?: Transaction | VersionedTransaction; settlePnlTx?: Transaction | VersionedTransaction; @@ -4623,18 +4816,25 @@ export class DriftClient { const txKeys = Object.keys(ixPromisesForTxs); - const marketOrderTxIxs = positionMaxLev - ? this.getPlaceOrdersAndSetPositionMaxLevIx( - [orderParams, ...bracketOrdersParams], - positionMaxLev, - userAccount.subAccountId - ) - : this.getPlaceOrdersIx( - [orderParams, ...bracketOrdersParams], - userAccount.subAccountId - ); + const preIxs: TransactionInstruction[] = await this.getPrePlaceOrderIxs( + orderParams, + userAccount, + { + positionMaxLev, + isolatedPositionDepositAmount, + } + ); - ixPromisesForTxs.marketOrderTx = marketOrderTxIxs; + ixPromisesForTxs.marketOrderTx = (async () => { + const placeOrdersIx = await this.getPlaceOrdersIx( + [orderParams, ...bracketOrdersParams], + userAccount.subAccountId + ); + if (preIxs.length) { + return [...preIxs, placeOrdersIx] as unknown as TransactionInstruction; + } + return placeOrdersIx; + })(); /* Cancel open orders in market if requested */ if (cancelExistingOrders && isVariant(orderParams.marketType, 'perp')) { @@ -4752,12 +4952,32 @@ export class DriftClient { public async placePerpOrder( orderParams: OptionalOrderParams, txParams?: TxParams, - subAccountId?: number + subAccountId?: number, + isolatedPositionDepositAmount?: BN ): Promise { + const preIxs: TransactionInstruction[] = []; + if ( + isolatedPositionDepositAmount?.gt?.(ZERO) && + this.isOrderIncreasingPosition(orderParams, subAccountId) + ) { + preIxs.push( + await this.getTransferIsolatedPerpPositionDepositIx( + isolatedPositionDepositAmount as BN, + orderParams.marketIndex, + subAccountId + ) + ); + } + const { txSig, slot } = await this.sendTransaction( await this.buildTransaction( await this.getPlacePerpOrderIx(orderParams, subAccountId), - txParams + txParams, + undefined, + undefined, + undefined, + undefined, + preIxs ), [], this.opts @@ -4945,13 +5165,31 @@ export class DriftClient { public async cancelOrder( orderId?: number, txParams?: TxParams, - subAccountId?: number + subAccountId?: number, + overrides?: { withdrawIsolatedDepositAmount?: BN } ): Promise { + const cancelIx = await this.getCancelOrderIx(orderId, subAccountId); + + const instructions: TransactionInstruction[] = [cancelIx]; + + if (overrides?.withdrawIsolatedDepositAmount !== undefined) { + const order = this.getOrder(orderId, subAccountId); + const perpMarketIndex = order?.marketIndex; + const withdrawAmount = overrides.withdrawIsolatedDepositAmount; + + if (withdrawAmount.gt(ZERO)) { + const withdrawIxs = + await this.getWithdrawFromIsolatedPerpPositionIxsBundle( + withdrawAmount, + perpMarketIndex, + subAccountId + ); + instructions.push(...withdrawIxs); + } + } + const { txSig } = await this.sendTransaction( - await this.buildTransaction( - await this.getCancelOrderIx(orderId, subAccountId), - txParams - ), + await this.buildTransaction(instructions, txParams), [], this.opts ); @@ -5185,7 +5423,8 @@ export class DriftClient { params: OrderParams[], txParams?: TxParams, subAccountId?: number, - optionalIxs?: TransactionInstruction[] + optionalIxs?: TransactionInstruction[], + isolatedPositionDepositAmount?: BN ): Promise { const { txSig } = await this.sendTransaction( ( @@ -5193,7 +5432,8 @@ export class DriftClient { params, txParams, subAccountId, - optionalIxs + optionalIxs, + isolatedPositionDepositAmount ) ).placeOrdersTx, [], @@ -5207,10 +5447,29 @@ export class DriftClient { params: OrderParams[], txParams?: TxParams, subAccountId?: number, - optionalIxs?: TransactionInstruction[] + optionalIxs?: TransactionInstruction[], + isolatedPositionDepositAmount?: BN ) { const lookupTableAccounts = await this.fetchAllLookupTableAccounts(); + const preIxs: TransactionInstruction[] = []; + if (params?.length === 1) { + const p = params[0]; + if ( + isVariant(p.marketType, 'perp') && + isolatedPositionDepositAmount?.gt?.(ZERO) && + this.isOrderIncreasingPosition(p, subAccountId) + ) { + preIxs.push( + await this.getTransferIsolatedPerpPositionDepositIx( + isolatedPositionDepositAmount as BN, + p.marketIndex, + subAccountId + ) + ); + } + } + const tx = await this.buildTransaction( await this.getPlaceOrdersIx(params, subAccountId), txParams, @@ -5218,14 +5477,13 @@ export class DriftClient { lookupTableAccounts, undefined, undefined, - optionalIxs + [...preIxs, ...(optionalIxs ?? [])] ); return { placeOrdersTx: tx, }; } - public async getPlaceOrdersIx( params: OptionalOrderParams[], subAccountId?: number, @@ -5334,8 +5592,7 @@ export class DriftClient { const marginRatio = Math.floor( (1 / positionMaxLev) * MARGIN_PRECISION.toNumber() ); - - // TODO: Handle multiple markets? + // Keep existing behavior but note: prefer using getPostPlaceOrderIxs path const setPositionMaxLevIxs = await this.getUpdateUserPerpPositionCustomMarginRatioIx( readablePerpMarketIndex[0], @@ -5346,6 +5603,96 @@ export class DriftClient { return [placeOrdersIxs, setPositionMaxLevIxs]; } + /** + * Place scale orders - multiple limit orders distributed across a price range + * @param params Scale order parameters + * @param txParams Optional transaction parameters + * @param subAccountId Optional sub account ID + * @returns Transaction signature + */ + public async placeScaleOrders( + params: ScaleOrderParams, + txParams?: TxParams, + subAccountId?: number + ): Promise { + const { txSig } = await this.sendTransaction( + (await this.preparePlaceScaleOrdersTx(params, txParams, subAccountId)) + .placeScaleOrdersTx, + [], + this.opts, + false + ); + return txSig; + } + + public async preparePlaceScaleOrdersTx( + params: ScaleOrderParams, + txParams?: TxParams, + subAccountId?: number + ) { + const lookupTableAccounts = await this.fetchAllLookupTableAccounts(); + + const tx = await this.buildTransaction( + await this.getPlaceScaleOrdersIx(params, subAccountId), + txParams, + undefined, + lookupTableAccounts + ); + + return { + placeScaleOrdersTx: tx, + }; + } + + public async getPlaceScaleOrdersIx( + params: ScaleOrderParams, + subAccountId?: number + ): Promise { + const user = await this.getUserAccountPublicKey(subAccountId); + + const isPerp = isVariant(params.marketType, 'perp'); + + const remainingAccounts = this.getRemainingAccounts({ + userAccounts: [this.getUserAccount(subAccountId)], + readablePerpMarketIndex: isPerp ? [params.marketIndex] : [], + readableSpotMarketIndexes: isPerp ? [] : [params.marketIndex], + useMarketLastSlotCache: true, + }); + + if (isUpdateHighLeverageMode(params.bitFlags)) { + remainingAccounts.push({ + pubkey: getHighLeverageModeConfigPublicKey(this.program.programId), + isWritable: true, + isSigner: false, + }); + } + + const formattedParams = { + marketType: params.marketType, + direction: params.direction, + marketIndex: params.marketIndex, + totalBaseAssetAmount: params.totalBaseAssetAmount, + startPrice: params.startPrice, + endPrice: params.endPrice, + orderCount: params.orderCount, + sizeDistribution: params.sizeDistribution, + reduceOnly: params.reduceOnly, + postOnly: params.postOnly, + bitFlags: params.bitFlags, + maxTs: params.maxTs, + }; + + return await this.program.instruction.placeScaleOrders(formattedParams, { + accounts: { + state: await this.getStatePublicKey(), + user, + userStats: this.getUserStatsAccountPublicKey(), + authority: this.wallet.publicKey, + }, + remainingAccounts, + }); + } + public async fillPerpOrder( userAccountPublicKey: PublicKey, user: UserAccount, @@ -7053,7 +7400,6 @@ export class DriftClient { this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot); return txSig; } - public async preparePlaceAndTakePerpOrderWithAdditionalOrders( orderParams: OptionalOrderParams, makerInfo?: MakerInfo | MakerInfo[], @@ -7065,7 +7411,8 @@ export class DriftClient { settlePnl?: boolean, exitEarlyIfSimFails?: boolean, auctionDurationPercentage?: number, - optionalIxs?: TransactionInstruction[] + optionalIxs?: TransactionInstruction[], + isolatedPositionDepositAmount?: BN ): Promise<{ placeAndTakeTx: Transaction | VersionedTransaction; cancelExistingOrdersTx: Transaction | VersionedTransaction; @@ -7099,6 +7446,20 @@ export class DriftClient { subAccountId ); + if ( + isVariant(orderParams.marketType, 'perp') && + isolatedPositionDepositAmount?.gt?.(ZERO) && + this.isOrderIncreasingPosition(orderParams, subAccountId) + ) { + placeAndTakeIxs.push( + await this.getTransferIsolatedPerpPositionDepositIx( + isolatedPositionDepositAmount as BN, + orderParams.marketIndex, + subAccountId + ) + ); + } + placeAndTakeIxs.push(placeAndTakeIx); if (bracketOrdersParams.length > 0) { @@ -7109,6 +7470,11 @@ export class DriftClient { placeAndTakeIxs.push(bracketOrdersIx); } + // Optional extra ixs can be appended at the front + if (optionalIxs?.length) { + placeAndTakeIxs.unshift(...optionalIxs); + } + const shouldUseSimulationComputeUnits = txParams?.useSimulatedComputeUnits; const shouldExitIfSimulationFails = exitEarlyIfSimFails; @@ -7916,7 +8282,6 @@ export class DriftClient { this.spotMarketLastSlotCache.set(QUOTE_SPOT_MARKET_INDEX, slot); return txSig; } - public async getPlaceAndTakeSpotOrderIx( orderParams: OptionalOrderParams, fulfillmentConfig?: SerumV3FulfillmentConfigAccount, @@ -8379,7 +8744,6 @@ export class DriftClient { bitFlags?: number; policy?: ModifyOrderPolicy; maxTs?: BN; - txParams?: TxParams; }, subAccountId?: number ): Promise { @@ -8905,7 +9269,6 @@ export class DriftClient { this.perpMarketLastSlotCache.set(marketIndex, slot); return txSig; } - public async getLiquidatePerpIx( userAccountPublicKey: PublicKey, userAccount: UserAccount, @@ -9696,7 +10059,6 @@ export class DriftClient { } ); } - public async resolveSpotBankruptcy( userAccountPublicKey: PublicKey, userAccount: UserAccount, @@ -10533,7 +10895,6 @@ export class DriftClient { const { txSig } = await this.sendTransaction(tx, [], this.opts); return txSig; } - public async getSettleRevenueToInsuranceFundIx( spotMarketIndex: number ): Promise { @@ -11149,7 +11510,7 @@ export class DriftClient { if (this.wormholeProgram === undefined) { this.wormholeProgram = new Program( - wormholeCoreBridgeIdl, + WORMHOLE_CORE_BRIDGE_SOLANA_IDL, DEFAULT_WORMHOLE_PROGRAM_ID, this.provider ); @@ -11337,7 +11698,6 @@ export class DriftClient { ); return config as ProtectedMakerModeConfig; } - public async updateUserProtectedMakerOrders( subAccountId: number, protectedOrders: boolean, @@ -12731,4 +13091,24 @@ export class DriftClient { forceVersionedTransaction, }); } + + isOrderIncreasingPosition( + orderParams: OptionalOrderParams, + subAccountId: number + ): boolean { + const userAccount = this.getUserAccount(subAccountId); + const perpPosition = userAccount.perpPositions.find( + (p) => p.marketIndex === orderParams.marketIndex + ); + if (!perpPosition) return true; + + const currentBase = perpPosition.baseAssetAmount; + if (currentBase.eq(ZERO)) return true; + + const orderBaseAmount = isVariant(orderParams.direction, 'long') + ? orderParams.baseAssetAmount + : orderParams.baseAssetAmount.neg(); + + return currentBase.add(orderBaseAmount).abs().gt(currentBase.abs()); + } } diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index ae07109fda..43a3d9169b 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -1,5 +1,5 @@ { - "version": "2.152.0", + "version": "2.156.0", "name": "drift", "instructions": [ { @@ -1382,6 +1382,34 @@ } ] }, + { + "name": "placeScaleOrders", + "accounts": [ + { + "name": "state", + "isMut": false, + "isSigner": false + }, + { + "name": "user", + "isMut": true, + "isSigner": false + }, + { + "name": "authority", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "params", + "type": { + "defined": "ScaleOrderParams" + } + } + ] + }, { "name": "beginSwap", "accounts": [ @@ -13260,6 +13288,102 @@ ] } }, + { + "name": "ScaleOrderParams", + "docs": [ + "Parameters for placing scale orders - multiple limit orders distributed across a price range" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "marketType", + "type": { + "defined": "MarketType" + } + }, + { + "name": "direction", + "type": { + "defined": "PositionDirection" + } + }, + { + "name": "marketIndex", + "type": "u16" + }, + { + "name": "totalBaseAssetAmount", + "docs": [ + "Total base asset amount to distribute across all orders" + ], + "type": "u64" + }, + { + "name": "startPrice", + "docs": [ + "Starting price for the scale (in PRICE_PRECISION)" + ], + "type": "u64" + }, + { + "name": "endPrice", + "docs": [ + "Ending price for the scale (in PRICE_PRECISION)" + ], + "type": "u64" + }, + { + "name": "orderCount", + "docs": [ + "Number of orders to place (min 2, max 32)" + ], + "type": "u8" + }, + { + "name": "sizeDistribution", + "docs": [ + "How to distribute sizes across orders" + ], + "type": { + "defined": "SizeDistribution" + } + }, + { + "name": "reduceOnly", + "docs": [ + "Whether orders should be reduce-only" + ], + "type": "bool" + }, + { + "name": "postOnly", + "docs": [ + "Post-only setting for all orders" + ], + "type": { + "defined": "PostOnlyParam" + } + }, + { + "name": "bitFlags", + "docs": [ + "Bit flags (e.g., for high leverage mode)" + ], + "type": "u8" + }, + { + "name": "maxTs", + "docs": [ + "Maximum timestamp for orders to be valid" + ], + "type": { + "option": "i64" + } + } + ] + } + }, { "name": "InsuranceClaim", "type": { @@ -15607,6 +15731,26 @@ ] } }, + { + "name": "SizeDistribution", + "docs": [ + "How to distribute order sizes across scale orders" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "Flat" + }, + { + "name": "Ascending" + }, + { + "name": "Descending" + } + ] + } + }, { "name": "PerpOperation", "type": { @@ -19824,9 +19968,16 @@ "code": 6345, "name": "InvalidIsolatedPerpMarket", "msg": "Invalid Isolated Perp Market" + }, + { + "code": 6346, + "name": "InvalidOrderScaleOrderCount", + "msg": "Invalid scale order count - must be between 2 and 10" + }, + { + "code": 6347, + "name": "InvalidOrderScalePriceRange", + "msg": "Invalid scale order price range" } - ], - "metadata": { - "address": "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH" - } + ] } \ No newline at end of file diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 6c73a0de63..392178a202 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -34,6 +34,13 @@ export * from './accounts/types'; export * from './addresses/pda'; export * from './adminClient'; export * from './assert/assert'; +export { + PythLazerSubscriber, + PythLazerPriceFeedArray, + PriceUpdateAccount, + PythSolanaReceiver, + WormholeCoreBridgeSolana, +} from './pyth'; export * from './testClient'; export * from './user'; export * from './userConfig'; diff --git a/sdk/src/jupiter/jupiterClient.ts b/sdk/src/jupiter/jupiterClient.ts index a2a2ba483b..b6be1b957e 100644 --- a/sdk/src/jupiter/jupiterClient.ts +++ b/sdk/src/jupiter/jupiterClient.ts @@ -224,16 +224,48 @@ export interface QuoteResponse { } export const RECOMMENDED_JUPITER_API_VERSION = '/v1'; -export const RECOMMENDED_JUPITER_API = 'https://lite-api.jup.ag/swap'; +/** @deprecated Use RECOMMENDED_JUPITER_API instead. lite-api.jup.ag requires migration to api.jup.ag with API key. */ +export const LEGACY_JUPITER_API = 'https://lite-api.jup.ag/swap'; +export const RECOMMENDED_JUPITER_API = 'https://api.jup.ag/swap'; export class JupiterClient { url: string; connection: Connection; lookupTableCahce = new Map(); + private apiKey?: string; - constructor({ connection, url }: { connection: Connection; url?: string }) { + /** + * Create a Jupiter client + * @param connection - Solana connection + * @param url - Optional custom API URL. Defaults to https://api.jup.ag/swap + * @param apiKey - API key for Jupiter API. Required for api.jup.ag (free tier available at https://portal.jup.ag) + */ + constructor({ + connection, + url, + apiKey, + }: { + connection: Connection; + url?: string; + apiKey?: string; + }) { this.connection = connection; this.url = url ?? RECOMMENDED_JUPITER_API; + this.apiKey = apiKey; + } + + /** + * Get the headers for API requests, including API key if configured + */ + private getHeaders(contentType?: string): Record { + const headers: Record = {}; + if (contentType) { + headers['Content-Type'] = contentType; + } + if (this.apiKey) { + headers['x-api-key'] = this.apiKey; + } + return headers; } /** @@ -289,11 +321,17 @@ export class JupiterClient { params.delete('maxAccounts'); } const apiVersionParam = - this.url === RECOMMENDED_JUPITER_API + this.url === RECOMMENDED_JUPITER_API || this.url === LEGACY_JUPITER_API ? RECOMMENDED_JUPITER_API_VERSION : ''; + const headers = this.getHeaders(); + const fetchOptions: RequestInit = + Object.keys(headers).length > 0 ? { headers } : {}; const quote = await ( - await fetch(`${this.url}${apiVersionParam}/quote?${params.toString()}`) + await fetch( + `${this.url}${apiVersionParam}/quote?${params.toString()}`, + fetchOptions + ) ).json(); return quote as QuoteResponse; } @@ -318,15 +356,13 @@ export class JupiterClient { } const apiVersionParam = - this.url === RECOMMENDED_JUPITER_API + this.url === RECOMMENDED_JUPITER_API || this.url === LEGACY_JUPITER_API ? RECOMMENDED_JUPITER_API_VERSION : ''; const resp = await ( await fetch(`${this.url}${apiVersionParam}/swap`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: this.getHeaders('application/json'), body: JSON.stringify({ quoteResponse: quote, userPublicKey, diff --git a/sdk/src/margin/README.md b/sdk/src/margin/README.md new file mode 100644 index 0000000000..68174728e0 --- /dev/null +++ b/sdk/src/margin/README.md @@ -0,0 +1,139 @@ +## Margin Calculation Snapshot (SDK) + +This document describes the single-source-of-truth margin engine in the SDK that mirrors the on-chain `MarginCalculation` and related semantics. The goal is to compute an immutable snapshot in one pass and have existing `User` getters delegate to it, eliminating duplicative work across getters and UI hooks while maintaining parity with the program. + +### Alignment with on-chain + +- The SDK snapshot shape mirrors `programs/drift/src/state/margin_calculation.rs` field-for-field. +- The inputs and ordering mirror `calculate_margin_requirement_and_total_collateral_and_liability_info` in `programs/drift/src/math/margin.rs`. +- Isolated positions are represented as `isolatedMarginCalculations` keyed by perp `marketIndex`, matching program logic. + +### Core SDK types (shape parity) + +```ts +// Types reflect on-chain names and numeric signs +import { MarketType } from './types'; + +export type MarginCategory = 'Initial' | 'Maintenance' | 'Fill'; + +export type MarginCalculationMode = + | { type: 'Standard' } + | { type: 'Liquidation' }; + +export class MarketIdentifier { + marketType: MarketType; + marketIndex: number; + + static spot(marketIndex: number): MarketIdentifier; + static perp(marketIndex: number): MarketIdentifier; + equals(other: MarketIdentifier | undefined): boolean; +} + +export class MarginContext { + marginType: MarginCategory; + mode: MarginCalculationMode; + strict: boolean; + ignoreInvalidDepositOracles: boolean; + isolatedMarginBuffers: Map; + crossMarginBuffer: BN; + + // Factory methods + static standard(marginType: MarginCategory): MarginContext; + static liquidation( + crossMarginBuffer: BN, + isolatedMarginBuffers: Map + ): MarginContext; + + // Builder methods (return this for chaining) + strictMode(strict: boolean): this; + ignoreInvalidDeposits(ignore: boolean): this; + setCrossMarginBuffer(crossMarginBuffer: BN): this; + setIsolatedMarginBuffers(isolatedMarginBuffers: Map): this; + setIsolatedMarginBuffer(marketIndex: number, isolatedMarginBuffer: BN): this; +} + +export class IsolatedMarginCalculation { + marginRequirement: BN; // u128 + totalCollateral: BN; // i128 (deposit + pnl) + totalCollateralBuffer: BN; // i128 + marginRequirementPlusBuffer: BN; // u128 + + getTotalCollateralPlusBuffer(): BN; + meetsMarginRequirement(): boolean; + meetsMarginRequirementWithBuffer(): boolean; + marginShortage(): BN; +} + +export class MarginCalculation { + context: MarginContext; + + totalCollateral: BN; // i128 + totalCollateralBuffer: BN; // i128 + marginRequirement: BN; // u128 + marginRequirementPlusBuffer: BN; // u128 + + isolatedMarginCalculations: Map; + + totalPerpLiabilityValue: BN; // u128 + + // Cross margin helpers + getCrossTotalCollateralPlusBuffer(): BN; + meetsCrossMarginRequirement(): boolean; + meetsCrossMarginRequirementWithBuffer(): boolean; + getCrossFreeCollateral(): BN; + + // Combined (cross + isolated) helpers + meetsMarginRequirement(): boolean; + meetsMarginRequirementWithBuffer(): boolean; + + // Isolated margin helpers + getIsolatedFreeCollateral(marketIndex: number): BN; + getIsolatedMarginCalculation(marketIndex: number): IsolatedMarginCalculation | undefined; + hasIsolatedMarginCalculation(marketIndex: number): boolean; +} +``` + +### Computation model (on-demand) + +- The SDK computes the snapshot on-demand when `getMarginCalculation(...)` is called. +- No event-driven recomputation by default (oracle prices can change every slot; recomputing every update would be wasteful). +- Callers (UI/bots) decide polling frequency (e.g., UI can refresh every ~1s on active trade forms). + +### User integration + +`User` class provides the primary entrypoint: + +```ts +public getMarginCalculation( + marginCategory: MarginCategory = 'Initial', + opts?: { + strict?: boolean; // mirror StrictOraclePrice application + includeOpenOrders?: boolean; // include open orders in margin calc + enteringHighLeverage?: boolean; // entering high leverage mode + liquidationBufferMap?: Map; // margin buffer for liquidation mode + } +): MarginCalculation; +``` + +Existing getters delegate to the snapshot to avoid duplicate work: +- `getTotalCollateral()` → `snapshot.totalCollateral` +- `getMarginRequirement(mode)` → `snapshot.marginRequirement` +- `getFreeCollateral()` → `snapshot.getCrossFreeCollateral()` +- Per-market isolated FC → `snapshot.getIsolatedFreeCollateral(marketIndex)` + +### UI compatibility + +- All existing `User` getters remain and delegate to the snapshot, so current UI keeps working without call-site changes. +- New consumers can call `user.getMarginCalculation()` to access isolated breakdowns via `isolatedMarginCalculations`. + +### Testing and parity + +- Golden tests comparing SDK snapshot against program outputs (cross and isolated, edge cases). +- Keep math/rounding identical to program (ordering, buffers, funding, open-order IM, oracle strictness). + +### Migration plan (brief) + +1. Implement `types` and `engine` with strict parity; land behind a feature flag. +2. Add `user.getMarginCalculation()` and delegate legacy getters. +3. Optionally update UI hooks to read richer fields; not required for compatibility. +4. Expand parity tests; enable by default after validation. diff --git a/sdk/src/marginCalculation.ts b/sdk/src/marginCalculation.ts index f919233f04..bff3d2e605 100644 --- a/sdk/src/marginCalculation.ts +++ b/sdk/src/marginCalculation.ts @@ -131,15 +131,7 @@ export class MarginCalculation { marginRequirement: BN; marginRequirementPlusBuffer: BN; isolatedMarginCalculations: Map; - allDepositOraclesValid: boolean; - allLiabilityOraclesValid: boolean; - withPerpIsolatedLiability: boolean; - withSpotIsolatedLiability: boolean; totalPerpLiabilityValue: BN; - trackedMarketMarginRequirement: BN; - fuelDeposits: number; - fuelBorrows: number; - fuelPositions: number; constructor(context: MarginContext) { this.context = context; @@ -148,15 +140,7 @@ export class MarginCalculation { this.marginRequirement = ZERO; this.marginRequirementPlusBuffer = ZERO; this.isolatedMarginCalculations = new Map(); - this.allDepositOraclesValid = true; - this.allLiabilityOraclesValid = true; - this.withPerpIsolatedLiability = false; - this.withSpotIsolatedLiability = false; this.totalPerpLiabilityValue = ZERO; - this.trackedMarketMarginRequirement = ZERO; - this.fuelDeposits = 0; - this.fuelBorrows = 0; - this.fuelPositions = 0; } addCrossMarginTotalCollateral(delta: BN): void { @@ -216,22 +200,6 @@ export class MarginCalculation { this.totalPerpLiabilityValue.add(perpLiabilityValue); } - updateAllDepositOraclesValid(valid: boolean): void { - this.allDepositOraclesValid = this.allDepositOraclesValid && valid; - } - - updateAllLiabilityOraclesValid(valid: boolean): void { - this.allLiabilityOraclesValid = this.allLiabilityOraclesValid && valid; - } - - updateWithSpotIsolatedLiability(isolated: boolean): void { - this.withSpotIsolatedLiability = this.withSpotIsolatedLiability || isolated; - } - - updateWithPerpIsolatedLiability(isolated: boolean): void { - this.withPerpIsolatedLiability = this.withPerpIsolatedLiability || isolated; - } - getCrossTotalCollateralPlusBuffer(): BN { return this.totalCollateral.add(this.totalCollateralBuffer); } diff --git a/sdk/src/math/margin.ts b/sdk/src/math/margin.ts index c65912eab7..ea29346f06 100644 --- a/sdk/src/math/margin.ts +++ b/sdk/src/math/margin.ts @@ -166,10 +166,10 @@ export function calculateWorstCasePerpLiabilityValue( perpPosition: PerpPosition, perpMarket: PerpMarketAccount, oraclePrice: BN, - includeOpenOrders = true + includeOpenOrders: boolean = true ): { worstCaseBaseAssetAmount: BN; worstCaseLiabilityValue: BN } { const isPredictionMarket = isVariant(perpMarket.contractType, 'prediction'); - + // return early if no open orders required if (!includeOpenOrders) { return { worstCaseBaseAssetAmount: perpPosition.baseAssetAmount, @@ -180,7 +180,6 @@ export function calculateWorstCasePerpLiabilityValue( ), }; } - const allBids = perpPosition.baseAssetAmount.add(perpPosition.openBids); const allAsks = perpPosition.baseAssetAmount.add(perpPosition.openAsks); diff --git a/sdk/src/math/position.ts b/sdk/src/math/position.ts index 3db5007a20..d0c3a16a0d 100644 --- a/sdk/src/math/position.ts +++ b/sdk/src/math/position.ts @@ -14,6 +14,7 @@ import { PositionDirection, PerpPosition, SpotMarketAccount, + PositionFlag, } from '../types'; import { calculateUpdatedAMM, @@ -127,7 +128,6 @@ export function calculatePositionPNL( if (withFunding) { const fundingRatePnL = calculateUnsettledFundingPnl(market, perpPosition); - pnl = pnl.add(fundingRatePnL); } @@ -244,7 +244,17 @@ export function positionIsAvailable(position: PerpPosition): boolean { position.baseAssetAmount.eq(ZERO) && position.openOrders === 0 && position.quoteAssetAmount.eq(ZERO) && - position.lpShares.eq(ZERO) + position.lpShares.eq(ZERO) && + position.isolatedPositionScaledBalance.eq(ZERO) && + !positionIsBeingLiquidated(position) + ); +} + +export function positionIsBeingLiquidated(position: PerpPosition): boolean { + return ( + (position.positionFlag & + (PositionFlag.BeingLiquidated | PositionFlag.Bankruptcy)) > + 0 ); } diff --git a/sdk/src/math/spotBalance.ts b/sdk/src/math/spotBalance.ts index cd6bb053d7..f33ae5729c 100644 --- a/sdk/src/math/spotBalance.ts +++ b/sdk/src/math/spotBalance.ts @@ -68,7 +68,6 @@ export function getTokenAmount( balanceType: SpotBalanceType ): BN { const precisionDecrease = TEN.pow(new BN(19 - spotMarket.decimals)); - if (isVariant(balanceType, 'deposit')) { return balanceAmount .mul(spotMarket.cumulativeDepositInterest) diff --git a/sdk/src/oracles/pythPullClient.ts b/sdk/src/oracles/pythPullClient.ts index 0359b35928..100c010a71 100644 --- a/sdk/src/oracles/pythPullClient.ts +++ b/sdk/src/oracles/pythPullClient.ts @@ -7,13 +7,12 @@ import { QUOTE_PRECISION, TEN, } from '../constants/numericConstants'; -import { - PythSolanaReceiverProgram, - pythSolanaReceiverIdl, -} from '@pythnetwork/pyth-solana-receiver'; -import { PriceUpdateAccount } from '@pythnetwork/pyth-solana-receiver/lib/PythSolanaReceiver'; + +import pythSolanaReceiverIdl from '../idl/pyth_solana_receiver.json'; +import { PythSolanaReceiver as PythSolanaReceiverProgram } from '../pyth'; import { DRIFT_ORACLE_RECEIVER_ID } from '../config'; import { Wallet } from '../wallet'; +import { PriceUpdateAccount } from '../pyth'; export class PythPullClient implements OracleClient { private connection: Connection; diff --git a/sdk/src/pyth/constants.ts b/sdk/src/pyth/constants.ts new file mode 100644 index 0000000000..89db604ea9 --- /dev/null +++ b/sdk/src/pyth/constants.ts @@ -0,0 +1,9 @@ +import { PublicKey } from '@solana/web3.js'; + +export const DEFAULT_WORMHOLE_PROGRAM_ID = new PublicKey( + 'HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ' +); + +export const DEFAULT_RECEIVER_PROGRAM_ID = new PublicKey( + 'rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ' +); diff --git a/sdk/src/pyth/index.ts b/sdk/src/pyth/index.ts new file mode 100644 index 0000000000..b8525ae371 --- /dev/null +++ b/sdk/src/pyth/index.ts @@ -0,0 +1,15 @@ +export { + WormholeCoreBridgeSolana, + WORMHOLE_CORE_BRIDGE_SOLANA_IDL, + PythSolanaReceiver, + PriceUpdateAccount, +} from './types'; +export { + DEFAULT_WORMHOLE_PROGRAM_ID, + DEFAULT_RECEIVER_PROGRAM_ID, +} from './constants'; +export { getGuardianSetPda } from './utils'; +export { + PythLazerSubscriber, + PythLazerPriceFeedArray, +} from './pythLazerSubscriber'; diff --git a/sdk/src/pyth/pythLazerSubscriber.ts b/sdk/src/pyth/pythLazerSubscriber.ts new file mode 100644 index 0000000000..2059805020 --- /dev/null +++ b/sdk/src/pyth/pythLazerSubscriber.ts @@ -0,0 +1,342 @@ +import { Channel, PythLazerClient } from '@pythnetwork/pyth-lazer-sdk'; +import { DriftEnv } from '../config'; +import { PerpMarkets } from '../constants/perpMarkets'; + +/** + * Configuration for a group of Pyth Lazer price feeds. + */ +export type PythLazerPriceFeedArray = { + /** Optional channel for update frequency (e.g., 'fixed_rate@200ms') */ + channel?: Channel; + /** Array of Pyth Lazer price feed IDs to subscribe to */ + priceFeedIds: number[]; +}; + +type FeedSymbolInfo = { + name: string; + state: string; +}; + +/** + * Manages subscriptions to Pyth Lazer price feeds and provides access to real-time price data. + * Automatically filters out non-stable feeds and handles reconnection logic. + */ +export class PythLazerSubscriber { + private static readonly SYMBOLS_API_URL = + 'https://history.pyth-lazer.dourolabs.app/history/v1/symbols'; + private symbolsCache: Map | null = null; + private pythLazerClient?: PythLazerClient; + feedIdChunkToPriceMessage: Map = new Map(); + feedIdToPrice: Map = new Map(); + feedIdHashToFeedIds: Map = new Map(); + subscriptionIdsToFeedIdsHash: Map = new Map(); + allSubscribedIds: number[] = []; + + timeoutId?: NodeJS.Timeout; + receivingData = false; + isUnsubscribing = false; + + marketIndextoPriceFeedIdChunk: Map = new Map(); + marketIndextoPriceFeedId: Map = new Map(); + + /** + * Creates a new PythLazerSubscriber instance. + * @param endpoints - Array of WebSocket endpoint URLs for Pyth Lazer + * @param token - Authentication token for Pyth Lazer API + * @param priceFeedArrays - Array of price feed configurations to subscribe to + * @param env - Drift environment (mainnet-beta, devnet, etc.) + * @param resubTimeoutMs - Milliseconds to wait before resubscribing on data timeout + * @param sdkLogging - Whether to log Pyth SDK logs to the console. This is very noisy but could be useful for debugging. + */ + constructor( + private endpoints: string[], + private token: string, + private priceFeedArrays: PythLazerPriceFeedArray[], + env: DriftEnv = 'devnet', + private resubTimeoutMs: number = 2000, + private sdkLogging: boolean = false + ) { + const markets = PerpMarkets[env].filter( + (market) => market.pythLazerId !== undefined + ); + + this.allSubscribedIds = this.priceFeedArrays + .map((array) => array.priceFeedIds) + .flat(); + + for (const priceFeedIds of priceFeedArrays) { + const filteredMarkets = markets.filter((market) => + priceFeedIds.priceFeedIds.includes(market.pythLazerId!) + ); + for (const market of filteredMarkets) { + this.marketIndextoPriceFeedIdChunk.set( + market.marketIndex, + priceFeedIds.priceFeedIds + ); + this.marketIndextoPriceFeedId.set( + market.marketIndex, + market.pythLazerId! + ); + } + } + } + + private async fetchSymbolsIfNeeded(): Promise { + if (this.symbolsCache !== null) return; + + try { + const response = await fetch(PythLazerSubscriber.SYMBOLS_API_URL); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const symbols = await response.json(); + + this.symbolsCache = new Map(); + for (const symbol of symbols) { + this.symbolsCache.set(symbol.pyth_lazer_id, { + name: symbol.name, + state: symbol.state, + }); + } + } catch (error) { + console.warn( + `Failed to fetch Pyth Lazer symbols, proceeding with all feeds: ${error}` + ); + this.symbolsCache = new Map(); // Empty map = no filtering + } + } + + private filterStableFeeds(feedIds: number[]): number[] { + if (this.symbolsCache === null || this.symbolsCache.size === 0) { + return feedIds; // No filtering if cache unavailable + } + + return feedIds.filter((feedId) => { + const info = this.symbolsCache!.get(feedId); + if (!info) { + console.warn( + `Feed ID ${feedId} not found in symbols API, including anyway` + ); + return true; + } + if (info.state !== 'stable') { + console.warn( + `Removing feed ID ${feedId} (${info.name}) - state is "${info.state}", not "stable"` + ); + return false; + } + return true; + }); + } + + /** + * Subscribes to Pyth Lazer price feeds. Automatically filters out non-stable feeds + * and establishes WebSocket connections for real-time price updates. + */ + async subscribe() { + await this.fetchSymbolsIfNeeded(); + + this.pythLazerClient = await PythLazerClient.create({ + token: this.token, + logger: this.sdkLogging ? console : undefined, + webSocketPoolConfig: { + urls: this.endpoints, + numConnections: 4, // Optionally specify number of parallel redundant connections to reduce the chance of dropped messages. The connections will round-robin across the provided URLs. Default is 4. + onError: (error) => { + console.error('⛔️ PythLazerClient error:', error.message); + }, + onWebSocketError: (error) => { + console.error('⛔️ WebSocket error:', error.message); + }, + onWebSocketPoolError: (error) => { + console.error('⛔️ WebSocket pool error:', error.message); + }, + // Optional configuration for resilient WebSocket connections + rwsConfig: { + heartbeatTimeoutDurationMs: 5000, // Optional heartbeat timeout duration in milliseconds + maxRetryDelayMs: 1000, // Optional maximum retry delay in milliseconds + logAfterRetryCount: 10, // Optional log after how many retries + }, + }, + }); + // Reset allSubscribedIds to rebuild with only stable feeds + this.allSubscribedIds = []; + + let subscriptionId = 1; + for (const priceFeedArray of this.priceFeedArrays) { + const filteredFeedIds = this.filterStableFeeds( + priceFeedArray.priceFeedIds + ); + + if (filteredFeedIds.length === 0) { + console.warn( + `All feeds filtered out for subscription ${subscriptionId}, skipping` + ); + continue; + } + + // Update allSubscribedIds with only stable feeds + this.allSubscribedIds.push(...filteredFeedIds); + + const feedIdsHash = this.hash(filteredFeedIds); + this.feedIdHashToFeedIds.set(feedIdsHash, filteredFeedIds); + this.subscriptionIdsToFeedIdsHash.set(subscriptionId, feedIdsHash); + + // Update marketIndextoPriceFeedIdChunk to use filtered feeds + for (const [ + marketIndex, + chunk, + ] of this.marketIndextoPriceFeedIdChunk.entries()) { + if (this.hash(chunk) === this.hash(priceFeedArray.priceFeedIds)) { + this.marketIndextoPriceFeedIdChunk.set(marketIndex, filteredFeedIds); + } + } + + // Remove entries from marketIndextoPriceFeedId for filtered-out feeds + for (const [ + marketIndex, + feedId, + ] of this.marketIndextoPriceFeedId.entries()) { + if ( + !filteredFeedIds.includes(feedId) && + priceFeedArray.priceFeedIds.includes(feedId) + ) { + this.marketIndextoPriceFeedId.delete(marketIndex); + this.marketIndextoPriceFeedIdChunk.delete(marketIndex); + } + } + + this.pythLazerClient.addMessageListener((message) => { + this.receivingData = true; + clearTimeout(this.timeoutId); + switch (message.type) { + case 'json': { + if (message.value.type == 'streamUpdated') { + if (message.value.solana?.data) { + this.feedIdChunkToPriceMessage.set( + this.subscriptionIdsToFeedIdsHash.get( + message.value.subscriptionId + )!, + message.value.solana.data + ); + } + if (message.value.parsed?.priceFeeds) { + for (const priceFeed of message.value.parsed.priceFeeds) { + const price = + Number(priceFeed.price!) * + Math.pow(10, Number(priceFeed.exponent!)); + this.feedIdToPrice.set(priceFeed.priceFeedId, price); + } + } + } + break; + } + default: { + break; + } + } + this.setTimeout(); + }); + + this.pythLazerClient.send({ + type: 'subscribe', + subscriptionId, + priceFeedIds: filteredFeedIds, + properties: ['price', 'bestAskPrice', 'bestBidPrice', 'exponent'], + formats: ['solana'], + deliveryFormat: 'json', + channel: priceFeedArray.channel ?? ('fixed_rate@200ms' as Channel), + jsonBinaryEncoding: 'hex', + }); + subscriptionId++; + } + + this.receivingData = true; + this.setTimeout(); + } + + protected setTimeout(): void { + this.timeoutId = setTimeout(async () => { + if (this.isUnsubscribing) { + // If we are in the process of unsubscribing, do not attempt to resubscribe + return; + } + + if (this.receivingData) { + console.log(`No ws data from pyth lazer client resubscribing`); + await this.unsubscribe(); + this.receivingData = false; + await this.subscribe(); + } + }, this.resubTimeoutMs); + } + + /** + * Unsubscribes from all Pyth Lazer price feeds and closes WebSocket connections. + */ + async unsubscribe() { + this.isUnsubscribing = true; + this.pythLazerClient?.shutdown(); + this.pythLazerClient = undefined; + clearTimeout(this.timeoutId); + this.timeoutId = undefined; + this.isUnsubscribing = false; + } + + hash(arr: number[]): string { + return 'h:' + arr.join('|'); + } + + /** + * Retrieves the latest Solana-format price message for a group of feed IDs. + * @param feedIds - Array of price feed IDs + * @returns Hex-encoded price message data, or undefined if not available + */ + async getLatestPriceMessage(feedIds: number[]): Promise { + return this.feedIdChunkToPriceMessage.get(this.hash(feedIds)); + } + + /** + * Retrieves the latest Solana-format price message for a specific market. + * @param marketIndex - The market index to get price data for + * @returns Hex-encoded price message data, or undefined if not found + */ + async getLatestPriceMessageForMarketIndex( + marketIndex: number + ): Promise { + const feedIds = this.marketIndextoPriceFeedIdChunk.get(marketIndex); + if (!feedIds) { + return undefined; + } + return await this.getLatestPriceMessage(feedIds); + } + + /** + * Gets the array of price feed IDs associated with a market index. + * @param marketIndex - The market index to look up + * @returns Array of price feed IDs, or empty array if not found + */ + getPriceFeedIdsFromMarketIndex(marketIndex: number): number[] { + return this.marketIndextoPriceFeedIdChunk.get(marketIndex) || []; + } + + /** + * Gets the array of price feed IDs from a subscription hash. + * @param hash - The subscription hash + * @returns Array of price feed IDs, or empty array if not found + */ + getPriceFeedIdsFromHash(hash: string): number[] { + return this.feedIdHashToFeedIds.get(hash) || []; + } + + /** + * Gets the current parsed price for a specific market index. + * @param marketIndex - The market index to get the price for + * @returns The price as a number, or undefined if not available + */ + getPriceFromMarketIndex(marketIndex: number): number | undefined { + const feedId = this.marketIndextoPriceFeedId.get(marketIndex); + if (feedId === undefined) { + return undefined; + } + return this.feedIdToPrice.get(feedId); + } +} diff --git a/sdk/src/pyth/types.ts b/sdk/src/pyth/types.ts new file mode 100644 index 0000000000..6e0bbc6be3 --- /dev/null +++ b/sdk/src/pyth/types.ts @@ -0,0 +1,4453 @@ +import type { IdlAccounts } from '@coral-xyz/anchor'; + +export type WormholeCoreBridgeSolana = { + version: '0.0.1-alpha.5'; + name: 'wormhole_core_bridge_solana'; + constants: [ + { + name: 'SOLANA_CHAIN'; + type: 'u16'; + value: '1'; + }, + { + name: 'FEE_COLLECTOR_SEED_PREFIX'; + type: 'bytes'; + value: '[102, 101, 101, 95, 99, 111, 108, 108, 101, 99, 116, 111, 114]'; + }, + { + name: 'UPGRADE_SEED_PREFIX'; + type: 'bytes'; + value: '[117, 112, 103, 114, 97, 100, 101]'; + }, + { + name: 'PROGRAM_EMITTER_SEED_PREFIX'; + type: 'bytes'; + value: '[101, 109, 105, 116, 116, 101, 114]'; + }, + { + name: 'MAX_MESSAGE_PAYLOAD_SIZE'; + type: { + defined: 'usize'; + }; + value: '30 * 1_024'; + }, + ]; + instructions: [ + { + name: 'initMessageV1'; + docs: [ + 'Processor for initializing a new draft [PostedMessageV1](crate::state::PostedMessageV1)', + 'account for writing. The emitter authority is established at this point and the payload size', + 'is inferred from the size of the created account. This instruction handler also allows an', + "integrator to publish Wormhole messages using his program's ID as the emitter address", + '(by passing `Some(crate::ID)` to the [cpi_program_id](InitMessageV1Args::cpi_program_id)', + 'argument). **Be aware that the emitter authority\'s seeds must only be \\[b"emitter"\\] in this', + 'case.**', + '', + 'This instruction should be followed up with `write_message_v1` and `finalize_message_v1` to', + 'write and indicate that the message is ready for publishing respectively (to prepare it for', + 'publishing via the', + '[post message instruction](crate::legacy::instruction::LegacyInstruction::PostMessage)).', + '', + 'NOTE: If you wish to publish a small message (one where the data does not overflow the', + 'Solana transaction size), it is recommended that you use an [sdk](crate::sdk::cpi) method to', + 'either prepare your message or post a message as a program ID emitter.', + ]; + accounts: [ + { + name: 'emitterAuthority'; + isMut: false; + isSigner: true; + docs: [ + 'This authority is the only one who can write to the draft message account.', + ]; + }, + { + name: 'draftMessage'; + isMut: true; + isSigner: false; + docs: ['Bridge.']; + }, + ]; + args: [ + { + name: 'args'; + type: { + defined: 'InitMessageV1Args'; + }; + }, + ]; + }, + { + name: 'writeMessageV1'; + docs: [ + 'Processor used to write to a draft [PostedMessageV1](crate::state::PostedMessageV1) account.', + 'This instruction requires an authority (the emitter authority) to interact with the message', + 'account.', + ]; + accounts: [ + { + name: 'emitterAuthority'; + isMut: false; + isSigner: true; + }, + { + name: 'draftMessage'; + isMut: true; + isSigner: false; + docs: ['only be published when the message is finalized.']; + }, + ]; + args: [ + { + name: 'args'; + type: { + defined: 'WriteMessageV1Args'; + }; + }, + ]; + }, + { + name: 'finalizeMessageV1'; + docs: [ + 'Processor used to finalize a draft [PostedMessageV1](crate::state::PostedMessageV1) account.', + 'Once finalized, this message account cannot be written to again. A finalized message is the', + 'only state the legacy post message instruction can accept before publishing. This', + 'instruction requires an authority (the emitter authority) to interact with the message', + 'account.', + ]; + accounts: [ + { + name: 'emitterAuthority'; + isMut: false; + isSigner: true; + }, + { + name: 'draftMessage'; + isMut: true; + isSigner: false; + docs: ['only be published when the message is finalized.']; + }, + ]; + args: []; + }, + { + name: 'closeMessageV1'; + docs: [ + 'Processor used to process a draft [PostedMessageV1](crate::state::PostedMessageV1) account.', + 'This instruction requires an authority (the emitter authority) to interact with the message', + 'account.', + ]; + accounts: [ + { + name: 'emitterAuthority'; + isMut: false; + isSigner: true; + }, + { + name: 'draftMessage'; + isMut: true; + isSigner: false; + docs: ['only be published when the message is finalized.']; + }, + { + name: 'closeAccountDestination'; + isMut: true; + isSigner: false; + }, + ]; + args: []; + }, + { + name: 'initEncodedVaa'; + docs: [ + 'Processor used to intialize a created account as [EncodedVaa](crate::state::EncodedVaa). An', + 'authority (the write authority) is established with this instruction.', + ]; + accounts: [ + { + name: 'writeAuthority'; + isMut: false; + isSigner: true; + docs: [ + 'The authority who can write to the VAA account when it is being processed.', + ]; + }, + { + name: 'encodedVaa'; + isMut: true; + isSigner: false; + docs: ['Bridge.']; + }, + ]; + args: []; + }, + { + name: 'closeEncodedVaa'; + docs: [ + 'Processor used to close an [EncodedVaa](crate::state::EncodedVaa). This instruction requires', + 'an authority (the write authority) to interact witht he encoded VAA account.', + ]; + accounts: [ + { + name: 'writeAuthority'; + isMut: true; + isSigner: true; + docs: [ + 'This account is only required to be mutable for the `CloseVaaAccount` directive. This', + 'authority is the same signer that originally created the VAA accounts, so he is the one that', + 'will receive the lamports back for the closed accounts.', + ]; + }, + { + name: 'encodedVaa'; + isMut: true; + isSigner: false; + docs: ['written to and then verified.']; + }, + ]; + args: []; + }, + { + name: 'writeEncodedVaa'; + docs: [ + 'Processor used to write to an [EncodedVaa](crate::state::EncodedVaa) account. This', + 'instruction requires an authority (the write authority) to interact with the encoded VAA', + 'account.', + ]; + accounts: [ + { + name: 'writeAuthority'; + isMut: false; + isSigner: true; + docs: [ + 'The only authority that can write to the encoded VAA account.', + ]; + }, + { + name: 'draftVaa'; + isMut: true; + isSigner: false; + docs: ['written to and then verified.']; + }, + ]; + args: [ + { + name: 'args'; + type: { + defined: 'WriteEncodedVaaArgs'; + }; + }, + ]; + }, + { + name: 'verifyEncodedVaaV1'; + docs: [ + 'Processor used to verify an [EncodedVaa](crate::state::EncodedVaa) account as a version 1', + 'VAA (guardian signatures attesting to this observation). This instruction requires an', + 'authority (the write authority) to interact with the encoded VAA account.', + ]; + accounts: [ + { + name: 'writeAuthority'; + isMut: false; + isSigner: true; + }, + { + name: 'draftVaa'; + isMut: true; + isSigner: false; + docs: ['written to and then verified.']; + }, + { + name: 'guardianSet'; + isMut: false; + isSigner: false; + docs: [ + 'Guardian set account, which should be the same one that was used to attest for the VAA. The', + 'signatures in the encoded VAA are verified against this guardian set.', + ]; + }, + ]; + args: []; + }, + { + name: 'postVaaV1'; + docs: [ + 'Processor used to close an [EncodedVaa](crate::state::EncodedVaa) account to create a', + '[PostedMessageV1](crate::state::PostedMessageV1) account in its place.', + '', + 'NOTE: Because the legacy verify signatures instruction was not required for the Posted VAA', + 'account to exist, the encoded [SignatureSet](crate::state::SignatureSet) is the default', + '[Pubkey].', + ]; + accounts: [ + { + name: 'payer'; + isMut: true; + isSigner: true; + docs: [ + 'Payer to create the posted VAA account. This instruction allows anyone with an encoded VAA', + 'to create a posted VAA account.', + ]; + }, + { + name: 'encodedVaa'; + isMut: false; + isSigner: false; + docs: [ + 'Encoded VAA, whose body will be serialized into the posted VAA account.', + '', + 'NOTE: This instruction handler only exists to support integrators that still rely on posted', + 'VAA accounts. While we encourage integrators to use the encoded VAA account instead, we', + 'allow a pathway to convert the encoded VAA into a posted VAA. However, the payload is', + 'restricted to 9.5KB, which is much larger than what was possible with the old implementation', + 'using the legacy post vaa instruction. The Core Bridge program will not support posting VAAs', + 'larger than this payload size.', + ]; + }, + { + name: 'postedVaa'; + isMut: true; + isSigner: false; + }, + { + name: 'systemProgram'; + isMut: false; + isSigner: false; + }, + ]; + args: []; + }, + { + name: 'closeSignatureSet'; + docs: [ + 'Processor used to close a [SignatureSet](crate::state::SignatureSet), which was used to', + 'verify the VAA using the legacy parse and verify procedure.', + ]; + accounts: [ + { + name: 'solDestination'; + isMut: true; + isSigner: true; + }, + { + name: 'postedVaa'; + isMut: false; + isSigner: false; + docs: ['Posted VAA.']; + }, + { + name: 'signatureSet'; + isMut: true; + isSigner: false; + docs: [ + 'Signature set that may have been used to create the posted VAA account. If the `post_vaa_v1`', + 'instruction were used to create the posted VAA account, then the encoded signature set', + 'pubkey would be all zeroes.', + ]; + }, + ]; + args: []; + }, + ]; + accounts: [ + { + name: 'guardianSet'; + docs: [ + 'Account used to store a guardian set. The keys encoded in this account are Ethereum pubkeys.', + 'Its expiration time is determined at the time a guardian set is updated to a new set, where the', + 'current network clock time is used with', + '[guardian_set_ttl](crate::state::Config::guardian_set_ttl).', + '', + 'NOTE: The account schema is the same as legacy guardian sets, but this account now has a', + "discriminator generated by Anchor's [account] macro. When the Core Bridge program performs a", + 'guardian set update with this implementation, guardian sets will now have this Anchor-generated', + 'discriminator.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'index'; + docs: [ + 'Index representing an incrementing version number for this guardian set.', + ]; + type: 'u32'; + }, + { + name: 'keys'; + docs: ['Ethereum-style public keys.']; + type: { + vec: { + array: ['u8', 20]; + }; + }; + }, + { + name: 'creationTime'; + docs: [ + 'Timestamp representing the time this guardian became active.', + ]; + type: { + defined: 'Timestamp'; + }; + }, + { + name: 'expirationTime'; + docs: [ + 'Expiration time when VAAs issued by this set are no longer valid.', + ]; + type: { + defined: 'Timestamp'; + }; + }, + ]; + }; + }, + { + name: 'signatureSet'; + docs: [ + 'Account used to store information about a guardian set used to sign a VAA. There is only one', + 'signature set for each verified VAA (associated with a', + '[PostedVaaV1](crate::legacy::state::PostedVaaV1) account). This account is created using the', + 'verify signatures legacy instruction.', + '', + 'NOTE: The account schema is the same as legacy signature sets, but this account now has a', + "discriminator generated by Anchor's [account] macro. When the Core Bridge program upgrades to", + 'this implementation from the old one, integrators in the middle of verifying signatures will', + 'have to use a new keypair for this account and try again.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'sigVerifySuccesses'; + docs: ['Signatures of validators']; + type: { + vec: 'bool'; + }; + }, + { + name: 'messageHash'; + docs: ['Hash of the VAA message body.']; + type: { + defined: 'MessageHash'; + }; + }, + { + name: 'guardianSetIndex'; + docs: ['Index of the guardian set']; + type: 'u32'; + }, + ]; + }; + }, + { + name: 'encodedVaa'; + docs: [ + 'Account used to warehouse VAA buffer.', + '', + "NOTE: This account should not be used by an external application unless the header's status is", + '`Verified`. It is encouraged to use the `EncodedVaa` zero-copy account struct instead.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'header'; + docs: ['Status, write authority and VAA version.']; + type: { + defined: 'Header'; + }; + }, + { + name: 'buf'; + docs: ['VAA buffer.']; + type: 'bytes'; + }, + ]; + }; + }, + ]; + types: [ + { + name: 'InitializeArgs'; + docs: ['Arguments used to initialize the Core Bridge program.']; + type: { + kind: 'struct'; + fields: [ + { + name: 'guardianSetTtlSeconds'; + type: 'u32'; + }, + { + name: 'feeLamports'; + type: 'u64'; + }, + { + name: 'initialGuardians'; + type: { + vec: { + array: ['u8', 20]; + }; + }; + }, + ]; + }; + }, + { + name: 'PostMessageArgs'; + docs: [ + 'Arguments used to post a new Wormhole (Core Bridge) message either using', + '[post_message](crate::legacy::instruction::post_message) or', + '[post_message_unreliable](crate::legacy::instruction::post_message_unreliable).', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'nonce'; + docs: ['Unique id for this message.']; + type: 'u32'; + }, + { + name: 'payload'; + docs: ['Encoded message.']; + type: 'bytes'; + }, + { + name: 'commitment'; + docs: ['Solana commitment level for Guardian observation.']; + type: { + defined: 'Commitment'; + }; + }, + ]; + }; + }, + { + name: 'PostVaaArgs'; + docs: [ + 'Arguments to post new VAA data after signature verification.', + '', + 'NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor', + 'instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and', + '[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'gap0'; + docs: ['Unused data.']; + type: { + array: ['u8', 5]; + }; + }, + { + name: 'timestamp'; + docs: ['Time the message was submitted.']; + type: 'u32'; + }, + { + name: 'nonce'; + docs: ['Unique ID for this message.']; + type: 'u32'; + }, + { + name: 'emitterChain'; + docs: [ + 'The Wormhole chain ID denoting the origin of this message.', + ]; + type: 'u16'; + }, + { + name: 'emitterAddress'; + docs: ['Emitter of the message.']; + type: { + array: ['u8', 32]; + }; + }, + { + name: 'sequence'; + docs: ['Sequence number of this message.']; + type: 'u64'; + }, + { + name: 'consistencyLevel'; + docs: ['Level of consistency requested by the emitter.']; + type: 'u8'; + }, + { + name: 'payload'; + docs: ['Message payload.']; + type: 'bytes'; + }, + ]; + }; + }, + { + name: 'VerifySignaturesArgs'; + docs: [ + 'Arguments to verify specific guardian indices.', + '', + 'NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor', + 'instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and', + '[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'signerIndices'; + docs: [ + 'Indices of verified guardian signatures, where -1 indicates a missing value. There is a', + 'missing value if the guardian at this index is not expected to have its signature verfied by', + 'the Sig Verify native program in the instruction invoked prior).', + '', + 'NOTE: In the legacy implementation, this argument being a fixed-sized array of 19 only', + 'allows the first 19 guardians of any size guardian set to be verified. Because of this, it', + 'is absolutely important to use the new process of verifying a VAA.', + ]; + type: { + array: ['i8', 19]; + }; + }, + ]; + }; + }, + { + name: 'EmptyArgs'; + docs: ['Unit struct used to represent an empty instruction argument.']; + type: { + kind: 'struct'; + fields: []; + }; + }, + { + name: 'Config'; + docs: [ + 'Account used to store the current configuration of the bridge, including tracking Wormhole fee', + 'payments. For governance decrees, the guardian set index is used to determine whether a decree', + 'was attested for using the latest guardian set.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'guardianSetIndex'; + docs: [ + 'The current guardian set index, used to decide which signature sets to accept.', + ]; + type: 'u32'; + }, + { + name: 'gap0'; + docs: [ + 'Gap. In the old implementation, this was an amount that kept track of message fees that', + "were paid to the program's fee collector.", + ]; + type: { + array: ['u8', 8]; + }; + }, + { + name: 'guardianSetTtl'; + docs: [ + 'Period for how long a guardian set is valid after it has been replaced by a new one. This', + 'guarantees that VAAs issued by that set can still be submitted for a certain period. In', + 'this period we still trust the old guardian set.', + ]; + type: { + defined: 'Duration'; + }; + }, + { + name: 'feeLamports'; + docs: [ + 'Amount of lamports that needs to be paid to the protocol to post a message', + ]; + type: 'u64'; + }, + ]; + }; + }, + { + name: 'LegacyEmitterSequence'; + docs: [ + 'Account used to store the current sequence number for a given emitter.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'value'; + docs: [ + 'Current sequence number, which will be used the next time this emitter publishes a message.', + ]; + type: 'u64'; + }, + ]; + }; + }, + { + name: 'EmitterSequence'; + type: { + kind: 'struct'; + fields: [ + { + name: 'legacy'; + type: { + defined: 'LegacyEmitterSequence'; + }; + }, + { + name: 'bump'; + type: 'u8'; + }, + { + name: 'emitterType'; + type: { + defined: 'EmitterType'; + }; + }, + ]; + }; + }, + { + name: 'PostedMessageV1Unreliable'; + docs: ['Account used to store a published (reusable) Wormhole message.']; + type: { + kind: 'struct'; + fields: [ + { + name: 'data'; + type: { + defined: 'PostedMessageV1Data'; + }; + }, + ]; + }; + }, + { + name: 'PostedMessageV1Info'; + docs: [ + 'Message metadata defining information about a published Wormhole message.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'consistencyLevel'; + docs: ['Level of consistency requested by the emitter.']; + type: 'u8'; + }, + { + name: 'emitterAuthority'; + docs: [ + 'Authority used to write the message. This field is set to default when the message is', + 'posted.', + ]; + type: 'publicKey'; + }, + { + name: 'status'; + docs: [ + 'If a message is being written to, this status is used to determine which state this', + 'account is in (e.g. [MessageStatus::Writing] indicates that the emitter authority is still', + 'writing its message to this account). When this message is posted, this value will be', + 'set to [MessageStatus::Published].', + ]; + type: { + defined: 'MessageStatus'; + }; + }, + { + name: 'gap0'; + docs: ['No data is stored here.']; + type: { + array: ['u8', 3]; + }; + }, + { + name: 'postedTimestamp'; + docs: ['Time the posted message was created.']; + type: { + defined: 'Timestamp'; + }; + }, + { + name: 'nonce'; + docs: ['Unique id for this message.']; + type: 'u32'; + }, + { + name: 'sequence'; + docs: ['Sequence number of this message.']; + type: 'u64'; + }, + { + name: 'solanaChainId'; + docs: [ + 'Always `1`.', + '', + 'NOTE: Saving this value is silly, but we are keeping it to be consistent with how the posted', + 'message account is written.', + ]; + type: { + defined: 'ChainIdSolanaOnly'; + }; + }, + { + name: 'emitter'; + docs: [ + 'Emitter of the message. This may either be the emitter authority or a program ID.', + ]; + type: 'publicKey'; + }, + ]; + }; + }, + { + name: 'PostedMessageV1Data'; + docs: [ + 'Underlying data for either [PostedMessageV1](crate::legacy::state::PostedMessageV1) or', + '[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable).', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'info'; + docs: ['Message metadata.']; + type: { + defined: 'PostedMessageV1Info'; + }; + }, + { + name: 'payload'; + docs: ['Encoded message.']; + type: 'bytes'; + }, + ]; + }; + }, + { + name: 'PostedMessageV1'; + docs: [ + 'Account used to store a published Wormhole message.', + '', + 'NOTE: If your integration requires reusable message accounts, please see', + '[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable).', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'data'; + docs: ['Message data.']; + type: { + defined: 'PostedMessageV1Data'; + }; + }, + ]; + }; + }, + { + name: 'PostedVaaV1Info'; + docs: [ + 'VAA metadata defining information about a Wormhole message attested for by an active guardian', + 'set.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'consistencyLevel'; + docs: ['Level of consistency requested by the emitter.']; + type: 'u8'; + }, + { + name: 'timestamp'; + docs: ['Time the message was submitted.']; + type: { + defined: 'Timestamp'; + }; + }, + { + name: 'signatureSet'; + docs: [ + "Pubkey of [SignatureSet](crate::state::SignatureSet) account that represents this VAA's", + 'signature verification.', + ]; + type: 'publicKey'; + }, + { + name: 'guardianSetIndex'; + docs: [ + 'Guardian set index used to verify signatures for [SignatureSet](crate::state::SignatureSet).', + '', + 'NOTE: In the previous implementation, this member was referred to as the "posted timestamp",', + 'which is zero for VAA data (posted messages and VAAs resemble the same account schema). By', + 'changing this to the guardian set index, we patch a bug with verifying governance VAAs for', + 'the Core Bridge (other Core Bridge implementations require that the guardian set that', + 'attested for the governance VAA is the current one).', + ]; + type: 'u32'; + }, + { + name: 'nonce'; + docs: ['Unique ID for this message.']; + type: 'u32'; + }, + { + name: 'sequence'; + docs: ['Sequence number of this message.']; + type: 'u64'; + }, + { + name: 'emitterChain'; + docs: [ + 'The Wormhole chain ID denoting the origin of this message.', + ]; + type: 'u16'; + }, + { + name: 'emitterAddress'; + docs: ['Emitter of the message.']; + type: { + array: ['u8', 32]; + }; + }, + ]; + }; + }, + { + name: 'PostedVaaV1'; + docs: ['Account used to store a verified VAA.']; + type: { + kind: 'struct'; + fields: [ + { + name: 'info'; + docs: ['VAA metadata.']; + type: { + defined: 'PostedVaaV1Info'; + }; + }, + { + name: 'payload'; + docs: ['Message payload.']; + type: 'bytes'; + }, + ]; + }; + }, + { + name: 'WriteEncodedVaaArgs'; + docs: [ + 'Arguments for the [write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa)', + 'instruction.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'index'; + docs: ['Index of VAA buffer.']; + type: 'u32'; + }, + { + name: 'data'; + docs: [ + 'Data representing subset of VAA buffer starting at specified index.', + ]; + type: 'bytes'; + }, + ]; + }; + }, + { + name: 'InitMessageV1Args'; + docs: [ + 'Arguments for the [init_message_v1](crate::wormhole_core_bridge_solana::init_message_v1)', + 'instruction.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'nonce'; + docs: ['Unique id for this message.']; + type: 'u32'; + }, + { + name: 'commitment'; + docs: ['Solana commitment level for Guardian observation.']; + type: { + defined: 'Commitment'; + }; + }, + { + name: 'cpiProgramId'; + docs: [ + 'Optional program ID if the emitter address will be your program ID.', + '', + 'NOTE: If `Some(program_id)`, your emitter authority seeds to be \\[b"emitter\\].', + ]; + type: { + option: 'publicKey'; + }; + }, + ]; + }; + }, + { + name: 'WriteMessageV1Args'; + docs: [ + 'Arguments for the [write_message_v1](crate::wormhole_core_bridge_solana::write_message_v1)', + 'instruction.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'index'; + docs: ['Index of message buffer.']; + type: 'u32'; + }, + { + name: 'data'; + docs: [ + 'Data representing subset of message buffer starting at specified index.', + ]; + type: 'bytes'; + }, + ]; + }; + }, + { + name: 'Header'; + docs: ['`EncodedVaa` account header.']; + type: { + kind: 'struct'; + fields: [ + { + name: 'status'; + docs: [ + 'Processing status. **This encoded VAA is only considered usable when this status is set', + 'to [Verified](ProcessingStatus::Verified).**', + ]; + type: { + defined: 'ProcessingStatus'; + }; + }, + { + name: 'writeAuthority'; + docs: ['The authority that has write privilege to this account.']; + type: 'publicKey'; + }, + { + name: 'version'; + docs: [ + 'VAA version. Only when the VAA is verified is this version set to a value.', + ]; + type: 'u8'; + }, + ]; + }; + }, + { + name: 'Timestamp'; + docs: [ + 'This struct defines unix timestamp as u32 (as opposed to more modern systems that have adopted', + "i64). Methods for this struct are meant to convert Solana's clock type to this type assuming we", + 'are far from year 2038.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'value'; + type: 'u32'; + }, + ]; + }; + }, + { + name: 'Duration'; + docs: [ + 'To be used with the [Timestamp] type, this struct defines a duration in seconds.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'seconds'; + type: 'u32'; + }, + ]; + }; + }, + { + name: 'MessageHash'; + docs: ['This type is used to represent a message hash (keccak).']; + type: { + kind: 'struct'; + fields: [ + { + name: 'bytes'; + type: { + array: ['u8', 32]; + }; + }, + ]; + }; + }, + { + name: 'ChainIdSolanaOnly'; + docs: [ + 'This type is kind of silly. But because [PostedMessageV1](crate::state::PostedMessageV1) has the', + "emitter chain ID as a field, which is unnecessary since it is always Solana's chain ID, we use", + 'this type to guarantee that the encoded chain ID is always `1`.', + ]; + type: { + kind: 'struct'; + fields: [ + { + name: 'chainId'; + type: 'u16'; + }, + ]; + }; + }, + { + name: 'EmitterInfo'; + type: { + kind: 'struct'; + fields: [ + { + name: 'chain'; + type: 'u16'; + }, + { + name: 'address'; + type: { + array: ['u8', 32]; + }; + }, + { + name: 'sequence'; + type: 'u64'; + }, + ]; + }; + }, + { + name: 'LegacyInstruction'; + docs: [ + 'Legacy instruction selector.', + '', + 'NOTE: No more instructions should be added to this enum. Instead, add them as Anchor instruction', + 'handlers, which will inevitably live in', + '[wormhole_core_bridge_solana](crate::wormhole_core_bridge_solana).', + ]; + type: { + kind: 'enum'; + variants: [ + { + name: 'Initialize'; + }, + { + name: 'PostMessage'; + }, + { + name: 'PostVaa'; + }, + { + name: 'SetMessageFee'; + }, + { + name: 'TransferFees'; + }, + { + name: 'UpgradeContract'; + }, + { + name: 'GuardianSetUpdate'; + }, + { + name: 'VerifySignatures'; + }, + { + name: 'PostMessageUnreliable'; + }, + ]; + }; + }, + { + name: 'EmitterType'; + type: { + kind: 'enum'; + variants: [ + { + name: 'Unset'; + }, + { + name: 'Legacy'; + }, + { + name: 'Executable'; + }, + ]; + }; + }, + { + name: 'MessageStatus'; + docs: [ + 'Status of a message. When a message is posted, its status is', + '[Published](MessageStatus::Published).', + ]; + type: { + kind: 'enum'; + variants: [ + { + name: 'Published'; + }, + { + name: 'Writing'; + }, + { + name: 'ReadyForPublishing'; + }, + ]; + }; + }, + { + name: 'PublishMessageDirective'; + docs: ['Directive used to determine how to post a Core Bridge message.']; + type: { + kind: 'enum'; + variants: [ + { + name: 'Message'; + fields: [ + { + name: 'nonce'; + type: 'u32'; + }, + { + name: 'payload'; + type: 'bytes'; + }, + { + name: 'commitment'; + type: { + defined: 'Commitment'; + }; + }, + ]; + }, + { + name: 'ProgramMessage'; + fields: [ + { + name: 'programId'; + type: 'publicKey'; + }, + { + name: 'nonce'; + type: 'u32'; + }, + { + name: 'payload'; + type: 'bytes'; + }, + { + name: 'commitment'; + type: { + defined: 'Commitment'; + }; + }, + ]; + }, + { + name: 'PreparedMessage'; + }, + ]; + }; + }, + { + name: 'ProcessingStatus'; + docs: ["Encoded VAA's processing status."]; + type: { + kind: 'enum'; + variants: [ + { + name: 'Unset'; + }, + { + name: 'Writing'; + }, + { + name: 'Verified'; + }, + ]; + }; + }, + { + name: 'Commitment'; + docs: [ + "Representation of Solana's commitment levels. This enum is not exhaustive because Wormhole only", + 'considers these two commitment levels in its Guardian observation.', + '', + 'See for more info.', + ]; + type: { + kind: 'enum'; + variants: [ + { + name: 'Confirmed'; + }, + { + name: 'Finalized'; + }, + ]; + }; + }, + ]; + errors: [ + { + code: 6002; + name: 'InvalidInstructionArgument'; + msg: 'InvalidInstructionArgument'; + }, + { + code: 6003; + name: 'AccountNotZeroed'; + msg: 'AccountNotZeroed'; + }, + { + code: 6004; + name: 'InvalidDataConversion'; + msg: 'InvalidDataConversion'; + }, + { + code: 6006; + name: 'U64Overflow'; + msg: 'U64Overflow'; + }, + { + code: 6008; + name: 'InvalidComputeSize'; + msg: 'InvalidComputeSize'; + }, + { + code: 6016; + name: 'InvalidChain'; + msg: 'InvalidChain'; + }, + { + code: 6032; + name: 'InvalidGovernanceEmitter'; + msg: 'InvalidGovernanceEmitter'; + }, + { + code: 6034; + name: 'InvalidGovernanceAction'; + msg: 'InvalidGovernanceAction'; + }, + { + code: 6036; + name: 'LatestGuardianSetRequired'; + msg: 'LatestGuardianSetRequired'; + }, + { + code: 6038; + name: 'GovernanceForAnotherChain'; + msg: 'GovernanceForAnotherChain'; + }, + { + code: 6040; + name: 'InvalidGovernanceVaa'; + msg: 'InvalidGovernanceVaa'; + }, + { + code: 6256; + name: 'InsufficientFees'; + msg: 'InsufficientFees'; + }, + { + code: 6258; + name: 'EmitterMismatch'; + msg: 'EmitterMismatch'; + }, + { + code: 6260; + name: 'NotReadyForPublishing'; + msg: 'NotReadyForPublishing'; + }, + { + code: 6262; + name: 'InvalidPreparedMessage'; + msg: 'InvalidPreparedMessage'; + }, + { + code: 6264; + name: 'ExecutableEmitter'; + msg: 'ExecutableEmitter'; + }, + { + code: 6266; + name: 'LegacyEmitter'; + msg: 'LegacyEmitter'; + }, + { + code: 6512; + name: 'InvalidSignatureSet'; + msg: 'InvalidSignatureSet'; + }, + { + code: 6514; + name: 'InvalidMessageHash'; + msg: 'InvalidMessageHash'; + }, + { + code: 6515; + name: 'NoQuorum'; + msg: 'NoQuorum'; + }, + { + code: 6516; + name: 'MessageMismatch'; + msg: 'MessageMismatch'; + }, + { + code: 7024; + name: 'NotEnoughLamports'; + msg: 'NotEnoughLamports'; + }, + { + code: 7026; + name: 'InvalidFeeRecipient'; + msg: 'InvalidFeeRecipient'; + }, + { + code: 7280; + name: 'ImplementationMismatch'; + msg: 'ImplementationMismatch'; + }, + { + code: 7536; + name: 'InvalidGuardianSetIndex'; + msg: 'InvalidGuardianSetIndex'; + }, + { + code: 7792; + name: 'GuardianSetMismatch'; + msg: 'GuardianSetMismatch'; + }, + { + code: 7794; + name: 'InstructionAtWrongIndex'; + msg: 'InstructionAtWrongIndex'; + }, + { + code: 7795; + name: 'EmptySigVerifyInstruction'; + msg: 'EmptySigVerifyInstruction'; + }, + { + code: 7796; + name: 'InvalidSigVerifyInstruction'; + msg: 'InvalidSigVerifyInstruction'; + }, + { + code: 7798; + name: 'GuardianSetExpired'; + msg: 'GuardianSetExpired'; + }, + { + code: 7800; + name: 'InvalidGuardianKeyRecovery'; + msg: 'InvalidGuardianKeyRecovery'; + }, + { + code: 7802; + name: 'SignerIndicesMismatch'; + msg: 'SignerIndicesMismatch'; + }, + { + code: 8048; + name: 'PayloadSizeMismatch'; + msg: 'PayloadSizeMismatch'; + }, + { + code: 10112; + name: 'ZeroGuardians'; + msg: 'ZeroGuardians'; + }, + { + code: 10128; + name: 'GuardianZeroAddress'; + msg: 'GuardianZeroAddress'; + }, + { + code: 10144; + name: 'DuplicateGuardianAddress'; + msg: 'DuplicateGuardianAddress'; + }, + { + code: 10160; + name: 'MessageAlreadyPublished'; + msg: 'MessageAlreadyPublished'; + }, + { + code: 10176; + name: 'VaaWritingDisallowed'; + msg: 'VaaWritingDisallowed'; + }, + { + code: 10192; + name: 'VaaAlreadyVerified'; + msg: 'VaaAlreadyVerified'; + }, + { + code: 10208; + name: 'InvalidGuardianIndex'; + msg: 'InvalidGuardianIndex'; + }, + { + code: 10224; + name: 'InvalidSignature'; + msg: 'InvalidSignature'; + }, + { + code: 10256; + name: 'UnverifiedVaa'; + msg: 'UnverifiedVaa'; + }, + { + code: 10258; + name: 'VaaStillProcessing'; + msg: 'VaaStillProcessing'; + }, + { + code: 10260; + name: 'InWritingStatus'; + msg: 'InWritingStatus'; + }, + { + code: 10262; + name: 'NotInWritingStatus'; + msg: 'NotInWritingStatus'; + }, + { + code: 10264; + name: 'InvalidMessageStatus'; + msg: 'InvalidMessageStatus'; + }, + { + code: 10266; + name: 'HashNotComputed'; + msg: 'HashNotComputed'; + }, + { + code: 10268; + name: 'InvalidVaaVersion'; + msg: 'InvalidVaaVersion'; + }, + { + code: 10270; + name: 'InvalidCreatedAccountSize'; + msg: 'InvalidCreatedAccountSize'; + }, + { + code: 10272; + name: 'DataOverflow'; + msg: 'DataOverflow'; + }, + { + code: 10274; + name: 'ExceedsMaxPayloadSize'; + msg: 'ExceedsMaxPayloadSize (30KB)'; + }, + { + code: 10276; + name: 'CannotParseVaa'; + msg: 'CannotParseVaa'; + }, + { + code: 10278; + name: 'EmitterAuthorityMismatch'; + msg: 'EmitterAuthorityMismatch'; + }, + { + code: 10280; + name: 'InvalidProgramEmitter'; + msg: 'InvalidProgramEmitter'; + }, + { + code: 10282; + name: 'WriteAuthorityMismatch'; + msg: 'WriteAuthorityMismatch'; + }, + { + code: 10284; + name: 'PostedVaaPayloadTooLarge'; + msg: 'PostedVaaPayloadTooLarge'; + }, + { + code: 10286; + name: 'ExecutableDisallowed'; + msg: 'ExecutableDisallowed'; + }, + ]; +}; + +export const WORMHOLE_CORE_BRIDGE_SOLANA_IDL: WormholeCoreBridgeSolana = { + version: '0.0.1-alpha.5', + name: 'wormhole_core_bridge_solana', + constants: [ + { + name: 'SOLANA_CHAIN', + type: 'u16', + value: '1', + }, + { + name: 'FEE_COLLECTOR_SEED_PREFIX', + type: 'bytes', + value: '[102, 101, 101, 95, 99, 111, 108, 108, 101, 99, 116, 111, 114]', + }, + { + name: 'UPGRADE_SEED_PREFIX', + type: 'bytes', + value: '[117, 112, 103, 114, 97, 100, 101]', + }, + { + name: 'PROGRAM_EMITTER_SEED_PREFIX', + type: 'bytes', + value: '[101, 109, 105, 116, 116, 101, 114]', + }, + { + name: 'MAX_MESSAGE_PAYLOAD_SIZE', + type: { + defined: 'usize', + }, + value: '30 * 1_024', + }, + ], + instructions: [ + { + name: 'initMessageV1', + docs: [ + 'Processor for initializing a new draft [PostedMessageV1](crate::state::PostedMessageV1)', + 'account for writing. The emitter authority is established at this point and the payload size', + 'is inferred from the size of the created account. This instruction handler also allows an', + "integrator to publish Wormhole messages using his program's ID as the emitter address", + '(by passing `Some(crate::ID)` to the [cpi_program_id](InitMessageV1Args::cpi_program_id)', + 'argument). **Be aware that the emitter authority\'s seeds must only be \\[b"emitter"\\] in this', + 'case.**', + '', + 'This instruction should be followed up with `write_message_v1` and `finalize_message_v1` to', + 'write and indicate that the message is ready for publishing respectively (to prepare it for', + 'publishing via the', + '[post message instruction](crate::legacy::instruction::LegacyInstruction::PostMessage)).', + '', + 'NOTE: If you wish to publish a small message (one where the data does not overflow the', + 'Solana transaction size), it is recommended that you use an [sdk](crate::sdk::cpi) method to', + 'either prepare your message or post a message as a program ID emitter.', + ], + accounts: [ + { + name: 'emitterAuthority', + isMut: false, + isSigner: true, + docs: [ + 'This authority is the only one who can write to the draft message account.', + ], + }, + { + name: 'draftMessage', + isMut: true, + isSigner: false, + docs: ['Bridge.'], + }, + ], + args: [ + { + name: 'args', + type: { + defined: 'InitMessageV1Args', + }, + }, + ], + }, + { + name: 'writeMessageV1', + docs: [ + 'Processor used to write to a draft [PostedMessageV1](crate::state::PostedMessageV1) account.', + 'This instruction requires an authority (the emitter authority) to interact with the message', + 'account.', + ], + accounts: [ + { + name: 'emitterAuthority', + isMut: false, + isSigner: true, + }, + { + name: 'draftMessage', + isMut: true, + isSigner: false, + docs: ['only be published when the message is finalized.'], + }, + ], + args: [ + { + name: 'args', + type: { + defined: 'WriteMessageV1Args', + }, + }, + ], + }, + { + name: 'finalizeMessageV1', + docs: [ + 'Processor used to finalize a draft [PostedMessageV1](crate::state::PostedMessageV1) account.', + 'Once finalized, this message account cannot be written to again. A finalized message is the', + 'only state the legacy post message instruction can accept before publishing. This', + 'instruction requires an authority (the emitter authority) to interact with the message', + 'account.', + ], + accounts: [ + { + name: 'emitterAuthority', + isMut: false, + isSigner: true, + }, + { + name: 'draftMessage', + isMut: true, + isSigner: false, + docs: ['only be published when the message is finalized.'], + }, + ], + args: [], + }, + { + name: 'closeMessageV1', + docs: [ + 'Processor used to process a draft [PostedMessageV1](crate::state::PostedMessageV1) account.', + 'This instruction requires an authority (the emitter authority) to interact with the message', + 'account.', + ], + accounts: [ + { + name: 'emitterAuthority', + isMut: false, + isSigner: true, + }, + { + name: 'draftMessage', + isMut: true, + isSigner: false, + docs: ['only be published when the message is finalized.'], + }, + { + name: 'closeAccountDestination', + isMut: true, + isSigner: false, + }, + ], + args: [], + }, + { + name: 'initEncodedVaa', + docs: [ + 'Processor used to intialize a created account as [EncodedVaa](crate::state::EncodedVaa). An', + 'authority (the write authority) is established with this instruction.', + ], + accounts: [ + { + name: 'writeAuthority', + isMut: false, + isSigner: true, + docs: [ + 'The authority who can write to the VAA account when it is being processed.', + ], + }, + { + name: 'encodedVaa', + isMut: true, + isSigner: false, + docs: ['Bridge.'], + }, + ], + args: [], + }, + { + name: 'closeEncodedVaa', + docs: [ + 'Processor used to close an [EncodedVaa](crate::state::EncodedVaa). This instruction requires', + 'an authority (the write authority) to interact witht he encoded VAA account.', + ], + accounts: [ + { + name: 'writeAuthority', + isMut: true, + isSigner: true, + docs: [ + 'This account is only required to be mutable for the `CloseVaaAccount` directive. This', + 'authority is the same signer that originally created the VAA accounts, so he is the one that', + 'will receive the lamports back for the closed accounts.', + ], + }, + { + name: 'encodedVaa', + isMut: true, + isSigner: false, + docs: ['written to and then verified.'], + }, + ], + args: [], + }, + { + name: 'writeEncodedVaa', + docs: [ + 'Processor used to write to an [EncodedVaa](crate::state::EncodedVaa) account. This', + 'instruction requires an authority (the write authority) to interact with the encoded VAA', + 'account.', + ], + accounts: [ + { + name: 'writeAuthority', + isMut: false, + isSigner: true, + docs: [ + 'The only authority that can write to the encoded VAA account.', + ], + }, + { + name: 'draftVaa', + isMut: true, + isSigner: false, + docs: ['written to and then verified.'], + }, + ], + args: [ + { + name: 'args', + type: { + defined: 'WriteEncodedVaaArgs', + }, + }, + ], + }, + { + name: 'verifyEncodedVaaV1', + docs: [ + 'Processor used to verify an [EncodedVaa](crate::state::EncodedVaa) account as a version 1', + 'VAA (guardian signatures attesting to this observation). This instruction requires an', + 'authority (the write authority) to interact with the encoded VAA account.', + ], + accounts: [ + { + name: 'writeAuthority', + isMut: false, + isSigner: true, + }, + { + name: 'draftVaa', + isMut: true, + isSigner: false, + docs: ['written to and then verified.'], + }, + { + name: 'guardianSet', + isMut: false, + isSigner: false, + docs: [ + 'Guardian set account, which should be the same one that was used to attest for the VAA. The', + 'signatures in the encoded VAA are verified against this guardian set.', + ], + }, + ], + args: [], + }, + { + name: 'postVaaV1', + docs: [ + 'Processor used to close an [EncodedVaa](crate::state::EncodedVaa) account to create a', + '[PostedMessageV1](crate::state::PostedMessageV1) account in its place.', + '', + 'NOTE: Because the legacy verify signatures instruction was not required for the Posted VAA', + 'account to exist, the encoded [SignatureSet](crate::state::SignatureSet) is the default', + '[Pubkey].', + ], + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + docs: [ + 'Payer to create the posted VAA account. This instruction allows anyone with an encoded VAA', + 'to create a posted VAA account.', + ], + }, + { + name: 'encodedVaa', + isMut: false, + isSigner: false, + docs: [ + 'Encoded VAA, whose body will be serialized into the posted VAA account.', + '', + 'NOTE: This instruction handler only exists to support integrators that still rely on posted', + 'VAA accounts. While we encourage integrators to use the encoded VAA account instead, we', + 'allow a pathway to convert the encoded VAA into a posted VAA. However, the payload is', + 'restricted to 9.5KB, which is much larger than what was possible with the old implementation', + 'using the legacy post vaa instruction. The Core Bridge program will not support posting VAAs', + 'larger than this payload size.', + ], + }, + { + name: 'postedVaa', + isMut: true, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: 'closeSignatureSet', + docs: [ + 'Processor used to close a [SignatureSet](crate::state::SignatureSet), which was used to', + 'verify the VAA using the legacy parse and verify procedure.', + ], + accounts: [ + { + name: 'solDestination', + isMut: true, + isSigner: true, + }, + { + name: 'postedVaa', + isMut: false, + isSigner: false, + docs: ['Posted VAA.'], + }, + { + name: 'signatureSet', + isMut: true, + isSigner: false, + docs: [ + 'Signature set that may have been used to create the posted VAA account. If the `post_vaa_v1`', + 'instruction were used to create the posted VAA account, then the encoded signature set', + 'pubkey would be all zeroes.', + ], + }, + ], + args: [], + }, + ], + accounts: [ + { + name: 'guardianSet', + docs: [ + 'Account used to store a guardian set. The keys encoded in this account are Ethereum pubkeys.', + 'Its expiration time is determined at the time a guardian set is updated to a new set, where the', + 'current network clock time is used with', + '[guardian_set_ttl](crate::state::Config::guardian_set_ttl).', + '', + 'NOTE: The account schema is the same as legacy guardian sets, but this account now has a', + "discriminator generated by Anchor's [account] macro. When the Core Bridge program performs a", + 'guardian set update with this implementation, guardian sets will now have this Anchor-generated', + 'discriminator.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'index', + docs: [ + 'Index representing an incrementing version number for this guardian set.', + ], + type: 'u32', + }, + { + name: 'keys', + docs: ['Ethereum-style public keys.'], + type: { + vec: { + array: ['u8', 20], + }, + }, + }, + { + name: 'creationTime', + docs: [ + 'Timestamp representing the time this guardian became active.', + ], + type: { + defined: 'Timestamp', + }, + }, + { + name: 'expirationTime', + docs: [ + 'Expiration time when VAAs issued by this set are no longer valid.', + ], + type: { + defined: 'Timestamp', + }, + }, + ], + }, + }, + { + name: 'signatureSet', + docs: [ + 'Account used to store information about a guardian set used to sign a VAA. There is only one', + 'signature set for each verified VAA (associated with a', + '[PostedVaaV1](crate::legacy::state::PostedVaaV1) account). This account is created using the', + 'verify signatures legacy instruction.', + '', + 'NOTE: The account schema is the same as legacy signature sets, but this account now has a', + "discriminator generated by Anchor's [account] macro. When the Core Bridge program upgrades to", + 'this implementation from the old one, integrators in the middle of verifying signatures will', + 'have to use a new keypair for this account and try again.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'sigVerifySuccesses', + docs: ['Signatures of validators'], + type: { + vec: 'bool', + }, + }, + { + name: 'messageHash', + docs: ['Hash of the VAA message body.'], + type: { + defined: 'MessageHash', + }, + }, + { + name: 'guardianSetIndex', + docs: ['Index of the guardian set'], + type: 'u32', + }, + ], + }, + }, + { + name: 'encodedVaa', + docs: [ + 'Account used to warehouse VAA buffer.', + '', + "NOTE: This account should not be used by an external application unless the header's status is", + '`Verified`. It is encouraged to use the `EncodedVaa` zero-copy account struct instead.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'header', + docs: ['Status, write authority and VAA version.'], + type: { + defined: 'Header', + }, + }, + { + name: 'buf', + docs: ['VAA buffer.'], + type: 'bytes', + }, + ], + }, + }, + ], + types: [ + { + name: 'InitializeArgs', + docs: ['Arguments used to initialize the Core Bridge program.'], + type: { + kind: 'struct', + fields: [ + { + name: 'guardianSetTtlSeconds', + type: 'u32', + }, + { + name: 'feeLamports', + type: 'u64', + }, + { + name: 'initialGuardians', + type: { + vec: { + array: ['u8', 20], + }, + }, + }, + ], + }, + }, + { + name: 'PostMessageArgs', + docs: [ + 'Arguments used to post a new Wormhole (Core Bridge) message either using', + '[post_message](crate::legacy::instruction::post_message) or', + '[post_message_unreliable](crate::legacy::instruction::post_message_unreliable).', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'nonce', + docs: ['Unique id for this message.'], + type: 'u32', + }, + { + name: 'payload', + docs: ['Encoded message.'], + type: 'bytes', + }, + { + name: 'commitment', + docs: ['Solana commitment level for Guardian observation.'], + type: { + defined: 'Commitment', + }, + }, + ], + }, + }, + { + name: 'PostVaaArgs', + docs: [ + 'Arguments to post new VAA data after signature verification.', + '', + 'NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor', + 'instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and', + '[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'gap0', + docs: ['Unused data.'], + type: { + array: ['u8', 5], + }, + }, + { + name: 'timestamp', + docs: ['Time the message was submitted.'], + type: 'u32', + }, + { + name: 'nonce', + docs: ['Unique ID for this message.'], + type: 'u32', + }, + { + name: 'emitterChain', + docs: [ + 'The Wormhole chain ID denoting the origin of this message.', + ], + type: 'u16', + }, + { + name: 'emitterAddress', + docs: ['Emitter of the message.'], + type: { + array: ['u8', 32], + }, + }, + { + name: 'sequence', + docs: ['Sequence number of this message.'], + type: 'u64', + }, + { + name: 'consistencyLevel', + docs: ['Level of consistency requested by the emitter.'], + type: 'u8', + }, + { + name: 'payload', + docs: ['Message payload.'], + type: 'bytes', + }, + ], + }, + }, + { + name: 'VerifySignaturesArgs', + docs: [ + 'Arguments to verify specific guardian indices.', + '', + 'NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor', + 'instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and', + '[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'signerIndices', + docs: [ + 'Indices of verified guardian signatures, where -1 indicates a missing value. There is a', + 'missing value if the guardian at this index is not expected to have its signature verfied by', + 'the Sig Verify native program in the instruction invoked prior).', + '', + 'NOTE: In the legacy implementation, this argument being a fixed-sized array of 19 only', + 'allows the first 19 guardians of any size guardian set to be verified. Because of this, it', + 'is absolutely important to use the new process of verifying a VAA.', + ], + type: { + array: ['i8', 19], + }, + }, + ], + }, + }, + { + name: 'EmptyArgs', + docs: ['Unit struct used to represent an empty instruction argument.'], + type: { + kind: 'struct', + fields: [], + }, + }, + { + name: 'Config', + docs: [ + 'Account used to store the current configuration of the bridge, including tracking Wormhole fee', + 'payments. For governance decrees, the guardian set index is used to determine whether a decree', + 'was attested for using the latest guardian set.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'guardianSetIndex', + docs: [ + 'The current guardian set index, used to decide which signature sets to accept.', + ], + type: 'u32', + }, + { + name: 'gap0', + docs: [ + 'Gap. In the old implementation, this was an amount that kept track of message fees that', + "were paid to the program's fee collector.", + ], + type: { + array: ['u8', 8], + }, + }, + { + name: 'guardianSetTtl', + docs: [ + 'Period for how long a guardian set is valid after it has been replaced by a new one. This', + 'guarantees that VAAs issued by that set can still be submitted for a certain period. In', + 'this period we still trust the old guardian set.', + ], + type: { + defined: 'Duration', + }, + }, + { + name: 'feeLamports', + docs: [ + 'Amount of lamports that needs to be paid to the protocol to post a message', + ], + type: 'u64', + }, + ], + }, + }, + { + name: 'LegacyEmitterSequence', + docs: [ + 'Account used to store the current sequence number for a given emitter.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'value', + docs: [ + 'Current sequence number, which will be used the next time this emitter publishes a message.', + ], + type: 'u64', + }, + ], + }, + }, + { + name: 'EmitterSequence', + type: { + kind: 'struct', + fields: [ + { + name: 'legacy', + type: { + defined: 'LegacyEmitterSequence', + }, + }, + { + name: 'bump', + type: 'u8', + }, + { + name: 'emitterType', + type: { + defined: 'EmitterType', + }, + }, + ], + }, + }, + { + name: 'PostedMessageV1Unreliable', + docs: ['Account used to store a published (reusable) Wormhole message.'], + type: { + kind: 'struct', + fields: [ + { + name: 'data', + type: { + defined: 'PostedMessageV1Data', + }, + }, + ], + }, + }, + { + name: 'PostedMessageV1Info', + docs: [ + 'Message metadata defining information about a published Wormhole message.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'consistencyLevel', + docs: ['Level of consistency requested by the emitter.'], + type: 'u8', + }, + { + name: 'emitterAuthority', + docs: [ + 'Authority used to write the message. This field is set to default when the message is', + 'posted.', + ], + type: 'publicKey', + }, + { + name: 'status', + docs: [ + 'If a message is being written to, this status is used to determine which state this', + 'account is in (e.g. [MessageStatus::Writing] indicates that the emitter authority is still', + 'writing its message to this account). When this message is posted, this value will be', + 'set to [MessageStatus::Published].', + ], + type: { + defined: 'MessageStatus', + }, + }, + { + name: 'gap0', + docs: ['No data is stored here.'], + type: { + array: ['u8', 3], + }, + }, + { + name: 'postedTimestamp', + docs: ['Time the posted message was created.'], + type: { + defined: 'Timestamp', + }, + }, + { + name: 'nonce', + docs: ['Unique id for this message.'], + type: 'u32', + }, + { + name: 'sequence', + docs: ['Sequence number of this message.'], + type: 'u64', + }, + { + name: 'solanaChainId', + docs: [ + 'Always `1`.', + '', + 'NOTE: Saving this value is silly, but we are keeping it to be consistent with how the posted', + 'message account is written.', + ], + type: { + defined: 'ChainIdSolanaOnly', + }, + }, + { + name: 'emitter', + docs: [ + 'Emitter of the message. This may either be the emitter authority or a program ID.', + ], + type: 'publicKey', + }, + ], + }, + }, + { + name: 'PostedMessageV1Data', + docs: [ + 'Underlying data for either [PostedMessageV1](crate::legacy::state::PostedMessageV1) or', + '[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable).', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'info', + docs: ['Message metadata.'], + type: { + defined: 'PostedMessageV1Info', + }, + }, + { + name: 'payload', + docs: ['Encoded message.'], + type: 'bytes', + }, + ], + }, + }, + { + name: 'PostedMessageV1', + docs: [ + 'Account used to store a published Wormhole message.', + '', + 'NOTE: If your integration requires reusable message accounts, please see', + '[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable).', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'data', + docs: ['Message data.'], + type: { + defined: 'PostedMessageV1Data', + }, + }, + ], + }, + }, + { + name: 'PostedVaaV1Info', + docs: [ + 'VAA metadata defining information about a Wormhole message attested for by an active guardian', + 'set.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'consistencyLevel', + docs: ['Level of consistency requested by the emitter.'], + type: 'u8', + }, + { + name: 'timestamp', + docs: ['Time the message was submitted.'], + type: { + defined: 'Timestamp', + }, + }, + { + name: 'signatureSet', + docs: [ + "Pubkey of [SignatureSet](crate::state::SignatureSet) account that represents this VAA's", + 'signature verification.', + ], + type: 'publicKey', + }, + { + name: 'guardianSetIndex', + docs: [ + 'Guardian set index used to verify signatures for [SignatureSet](crate::state::SignatureSet).', + '', + 'NOTE: In the previous implementation, this member was referred to as the "posted timestamp",', + 'which is zero for VAA data (posted messages and VAAs resemble the same account schema). By', + 'changing this to the guardian set index, we patch a bug with verifying governance VAAs for', + 'the Core Bridge (other Core Bridge implementations require that the guardian set that', + 'attested for the governance VAA is the current one).', + ], + type: 'u32', + }, + { + name: 'nonce', + docs: ['Unique ID for this message.'], + type: 'u32', + }, + { + name: 'sequence', + docs: ['Sequence number of this message.'], + type: 'u64', + }, + { + name: 'emitterChain', + docs: [ + 'The Wormhole chain ID denoting the origin of this message.', + ], + type: 'u16', + }, + { + name: 'emitterAddress', + docs: ['Emitter of the message.'], + type: { + array: ['u8', 32], + }, + }, + ], + }, + }, + { + name: 'PostedVaaV1', + docs: ['Account used to store a verified VAA.'], + type: { + kind: 'struct', + fields: [ + { + name: 'info', + docs: ['VAA metadata.'], + type: { + defined: 'PostedVaaV1Info', + }, + }, + { + name: 'payload', + docs: ['Message payload.'], + type: 'bytes', + }, + ], + }, + }, + { + name: 'WriteEncodedVaaArgs', + docs: [ + 'Arguments for the [write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa)', + 'instruction.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'index', + docs: ['Index of VAA buffer.'], + type: 'u32', + }, + { + name: 'data', + docs: [ + 'Data representing subset of VAA buffer starting at specified index.', + ], + type: 'bytes', + }, + ], + }, + }, + { + name: 'InitMessageV1Args', + docs: [ + 'Arguments for the [init_message_v1](crate::wormhole_core_bridge_solana::init_message_v1)', + 'instruction.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'nonce', + docs: ['Unique id for this message.'], + type: 'u32', + }, + { + name: 'commitment', + docs: ['Solana commitment level for Guardian observation.'], + type: { + defined: 'Commitment', + }, + }, + { + name: 'cpiProgramId', + docs: [ + 'Optional program ID if the emitter address will be your program ID.', + '', + 'NOTE: If `Some(program_id)`, your emitter authority seeds to be \\[b"emitter\\].', + ], + type: { + option: 'publicKey', + }, + }, + ], + }, + }, + { + name: 'WriteMessageV1Args', + docs: [ + 'Arguments for the [write_message_v1](crate::wormhole_core_bridge_solana::write_message_v1)', + 'instruction.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'index', + docs: ['Index of message buffer.'], + type: 'u32', + }, + { + name: 'data', + docs: [ + 'Data representing subset of message buffer starting at specified index.', + ], + type: 'bytes', + }, + ], + }, + }, + { + name: 'Header', + docs: ['`EncodedVaa` account header.'], + type: { + kind: 'struct', + fields: [ + { + name: 'status', + docs: [ + 'Processing status. **This encoded VAA is only considered usable when this status is set', + 'to [Verified](ProcessingStatus::Verified).**', + ], + type: { + defined: 'ProcessingStatus', + }, + }, + { + name: 'writeAuthority', + docs: ['The authority that has write privilege to this account.'], + type: 'publicKey', + }, + { + name: 'version', + docs: [ + 'VAA version. Only when the VAA is verified is this version set to a value.', + ], + type: 'u8', + }, + ], + }, + }, + { + name: 'Timestamp', + docs: [ + 'This struct defines unix timestamp as u32 (as opposed to more modern systems that have adopted', + "i64). Methods for this struct are meant to convert Solana's clock type to this type assuming we", + 'are far from year 2038.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'value', + type: 'u32', + }, + ], + }, + }, + { + name: 'Duration', + docs: [ + 'To be used with the [Timestamp] type, this struct defines a duration in seconds.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'seconds', + type: 'u32', + }, + ], + }, + }, + { + name: 'MessageHash', + docs: ['This type is used to represent a message hash (keccak).'], + type: { + kind: 'struct', + fields: [ + { + name: 'bytes', + type: { + array: ['u8', 32], + }, + }, + ], + }, + }, + { + name: 'ChainIdSolanaOnly', + docs: [ + 'This type is kind of silly. But because [PostedMessageV1](crate::state::PostedMessageV1) has the', + "emitter chain ID as a field, which is unnecessary since it is always Solana's chain ID, we use", + 'this type to guarantee that the encoded chain ID is always `1`.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'chainId', + type: 'u16', + }, + ], + }, + }, + { + name: 'EmitterInfo', + type: { + kind: 'struct', + fields: [ + { + name: 'chain', + type: 'u16', + }, + { + name: 'address', + type: { + array: ['u8', 32], + }, + }, + { + name: 'sequence', + type: 'u64', + }, + ], + }, + }, + { + name: 'LegacyInstruction', + docs: [ + 'Legacy instruction selector.', + '', + 'NOTE: No more instructions should be added to this enum. Instead, add them as Anchor instruction', + 'handlers, which will inevitably live in', + '[wormhole_core_bridge_solana](crate::wormhole_core_bridge_solana).', + ], + type: { + kind: 'enum', + variants: [ + { + name: 'Initialize', + }, + { + name: 'PostMessage', + }, + { + name: 'PostVaa', + }, + { + name: 'SetMessageFee', + }, + { + name: 'TransferFees', + }, + { + name: 'UpgradeContract', + }, + { + name: 'GuardianSetUpdate', + }, + { + name: 'VerifySignatures', + }, + { + name: 'PostMessageUnreliable', + }, + ], + }, + }, + { + name: 'EmitterType', + type: { + kind: 'enum', + variants: [ + { + name: 'Unset', + }, + { + name: 'Legacy', + }, + { + name: 'Executable', + }, + ], + }, + }, + { + name: 'MessageStatus', + docs: [ + 'Status of a message. When a message is posted, its status is', + '[Published](MessageStatus::Published).', + ], + type: { + kind: 'enum', + variants: [ + { + name: 'Published', + }, + { + name: 'Writing', + }, + { + name: 'ReadyForPublishing', + }, + ], + }, + }, + { + name: 'PublishMessageDirective', + docs: ['Directive used to determine how to post a Core Bridge message.'], + type: { + kind: 'enum', + variants: [ + { + name: 'Message', + fields: [ + { + name: 'nonce', + type: 'u32', + }, + { + name: 'payload', + type: 'bytes', + }, + { + name: 'commitment', + type: { + defined: 'Commitment', + }, + }, + ], + }, + { + name: 'ProgramMessage', + fields: [ + { + name: 'programId', + type: 'publicKey', + }, + { + name: 'nonce', + type: 'u32', + }, + { + name: 'payload', + type: 'bytes', + }, + { + name: 'commitment', + type: { + defined: 'Commitment', + }, + }, + ], + }, + { + name: 'PreparedMessage', + }, + ], + }, + }, + { + name: 'ProcessingStatus', + docs: ["Encoded VAA's processing status."], + type: { + kind: 'enum', + variants: [ + { + name: 'Unset', + }, + { + name: 'Writing', + }, + { + name: 'Verified', + }, + ], + }, + }, + { + name: 'Commitment', + docs: [ + "Representation of Solana's commitment levels. This enum is not exhaustive because Wormhole only", + 'considers these two commitment levels in its Guardian observation.', + '', + 'See for more info.', + ], + type: { + kind: 'enum', + variants: [ + { + name: 'Confirmed', + }, + { + name: 'Finalized', + }, + ], + }, + }, + ], + errors: [ + { + code: 6002, + name: 'InvalidInstructionArgument', + msg: 'InvalidInstructionArgument', + }, + { + code: 6003, + name: 'AccountNotZeroed', + msg: 'AccountNotZeroed', + }, + { + code: 6004, + name: 'InvalidDataConversion', + msg: 'InvalidDataConversion', + }, + { + code: 6006, + name: 'U64Overflow', + msg: 'U64Overflow', + }, + { + code: 6008, + name: 'InvalidComputeSize', + msg: 'InvalidComputeSize', + }, + { + code: 6016, + name: 'InvalidChain', + msg: 'InvalidChain', + }, + { + code: 6032, + name: 'InvalidGovernanceEmitter', + msg: 'InvalidGovernanceEmitter', + }, + { + code: 6034, + name: 'InvalidGovernanceAction', + msg: 'InvalidGovernanceAction', + }, + { + code: 6036, + name: 'LatestGuardianSetRequired', + msg: 'LatestGuardianSetRequired', + }, + { + code: 6038, + name: 'GovernanceForAnotherChain', + msg: 'GovernanceForAnotherChain', + }, + { + code: 6040, + name: 'InvalidGovernanceVaa', + msg: 'InvalidGovernanceVaa', + }, + { + code: 6256, + name: 'InsufficientFees', + msg: 'InsufficientFees', + }, + { + code: 6258, + name: 'EmitterMismatch', + msg: 'EmitterMismatch', + }, + { + code: 6260, + name: 'NotReadyForPublishing', + msg: 'NotReadyForPublishing', + }, + { + code: 6262, + name: 'InvalidPreparedMessage', + msg: 'InvalidPreparedMessage', + }, + { + code: 6264, + name: 'ExecutableEmitter', + msg: 'ExecutableEmitter', + }, + { + code: 6266, + name: 'LegacyEmitter', + msg: 'LegacyEmitter', + }, + { + code: 6512, + name: 'InvalidSignatureSet', + msg: 'InvalidSignatureSet', + }, + { + code: 6514, + name: 'InvalidMessageHash', + msg: 'InvalidMessageHash', + }, + { + code: 6515, + name: 'NoQuorum', + msg: 'NoQuorum', + }, + { + code: 6516, + name: 'MessageMismatch', + msg: 'MessageMismatch', + }, + { + code: 7024, + name: 'NotEnoughLamports', + msg: 'NotEnoughLamports', + }, + { + code: 7026, + name: 'InvalidFeeRecipient', + msg: 'InvalidFeeRecipient', + }, + { + code: 7280, + name: 'ImplementationMismatch', + msg: 'ImplementationMismatch', + }, + { + code: 7536, + name: 'InvalidGuardianSetIndex', + msg: 'InvalidGuardianSetIndex', + }, + { + code: 7792, + name: 'GuardianSetMismatch', + msg: 'GuardianSetMismatch', + }, + { + code: 7794, + name: 'InstructionAtWrongIndex', + msg: 'InstructionAtWrongIndex', + }, + { + code: 7795, + name: 'EmptySigVerifyInstruction', + msg: 'EmptySigVerifyInstruction', + }, + { + code: 7796, + name: 'InvalidSigVerifyInstruction', + msg: 'InvalidSigVerifyInstruction', + }, + { + code: 7798, + name: 'GuardianSetExpired', + msg: 'GuardianSetExpired', + }, + { + code: 7800, + name: 'InvalidGuardianKeyRecovery', + msg: 'InvalidGuardianKeyRecovery', + }, + { + code: 7802, + name: 'SignerIndicesMismatch', + msg: 'SignerIndicesMismatch', + }, + { + code: 8048, + name: 'PayloadSizeMismatch', + msg: 'PayloadSizeMismatch', + }, + { + code: 10112, + name: 'ZeroGuardians', + msg: 'ZeroGuardians', + }, + { + code: 10128, + name: 'GuardianZeroAddress', + msg: 'GuardianZeroAddress', + }, + { + code: 10144, + name: 'DuplicateGuardianAddress', + msg: 'DuplicateGuardianAddress', + }, + { + code: 10160, + name: 'MessageAlreadyPublished', + msg: 'MessageAlreadyPublished', + }, + { + code: 10176, + name: 'VaaWritingDisallowed', + msg: 'VaaWritingDisallowed', + }, + { + code: 10192, + name: 'VaaAlreadyVerified', + msg: 'VaaAlreadyVerified', + }, + { + code: 10208, + name: 'InvalidGuardianIndex', + msg: 'InvalidGuardianIndex', + }, + { + code: 10224, + name: 'InvalidSignature', + msg: 'InvalidSignature', + }, + { + code: 10256, + name: 'UnverifiedVaa', + msg: 'UnverifiedVaa', + }, + { + code: 10258, + name: 'VaaStillProcessing', + msg: 'VaaStillProcessing', + }, + { + code: 10260, + name: 'InWritingStatus', + msg: 'InWritingStatus', + }, + { + code: 10262, + name: 'NotInWritingStatus', + msg: 'NotInWritingStatus', + }, + { + code: 10264, + name: 'InvalidMessageStatus', + msg: 'InvalidMessageStatus', + }, + { + code: 10266, + name: 'HashNotComputed', + msg: 'HashNotComputed', + }, + { + code: 10268, + name: 'InvalidVaaVersion', + msg: 'InvalidVaaVersion', + }, + { + code: 10270, + name: 'InvalidCreatedAccountSize', + msg: 'InvalidCreatedAccountSize', + }, + { + code: 10272, + name: 'DataOverflow', + msg: 'DataOverflow', + }, + { + code: 10274, + name: 'ExceedsMaxPayloadSize', + msg: 'ExceedsMaxPayloadSize (30KB)', + }, + { + code: 10276, + name: 'CannotParseVaa', + msg: 'CannotParseVaa', + }, + { + code: 10278, + name: 'EmitterAuthorityMismatch', + msg: 'EmitterAuthorityMismatch', + }, + { + code: 10280, + name: 'InvalidProgramEmitter', + msg: 'InvalidProgramEmitter', + }, + { + code: 10282, + name: 'WriteAuthorityMismatch', + msg: 'WriteAuthorityMismatch', + }, + { + code: 10284, + name: 'PostedVaaPayloadTooLarge', + msg: 'PostedVaaPayloadTooLarge', + }, + { + code: 10286, + name: 'ExecutableDisallowed', + msg: 'ExecutableDisallowed', + }, + ], +}; + +export type PythSolanaReceiver = { + version: '0.1.0'; + name: 'pyth_solana_receiver'; + instructions: [ + { + name: 'initialize'; + accounts: [ + { + name: 'payer'; + isMut: true; + isSigner: true; + }, + { + name: 'config'; + isMut: true; + isSigner: false; + }, + { + name: 'systemProgram'; + isMut: false; + isSigner: false; + }, + ]; + args: [ + { + name: 'initialConfig'; + type: { + defined: 'Config'; + }; + }, + ]; + }, + { + name: 'requestGovernanceAuthorityTransfer'; + accounts: [ + { + name: 'payer'; + isMut: false; + isSigner: true; + }, + { + name: 'config'; + isMut: true; + isSigner: false; + }, + ]; + args: [ + { + name: 'targetGovernanceAuthority'; + type: 'publicKey'; + }, + ]; + }, + { + name: 'acceptGovernanceAuthorityTransfer'; + accounts: [ + { + name: 'payer'; + isMut: false; + isSigner: true; + }, + { + name: 'config'; + isMut: true; + isSigner: false; + }, + ]; + args: []; + }, + { + name: 'setDataSources'; + accounts: [ + { + name: 'payer'; + isMut: false; + isSigner: true; + }, + { + name: 'config'; + isMut: true; + isSigner: false; + }, + ]; + args: [ + { + name: 'validDataSources'; + type: { + vec: { + defined: 'DataSource'; + }; + }; + }, + ]; + }, + { + name: 'setFee'; + accounts: [ + { + name: 'payer'; + isMut: false; + isSigner: true; + }, + { + name: 'config'; + isMut: true; + isSigner: false; + }, + ]; + args: [ + { + name: 'singleUpdateFeeInLamports'; + type: 'u64'; + }, + ]; + }, + { + name: 'setWormholeAddress'; + accounts: [ + { + name: 'payer'; + isMut: false; + isSigner: true; + }, + { + name: 'config'; + isMut: true; + isSigner: false; + }, + ]; + args: [ + { + name: 'wormhole'; + type: 'publicKey'; + }, + ]; + }, + { + name: 'setMinimumSignatures'; + accounts: [ + { + name: 'payer'; + isMut: false; + isSigner: true; + }, + { + name: 'config'; + isMut: true; + isSigner: false; + }, + ]; + args: [ + { + name: 'minimumSignatures'; + type: 'u8'; + }, + ]; + }, + { + name: 'postUpdateAtomic'; + docs: [ + 'Post a price update using a VAA and a MerklePriceUpdate.', + 'This function allows you to post a price update in a single transaction.', + "Compared to post_update, it is less secure since you won't be able to verify all guardian signatures if you use this function because of transaction size limitations.", + 'Typically, you can fit 5 guardian signatures in a transaction that uses this.', + ]; + accounts: [ + { + name: 'payer'; + isMut: true; + isSigner: true; + }, + { + name: 'guardianSet'; + isMut: false; + isSigner: false; + docs: [ + 'Instead we do the same steps in deserialize_guardian_set_checked.', + ]; + }, + { + name: 'config'; + isMut: false; + isSigner: false; + }, + { + name: 'treasury'; + isMut: true; + isSigner: false; + }, + { + name: 'priceUpdateAccount'; + isMut: true; + isSigner: true; + docs: [ + 'The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.', + "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized", + ]; + }, + { + name: 'systemProgram'; + isMut: false; + isSigner: false; + }, + { + name: 'writeAuthority'; + isMut: false; + isSigner: true; + }, + ]; + args: [ + { + name: 'params'; + type: { + defined: 'PostUpdateAtomicParams'; + }; + }, + ]; + }, + { + name: 'postUpdate'; + docs: [ + 'Post a price update using an encoded_vaa account and a MerklePriceUpdate calldata.', + 'This should be called after the client has already verified the Vaa via the Wormhole contract.', + 'Check out target_chains/solana/cli/src/main.rs for an example of how to do this.', + ]; + accounts: [ + { + name: 'payer'; + isMut: true; + isSigner: true; + }, + { + name: 'encodedVaa'; + isMut: false; + isSigner: false; + }, + { + name: 'config'; + isMut: false; + isSigner: false; + }, + { + name: 'treasury'; + isMut: true; + isSigner: false; + }, + { + name: 'priceUpdateAccount'; + isMut: true; + isSigner: true; + docs: [ + 'The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.', + "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized", + ]; + }, + { + name: 'systemProgram'; + isMut: false; + isSigner: false; + }, + { + name: 'writeAuthority'; + isMut: false; + isSigner: true; + }, + ]; + args: [ + { + name: 'params'; + type: { + defined: 'PostUpdateParams'; + }; + }, + ]; + }, + { + name: 'reclaimRent'; + accounts: [ + { + name: 'payer'; + isMut: true; + isSigner: true; + }, + { + name: 'priceUpdateAccount'; + isMut: true; + isSigner: false; + }, + ]; + args: []; + }, + ]; + accounts: [ + { + name: 'Config'; + type: { + kind: 'struct'; + fields: [ + { + name: 'governanceAuthority'; + type: 'publicKey'; + }, + { + name: 'targetGovernanceAuthority'; + type: { + option: 'publicKey'; + }; + }, + { + name: 'wormhole'; + type: 'publicKey'; + }, + { + name: 'validDataSources'; + type: { + vec: { + defined: 'DataSource'; + }; + }; + }, + { + name: 'singleUpdateFeeInLamports'; + type: 'u64'; + }, + { + name: 'minimumSignatures'; + type: 'u8'; + }, + ]; + }; + }, + { + name: 'priceUpdateV2'; + type: { + kind: 'struct'; + fields: [ + { + name: 'writeAuthority'; + type: 'publicKey'; + }, + { + name: 'verificationLevel'; + type: { + defined: 'VerificationLevel'; + }; + }, + { + name: 'priceMessage'; + type: { + defined: 'PriceFeedMessage'; + }; + }, + { + name: 'postedSlot'; + type: 'u64'; + }, + ]; + }; + }, + ]; + types: [ + { + name: 'PriceFeedMessage'; + type: { + kind: 'struct'; + fields: [ + { + name: 'feedId'; + type: { + array: ['u8', 32]; + }; + }, + { + name: 'price'; + type: 'i64'; + }, + { + name: 'conf'; + type: 'u64'; + }, + { + name: 'exponent'; + type: 'i32'; + }, + { + name: 'publishTime'; + type: 'i64'; + }, + { + name: 'prevPublishTime'; + type: 'i64'; + }, + { + name: 'emaPrice'; + type: 'i64'; + }, + { + name: 'emaConf'; + type: 'u64'; + }, + ]; + }; + }, + { + name: 'MerklePriceUpdate'; + type: { + kind: 'struct'; + fields: [ + { + name: 'message'; + type: 'bytes'; + }, + { + name: 'proof'; + type: { + vec: { + array: ['u8', 20]; + }; + }; + }, + ]; + }; + }, + { + name: 'DataSource'; + type: { + kind: 'struct'; + fields: [ + { + name: 'chain'; + type: 'u16'; + }, + { + name: 'emitter'; + type: 'publicKey'; + }, + ]; + }; + }, + { + name: 'PostUpdateAtomicParams'; + type: { + kind: 'struct'; + fields: [ + { + name: 'vaa'; + type: 'bytes'; + }, + { + name: 'merklePriceUpdate'; + type: { + defined: 'MerklePriceUpdate'; + }; + }, + { + name: 'treasuryId'; + type: 'u8'; + }, + ]; + }; + }, + { + name: 'PostUpdateParams'; + type: { + kind: 'struct'; + fields: [ + { + name: 'merklePriceUpdate'; + type: { + defined: 'MerklePriceUpdate'; + }; + }, + { + name: 'treasuryId'; + type: 'u8'; + }, + ]; + }; + }, + { + name: 'VerificationLevel'; + docs: [ + '* This enum represents how many guardian signatures were checked for a Pythnet price update\n * If full, guardian quorum has been attained\n * If partial, at least config.minimum signatures have been verified, but in the case config.minimum_signatures changes in the future we also include the number of signatures that were checked', + ]; + type: { + kind: 'enum'; + variants: [ + { + name: 'Partial'; + fields: [ + { + name: 'numSignatures'; + type: 'u8'; + }, + ]; + }, + { + name: 'Full'; + }, + ]; + }; + }, + ]; + errors: [ + { + code: 6000; + name: 'InvalidWormholeMessage'; + msg: 'Received an invalid wormhole message'; + }, + { + code: 6001; + name: 'DeserializeMessageFailed'; + msg: 'An error occurred when deserializing the message'; + }, + { + code: 6002; + name: 'InvalidPriceUpdate'; + msg: 'Received an invalid price update'; + }, + { + code: 6003; + name: 'UnsupportedMessageType'; + msg: 'This type of message is not supported currently'; + }, + { + code: 6004; + name: 'InvalidDataSource'; + msg: "The tuple emitter chain, emitter doesn't match one of the valid data sources."; + }, + { + code: 6005; + name: 'InsufficientFunds'; + msg: 'Funds are insufficient to pay the receiving fee'; + }, + { + code: 6006; + name: 'WrongWriteAuthority'; + msg: "This signer can't write to price update account"; + }, + { + code: 6007; + name: 'WrongVaaOwner'; + msg: 'The posted VAA account has the wrong owner.'; + }, + { + code: 6008; + name: 'DeserializeVaaFailed'; + msg: 'An error occurred when deserializing the VAA.'; + }, + { + code: 6009; + name: 'InsufficientGuardianSignatures'; + msg: 'The number of guardian signatures is below the minimum'; + }, + { + code: 6010; + name: 'InvalidVaaVersion'; + msg: 'Invalid VAA version'; + }, + { + code: 6011; + name: 'GuardianSetMismatch'; + msg: "Guardian set version in the VAA doesn't match the guardian set passed"; + }, + { + code: 6012; + name: 'InvalidGuardianOrder'; + msg: 'Guardian signature indices must be increasing'; + }, + { + code: 6013; + name: 'InvalidGuardianIndex'; + msg: 'Guardian index exceeds the number of guardians in the set'; + }, + { + code: 6014; + name: 'InvalidSignature'; + msg: 'A VAA signature is invalid'; + }, + { + code: 6015; + name: 'InvalidGuardianKeyRecovery'; + msg: "The recovered guardian public key doesn't match the guardian set"; + }, + { + code: 6016; + name: 'WrongGuardianSetOwner'; + msg: 'The guardian set account is owned by the wrong program'; + }, + { + code: 6017; + name: 'InvalidGuardianSetPda'; + msg: "The Guardian Set account doesn't match the PDA derivation"; + }, + { + code: 6018; + name: 'GuardianSetExpired'; + msg: 'The Guardian Set is expired'; + }, + { + code: 6019; + name: 'GovernanceAuthorityMismatch'; + msg: 'The signer is not authorized to perform this governance action'; + }, + { + code: 6020; + name: 'TargetGovernanceAuthorityMismatch'; + msg: 'The signer is not authorized to accept the governance authority'; + }, + { + code: 6021; + name: 'NonexistentGovernanceAuthorityTransferRequest'; + msg: 'The governance authority needs to request a transfer first'; + }, + ]; +}; + +export const IDL: PythSolanaReceiver = { + version: '0.1.0', + name: 'pyth_solana_receiver', + instructions: [ + { + name: 'initialize', + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'config', + isMut: true, + isSigner: false, + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: 'initialConfig', + type: { + defined: 'Config', + }, + }, + ], + }, + { + name: 'requestGovernanceAuthorityTransfer', + accounts: [ + { + name: 'payer', + isMut: false, + isSigner: true, + }, + { + name: 'config', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'targetGovernanceAuthority', + type: 'publicKey', + }, + ], + }, + { + name: 'acceptGovernanceAuthorityTransfer', + accounts: [ + { + name: 'payer', + isMut: false, + isSigner: true, + }, + { + name: 'config', + isMut: true, + isSigner: false, + }, + ], + args: [], + }, + { + name: 'setDataSources', + accounts: [ + { + name: 'payer', + isMut: false, + isSigner: true, + }, + { + name: 'config', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'validDataSources', + type: { + vec: { + defined: 'DataSource', + }, + }, + }, + ], + }, + { + name: 'setFee', + accounts: [ + { + name: 'payer', + isMut: false, + isSigner: true, + }, + { + name: 'config', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'singleUpdateFeeInLamports', + type: 'u64', + }, + ], + }, + { + name: 'setWormholeAddress', + accounts: [ + { + name: 'payer', + isMut: false, + isSigner: true, + }, + { + name: 'config', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'wormhole', + type: 'publicKey', + }, + ], + }, + { + name: 'setMinimumSignatures', + accounts: [ + { + name: 'payer', + isMut: false, + isSigner: true, + }, + { + name: 'config', + isMut: true, + isSigner: false, + }, + ], + args: [ + { + name: 'minimumSignatures', + type: 'u8', + }, + ], + }, + { + name: 'postUpdateAtomic', + docs: [ + 'Post a price update using a VAA and a MerklePriceUpdate.', + 'This function allows you to post a price update in a single transaction.', + "Compared to post_update, it is less secure since you won't be able to verify all guardian signatures if you use this function because of transaction size limitations.", + 'Typically, you can fit 5 guardian signatures in a transaction that uses this.', + ], + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'guardianSet', + isMut: false, + isSigner: false, + docs: [ + 'Instead we do the same steps in deserialize_guardian_set_checked.', + ], + }, + { + name: 'config', + isMut: false, + isSigner: false, + }, + { + name: 'treasury', + isMut: true, + isSigner: false, + }, + { + name: 'priceUpdateAccount', + isMut: true, + isSigner: true, + docs: [ + 'The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.', + "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized", + ], + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'writeAuthority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'PostUpdateAtomicParams', + }, + }, + ], + }, + { + name: 'postUpdate', + docs: [ + 'Post a price update using an encoded_vaa account and a MerklePriceUpdate calldata.', + 'This should be called after the client has already verified the Vaa via the Wormhole contract.', + 'Check out target_chains/solana/cli/src/main.rs for an example of how to do this.', + ], + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'encodedVaa', + isMut: false, + isSigner: false, + }, + { + name: 'config', + isMut: false, + isSigner: false, + }, + { + name: 'treasury', + isMut: true, + isSigner: false, + }, + { + name: 'priceUpdateAccount', + isMut: true, + isSigner: true, + docs: [ + 'The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.', + "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized", + ], + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'writeAuthority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'params', + type: { + defined: 'PostUpdateParams', + }, + }, + ], + }, + { + name: 'reclaimRent', + accounts: [ + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'priceUpdateAccount', + isMut: true, + isSigner: false, + }, + ], + args: [], + }, + ], + accounts: [ + { + name: 'Config', + type: { + kind: 'struct', + fields: [ + { + name: 'governanceAuthority', + type: 'publicKey', + }, + { + name: 'targetGovernanceAuthority', + type: { + option: 'publicKey', + }, + }, + { + name: 'wormhole', + type: 'publicKey', + }, + { + name: 'validDataSources', + type: { + vec: { + defined: 'DataSource', + }, + }, + }, + { + name: 'singleUpdateFeeInLamports', + type: 'u64', + }, + { + name: 'minimumSignatures', + type: 'u8', + }, + ], + }, + }, + { + name: 'priceUpdateV2', + type: { + kind: 'struct', + fields: [ + { + name: 'writeAuthority', + type: 'publicKey', + }, + { + name: 'verificationLevel', + type: { + defined: 'VerificationLevel', + }, + }, + { + name: 'priceMessage', + type: { + defined: 'PriceFeedMessage', + }, + }, + { + name: 'postedSlot', + type: 'u64', + }, + ], + }, + }, + ], + types: [ + { + name: 'PriceFeedMessage', + type: { + kind: 'struct', + fields: [ + { + name: 'feedId', + type: { + array: ['u8', 32], + }, + }, + { + name: 'price', + type: 'i64', + }, + { + name: 'conf', + type: 'u64', + }, + { + name: 'exponent', + type: 'i32', + }, + { + name: 'publishTime', + type: 'i64', + }, + { + name: 'prevPublishTime', + type: 'i64', + }, + { + name: 'emaPrice', + type: 'i64', + }, + { + name: 'emaConf', + type: 'u64', + }, + ], + }, + }, + { + name: 'MerklePriceUpdate', + type: { + kind: 'struct', + fields: [ + { + name: 'message', + type: 'bytes', + }, + { + name: 'proof', + type: { + vec: { + array: ['u8', 20], + }, + }, + }, + ], + }, + }, + { + name: 'DataSource', + type: { + kind: 'struct', + fields: [ + { + name: 'chain', + type: 'u16', + }, + { + name: 'emitter', + type: 'publicKey', + }, + ], + }, + }, + { + name: 'PostUpdateAtomicParams', + type: { + kind: 'struct', + fields: [ + { + name: 'vaa', + type: 'bytes', + }, + { + name: 'merklePriceUpdate', + type: { + defined: 'MerklePriceUpdate', + }, + }, + { + name: 'treasuryId', + type: 'u8', + }, + ], + }, + }, + { + name: 'PostUpdateParams', + type: { + kind: 'struct', + fields: [ + { + name: 'merklePriceUpdate', + type: { + defined: 'MerklePriceUpdate', + }, + }, + { + name: 'treasuryId', + type: 'u8', + }, + ], + }, + }, + { + name: 'VerificationLevel', + docs: [ + '* This enum represents how many guardian signatures were checked for a Pythnet price update\n * If full, guardian quorum has been attained\n * If partial, at least config.minimum signatures have been verified, but in the case config.minimum_signatures changes in the future we also include the number of signatures that were checked', + ], + type: { + kind: 'enum', + variants: [ + { + name: 'Partial', + fields: [ + { + name: 'numSignatures', + type: 'u8', + }, + ], + }, + { + name: 'Full', + }, + ], + }, + }, + ], + errors: [ + { + code: 6000, + name: 'InvalidWormholeMessage', + msg: 'Received an invalid wormhole message', + }, + { + code: 6001, + name: 'DeserializeMessageFailed', + msg: 'An error occurred when deserializing the message', + }, + { + code: 6002, + name: 'InvalidPriceUpdate', + msg: 'Received an invalid price update', + }, + { + code: 6003, + name: 'UnsupportedMessageType', + msg: 'This type of message is not supported currently', + }, + { + code: 6004, + name: 'InvalidDataSource', + msg: "The tuple emitter chain, emitter doesn't match one of the valid data sources.", + }, + { + code: 6005, + name: 'InsufficientFunds', + msg: 'Funds are insufficient to pay the receiving fee', + }, + { + code: 6006, + name: 'WrongWriteAuthority', + msg: "This signer can't write to price update account", + }, + { + code: 6007, + name: 'WrongVaaOwner', + msg: 'The posted VAA account has the wrong owner.', + }, + { + code: 6008, + name: 'DeserializeVaaFailed', + msg: 'An error occurred when deserializing the VAA.', + }, + { + code: 6009, + name: 'InsufficientGuardianSignatures', + msg: 'The number of guardian signatures is below the minimum', + }, + { + code: 6010, + name: 'InvalidVaaVersion', + msg: 'Invalid VAA version', + }, + { + code: 6011, + name: 'GuardianSetMismatch', + msg: "Guardian set version in the VAA doesn't match the guardian set passed", + }, + { + code: 6012, + name: 'InvalidGuardianOrder', + msg: 'Guardian signature indices must be increasing', + }, + { + code: 6013, + name: 'InvalidGuardianIndex', + msg: 'Guardian index exceeds the number of guardians in the set', + }, + { + code: 6014, + name: 'InvalidSignature', + msg: 'A VAA signature is invalid', + }, + { + code: 6015, + name: 'InvalidGuardianKeyRecovery', + msg: "The recovered guardian public key doesn't match the guardian set", + }, + { + code: 6016, + name: 'WrongGuardianSetOwner', + msg: 'The guardian set account is owned by the wrong program', + }, + { + code: 6017, + name: 'InvalidGuardianSetPda', + msg: "The Guardian Set account doesn't match the PDA derivation", + }, + { + code: 6018, + name: 'GuardianSetExpired', + msg: 'The Guardian Set is expired', + }, + { + code: 6019, + name: 'GovernanceAuthorityMismatch', + msg: 'The signer is not authorized to perform this governance action', + }, + { + code: 6020, + name: 'TargetGovernanceAuthorityMismatch', + msg: 'The signer is not authorized to accept the governance authority', + }, + { + code: 6021, + name: 'NonexistentGovernanceAuthorityTransferRequest', + msg: 'The governance authority needs to request a transfer first', + }, + ], +}; + +export declare const PYTH_SOLANA_RECEIVER_IDL: PythSolanaReceiver; +//# sourceMappingURL=pyth_solana_receiver.d.ts.map + +export type PriceUpdateAccount = + IdlAccounts['priceUpdateV2']; diff --git a/sdk/src/pyth/utils.ts b/sdk/src/pyth/utils.ts new file mode 100644 index 0000000000..4c3492042d --- /dev/null +++ b/sdk/src/pyth/utils.ts @@ -0,0 +1,13 @@ +import { PublicKey } from '@solana/web3.js'; + +export const getGuardianSetPda = ( + guardianSetIndex: number, + wormholeProgramId: PublicKey +) => { + const guardianSetIndexBuf = Buffer.alloc(4); + guardianSetIndexBuf.writeUInt32BE(guardianSetIndex, 0); + return PublicKey.findProgramAddressSync( + [Buffer.from('GuardianSet'), guardianSetIndexBuf], + wormholeProgramId + )[0]; +}; diff --git a/sdk/src/swap/UnifiedSwapClient.ts b/sdk/src/swap/UnifiedSwapClient.ts index fdf75129fa..35219af746 100644 --- a/sdk/src/swap/UnifiedSwapClient.ts +++ b/sdk/src/swap/UnifiedSwapClient.ts @@ -11,11 +11,7 @@ import { JupiterClient, QuoteResponse as JupiterQuoteResponse, } from '../jupiter/jupiterClient'; -import { - TitanClient, - QuoteResponse as TitanQuoteResponse, - SwapMode as TitanSwapMode, -} from '../titan/titanClient'; +import { TitanClient, SwapMode as TitanSwapMode } from '../titan/titanClient'; export type SwapMode = 'ExactIn' | 'ExactOut'; export type SwapClientType = 'jupiter' | 'titan'; @@ -77,6 +73,14 @@ export class UnifiedSwapClient { private client: JupiterClient | TitanClient; private clientType: SwapClientType; + /** + * Create a unified swap client + * @param clientType - 'jupiter' or 'titan' + * @param connection - Solana connection + * @param authToken - For Titan: auth token (required when not using proxy). For Jupiter: API key (required for api.jup.ag, get free key at https://portal.jup.ag) + * @param url - Optional custom URL + * @param proxyUrl - Optional proxy URL for Titan + */ constructor({ clientType, connection, @@ -86,7 +90,7 @@ export class UnifiedSwapClient { }: { clientType: SwapClientType; connection: Connection; - authToken?: string; // Required for Titan when not using proxy, optional for Jupiter + authToken?: string; // For Titan: auth token. For Jupiter: API key (required for api.jup.ag) url?: string; // Optional custom URL proxyUrl?: string; // Optional proxy URL for Titan }) { @@ -96,6 +100,7 @@ export class UnifiedSwapClient { this.client = new JupiterClient({ connection, url, + apiKey: authToken, }); } else if (clientType === 'titan') { this.client = new TitanClient({ @@ -167,18 +172,11 @@ export class UnifiedSwapClient { return { transaction }; } else { const titanClient = this.client as TitanClient; - const { quote, userPublicKey, slippageBps } = params; + const { userPublicKey } = params; // For Titan, we need to reconstruct the parameters from the quote - const titanQuote = quote as TitanQuoteResponse; const result = await titanClient.getSwap({ - inputMint: new PublicKey(titanQuote.inputMint), - outputMint: new PublicKey(titanQuote.outputMint), - amount: new BN(titanQuote.inAmount), userPublicKey, - slippageBps: slippageBps || titanQuote.slippageBps, - swapMode: titanQuote.swapMode, - sizeConstraint: 1280 - 375, // MAX_TX_BYTE_SIZE - buffer for drift instructions }); return { diff --git a/sdk/src/titan/titanClient.ts b/sdk/src/titan/titanClient.ts index 339a8e5d17..6eb8f81220 100644 --- a/sdk/src/titan/titanClient.ts +++ b/sdk/src/titan/titanClient.ts @@ -287,21 +287,11 @@ export class TitanClient { * Get a swap transaction for quote */ public async getSwap({ - inputMint, - outputMint, - amount, userPublicKey, - maxAccounts = 50, // 50 is an estimated amount with buffer - slippageBps, - swapMode, - onlyDirectRoutes, - excludeDexes, - sizeConstraint, - accountsLimitWritable, }: { - inputMint: PublicKey; - outputMint: PublicKey; - amount: BN; + inputMint?: PublicKey; + outputMint?: PublicKey; + amount?: BN; userPublicKey: PublicKey; maxAccounts?: number; slippageBps?: number; @@ -314,22 +304,8 @@ export class TitanClient { transactionMessage: TransactionMessage; lookupTables: AddressLookupTableAccount[]; }> { - const params = this.buildParams({ - inputMint, - outputMint, - amount, - userPublicKey, - maxAccounts, - slippageBps, - swapMode, - onlyDirectRoutes, - excludeDexes, - sizeConstraint, - accountsLimitWritable, - }); - // Check if we have cached quote data that matches the current parameters - if (!this.lastQuoteData || this.lastQuoteParams !== params.toString()) { + if (!this.lastQuoteData) { throw new Error( 'No matching quote data found. Please get a fresh quote before attempting to swap.' ); diff --git a/sdk/src/tx/txHandler.ts b/sdk/src/tx/txHandler.ts index bd9b2b702e..99a1d37a64 100644 --- a/sdk/src/tx/txHandler.ts +++ b/sdk/src/tx/txHandler.ts @@ -495,9 +495,14 @@ export class TxHandler { const marketLookupTables = await fetchAllMarketLookupTableAccounts(); - lookupTables = lookupTables + // Combine and filter out any null/undefined lookup tables + const combinedLookupTables = lookupTables ? [...lookupTables, ...marketLookupTables] : marketLookupTables; + lookupTables = combinedLookupTables.filter( + (table): table is AddressLookupTableAccount => + table !== null && table !== undefined + ); // # Collect and process Tx Params let baseTxParams: BaseTxParams = { diff --git a/sdk/src/tx/utils.ts b/sdk/src/tx/utils.ts index 617b3f6ea6..bd40d87e22 100644 --- a/sdk/src/tx/utils.ts +++ b/sdk/src/tx/utils.ts @@ -58,8 +58,13 @@ export const getSizeOfTransaction = ( .reduce((a, b) => a + b, 0); let numberOfAddressLookups = 0; - if (addressLookupTables.length > 0) { - const lookupTableAddresses = addressLookupTables + // Filter out null/undefined lookup tables before accessing .state + const validLookupTables = addressLookupTables.filter( + (table): table is AddressLookupTableAccount => + table !== null && table !== undefined + ); + if (validLookupTables.length > 0) { + const lookupTableAddresses = validLookupTables .map((addressLookupTable) => addressLookupTable.state.addresses.map((address) => address.toBase58()) ) diff --git a/sdk/src/types.ts b/sdk/src/types.ts index ea111ce0c5..feda6c7905 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -591,6 +591,10 @@ export type SpotBankruptcyRecord = { ifPayment: BN; }; +export class LiquidationBitFlag { + static readonly IsolatedPosition = 1; +} + export type SettlePnlRecord = { ts: BN; user: PublicKey; @@ -1142,16 +1146,10 @@ export type PerpPosition = { maxMarginRatio: number; lastQuoteAssetAmountPerLp: BN; perLpBase: number; - isolatedPositionScaledBalance: BN; positionFlag: number; + isolatedPositionScaledBalance: BN; }; -export class PositionFlag { - static readonly IsolatedPosition = 1; - static readonly BeingLiquidated = 2; - static readonly Bankruptcy = 4; -} - export type UserStatsAccount = { numberOfSubAccounts: number; numberOfSubAccountsCreated: number; @@ -1297,11 +1295,53 @@ export class PostOnlyParams { static readonly SLIDE = { slide: {} }; // Modify price to be post only if can't be post only } +/** + * How to distribute order sizes across scale orders + */ +export class SizeDistribution { + static readonly FLAT = { flat: {} }; // Equal size for all orders + static readonly ASCENDING = { ascending: {} }; // Smallest at start price, largest at end price + static readonly DESCENDING = { descending: {} }; // Largest at start price, smallest at end price +} + +/** + * Parameters for placing scale orders - multiple limit orders distributed across a price range + */ +export type ScaleOrderParams = { + marketType: MarketType; + direction: PositionDirection; + marketIndex: number; + /** Total base asset amount to distribute across all orders */ + totalBaseAssetAmount: BN; + /** Starting price for the scale (in PRICE_PRECISION) */ + startPrice: BN; + /** Ending price for the scale (in PRICE_PRECISION) */ + endPrice: BN; + /** Number of orders to place (min 2, max 32). User cannot exceed 32 total open orders. */ + orderCount: number; + /** How to distribute sizes across orders */ + sizeDistribution: SizeDistribution; + /** Whether orders should be reduce-only */ + reduceOnly: boolean; + /** Post-only setting for all orders */ + postOnly: PostOnlyParams; + /** Bit flags (e.g., for high leverage mode) */ + bitFlags: number; + /** Maximum timestamp for orders to be valid */ + maxTs: BN | null; +}; + export class OrderParamsBitFlag { static readonly ImmediateOrCancel = 1; static readonly UpdateHighLeverageMode = 2; } +export class PositionFlag { + static readonly IsolatedPosition = 1; + static readonly BeingLiquidated = 2; + static readonly Bankruptcy = 4; +} + export type NecessaryOrderParams = { orderType: OrderType; marketIndex: number; diff --git a/sdk/src/user.ts b/sdk/src/user.ts index d6ebcd5257..15cf932245 100644 --- a/sdk/src/user.ts +++ b/sdk/src/user.ts @@ -504,8 +504,9 @@ export class User { ) : ZERO; - let freeCollateral: BN; - if (positionType === 'isolated' && this.isPositionEmpty(perpPosition)) { + let freeCollateral: BN = ZERO; + // if position is isolated, we always add on available quote from the cross account + if (positionType === 'isolated') { const { totalAssetValue: quoteSpotMarketAssetValue, totalLiabilityValue: quoteSpotMarketLiabilityValue, @@ -520,13 +521,16 @@ export class User { freeCollateral = quoteSpotMarketAssetValue.sub( quoteSpotMarketLiabilityValue ); - } else { - freeCollateral = this.getFreeCollateral( + } + + // adding free collateral from the cross account or from within isolated margin calc for this marketIndex + freeCollateral = freeCollateral.add( + this.getFreeCollateral( 'Initial', enterHighLeverageMode, positionType === 'isolated' ? marketIndex : undefined - ).sub(collateralBuffer); - } + ).sub(collateralBuffer) + ); return this.getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount( marketIndex, @@ -574,7 +578,12 @@ export class User { }); if (perpMarketIndex !== undefined) { - return calc.getIsolatedFreeCollateral(perpMarketIndex); + // getIsolatedFreeCollateral will throw if no existing isolated position but we are fetching for potential new position, so we wrap in a try/catch + try { + return calc.getIsolatedFreeCollateral(perpMarketIndex); + } catch (error) { + return ZERO; + } } else { return calc.getCrossFreeCollateral(); } @@ -1343,10 +1352,10 @@ export class User { const marginCalc = this.getMarginCalculation('Maintenance'); - let totalCollateral: BN; - let maintenanceMarginReq: BN; + let totalCollateral: BN = ZERO; + let maintenanceMarginReq: BN = ZERO; - if (perpMarketIndex) { + if (perpMarketIndex != null) { const isolatedMarginCalc = marginCalc.isolatedMarginCalculations.get(perpMarketIndex); if (isolatedMarginCalc) { @@ -2363,7 +2372,7 @@ export class User { enteringHighLeverage ); - if (freeCollateralDelta.eq(ZERO)) { + if (!freeCollateralDelta || freeCollateralDelta.eq(ZERO)) { return new BN(-1); } diff --git a/sdk/tests/decode/test.ts b/sdk/tests/decode/test.ts index 20f18bebdd..6156a4a7fd 100644 --- a/sdk/tests/decode/test.ts +++ b/sdk/tests/decode/test.ts @@ -187,10 +187,11 @@ function testPerpPosition(anchor: PerpPosition, custom: PerpPosition) { assert(anchor.openAsks.eq(custom.openAsks)); assert(anchor.settledPnl.eq(custom.settledPnl)); assert(anchor.lpShares.eq(custom.lpShares)); - assert(anchor.lastBaseAssetAmountPerLp.eq(custom.lastBaseAssetAmountPerLp)); assert(anchor.lastQuoteAssetAmountPerLp.eq(custom.lastQuoteAssetAmountPerLp)); assert(anchor.openOrders === custom.openOrders); assert(anchor.perLpBase === custom.perLpBase); + assert(anchor.isolatedPositionScaledBalance.eq(custom.isolatedPositionScaledBalance)); + assert(anchor.positionFlag === custom.positionFlag); } function* getOrders(orders: Order[]) { diff --git a/sdk/tests/dlob/helpers.ts b/sdk/tests/dlob/helpers.ts index 69ff661bc0..2ba67bb355 100644 --- a/sdk/tests/dlob/helpers.ts +++ b/sdk/tests/dlob/helpers.ts @@ -41,7 +41,6 @@ export const mockPerpPosition: PerpPosition = { settledPnl: new BN(0), lpShares: new BN(0), remainderBaseAssetAmount: 0, - lastBaseAssetAmountPerLp: new BN(0), lastQuoteAssetAmountPerLp: new BN(0), perLpBase: 0, maxMarginRatio: 1, @@ -782,4 +781,4 @@ export class MockUserMap implements UserMapInterface { ]; } } -} +} \ No newline at end of file diff --git a/sdk/tests/user/test.ts b/sdk/tests/user/test.ts index 6c431b7226..58448262e2 100644 --- a/sdk/tests/user/test.ts +++ b/sdk/tests/user/test.ts @@ -49,7 +49,7 @@ async function makeMockUser( oraclePriceMap[myMockSpotMarkets[i].oracle.toString()] = spotOraclePriceList[i]; } - // console.log(oraclePriceMap); + // console.log('oraclePriceMap:', oraclePriceMap); function getMockUserAccount(): UserAccount { return myMockUserAccount; diff --git a/sdk/yarn.lock b/sdk/yarn.lock index 26a6f824c0..2d1998979a 100644 --- a/sdk/yarn.lock +++ b/sdk/yarn.lock @@ -3,11 +3,11 @@ "@babel/code-frame@^7.12.13": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" + integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== dependencies: - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" picocolors "^1.1.1" @@ -16,30 +16,30 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== -"@babel/helper-validator-identifier@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" - integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== -"@babel/parser@^7.26.7", "@babel/parser@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" - integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== +"@babel/parser@^7.26.7", "@babel/parser@^7.28.5": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== dependencies: - "@babel/types" "^7.28.0" + "@babel/types" "^7.28.6" -"@babel/runtime@^7.10.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.25.0": - version "7.27.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6" - integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== +"@babel/runtime@^7.10.5", "@babel/runtime@^7.25.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" + integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== -"@babel/types@^7.28.0": - version "7.28.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" - integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ== +"@babel/types@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== dependencies: "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" "@coral-xyz/anchor-30@npm:@coral-xyz/anchor@0.30.1": version "0.30.1" @@ -263,16 +263,16 @@ integrity sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" "@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -302,24 +302,14 @@ "@grpc/proto-loader" "^0.8.0" "@js-sdsl/ordered-map" "^4.4.2" -"@grpc/grpc-js@^1.8.0", "@grpc/grpc-js@^1.8.13": - version "1.13.4" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.13.4.tgz#922fbc496e229c5fa66802d2369bf181c1df1c5a" - integrity sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg== +"@grpc/grpc-js@^1.8.0": + version "1.14.3" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.14.3.tgz#4c9b817a900ae4020ddc28515ae4b52c78cfb8da" + integrity sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA== dependencies: - "@grpc/proto-loader" "^0.7.13" + "@grpc/proto-loader" "^0.8.0" "@js-sdsl/ordered-map" "^4.4.2" -"@grpc/proto-loader@^0.7.13": - version "0.7.15" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" - integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.2.5" - yargs "^17.7.2" - "@grpc/proto-loader@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.8.0.tgz#b6c324dd909c458a0e4aa9bfd3d69cf78a4b9bd8" @@ -385,10 +375,10 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz#7358043433b2e5da569aa02cbc4c121da3af27d7" - integrity sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -459,22 +449,17 @@ spok "^1.4.3" "@msgpack/msgpack@^3.1.2": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.1.2.tgz#fdd25cc2202297519798bbaf4689152ad9609e19" - integrity sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ== + version "3.1.3" + resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.1.3.tgz#c4bff2b9539faf0882f3ee03537a7e9a4b3a7864" + integrity sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA== "@noble/curves@^1.4.2": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.2.tgz#73388356ce733922396214a933ff7c95afcef911" - integrity sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g== + version "1.9.7" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.7.tgz#79d04b4758a43e4bca2cbdc62e7771352fa6b951" + integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== dependencies: "@noble/hashes" "1.8.0" -"@noble/ed25519@^1.7.1": - version "1.7.5" - resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.5.tgz#94df8bdb9fec9c4644a56007eecb57b0e9fbd0d7" - integrity sha512-xuS0nwRMQBvSxDa7UxMb61xTiH3MxTgUfhyPUALVIe0FlOAz4sjELwyDRyUvqeEYfRSG9qNjFIycqLZppg4RSA== - "@noble/hashes@1.8.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" @@ -619,34 +604,16 @@ dependencies: bn.js "^5.2.1" -"@pythnetwork/price-service-sdk@>=1.6.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@pythnetwork/price-service-sdk/-/price-service-sdk-1.8.0.tgz#f5f01f654963eb9a0cf12127b4f1a89b60ef008a" - integrity sha512-tFZ1thj3Zja06DzPIX2dEWSi7kIfIyqreoywvw5NQ3Z1pl5OJHQGMEhxt6Li3UCGSp2ooYZS9wl8/8XfrfrNSA== - dependencies: - bn.js "^5.2.1" - -"@pythnetwork/pyth-solana-receiver@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@pythnetwork/pyth-solana-receiver/-/pyth-solana-receiver-0.7.0.tgz#253a0d15a135d625ceca7ba1b47940dd03b9cab6" - integrity sha512-OoEAHh92RPRdKkfjkcKGrjC+t0F3SEL754iKFmixN9zyS8pIfZSVfFntmkHa9pWmqEMxdx/i925a8B5ny8Tuvg== - dependencies: - "@coral-xyz/anchor" "^0.29.0" - "@noble/hashes" "^1.4.0" - "@pythnetwork/price-service-sdk" ">=1.6.0" - "@pythnetwork/solana-utils" "*" - "@solana/web3.js" "^1.90.0" - -"@pythnetwork/solana-utils@*": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@pythnetwork/solana-utils/-/solana-utils-0.4.5.tgz#7c5af4b6794769e57b56ad1c680faa6dbf70b919" - integrity sha512-NoLdC2rRAx9a66L0hSOAGt6Wj/YxfnKkw+mbb7Tn/Nn1du4HyShi41DiN6B+2XXqnMthNGbf9FSHvj4NXXABvA== +"@pythnetwork/pyth-lazer-sdk@5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@pythnetwork/pyth-lazer-sdk/-/pyth-lazer-sdk-5.2.1.tgz#7a35573f30c214c6e3014dcffbdec0053b3f7116" + integrity sha512-/0rrPi6sZdVH+cWtW+X/hmQzw+nP2fIF6BlrJRTrqwK2IXWZWW+40sHoj839HrbyUXOJyG9zKyco37eFuTE/nA== dependencies: - "@coral-xyz/anchor" "^0.29.0" - "@solana/web3.js" "^1.90.0" - bs58 "^5.0.0" - jito-ts "^3.0.1" + "@isaacs/ttlcache" "^1.4.1" + buffer "^6.0.3" + isomorphic-ws "^5.0.0" ts-log "^2.2.7" + ws "^8.18.0" "@sinclair/typebox@^0.24.1": version "0.24.51" @@ -675,12 +642,11 @@ "@sinonjs/commons" "^3.0.1" "@sinonjs/samsam@^8.0.0": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.2.tgz#e4386bf668ff36c95949e55a38dc5f5892fc2689" - integrity sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw== + version "8.0.3" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.3.tgz#eb6ffaef421e1e27783cc9b52567de20cb28072d" + integrity sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ== dependencies: "@sinonjs/commons" "^3.0.1" - lodash.get "^4.4.2" type-detect "^4.1.0" "@sinonjs/text-encoding@^0.7.3": @@ -703,7 +669,7 @@ resolved "https://registry.yarnpkg.com/@solana-program/system/-/system-0.7.0.tgz#3e21c9fb31d3795eb65ba5cb663947c19b305bad" integrity sha512-FKTBsKHpvHHNc1ATRm7SlC5nF/VdJtOSjldhcyfMN9R7xo712Mo2jHIzvBgn8zQO5Kg0DcWuKB7268Kv1ocicw== -"@solana-program/token-2022@^0.4.1": +"@solana-program/token-2022@^0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@solana-program/token-2022/-/token-2022-0.4.2.tgz#f90a638de82acb7933a114e884a24ac4be8ef635" integrity sha512-zIpR5t4s9qEU3hZKupzIBxJ6nUV5/UVyIT400tu9vT1HMs5JHxaTTsb5GUhYjiiTvNwU0MQavbwc4Dl29L0Xvw== @@ -886,7 +852,7 @@ "@solana/errors" "2.3.0" "@solana/nominal-types" "2.3.0" -"@solana/kit@^2.1.1": +"@solana/kit@^2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@solana/kit/-/kit-2.3.0.tgz#92deb7c4883293617209aecac9a43d5e41ccf092" integrity sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ== @@ -1111,7 +1077,7 @@ dependencies: "@solana/codecs" "2.0.0-rc.1" -"@solana/spl-token@0.4.13", "@solana/spl-token@^0.4.0": +"@solana/spl-token@0.4.13": version "0.4.13" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.13.tgz#8f65c3c2b315e1a00a91b8d0f60922c6eb71de62" integrity sha512-cite/pYWQZZVvLbg5lsodSovbetK/eA24gaR0eeUeMuBAMNrT8XFCwaygKy0N2WSg3gSyjjNpIeAGBAKZaY/1w== @@ -1144,6 +1110,17 @@ "@solana/spl-token-metadata" "^0.1.2" buffer "^6.0.3" +"@solana/spl-token@^0.4.0": + version "0.4.14" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.14.tgz#b86bc8a17f50e9680137b585eca5f5eb9d55c025" + integrity sha512-u09zr96UBpX4U685MnvQsNzlvw9TiY005hk1vJmJr7gMJldoPG1eYU5/wNEyOA5lkMLiR/gOi9SFD4MefOYEsA== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + "@solana/spl-token-group" "^0.0.7" + "@solana/spl-token-metadata" "^0.1.6" + buffer "^6.0.3" + "@solana/subscribable@2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@solana/subscribable/-/subscribable-2.3.0.tgz#4e48f1a4eeb1ccf22065b46fb8e3ed80d1a27f80" @@ -1210,7 +1187,7 @@ "@solana/rpc-types" "2.3.0" "@solana/transaction-messages" "2.3.0" -"@solana/web3.js@1.98.0", "@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.30.2", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.77.3", "@solana/web3.js@^1.90.0", "@solana/web3.js@^1.98.0", "@solana/web3.js@^1.98.2", "@solana/web3.js@~1.77.3": +"@solana/web3.js@1.98.0", "@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.30.2", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.77.3", "@solana/web3.js@^1.98.0", "@solana/web3.js@^1.98.2": version "1.98.0" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.0.tgz#21ecfe8198c10831df6f0cfde7f68370d0405917" integrity sha512-nz3Q5OeyGFpFCR+erX2f6JPt3sKhzhYcSycBCSPkWjzSVDh/Rr1FqTVMRe58FKO16/ivTUcuJjeS5MyBvpkbzA== @@ -1232,9 +1209,9 @@ superstruct "^2.0.2" "@swc/helpers@^0.5.11": - version "0.5.17" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" - integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== + version "0.5.18" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.18.tgz#feeeabea0d10106ee25aaf900165df911ab6d3b1" + integrity sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ== dependencies: tslib "^2.8.0" @@ -1255,9 +1232,9 @@ yaml "^2.6.1" "@switchboard-xyz/common@>=3.0.0": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@switchboard-xyz/common/-/common-3.4.1.tgz#3144c0730649e60129ea8c9b3c04062ceff35c6e" - integrity sha512-TropBlBYuDeBnmGHkPSmgC3clLqAxy51ZGbwk4ejAgadnszWOgYHcH7taRG4Ha17DYSCWw/LGMBKbunGo+Aoaw== + version "5.6.1" + resolved "https://registry.yarnpkg.com/@switchboard-xyz/common/-/common-5.6.1.tgz#a379005af3a72a504f8f6ba88ea8991d328f6336" + integrity sha512-2Sz3iAusCnpEp+lJLOcwstRxGQCkbCdLDNgAYl/gTqApCA+drdQni8WoeKdahAXtcxhr72pd9PhFi/4N25im+w== dependencies: "@solana/web3.js" "^1.98.2" axios "^1.9.0" @@ -1268,8 +1245,6 @@ decimal.js "^10.4.3" js-sha256 "^0.11.0" protobufjs "^7.4.0" - yaml "^2.6.1" - zod "4.0.0-beta.20250505T195954" "@switchboard-xyz/on-demand@2.4.1": version "2.4.1" @@ -1319,9 +1294,9 @@ "@ts-graphviz/common" "^2.1.5" "@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + version "1.0.12" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" + integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== "@tsconfig/node12@^1.0.7": version "1.0.11" @@ -1408,11 +1383,11 @@ integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/node@*", "@types/node@>=13.7.0": - version "24.0.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.13.tgz#93ed8c05c7b188a59760be0ce2ee3fa7ad0f83f6" - integrity sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ== + version "25.0.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.10.tgz#4864459c3c9459376b8b75fd051315071c8213e7" + integrity sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg== dependencies: - undici-types "~7.8.0" + undici-types "~7.16.0" "@types/node@^12.12.54": version "12.20.55" @@ -1420,9 +1395,9 @@ integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== "@types/node@^18.11.13": - version "18.19.118" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.118.tgz#e8ad99b8fb0c350773dfd9c5acda1a4bfb84a688" - integrity sha512-hIPK0hSrrcaoAu/gJMzN3QClXE4QdCdFvaenJ0JsjIbExP1JFFVH+RHcBt25c9n8bx5dkIfqKE+uw6BmBns7ug== + version "18.19.130" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.130.tgz#da4c6324793a79defb7a62cba3947ec5add00d59" + integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== dependencies: undici-types "~5.26.4" @@ -1434,9 +1409,9 @@ protobufjs "*" "@types/semver@^7.5.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.0.tgz#64c441bdae033b378b6eef7d0c3d77c329b9378e" - integrity sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA== + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== "@types/stack-utils@^2.0.0": version "2.0.3" @@ -1468,9 +1443,9 @@ integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== "@types/yargs@^17.0.8": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== dependencies: "@types/yargs-parser" "*" @@ -1502,14 +1477,14 @@ "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" -"@typescript-eslint/project-service@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.38.0.tgz#4900771f943163027fd7d2020a062892056b5e2f" - integrity sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg== +"@typescript-eslint/project-service@8.53.1": + version "8.53.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.53.1.tgz#4e47856a0b14a1ceb28b0294b4badef3be1e9734" + integrity sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.38.0" - "@typescript-eslint/types" "^8.38.0" - debug "^4.3.4" + "@typescript-eslint/tsconfig-utils" "^8.53.1" + "@typescript-eslint/types" "^8.53.1" + debug "^4.4.3" "@typescript-eslint/scope-manager@6.21.0": version "6.21.0" @@ -1519,10 +1494,10 @@ "@typescript-eslint/types" "6.21.0" "@typescript-eslint/visitor-keys" "6.21.0" -"@typescript-eslint/tsconfig-utils@8.38.0", "@typescript-eslint/tsconfig-utils@^8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz#6de4ce224a779601a8df667db56527255c42c4d0" - integrity sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ== +"@typescript-eslint/tsconfig-utils@8.53.1", "@typescript-eslint/tsconfig-utils@^8.53.1": + version "8.53.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz#efe80b8d019cd49e5a1cf46c2eb0cd2733076424" + integrity sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA== "@typescript-eslint/type-utils@6.21.0": version "6.21.0" @@ -1539,10 +1514,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== -"@typescript-eslint/types@8.38.0", "@typescript-eslint/types@^8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.38.0.tgz#297351c994976b93c82ac0f0e206c8143aa82529" - integrity sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw== +"@typescript-eslint/types@8.53.1", "@typescript-eslint/types@^8.53.1": + version "8.53.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.53.1.tgz#101f203f0807a63216cceceedb815fabe21d5793" + integrity sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A== "@typescript-eslint/typescript-estree@6.21.0": version "6.21.0" @@ -1559,20 +1534,19 @@ ts-api-utils "^1.0.1" "@typescript-eslint/typescript-estree@^8.23.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz#82262199eb6778bba28a319e25ad05b1158957df" - integrity sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ== - dependencies: - "@typescript-eslint/project-service" "8.38.0" - "@typescript-eslint/tsconfig-utils" "8.38.0" - "@typescript-eslint/types" "8.38.0" - "@typescript-eslint/visitor-keys" "8.38.0" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.1.0" + version "8.53.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz#b6dce2303c9e27e95b8dcd8c325868fff53e488f" + integrity sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg== + dependencies: + "@typescript-eslint/project-service" "8.53.1" + "@typescript-eslint/tsconfig-utils" "8.53.1" + "@typescript-eslint/types" "8.53.1" + "@typescript-eslint/visitor-keys" "8.53.1" + debug "^4.4.3" + minimatch "^9.0.5" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.4.0" "@typescript-eslint/utils@6.21.0": version "6.21.0" @@ -1595,12 +1569,12 @@ "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" -"@typescript-eslint/visitor-keys@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz#a9765a527b082cb8fc60fd8a16e47c7ad5b60ea5" - integrity sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g== +"@typescript-eslint/visitor-keys@8.53.1": + version "8.53.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz#405f04959be22b9be364939af8ac19c3649b6eb7" + integrity sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg== dependencies: - "@typescript-eslint/types" "8.38.0" + "@typescript-eslint/types" "8.53.1" eslint-visitor-keys "^4.2.1" "@ungap/structured-clone@^1.2.0": @@ -1608,57 +1582,52 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== -"@vue/compiler-core@3.5.18": - version "3.5.18" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz#521a138cdd970d9bfd27e42168d12f77a04b2074" - integrity sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw== +"@vue/compiler-core@3.5.27": + version "3.5.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.27.tgz#ce4402428e26095586eb889c41f6e172eb3960bd" + integrity sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ== dependencies: - "@babel/parser" "^7.28.0" - "@vue/shared" "3.5.18" - entities "^4.5.0" + "@babel/parser" "^7.28.5" + "@vue/shared" "3.5.27" + entities "^7.0.0" estree-walker "^2.0.2" source-map-js "^1.2.1" -"@vue/compiler-dom@3.5.18": - version "3.5.18" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz#e13504492c3061ec5bbe6a2e789f15261d4f03a7" - integrity sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A== +"@vue/compiler-dom@3.5.27": + version "3.5.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz#32b2bc87f0a652c253986796ace0ed6213093af8" + integrity sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w== dependencies: - "@vue/compiler-core" "3.5.18" - "@vue/shared" "3.5.18" + "@vue/compiler-core" "3.5.27" + "@vue/shared" "3.5.27" "@vue/compiler-sfc@^3.5.13": - version "3.5.18" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz#ba1e849561337d809937994cdaf900539542eeca" - integrity sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA== - dependencies: - "@babel/parser" "^7.28.0" - "@vue/compiler-core" "3.5.18" - "@vue/compiler-dom" "3.5.18" - "@vue/compiler-ssr" "3.5.18" - "@vue/shared" "3.5.18" + version "3.5.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz#84651b8816bf8e7d6e62fddd14db86efd6d6f1b6" + integrity sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ== + dependencies: + "@babel/parser" "^7.28.5" + "@vue/compiler-core" "3.5.27" + "@vue/compiler-dom" "3.5.27" + "@vue/compiler-ssr" "3.5.27" + "@vue/shared" "3.5.27" estree-walker "^2.0.2" - magic-string "^0.30.17" + magic-string "^0.30.21" postcss "^8.5.6" source-map-js "^1.2.1" -"@vue/compiler-ssr@3.5.18": - version "3.5.18" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz#aecde0b0bff268a9c9014ba66799307c4a784328" - integrity sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g== +"@vue/compiler-ssr@3.5.27": + version "3.5.27" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz#b480cad09dacf8f3d9c82b9843402f1a803baee7" + integrity sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw== dependencies: - "@vue/compiler-dom" "3.5.18" - "@vue/shared" "3.5.18" + "@vue/compiler-dom" "3.5.27" + "@vue/shared" "3.5.27" -"@vue/shared@3.5.18": - version "3.5.18" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.18.tgz#529f24a88d3ed678d50fd5c07455841fbe8ac95e" - integrity sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA== - -"@zod/core@0.11.6": - version "0.11.6" - resolved "https://registry.yarnpkg.com/@zod/core/-/core-0.11.6.tgz#9216e98848dc9364eda35e3da90f5362f10e8887" - integrity sha512-03Bv82fFSfjDAvMfdHHdGSS6SOJs0iCcJlWJv1kJHRtoTT02hZpyip/2Lk6oo4l4FtjuwTrsEQTwg/LD8I7dJA== +"@vue/shared@3.5.27": + version "3.5.27" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.27.tgz#33a63143d8fb9ca1b3efbc7ecf9bd0ab05f7e06e" + integrity sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ== acorn-jsx@^5.3.2: version "5.3.2" @@ -1677,7 +1646,7 @@ acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -agentkeepalive@^4.3.0, agentkeepalive@^4.5.0: +agentkeepalive@^4.5.0: version "4.6.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== @@ -1802,12 +1771,12 @@ available-typed-arrays@^1.0.7: possible-typed-array-names "^1.0.0" axios@^1.8.3, axios@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.10.0.tgz#af320aee8632eaf2a400b6a1979fa75856f38d54" - integrity sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw== + version "1.13.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687" + integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA== dependencies: follow-redirects "^1.15.6" - form-data "^4.0.0" + form-data "^4.0.4" proxy-from-env "^1.1.0" backslash@<0.2.1: @@ -1855,9 +1824,9 @@ bigint-buffer@^1.1.5: bindings "^1.3.0" bignumber.js@^9.0.1: - version "9.3.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.0.tgz#bdba7e2a4c1a2eba08290e8dcad4f36393c92acd" - integrity sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA== + version "9.3.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7" + integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== binary-extensions@^2.0.0: version "2.3.0" @@ -2034,9 +2003,9 @@ buffer@^5.5.0: ieee754 "^1.1.13" bufferutil@^4.0.1: - version "4.0.9" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.9.tgz#6e81739ad48a95cad45a279588e13e95e24a800a" - integrity sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw== + version "4.1.0" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.1.0.tgz#a4623541dd23867626bb08a051ec0d2ec0b70294" + integrity sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw== dependencies: node-gyp-build "^4.3.0" @@ -2316,7 +2285,7 @@ crypto-hash@^1.3.0: resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== -debug@<4.4.2, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5: +debug@<4.4.2, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.4.3: version "4.4.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== @@ -2485,14 +2454,14 @@ diff-sequences@^28.1.1: integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + version "4.0.4" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== diff@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" - integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + version "5.2.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" + integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== diffie-hellman@^5.0.3: version "5.0.3" @@ -2530,11 +2499,6 @@ dotenv@10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -dotenv@^16.0.3: - version "16.6.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" - integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== - dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -2570,17 +2534,17 @@ encoding@0.1.13: iconv-lite "^0.6.2" enhanced-resolve@^5.18.0: - version "5.18.2" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz#7903c5b32ffd4b2143eeb4b92472bd68effd5464" - integrity sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ== + version "5.18.4" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz#c22d33055f3952035ce6a144ce092447c525f828" + integrity sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" -entities@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +entities@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" + integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== error-ex@<1.3.3: version "1.3.2" @@ -2775,9 +2739,9 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" @@ -2809,9 +2773,9 @@ eventemitter3@^4.0.7: integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + version "5.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.4.tgz#a86d66170433712dde814707ac52b5271ceb1feb" + integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" @@ -2847,7 +2811,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.9, fast-glob@^3.3.2: +fast-glob@^3.2.9: version "3.3.3" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== @@ -2874,12 +2838,17 @@ fast-stable-stringify@^1.0.0: integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -2960,9 +2929,9 @@ flatted@^3.2.9: integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== follow-redirects@^1.15.6: - version "1.15.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" - integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== for-each@^0.3.5: version "0.3.5" @@ -2971,10 +2940,10 @@ for-each@^0.3.5: dependencies: is-callable "^1.2.7" -form-data@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae" - integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== +form-data@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -2997,6 +2966,11 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + get-amd-module-type@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-6.0.1.tgz#191f479ae8706c246b52bf402fbe1bb0965d9f1e" @@ -3036,7 +3010,7 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== -get-proto@^1.0.0, get-proto@^1.0.1: +get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -3045,17 +3019,17 @@ get-proto@^1.0.0, get-proto@^1.0.1: es-object-atoms "^1.0.0" gill@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/gill/-/gill-0.10.2.tgz#593c031c9964739739a07480199d0c409da40fb5" - integrity sha512-upWoY2dfOzKHOcX3UnD+B3h9WUunPv0oxeKzsIgKSaLyURpWK9oI+K2NHWbwrUFsXEK6ozu/sgkhuqyAcVTZCg== + version "0.10.3" + resolved "https://registry.yarnpkg.com/gill/-/gill-0.10.3.tgz#0eeeaf18b9a6ec7adc17967f51f86be042ee2f24" + integrity sha512-4LIVA32lKcWoqU/dbwu+YpJbR59QQT6mvCtqkElBWF2aT9upmewjKN3/anhfTGy+o/RJykAV21i3RzCj9FR0Xg== dependencies: "@solana-program/address-lookup-table" "^0.7.0" "@solana-program/compute-budget" "^0.8.0" "@solana-program/system" "^0.7.0" - "@solana-program/token-2022" "^0.4.1" + "@solana-program/token-2022" "^0.4.2" "@solana/assertions" "^2.1.1" "@solana/codecs" "^2.1.1" - "@solana/kit" "^2.1.1" + "@solana/kit" "^2.3.0" "@solana/transaction-confirmation" "^2.1.1" glob-parent@^5.1.2, glob-parent@~5.1.2: @@ -3344,7 +3318,7 @@ is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.16.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -3362,12 +3336,13 @@ is-fullwidth-code-point@^3.0.0: integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-function@^1.0.7: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" - integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== dependencies: - call-bound "^1.0.3" - get-proto "^1.0.0" + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" has-tostringtag "^1.0.2" safe-regex-test "^1.1.0" @@ -3468,10 +3443,15 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -jayson@^4.0.0, jayson@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.2.0.tgz#b71762393fa40bc9637eaf734ca6f40d3b8c0c93" - integrity sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + +jayson@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.3.0.tgz#22eb8f3dcf37a5e893830e5451f32bde6d1bde4d" + integrity sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ== dependencies: "@types/connect" "^3.4.33" "@types/node" "^12.12.54" @@ -3538,20 +3518,6 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jito-ts@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/jito-ts/-/jito-ts-3.0.1.tgz#24126389896e042c26d303c4e802064b249ed27e" - integrity sha512-TSofF7KqcwyaWGjPaSYC8RDoNBY1TPRNBHdrw24bdIi7mQ5bFEDdYK3D//llw/ml8YDvcZlgd644WxhjLTS9yg== - dependencies: - "@grpc/grpc-js" "^1.8.13" - "@noble/ed25519" "^1.7.1" - "@solana/web3.js" "~1.77.3" - agentkeepalive "^4.3.0" - dotenv "^16.0.3" - jayson "^4.0.0" - node-fetch "^2.6.7" - superstruct "^1.0.3" - js-sha256@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.11.1.tgz#712262e8fc9569d6f7f6eea72c0d8e5ccc7c976c" @@ -3568,9 +3534,9 @@ js-tokens@^4.0.0: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" @@ -3631,11 +3597,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -3696,12 +3657,12 @@ madge@^8.0.0: ts-graphviz "^2.1.2" walkdir "^0.4.1" -magic-string@^0.30.17: - version "0.30.17" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" - integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== +magic-string@^0.30.21: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/sourcemap-codec" "^1.5.5" make-error@^1.1.1: version "1.3.6" @@ -3791,7 +3752,7 @@ minimatch@^5.0.1, minimatch@^5.1.6: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4: +minimatch@^9.0.5: version "9.0.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -3893,7 +3854,7 @@ node-cache@5.1.2: dependencies: clone "2.x" -node-fetch@^2.6.7, node-fetch@^2.7.0: +node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -4063,9 +4024,9 @@ path-parse@^1.0.7: integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@^8.1.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" - integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== path-type@^4.0.0: version "4.0.0" @@ -4099,6 +4060,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -4154,9 +4120,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + version "1.0.1" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz#6a31f88a4bad6c7adda253de12ba4edaea80ebcd" + integrity sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg== dependencies: fast-diff "^1.1.2" @@ -4197,10 +4163,10 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -protobufjs@*, protobufjs@^7.5.3: - version "7.5.4" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" - integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== +protobufjs@*: + version "8.0.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-8.0.0.tgz#d884102c1fe8d0b1e2493789ad37bc7ea47c0893" + integrity sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -4215,10 +4181,10 @@ protobufjs@*, protobufjs@^7.5.3: "@types/node" ">=13.7.0" long "^5.0.0" -protobufjs@^7.2.5, protobufjs@^7.4.0: - version "7.5.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.3.tgz#13f95a9e3c84669995ec3652db2ac2fb00b89363" - integrity sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw== +protobufjs@^7.4.0, protobufjs@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" + integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -4338,9 +4304,9 @@ requirejs-config-file@^4.0.0: stringify-object "^3.2.1" requirejs@^2.3.7: - version "2.3.7" - resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.7.tgz#0b22032e51a967900e0ae9f32762c23a87036bd0" - integrity sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw== + version "2.3.8" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.8.tgz#bca0614b618ab2122462597e44878db7558bbba3" + integrity sha512-7/cTSLOdYkNBNJcDMWf+luFvMriVm7eYxp4BcFCsAX0wF421Vyce5SXP17c+Jd5otXKGNehIonFlyQXSowL6Mw== resolve-dependency-path@^4.0.1: version "4.0.1" @@ -4353,11 +4319,11 @@ resolve-from@^4.0.0: integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve@^1.22.10: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -4389,23 +4355,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.3: hash-base "^3.1.2" inherits "^2.0.4" -rpc-websockets@7.5.1: - version "7.5.1" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401" - integrity sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w== - dependencies: - "@babel/runtime" "^7.17.2" - eventemitter3 "^4.0.7" - uuid "^8.3.2" - ws "^8.5.0" - optionalDependencies: - bufferutil "^4.0.1" - utf-8-validate "^5.0.2" - rpc-websockets@^9.0.2: - version "9.1.1" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.1.1.tgz#5764336f3623ee1c5cc8653b7335183e3c0c78bd" - integrity sha512-1IXGM/TfPT6nfYMIXkJdzn+L4JEsmb0FL1O2OBjaH03V3yuUDdKFulGLMFG6ErV+8pZ5HVC0limve01RyO+saA== + version "9.3.3" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.3.3.tgz#b6be8b217f2a19bbc5fc184ef492e992b524556b" + integrity sha512-OkCsBBzrwxX4DoSv4Zlf9DgXKRB0MzVfCFg5MC+fNnf9ktr4SMWjsri0VNZQlDbCnGcImT6KNEv4ZoxktQhdpA== dependencies: "@swc/helpers" "^0.5.11" "@types/uuid" "^8.3.4" @@ -4457,10 +4410,10 @@ sass-lookup@^6.1.0: commander "^12.1.0" enhanced-resolve "^5.18.0" -semver@^7.3.7, semver@^7.5.4, semver@^7.6.0: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== +semver@^7.3.7, semver@^7.5.4, semver@^7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== serialize-javascript@^6.0.2: version "6.0.2" @@ -4710,11 +4663,6 @@ superstruct@^0.15.4: resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== -superstruct@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" - integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== - superstruct@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" @@ -4741,9 +4689,9 @@ supports-preserve-symlinks-flag@^1.0.0: integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== tapable@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.2.tgz#ab4984340d30cb9989a490032f086dbb8b56d872" - integrity sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== text-encoding-utf-8@^1.0.2: version "1.0.2" @@ -4755,6 +4703,14 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + to-buffer@^1.2.0, to-buffer@^1.2.1, to-buffer@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.2.tgz#ffe59ef7522ada0a2d1cb5dfe03bb8abc3cdc133" @@ -4791,10 +4747,10 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== -ts-api-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" - integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== +ts-api-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== ts-graphviz@^2.1.2: version "2.1.6" @@ -4891,24 +4847,24 @@ typescript@5.4.5: integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== typescript@^5.7.3, typescript@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== undici-types@^7.11.0: - version "7.12.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb" - integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ== + version "7.19.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.19.0.tgz#57b4f8ce26b99fb6a15a1581a4c012b6eec253b9" + integrity sha512-Rjk2OWDZf2eiXVQjY2HyE3XPjqW/wXnSZq0QkOsPKZEnaetNNBObTp91LYfGdB8hRbRZk4HFcM/cONw452B0AQ== undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici-types@~7.8.0: - version "7.8.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294" - integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== uri-js@^4.2.2: version "4.4.1" @@ -4981,9 +4937,9 @@ whatwg-url@^5.0.0: webidl-conversions "^3.0.0" which-typed-array@^1.1.16, which-typed-array@^1.1.2: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + version "1.1.20" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" + integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.8" @@ -5029,10 +4985,10 @@ ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.5.0: - version "8.18.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" - integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== +ws@^8.18.0, ws@^8.5.0: + version "8.19.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== y18n@^5.0.5: version "5.0.8" @@ -5040,9 +4996,9 @@ y18n@^5.0.5: integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yaml@^2.6.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6" - integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ== + version "2.8.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5" + integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A== yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" @@ -5100,13 +5056,6 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod@4.0.0-beta.20250505T195954: - version "4.0.0-beta.20250505T195954" - resolved "https://registry.yarnpkg.com/zod/-/zod-4.0.0-beta.20250505T195954.tgz#ba9da025671de2dde9d4d033089f03c37a35022f" - integrity sha512-iB8WvxkobVIXMARvQu20fKvbS7mUTiYRpcD8OQV1xjRhxO0EEpYIRJBk6yfBzHAHEdOSDh3SxDITr5Eajr2vtg== - dependencies: - "@zod/core" "0.11.6" - zod@4.0.17: version "4.0.17" resolved "https://registry.yarnpkg.com/zod/-/zod-4.0.17.tgz#95931170715f73f7426c385c237b7477750d6c8d" diff --git a/test-scripts/run-anchor-tests.sh b/test-scripts/run-anchor-tests.sh index 7b2fceedf2..8068b1d9c0 100644 --- a/test-scripts/run-anchor-tests.sh +++ b/test-scripts/run-anchor-tests.sh @@ -24,6 +24,7 @@ test_files=( # TODO BROKEN ^^ builderCodes.ts decodeUser.ts + scaleOrders.ts # fuel.ts # fuelSweep.ts admin.ts diff --git a/test-scripts/single-anchor-test.sh b/test-scripts/single-anchor-test.sh index f3fb157085..ccc088712f 100755 --- a/test-scripts/single-anchor-test.sh +++ b/test-scripts/single-anchor-test.sh @@ -1,3 +1,5 @@ +#!/bin/bash + if [ "$1" != "--skip-build" ] then anchor build -- --features anchor-test && anchor test --skip-build && @@ -7,8 +9,8 @@ fi export ANCHOR_WALLET=~/.config/solana/id.json test_files=( - lpPool.ts - lpPoolSwap.ts + scaleOrders.ts + order.ts ) for test_file in ${test_files[@]}; do diff --git a/tests/isolatedPositionDriftClient.ts b/tests/isolatedPositionDriftClient.ts index 0e7ad59b7f..4ebc003aaf 100644 --- a/tests/isolatedPositionDriftClient.ts +++ b/tests/isolatedPositionDriftClient.ts @@ -164,7 +164,14 @@ describe('drift client', () => { }); it('Transfer isolated perp position deposit', async () => { - await driftClient.transferIsolatedPerpPositionDeposit(usdcAmount.neg(), 0); + await driftClient.transferIsolatedPerpPositionDeposit( + usdcAmount.neg(), + 0, + undefined, + undefined, + undefined, + true + ); const quoteAssetTokenAmount = driftClient.getIsolatedPerpPositionTokenAmount(0); @@ -173,7 +180,14 @@ describe('drift client', () => { const quoteTokenAmount = driftClient.getQuoteAssetTokenAmount(); assert(quoteTokenAmount.eq(usdcAmount)); - await driftClient.transferIsolatedPerpPositionDeposit(usdcAmount, 0); + await driftClient.transferIsolatedPerpPositionDeposit( + usdcAmount, + 0, + undefined, + undefined, + undefined, + true + ); const quoteAssetTokenAmount2 = driftClient.getIsolatedPerpPositionTokenAmount(0); @@ -514,7 +528,14 @@ describe('drift client', () => { it('Open short position', async () => { // Re-Deposit USDC, assuming we have 0 balance here - await driftClient.transferIsolatedPerpPositionDeposit(new BN(9855998), 0); + await driftClient.transferIsolatedPerpPositionDeposit( + new BN(9855998), + 0, + undefined, + undefined, + undefined, + true + ); const baseAssetAmount = new BN(48000000000); await driftClient.openPosition(PositionDirection.SHORT, baseAssetAmount, 0); diff --git a/tests/isolatedPositionLiquidatePerp.ts b/tests/isolatedPositionLiquidatePerp.ts index c97feadfe0..9c702cbba3 100644 --- a/tests/isolatedPositionLiquidatePerp.ts +++ b/tests/isolatedPositionLiquidatePerp.ts @@ -151,7 +151,14 @@ describe('liquidate perp (no open orders)', () => { userUSDCAccount.publicKey ); - await driftClient.transferIsolatedPerpPositionDeposit(usdcAmount, 0); + await driftClient.transferIsolatedPerpPositionDeposit( + usdcAmount, + 0, + undefined, + undefined, + undefined, + true + ); await driftClient.openPosition( PositionDirection.LONG, diff --git a/tests/isolatedPositionLiquidatePerpwithFill.ts b/tests/isolatedPositionLiquidatePerpwithFill.ts index 787b5d42f5..c380b68ec2 100644 --- a/tests/isolatedPositionLiquidatePerpwithFill.ts +++ b/tests/isolatedPositionLiquidatePerpwithFill.ts @@ -156,7 +156,14 @@ describe('liquidate perp (no open orders)', () => { userUSDCAccount.publicKey ); - await driftClient.transferIsolatedPerpPositionDeposit(usdcAmount, 0); + await driftClient.transferIsolatedPerpPositionDeposit( + usdcAmount, + 0, + undefined, + undefined, + undefined, + true + ); await driftClient.openPosition( PositionDirection.LONG, diff --git a/tests/pythPull.ts b/tests/pythPull.ts index 5118651af6..62a77457f0 100644 --- a/tests/pythPull.ts +++ b/tests/pythPull.ts @@ -9,9 +9,9 @@ import { TestBulkAccountLoader } from '../sdk/src/accounts/testBulkAccountLoader import { BankrunContextWrapper } from '../sdk/src/bankrun/bankrunConnection'; import { startAnchor } from 'solana-bankrun'; import { AccountInfo, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; -import { DEFAULT_WORMHOLE_PROGRAM_ID } from '@pythnetwork/pyth-solana-receiver'; import { WORMHOLE_DATA } from './pythPullOracleData'; import { initializeQuoteSpotMarket, mockUSDCMint } from './testHelpers'; +import { DEFAULT_WORMHOLE_PROGRAM_ID } from '../sdk/src/pyth'; // set up account infos to load into banks client const GUARDIAN_SET_ACCOUNT_INFO: AccountInfo = { diff --git a/tests/scaleOrders.ts b/tests/scaleOrders.ts new file mode 100644 index 0000000000..1f3f4bd08c --- /dev/null +++ b/tests/scaleOrders.ts @@ -0,0 +1,856 @@ +import * as anchor from '@coral-xyz/anchor'; +import { assert } from 'chai'; + +import { Program } from '@coral-xyz/anchor'; + +import { PublicKey, Transaction } from '@solana/web3.js'; + +import { + TestClient, + BN, + PRICE_PRECISION, + PositionDirection, + User, + EventSubscriber, + PostOnlyParams, + SizeDistribution, + BASE_PRECISION, + isVariant, + MarketType, + MARGIN_PRECISION, + getUserAccountPublicKeySync, +} from '../sdk/src'; + +import { + mockOracleNoProgram, + mockUserUSDCAccount, + mockUSDCMint, + initializeQuoteSpotMarket, + initializeSolSpotMarket, + sleep, +} from './testHelpers'; +import { OracleSource, ZERO } from '../sdk'; +import { startAnchor } from 'solana-bankrun'; +import { TestBulkAccountLoader } from '../sdk/src/accounts/testBulkAccountLoader'; +import { BankrunContextWrapper } from '../sdk/src/bankrun/bankrunConnection'; + +describe('scale orders', () => { + const chProgram = anchor.workspace.Drift as Program; + + let driftClient: TestClient; + let driftClientUser: User; + let eventSubscriber: EventSubscriber; + + let bulkAccountLoader: TestBulkAccountLoader; + + let bankrunContextWrapper: BankrunContextWrapper; + + let _userAccountPublicKey: PublicKey; + + let usdcMint; + let userUSDCAccount; + + // ammInvariant == k == x * y + const mantissaSqrtScale = new BN(Math.sqrt(PRICE_PRECISION.toNumber())); + const ammInitialQuoteAssetReserve = new anchor.BN(5 * 10 ** 11).mul( + mantissaSqrtScale + ); + const ammInitialBaseAssetReserve = new anchor.BN(5 * 10 ** 11).mul( + mantissaSqrtScale + ); + + const usdcAmount = new BN(100000 * 10 ** 6); // $100k + + const perpMarketIndex = 0; + const spotMarketIndex = 1; // SOL spot market (USDC is 0) + + let solUsd; + + before(async () => { + const context = await startAnchor('', [], []); + + bankrunContextWrapper = new BankrunContextWrapper(context); + + bulkAccountLoader = new TestBulkAccountLoader( + bankrunContextWrapper.connection, + 'processed', + 1 + ); + + eventSubscriber = new EventSubscriber( + bankrunContextWrapper.connection.toConnection(), + chProgram + ); + + await eventSubscriber.subscribe(); + + usdcMint = await mockUSDCMint(bankrunContextWrapper); + userUSDCAccount = await mockUserUSDCAccount( + usdcMint, + usdcAmount, + bankrunContextWrapper + ); + + solUsd = await mockOracleNoProgram(bankrunContextWrapper, 100); + + const marketIndexes = [perpMarketIndex]; + const bankIndexes = [0, 1]; // USDC and SOL spot markets + const oracleInfos = [ + { publicKey: PublicKey.default, source: OracleSource.QUOTE_ASSET }, + { publicKey: solUsd, source: OracleSource.PYTH }, + ]; + + driftClient = new TestClient({ + connection: bankrunContextWrapper.connection.toConnection(), + wallet: bankrunContextWrapper.provider.wallet, + programID: chProgram.programId, + opts: { + commitment: 'confirmed', + }, + activeSubAccountId: 0, + perpMarketIndexes: marketIndexes, + spotMarketIndexes: bankIndexes, + subAccountIds: [], + oracleInfos, + accountSubscription: { + type: 'polling', + accountLoader: bulkAccountLoader, + }, + }); + await driftClient.initialize(usdcMint.publicKey, true); + await driftClient.subscribe(); + await initializeQuoteSpotMarket(driftClient, usdcMint.publicKey); + await driftClient.updatePerpAuctionDuration(new BN(0)); + + let oraclesLoaded = false; + while (!oraclesLoaded) { + await driftClient.accountSubscriber.setSpotOracleMap(); + const found = + !!driftClient.accountSubscriber.getOraclePriceDataAndSlotForSpotMarket( + 0 + ); + if (found) { + oraclesLoaded = true; + } + await sleep(1000); + } + + const periodicity = new BN(60 * 60); // 1 HOUR + + await driftClient.initializePerpMarket( + 0, + solUsd, + ammInitialBaseAssetReserve, + ammInitialQuoteAssetReserve, + periodicity + ); + + // Set step size to 0.001 (1e6 in base precision) + await driftClient.updatePerpMarketStepSizeAndTickSize( + 0, + new BN(1000000), // 0.001 in BASE_PRECISION + new BN(1) + ); + + // Initialize SOL spot market + await initializeSolSpotMarket(driftClient, solUsd); + + // Set step size for spot market + await driftClient.updateSpotMarketStepSizeAndTickSize( + spotMarketIndex, + new BN(1000000), // 0.001 in token precision + new BN(1) + ); + + // Enable margin trading on spot market (required for short orders) + await driftClient.updateSpotMarketMarginWeights( + spotMarketIndex, + MARGIN_PRECISION.toNumber() * 0.75, // initial asset weight + MARGIN_PRECISION.toNumber() * 0.8, // maintenance asset weight + MARGIN_PRECISION.toNumber() * 1.25, // initial liability weight + MARGIN_PRECISION.toNumber() * 1.2 // maintenance liability weight + ); + + // Get initialization instructions + const { ixs: initIxs, userAccountPublicKey } = + await driftClient.createInitializeUserAccountAndDepositCollateralIxs( + usdcAmount, + userUSDCAccount.publicKey + ); + _userAccountPublicKey = userAccountPublicKey; + + // Get margin trading enabled instruction (manually construct since user doesn't exist yet) + const marginTradingIx = + await driftClient.program.instruction.updateUserMarginTradingEnabled( + 0, // subAccountId + true, // marginTradingEnabled + { + accounts: { + user: getUserAccountPublicKeySync( + driftClient.program.programId, + bankrunContextWrapper.provider.wallet.publicKey, + 0 + ), + authority: bankrunContextWrapper.provider.wallet.publicKey, + }, + remainingAccounts: [], + } + ); + + // Bundle and send all instructions together + const allIxs = [...initIxs, marginTradingIx]; + const tx = await driftClient.buildTransaction(allIxs); + await driftClient.sendTransaction(tx as Transaction, [], driftClient.opts); + + // Add user to client + await driftClient.addUser(0); + + driftClientUser = new User({ + driftClient, + userAccountPublicKey: await driftClient.getUserAccountPublicKey(), + accountSubscription: { + type: 'polling', + accountLoader: bulkAccountLoader, + }, + }); + await driftClientUser.subscribe(); + }); + + after(async () => { + await driftClient.unsubscribe(); + await driftClientUser.unsubscribe(); + await eventSubscriber.unsubscribe(); + }); + + beforeEach(async () => { + // Clean up any orders from previous tests + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + const userAccount = driftClientUser.getUserAccount(); + const hasOpenOrders = userAccount.orders.some((order) => + isVariant(order.status, 'open') + ); + if (hasOpenOrders) { + await driftClient.cancelOrders(); + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + } + }); + + // ==================== PERP MARKET TESTS ==================== + + it('place perp scale orders - flat distribution', async () => { + const totalBaseAmount = BASE_PRECISION; // 1 SOL + const orderCount = 5; + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.PERP, + direction: PositionDirection.LONG, + marketIndex: perpMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(95).mul(PRICE_PRECISION), // $95 (end low) + orderCount: orderCount, + sizeDistribution: SizeDistribution.FLAT, + reduceOnly: false, + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders.filter((order) => + isVariant(order.status, 'open') + ); + + assert.equal(orders.length, orderCount, 'Should have 5 open orders'); + + // All orders should be perp market type + for (const order of orders) { + assert.ok(isVariant(order.marketType, 'perp'), 'Order should be perp'); + } + + // Check orders are distributed across prices (sorted low to high) + const prices = orders.map((o) => o.price.toNumber()).sort((a, b) => a - b); + assert.equal( + prices[0], + 95 * PRICE_PRECISION.toNumber(), + 'Lowest price should be $95' + ); + assert.equal( + prices[4], + 100 * PRICE_PRECISION.toNumber(), + 'Highest price should be $100' + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders for next test + await driftClient.cancelOrders(); + }); + + it('place perp scale orders - ascending distribution (long)', async () => { + const totalBaseAmount = BASE_PRECISION; // 1 SOL + const orderCount = 3; + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.PERP, + direction: PositionDirection.LONG, + marketIndex: perpMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(90).mul(PRICE_PRECISION), // $90 (end low) + orderCount: orderCount, + sizeDistribution: SizeDistribution.ASCENDING, + reduceOnly: false, + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders + .filter((order) => isVariant(order.status, 'open')) + .sort((a, b) => a.price.toNumber() - b.price.toNumber()); + + assert.equal(orders.length, orderCount, 'Should have 3 open orders'); + + // For ascending distribution, sizes increase from first to last order + // First order (at start price $100) is smallest, last order (at end price $90) is largest + console.log( + 'Order sizes (ascending):', + orders.map((o) => ({ + price: o.price.toString(), + size: o.baseAssetAmount.toString(), + })) + ); + + // Verify sizes - lowest price should have largest size (ascending from start to end) + assert.ok( + orders[0].baseAssetAmount.gt(orders[2].baseAssetAmount), + 'Order at lowest price ($90) should have largest size (ascending distribution ends there)' + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders for next test + await driftClient.cancelOrders(); + }); + + it('place perp scale orders - short direction', async () => { + const totalBaseAmount = BASE_PRECISION.div(new BN(2)); // 0.5 SOL + const orderCount = 4; + + // Short: start low, end high (scale out up) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.PERP, + direction: PositionDirection.SHORT, + marketIndex: perpMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(105).mul(PRICE_PRECISION), // $105 (start low) + endPrice: new BN(110).mul(PRICE_PRECISION), // $110 (end high) + orderCount: orderCount, + sizeDistribution: SizeDistribution.FLAT, + reduceOnly: false, + postOnly: PostOnlyParams.MUST_POST_ONLY, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders.filter((order) => + isVariant(order.status, 'open') + ); + + assert.equal(orders.length, orderCount, 'Should have 4 open orders'); + + // All orders should be short direction + for (const order of orders) { + assert.deepEqual( + order.direction, + PositionDirection.SHORT, + 'All orders should be SHORT' + ); + } + + // Check prices are distributed from 105 to 110 + const prices = orders.map((o) => o.price.toNumber()).sort((a, b) => a - b); + // Allow small rounding tolerance + const expectedStartPrice = 105 * PRICE_PRECISION.toNumber(); + assert.ok( + Math.abs(prices[0] - expectedStartPrice) <= 10, + `Lowest price should be ~$105 (got ${prices[0]}, expected ${expectedStartPrice})` + ); + const expectedEndPrice = 110 * PRICE_PRECISION.toNumber(); + assert.ok( + Math.abs(prices[3] - expectedEndPrice) <= 10, + `Highest price should be ~$110 (got ${prices[3]}, expected ${expectedEndPrice})` + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders for next test + await driftClient.cancelOrders(); + }); + + it('place perp scale orders - descending distribution', async () => { + const totalBaseAmount = BASE_PRECISION; // 1 SOL + const orderCount = 3; + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.PERP, + direction: PositionDirection.LONG, + marketIndex: perpMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(90).mul(PRICE_PRECISION), // $90 (end low) + orderCount: orderCount, + sizeDistribution: SizeDistribution.DESCENDING, + reduceOnly: false, + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders + .filter((order) => isVariant(order.status, 'open')) + .sort((a, b) => a.price.toNumber() - b.price.toNumber()); + + assert.equal(orders.length, orderCount, 'Should have 3 open orders'); + + // For descending distribution, sizes decrease from first order to last + // First order (at start price $100) gets largest size + // Last order (at end price $90) gets smallest size + console.log( + 'Order sizes (descending):', + orders.map((o) => ({ + price: o.price.toString(), + size: o.baseAssetAmount.toString(), + })) + ); + + // Verify sizes - highest price (start) has largest size, lowest price (end) has smallest + assert.ok( + orders[2].baseAssetAmount.gt(orders[0].baseAssetAmount), + 'Order at highest price ($100) should have largest size, lowest price ($90) smallest' + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders + await driftClient.cancelOrders(); + }); + + it('place perp scale orders - with reduce only flag', async () => { + // Test that reduce-only flag is properly set on scale orders + // Note: We don't need an actual position to test the flag is set correctly + const totalBaseAmount = BASE_PRECISION.div(new BN(2)); // 0.5 SOL + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.PERP, + direction: PositionDirection.LONG, + marketIndex: perpMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(95).mul(PRICE_PRECISION), // $95 (end low) + orderCount: 2, + sizeDistribution: SizeDistribution.FLAT, + reduceOnly: true, // Test reduce only flag + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders.filter((order) => + isVariant(order.status, 'open') + ); + + assert.equal(orders.length, 2, 'Should have 2 open orders'); + + // All orders should have reduce only flag set + for (const order of orders) { + assert.equal(order.reduceOnly, true, 'Order should be reduce only'); + } + + // Cancel all orders + await driftClient.cancelOrders(); + }); + + it('place perp scale orders - minimum 2 orders', async () => { + const totalBaseAmount = BASE_PRECISION; + const orderCount = 2; // Minimum allowed + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.PERP, + direction: PositionDirection.LONG, + marketIndex: perpMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(95).mul(PRICE_PRECISION), // $95 (end low) + orderCount: orderCount, + sizeDistribution: SizeDistribution.FLAT, + reduceOnly: false, + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders.filter((order) => + isVariant(order.status, 'open') + ); + + assert.equal(orders.length, 2, 'Should have exactly 2 orders'); + + const prices = orders.map((o) => o.price.toNumber()).sort((a, b) => a - b); + assert.equal( + prices[0], + 95 * PRICE_PRECISION.toNumber(), + 'Lowest price should be $95' + ); + assert.equal( + prices[1], + 100 * PRICE_PRECISION.toNumber(), + 'Highest price should be $100' + ); + + // Cancel all orders + await driftClient.cancelOrders(); + }); + + // ==================== SPOT MARKET TESTS ==================== + + it('place spot scale orders - flat distribution (long)', async () => { + const totalBaseAmount = BASE_PRECISION; // 1 SOL + const orderCount = 3; + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.SPOT, + direction: PositionDirection.LONG, + marketIndex: spotMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(95).mul(PRICE_PRECISION), // $95 (end low) + orderCount: orderCount, + sizeDistribution: SizeDistribution.FLAT, + reduceOnly: false, + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders.filter((order) => + isVariant(order.status, 'open') + ); + + assert.equal(orders.length, orderCount, 'Should have 3 open orders'); + + // All orders should be spot market type + for (const order of orders) { + assert.ok(isVariant(order.marketType, 'spot'), 'Order should be spot'); + assert.equal( + order.marketIndex, + spotMarketIndex, + 'Market index should match' + ); + } + + // Check orders are distributed across prices + const prices = orders.map((o) => o.price.toNumber()).sort((a, b) => a - b); + assert.equal( + prices[0], + 95 * PRICE_PRECISION.toNumber(), + 'Lowest price should be $95' + ); + assert.equal( + prices[2], + 100 * PRICE_PRECISION.toNumber(), + 'Highest price should be $100' + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders + await driftClient.cancelOrders(); + }); + + it('place spot scale orders - short direction', async () => { + const totalBaseAmount = BASE_PRECISION.div(new BN(2)); // 0.5 SOL + const orderCount = 4; + + // Short: start low, end high (scale out up) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.SPOT, + direction: PositionDirection.SHORT, + marketIndex: spotMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(105).mul(PRICE_PRECISION), // $105 (start low) + endPrice: new BN(110).mul(PRICE_PRECISION), // $110 (end high) + orderCount: orderCount, + sizeDistribution: SizeDistribution.FLAT, + reduceOnly: false, + postOnly: PostOnlyParams.MUST_POST_ONLY, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders.filter((order) => + isVariant(order.status, 'open') + ); + + assert.equal(orders.length, orderCount, 'Should have 4 open orders'); + + // All orders should be spot market type and short direction + for (const order of orders) { + assert.ok(isVariant(order.marketType, 'spot'), 'Order should be spot'); + assert.deepEqual( + order.direction, + PositionDirection.SHORT, + 'All orders should be SHORT' + ); + } + + // Check prices are distributed from 105 to 110 + const prices = orders.map((o) => o.price.toNumber()).sort((a, b) => a - b); + const expectedStartPrice = 105 * PRICE_PRECISION.toNumber(); + assert.ok( + Math.abs(prices[0] - expectedStartPrice) <= 10, + `Lowest price should be ~$105 (got ${prices[0]})` + ); + const expectedEndPrice = 110 * PRICE_PRECISION.toNumber(); + assert.ok( + Math.abs(prices[3] - expectedEndPrice) <= 10, + `Highest price should be ~$110 (got ${prices[3]})` + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders + await driftClient.cancelOrders(); + }); + + it('place spot scale orders - ascending distribution', async () => { + const totalBaseAmount = BASE_PRECISION; // 1 SOL + const orderCount = 3; + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.SPOT, + direction: PositionDirection.LONG, + marketIndex: spotMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(90).mul(PRICE_PRECISION), // $90 (end low) + orderCount: orderCount, + sizeDistribution: SizeDistribution.ASCENDING, + reduceOnly: false, + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders + .filter((order) => isVariant(order.status, 'open')) + .sort((a, b) => a.price.toNumber() - b.price.toNumber()); + + assert.equal(orders.length, orderCount, 'Should have 3 open orders'); + + // All orders should be spot market type + for (const order of orders) { + assert.ok(isVariant(order.marketType, 'spot'), 'Order should be spot'); + } + + // For ascending distribution, sizes increase from start to end + // Order at lowest price ($90 - end) should have largest size + console.log( + 'Spot order sizes (ascending):', + orders.map((o) => ({ + price: o.price.toString(), + size: o.baseAssetAmount.toString(), + })) + ); + + assert.ok( + orders[0].baseAssetAmount.gt(orders[2].baseAssetAmount), + 'Order at lowest price ($90) should have largest size' + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders + await driftClient.cancelOrders(); + }); + + it('place spot scale orders - descending distribution', async () => { + const totalBaseAmount = BASE_PRECISION; // 1 SOL + const orderCount = 3; + + // Long: start high, end low (DCA down) + const txSig = await driftClient.placeScaleOrders({ + marketType: MarketType.SPOT, + direction: PositionDirection.LONG, + marketIndex: spotMarketIndex, + totalBaseAssetAmount: totalBaseAmount, + startPrice: new BN(100).mul(PRICE_PRECISION), // $100 (start high) + endPrice: new BN(90).mul(PRICE_PRECISION), // $90 (end low) + orderCount: orderCount, + sizeDistribution: SizeDistribution.DESCENDING, + reduceOnly: false, + postOnly: PostOnlyParams.NONE, + bitFlags: 0, + maxTs: null, + }); + + bankrunContextWrapper.printTxLogs(txSig); + + await driftClient.fetchAccounts(); + await driftClientUser.fetchAccounts(); + + const userAccount = driftClientUser.getUserAccount(); + const orders = userAccount.orders + .filter((order) => isVariant(order.status, 'open')) + .sort((a, b) => a.price.toNumber() - b.price.toNumber()); + + assert.equal(orders.length, orderCount, 'Should have 3 open orders'); + + // For descending distribution, sizes decrease from start to end + // Order at highest price ($100 - start) should have largest size + console.log( + 'Spot order sizes (descending):', + orders.map((o) => ({ + price: o.price.toString(), + size: o.baseAssetAmount.toString(), + })) + ); + + assert.ok( + orders[2].baseAssetAmount.gt(orders[0].baseAssetAmount), + 'Order at highest price ($100) should have largest size' + ); + + // Check total base amount sums correctly + const totalBase = orders.reduce( + (sum, o) => sum.add(o.baseAssetAmount), + ZERO + ); + assert.ok( + totalBase.eq(totalBaseAmount), + 'Total base amount should equal input' + ); + + // Cancel all orders + await driftClient.cancelOrders(); + }); +}); diff --git a/yarn.lock b/yarn.lock index 2678bc1b59..42cc7be1db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -123,24 +123,6 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== -"@grpc/grpc-js@^1.8.13": - version "1.13.4" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.13.4.tgz#922fbc496e229c5fa66802d2369bf181c1df1c5a" - integrity sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg== - dependencies: - "@grpc/proto-loader" "^0.7.13" - "@js-sdsl/ordered-map" "^4.4.2" - -"@grpc/proto-loader@^0.7.13": - version "0.7.15" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" - integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== - dependencies: - lodash.camelcase "^4.3.0" - long "^5.0.0" - protobufjs "^7.2.5" - yargs "^17.7.2" - "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -165,11 +147,6 @@ resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2" integrity sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA== -"@js-sdsl/ordered-map@^4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" - integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== - "@metaplex-foundation/beet-solana@^0.3.0": version "0.3.1" resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet-solana/-/beet-solana-0.3.1.tgz#4b37cda5c7f32ffd2bdd8b3164edc05c6463ab35" @@ -225,19 +202,19 @@ snake-case "^3.0.4" spok "^1.4.3" -"@noble/curves@^1.0.0", "@noble/curves@^1.4.2": +"@noble/curves@^1.4.2": version "1.9.2" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.2.tgz#73388356ce733922396214a933ff7c95afcef911" integrity sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g== dependencies: "@noble/hashes" "1.8.0" -"@noble/ed25519@^1.7.0", "@noble/ed25519@^1.7.1": +"@noble/ed25519@^1.7.0": version "1.7.5" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.5.tgz#94df8bdb9fec9c4644a56007eecb57b0e9fbd0d7" integrity sha512-xuS0nwRMQBvSxDa7UxMb61xTiH3MxTgUfhyPUALVIe0FlOAz4sjELwyDRyUvqeEYfRSG9qNjFIycqLZppg4RSA== -"@noble/hashes@1.8.0", "@noble/hashes@^1.1.2", "@noble/hashes@^1.3.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": +"@noble/hashes@1.8.0", "@noble/hashes@^1.1.2", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== @@ -391,35 +368,13 @@ ts-log "^2.2.4" ws "^8.6.0" -"@pythnetwork/price-service-sdk@*", "@pythnetwork/price-service-sdk@>=1.6.0": +"@pythnetwork/price-service-sdk@*": version "1.8.0" resolved "https://registry.yarnpkg.com/@pythnetwork/price-service-sdk/-/price-service-sdk-1.8.0.tgz#f5f01f654963eb9a0cf12127b4f1a89b60ef008a" integrity sha512-tFZ1thj3Zja06DzPIX2dEWSi7kIfIyqreoywvw5NQ3Z1pl5OJHQGMEhxt6Li3UCGSp2ooYZS9wl8/8XfrfrNSA== dependencies: bn.js "^5.2.1" -"@pythnetwork/pyth-solana-receiver@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@pythnetwork/pyth-solana-receiver/-/pyth-solana-receiver-0.8.0.tgz#d7bf3c5c97a0f0eab8ac19f53b11664117e1152d" - integrity sha512-5lhLtggAqsiHtffTPM8vcKJmhBdxzidBmiNNUlqPyg9XmhZ4Z+roY0dfzluEoX5xer9rEA1ThsBpX0bG1DRIGA== - dependencies: - "@coral-xyz/anchor" "^0.29.0" - "@noble/hashes" "^1.4.0" - "@pythnetwork/price-service-sdk" ">=1.6.0" - "@pythnetwork/solana-utils" "*" - "@solana/web3.js" "^1.90.0" - -"@pythnetwork/solana-utils@*": - version "0.4.5" - resolved "https://registry.yarnpkg.com/@pythnetwork/solana-utils/-/solana-utils-0.4.5.tgz#7c5af4b6794769e57b56ad1c680faa6dbf70b919" - integrity sha512-NoLdC2rRAx9a66L0hSOAGt6Wj/YxfnKkw+mbb7Tn/Nn1du4HyShi41DiN6B+2XXqnMthNGbf9FSHvj4NXXABvA== - dependencies: - "@coral-xyz/anchor" "^0.29.0" - "@solana/web3.js" "^1.90.0" - bs58 "^5.0.0" - jito-ts "^3.0.1" - ts-log "^2.2.7" - "@solana/buffer-layout-utils@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca" @@ -592,7 +547,7 @@ rpc-websockets "^7.5.0" superstruct "^0.14.2" -"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.90.0", "@solana/web3.js@^1.98.0", "@solana/web3.js@^1.98.2": +"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.56.2", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.98.0", "@solana/web3.js@^1.98.2": version "1.98.2" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.2.tgz#45167a5cfb64436944bf4dc1e8be8482bd6d4c14" integrity sha512-BqVwEG+TaG2yCkBMbD3C4hdpustR4FpuUFRPUmqRZYYlPI9Hg4XMWxHWOWRzHE9Lkc9NDjzXFX7lDXSgzC7R1A== @@ -613,27 +568,6 @@ rpc-websockets "^9.0.2" superstruct "^2.0.2" -"@solana/web3.js@~1.77.3": - version "1.77.4" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.77.4.tgz#aad8c44a02ced319493308ef765a2b36a9e9fa8c" - integrity sha512-XdN0Lh4jdY7J8FYMyucxCwzn6Ga2Sr1DHDWRbqVzk7ZPmmpSPOVWHzO67X1cVT+jNi1D6gZi2tgjHgDPuj6e9Q== - dependencies: - "@babel/runtime" "^7.12.5" - "@noble/curves" "^1.0.0" - "@noble/hashes" "^1.3.0" - "@solana/buffer-layout" "^4.0.0" - agentkeepalive "^4.2.1" - bigint-buffer "^1.1.5" - bn.js "^5.0.0" - borsh "^0.7.0" - bs58 "^4.0.1" - buffer "6.0.3" - fast-stable-stringify "^1.0.0" - jayson "^4.1.0" - node-fetch "^2.6.7" - rpc-websockets "^7.5.1" - superstruct "^0.14.2" - "@swc/helpers@^0.5.11": version "0.5.17" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" @@ -873,7 +807,7 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -agentkeepalive@^4.2.1, agentkeepalive@^4.3.0, agentkeepalive@^4.5.0: +agentkeepalive@^4.2.1, agentkeepalive@^4.5.0: version "4.6.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== @@ -1199,15 +1133,6 @@ check-error@^1.0.3: dependencies: get-func-name "^2.0.2" -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - color-convert@<3.1.1, color-convert@^2.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-3.1.0.tgz#ce16ebb832f9d7522649ed9e11bc0ccb9433a524" @@ -1373,11 +1298,6 @@ dotenv@16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== -dotenv@^16.0.3: - version "16.6.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" - integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== - dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -1438,11 +1358,6 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -escalade@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -1698,11 +1613,6 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - get-func-name@^2.0.1, get-func-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" @@ -1997,7 +1907,7 @@ jayson@^3.4.4: uuid "^8.3.2" ws "^7.4.5" -jayson@^4.0.0, jayson@^4.1.0, jayson@^4.1.1: +jayson@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.2.0.tgz#b71762393fa40bc9637eaf734ca6f40d3b8c0c93" integrity sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg== @@ -2015,20 +1925,6 @@ jayson@^4.0.0, jayson@^4.1.0, jayson@^4.1.1: uuid "^8.3.2" ws "^7.5.10" -jito-ts@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/jito-ts/-/jito-ts-3.0.1.tgz#24126389896e042c26d303c4e802064b249ed27e" - integrity sha512-TSofF7KqcwyaWGjPaSYC8RDoNBY1TPRNBHdrw24bdIi7mQ5bFEDdYK3D//llw/ml8YDvcZlgd644WxhjLTS9yg== - dependencies: - "@grpc/grpc-js" "^1.8.13" - "@noble/ed25519" "^1.7.1" - "@solana/web3.js" "~1.77.3" - agentkeepalive "^4.3.0" - dotenv "^16.0.3" - jayson "^4.0.0" - node-fetch "^2.6.7" - superstruct "^1.0.3" - js-sha256@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.11.1.tgz#712262e8fc9569d6f7f6eea72c0d8e5ccc7c976c" @@ -2112,11 +2008,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -2240,7 +2131,7 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-fetch@2, node-fetch@^2.6.7, node-fetch@^2.7.0: +node-fetch@2, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -2379,7 +2270,7 @@ prettier@^2.5.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -protobufjs@^7.2.5, protobufjs@^7.4.0: +protobufjs@^7.4.0: version "7.5.3" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.3.tgz#13f95a9e3c84669995ec3652db2ac2fb00b89363" integrity sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw== @@ -2412,11 +2303,6 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -2447,7 +2333,7 @@ rpc-websockets@7.5.1: bufferutil "^4.0.1" utf-8-validate "^5.0.2" -rpc-websockets@^7.5.0, rpc-websockets@^7.5.1: +rpc-websockets@^7.5.0: version "7.11.2" resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.11.2.tgz#582910c425b9f2c860327481c1d1e0e431bf4a6d" integrity sha512-pL9r5N6AVHlMN/vT98+fcO+5+/UcPLf/4tq+WUaid/PPUGS/ttJ3y8e9IqmaWKtShNAysMSjkczuEA49NuV7UQ== @@ -2622,7 +2508,7 @@ stream-json@^1.9.1: dependencies: stream-chain "^2.2.5" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2668,11 +2554,6 @@ superstruct@^0.15.4: resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== -superstruct@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" - integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== - superstruct@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" @@ -2740,7 +2621,7 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== -ts-log@^2.2.4, ts-log@^2.2.7: +ts-log@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.7.tgz#4f4512144898b77c9984e91587076fcb8518688e" integrity sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg== @@ -2870,7 +2751,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +wrap-ansi@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -2894,34 +2775,11 @@ ws@^8.5.0, ws@^8.6.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - yaml@^2.6.1: version "2.8.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6" integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ== -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"