diff --git a/.gitignore b/.gitignore index a467214..6cbdfe9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ node_modules test-ledger/ sdk/dist/ sdk/node_modules +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e5791ae..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files.insertFinalNewline": true -} \ No newline at end of file diff --git a/Anchor.toml b/Anchor.toml index 9895a7b..b4477b4 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -14,4 +14,4 @@ wallet = "~/.config/solana/id.json" # program = "../metaplex-program-library/token-metadata/target/deploy/mpl_token_metadata.so" [scripts] -test = "ts-mocha -p sdk/tests/tsconfig.json -t 1000000 sdk/tests/**/*.ts" +test = "ts-mocha -p sdk/tests/tsconfig.json -t 1000000 sdk/tests/**/*.test.ts" diff --git a/README.md b/README.md index b8ff9a5..cb398a9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ This repository contains the Rust smart contract as well as the Typescript SDK ( The contract has been audited by Kudelski and Neodyme. ## Requirements + - Anchor 0.20.1 - Solana 1.9.3 - Rust 1.59.0 @@ -18,12 +19,14 @@ Set up a valid Solana keypair at the path specified in the `wallet` in `Anchor.t `$NODE_PATH` must be set to the `node_modules` directory of your global installs. For example, using Node 16.10.0 installed through `nvm`, the $NODE_PATH is the following: + ``` $ echo $NODE_PATH /Users//.nvm/versions/node/v16.10.0/lib/node_modules ``` ## Usage + Instructions on how to interact with the Whirlpools contract is documented in the Orca Developer Portal. ## Tests @@ -33,7 +36,6 @@ Instructions on how to interact with the Whirlpools contract is documented in th --- - # Whirlpool SDK Use the SDK to interact with a deployed Whirlpools program via Typescript. @@ -41,14 +43,25 @@ Use the SDK to interact with a deployed Whirlpools program via Typescript. ## Installation In your package, run: + ``` yarn add `@orca-so/whirlpool-sdk` ``` ## Usage + Read instructions on how to use the SDK on the Orca Developer Portal. +## Run Typescript tests via local validator + +In the whirlpools/sdk folder, run: + +``` +anchor test +``` + ## Generate TypeDoc + In the `sdk` folder, run `yarn run docs` --- diff --git a/programs/whirlpool/src/lib.rs b/programs/whirlpool/src/lib.rs index 0c11207..b5e7ef8 100644 --- a/programs/whirlpool/src/lib.rs +++ b/programs/whirlpool/src/lib.rs @@ -29,7 +29,7 @@ pub mod whirlpool { /// Initializes a WhirlpoolsConfig account that hosts info & authorities /// required to govern a set of Whirlpools. /// - /// # Parameters + /// ### Parameters /// - `fee_authority` - Authority authorized to initialize fee-tiers and set customs fees. /// - `collect_protocol_fees_authority` - Authority authorized to collect protocol fees. /// - `reward_emissions_super_authority` - Authority authorized to set reward authorities in pools. @@ -52,12 +52,12 @@ pub mod whirlpool { /// Initializes a Whirlpool account. /// Fee rate is set to the default values on the config and supplied fee_tier. /// - /// # Parameters + /// ### Parameters /// - `bumps` - The bump value when deriving the PDA of the Whirlpool address. /// - `tick_spacing` - The desired tick spacing for this pool. /// - `initial_sqrt_price` - The desired initial sqrt-price for this pool /// - /// # Special Errors + /// #### Special Errors /// `InvalidTokenMintOrder` - The order of mints have to be ordered by /// `SqrtPriceOutOfBounds` - provided initial_sqrt_price is not between 2^-64 to 2^64 /// @@ -77,11 +77,11 @@ pub mod whirlpool { /// Initializes a tick_array account to represent a tick-range in a Whirlpool. /// - /// # Parameters + /// ### Parameters /// - `start_tick_index` - The starting tick index for this tick-array. /// Has to be a multiple of TickArray size & the tick spacing of this pool. /// - /// # Special Errors + /// #### Special Errors /// - `InvalidStartTick` - if the provided start tick is out of bounds or is not a multiple of /// TICK_ARRAY_SIZE * tick spacing. pub fn initialize_tick_array( @@ -93,15 +93,15 @@ pub mod whirlpool { /// Initializes a fee_tier account usable by Whirlpools in a WhirlpoolConfig space. /// - /// # Authority + /// ### Authority /// - "fee_authority" - Set authority in the WhirlpoolConfig /// - /// # Parameters + /// ### Parameters /// - `tick_spacing` - The tick-spacing that this fee-tier suggests the default_fee_rate for. /// - `default_fee_rate` - The default fee rate that a pool will use if the pool uses this /// fee tier during initialization. /// - /// # Special Errors + /// #### Special Errors /// - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE. pub fn initialize_fee_tier( ctx: Context, @@ -113,14 +113,14 @@ pub mod whirlpool { /// Initialize reward for a Whirlpool. A pool can only support up to a set number of rewards. /// - /// # Authority + /// ### Authority /// - "reward_authority" - assigned authority by the reward_super_authority for the specified /// reward-index in this Whirlpool /// - /// # Parameters + /// ### Parameters /// - `reward_index` - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS) /// - /// # Special Errors + /// #### Special Errors /// - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized /// index in this pool, or exceeds NUM_REWARDS, or /// all reward slots for this pool has been initialized. @@ -130,15 +130,15 @@ pub mod whirlpool { /// Set the reward emissions for a reward in a Whirlpool. /// - /// # Authority + /// ### Authority /// - "reward_authority" - assigned authority by the reward_super_authority for the specified /// reward-index in this Whirlpool /// - /// # Parameters + /// ### Parameters /// - `reward_index` - The reward index (0 <= index <= NUM_REWARDS) that we'd like to modify. /// - `emissions_per_second_x64` - The amount of rewards emitted in this pool. /// - /// # Special Errors + /// #### Special Errors /// - `RewardVaultAmountInsufficient` - The amount of rewards in the reward vault cannot emit /// more than a day of desired emissions. /// - `InvalidTimestamp` - Provided timestamp is not in order with the previous timestamp. @@ -160,11 +160,11 @@ pub mod whirlpool { /// Open a position in a Whirlpool. A unique token will be minted to represent the position /// in the users wallet. The position will start off with 0 liquidity. /// - /// # Parameters + /// ### Parameters /// - `tick_lower_index` - The tick specifying the lower end of the position range. /// - `tick_upper_index` - The tick specifying the upper end of the position range. /// - /// # Special Errors + /// #### Special Errors /// - `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of /// the tick-spacing in this pool. pub fn open_position( @@ -185,11 +185,11 @@ pub mod whirlpool { /// in the users wallet. Additional Metaplex metadata is appended to identify the token. /// The position will start off with 0 liquidity. /// - /// # Parameters + /// ### Parameters /// - `tick_lower_index` - The tick specifying the lower end of the position range. /// - `tick_upper_index` - The tick specifying the upper end of the position range. /// - /// # Special Errors + /// #### Special Errors /// - `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of /// the tick-spacing in this pool. pub fn open_position_with_metadata( @@ -206,17 +206,17 @@ pub mod whirlpool { ); } - /// Add liquidity to a position in the Whirlpool. + /// Add liquidity to a position in the Whirlpool. This call also updates the position's accrued fees and rewards. /// - /// # Authority + /// ### Authority /// - `position_authority` - authority that owns the token corresponding to this desired position. /// - /// # Parameters + /// ### Parameters /// - `liquidity_amount` - The total amount of Liquidity the user is willing to deposit. /// - `token_max_a` - The maximum amount of tokenA the user is willing to deposit. /// - `token_max_b` - The maximum amount of tokenB the user is willing to deposit. /// - /// # Special Errors + /// #### Special Errors /// - `LiquidityZero` - Provided liquidity amount is zero. /// - `LiquidityTooHigh` - Provided liquidity exceeds u128::max. /// - `TokenMaxExceeded` - The required token to perform this operation exceeds the user defined amount. @@ -234,17 +234,17 @@ pub mod whirlpool { ); } - /// Withdraw liquidity from a position in the Whirlpool. + /// Withdraw liquidity from a position in the Whirlpool. This call also updates the position's accrued fees and rewards. /// - /// # Authority + /// ### Authority /// - `position_authority` - authority that owns the token corresponding to this desired position. /// - /// # Parameters + /// ### Parameters /// - `liquidity_amount` - The total amount of Liquidity the user desires to withdraw. /// - `token_min_a` - The minimum amount of tokenA the user is willing to withdraw. /// - `token_min_b` - The minimum amount of tokenB the user is willing to withdraw. /// - /// # Special Errors + /// #### Special Errors /// - `LiquidityZero` - Provided liquidity amount is zero. /// - `LiquidityTooHigh` - Provided liquidity exceeds u128::max. /// - `TokenMinSubceeded` - The required token to perform this operation subceeds the user defined amount. @@ -264,7 +264,7 @@ pub mod whirlpool { /// Update the accrued fees and rewards for a position. /// - /// # Special Errors + /// #### Special Errors /// - `TickNotFound` - Provided tick array account does not contain the tick for this position. /// - `LiquidityZero` - Position has zero liquidity and therefore already has the most updated fees and reward values. pub fn update_fees_and_rewards(ctx: Context) -> ProgramResult { @@ -273,7 +273,7 @@ pub mod whirlpool { /// Collect fees accrued for this position. /// - /// # Authority + /// ### Authority /// - `position_authority` - authority that owns the token corresponding to this desired position. pub fn collect_fees(ctx: Context) -> ProgramResult { return instructions::collect_fees::handler(ctx); @@ -281,7 +281,7 @@ pub mod whirlpool { /// Collect rewards accrued for this position. /// - /// # Authority + /// ### Authority /// - `position_authority` - authority that owns the token corresponding to this desired position. pub fn collect_reward(ctx: Context, reward_index: u8) -> ProgramResult { return instructions::collect_reward::handler(ctx, reward_index); @@ -289,7 +289,7 @@ pub mod whirlpool { /// Collect the protocol fees accrued in this Whirlpool /// - /// # Authority + /// ### Authority /// - `collect_protocol_fees_authority` - assigned authority in the WhirlpoolConfig that can collect protocol fees pub fn collect_protocol_fees(ctx: Context) -> ProgramResult { return instructions::collect_protocol_fees::handler(ctx); @@ -297,12 +297,25 @@ pub mod whirlpool { /// Perform a swap in this Whirlpool /// - /// # Parameters - /// - `amount` - /// - `other_amount_threshold` - /// - `sqrt_price_limit` - /// - `exact_input` - /// - `a_to_b` + /// ### Authority + /// - "token_authority" - The authority to withdraw tokens from the input token account. + /// + /// ### Parameters + /// - `amount` - The amount of input or output token to swap from (depending on exact_input). + /// - `other_amount_threshold` - The maximum/minimum of input/output token to swap into (depending on exact_input). + /// - `sqrt_price_limit` - The maximum/minimum price the swap will swap to. + /// - `exact_input` - Specifies the token the parameter `amount`represents. If true, the amount represents the input token of the swap. + /// - `a_to_b` - The direction of the swap. True if swapping from A to B. False if swapping from B to A. + /// + /// #### Special Errors + /// - `ZeroTradableAmount` - User provided parameter `amount` is 0. + /// - `InvalidSqrtPriceLimitDirection` - User provided parameter `sqrt_price_limit` does not match the direction of the trade. + /// - `SqrtPriceOutOfBounds` - User provided parameter `sqrt_price_limit` is over Whirlppool's max/min bounds for sqrt-price. + /// - `InvalidTickArraySequence` - User provided tick-arrays are not in sequential order required to proceed in this trade direction. + /// - `TickArraySequenceInvalidIndex` - The swap loop attempted to access an invalid array index during the query of the next initialized tick. + /// - `TickArrayIndexOutofBounds` - The swap loop attempted to access an invalid array index during tick crossing. + /// - `LiquidityOverflow` - Liquidity value overflowed 128bits during tick crossing. + /// - `InvalidTickSpacing` - The swap pool was initialized with tick-spacing of 0. pub fn swap( ctx: Context, amount: u64, @@ -321,6 +334,13 @@ pub mod whirlpool { ); } + /// Close a position in a Whirlpool. Burns the position token in the owner's wallet. + /// + /// ### Authority + /// - "position_authority" - The authority that owns the position token. + /// + /// #### Special Errors + /// - `ClosePositionNotEmpty` - The provided position account is not empty. pub fn close_position(ctx: Context) -> ProgramResult { return instructions::close_position::handler(ctx); } @@ -328,14 +348,14 @@ pub mod whirlpool { /// Set the default_fee_rate for a FeeTier /// Only the current fee authority has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "fee_authority" - Set authority in the WhirlpoolConfig /// - /// # Parameters + /// ### Parameters /// - `default_fee_rate` - The default fee rate that a pool will use if the pool uses this /// fee tier during initialization. /// - /// # Special Errors + /// #### Special Errors /// - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE. pub fn set_default_fee_rate( ctx: Context, @@ -348,13 +368,13 @@ pub mod whirlpool { /// Protocol fee rate is represented as a basis point. /// Only the current fee authority has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig /// - /// # Parameters + /// ### Parameters /// - `default_protocol_fee_rate` - Rate that is referenced during the initialization of a Whirlpool using this config. /// - /// # Special Errors + /// #### Special Errors /// - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE. pub fn set_default_protocol_fee_rate( ctx: Context, @@ -370,14 +390,14 @@ pub mod whirlpool { /// Fee rate is represented as hundredths of a basis point. /// Only the current fee authority has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig /// - /// # Parameters + /// ### Parameters /// - `fee_rate` - The rate that the pool will use to calculate fees going onwards. /// - /// # Special Errors - /// - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE. + /// #### Special Errors + /// - `FeeRateMaxExceeded` - If the provided fee_rate exceeds MAX_FEE_RATE. pub fn set_fee_rate(ctx: Context, fee_rate: u16) -> ProgramResult { return instructions::set_fee_rate::handler(ctx, fee_rate); } @@ -386,13 +406,13 @@ pub mod whirlpool { /// Protocol fee rate is represented as a basis point. /// Only the current fee authority has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig /// - /// # Parameters + /// ### Parameters /// - `protocol_fee_rate` - The rate that the pool will use to calculate protocol fees going onwards. /// - /// # Special Errors + /// #### Special Errors /// - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE. pub fn set_protocol_fee_rate( ctx: Context, @@ -404,9 +424,9 @@ pub mod whirlpool { /// Sets the fee authority for a WhirlpoolConfig. /// The fee authority can set the fee & protocol fee rate for individual pools or /// set the default fee rate for newly minted pools. - /// Only the current collect fee authority has permission to invoke this instruction. + /// Only the current fee authority has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "fee_authority" - Set authority that can modify pool fees in the WhirlpoolConfig pub fn set_fee_authority(ctx: Context) -> ProgramResult { return instructions::set_fee_authority::handler(ctx); @@ -415,7 +435,7 @@ pub mod whirlpool { /// Sets the fee authority to collect protocol fees for a WhirlpoolConfig. /// Only the current collect protocol fee authority has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "fee_authority" - Set authority that can collect protocol fees in the WhirlpoolConfig pub fn set_collect_protocol_fees_authority( ctx: Context, @@ -426,8 +446,13 @@ pub mod whirlpool { /// Set the whirlpool reward authority at the provided `reward_index`. /// Only the current reward authority for this reward index has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "reward_authority" - Set authority that can control reward emission for this particular reward. + /// + /// #### Special Errors + /// - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized + /// index in this pool, or exceeds NUM_REWARDS, or + /// all reward slots for this pool has been initialized. pub fn set_reward_authority( ctx: Context, reward_index: u8, @@ -438,8 +463,13 @@ pub mod whirlpool { /// Set the whirlpool reward authority at the provided `reward_index`. /// Only the current reward super authority has permission to invoke this instruction. /// - /// # Authority + /// ### Authority /// - "reward_authority" - Set authority that can control reward emission for this particular reward. + /// + /// #### Special Errors + /// - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized + /// index in this pool, or exceeds NUM_REWARDS, or + /// all reward slots for this pool has been initialized. pub fn set_reward_authority_by_super_authority( ctx: Context, reward_index: u8, @@ -451,7 +481,7 @@ pub mod whirlpool { /// Only the current reward super authority has permission to invoke this instruction. /// This instruction will not change the authority on any `WhirlpoolRewardInfo` whirlpool rewards. /// - /// # Authority + /// ### Authority /// - "reward_emissions_super_authority" - Set authority that can control reward authorities for all pools in this config space. pub fn set_reward_emissions_super_authority( ctx: Context, diff --git a/programs/whirlpool/src/util/swap_tick_sequence.rs b/programs/whirlpool/src/util/swap_tick_sequence.rs index 504c394..c426a57 100644 --- a/programs/whirlpool/src/util/swap_tick_sequence.rs +++ b/programs/whirlpool/src/util/swap_tick_sequence.rs @@ -99,9 +99,9 @@ impl<'info> SwapTickSequence<'info> { /// /// # Returns /// - `(usize, i32, &mut Tick)`: The array_index which the next initialized index was found, the next initialized tick-index & a mutable reference to that tick - /// - /// - `InvalidTickArraySequence`: - Unable to find the next initialized index with the provided tick-array sequence. - /// - Provided tick-arrays are not in sequential order to the trade direction. + /// - `TickArraySequenceInvalidIndex` - The swap loop provided an invalid array index to query the next tick in. + /// - `InvalidTickArraySequence`: - User provided tick-arrays are not in sequential order required to proceed in this trade direction. + pub fn get_next_initialized_tick_index( &self, tick_index: i32, diff --git a/sdk/jest.config.js b/sdk/jest.config.js new file mode 100644 index 0000000..4e1f8b0 --- /dev/null +++ b/sdk/jest.config.js @@ -0,0 +1,18 @@ +module.exports = { + "roots": [ + "/src", + "/tests/sdk" + ], + "testMatch": [ + "**/__tests__/**/*.+(ts|tsx|js)", + "**/?(*.)+(spec|test).+(ts|tsx|js)" + ], + "transform": { + "^.+\\.(ts|tsx)$": "ts-jest" + }, + globals: { + "ts-jest": { + tsconfig: "./tests/tsconfig.json" + } + } +} diff --git a/sdk/package.json b/sdk/package.json index 7d505ef..5b6786e 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,24 +1,29 @@ { - "name": "@orca-so/whirlpool-client-sdk", - "version": "0.0.8", + "name": "@orca-so/whirlpools-sdk", + "version": "1.0.0", "description": "Provides functions to generate instructions needed for Orca's Whirlpool contracts", "license": "MIT", "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { "@metaplex-foundation/mpl-token-metadata": "1.2.5", + "@orca-so/common-sdk": "^0.0.4", "@project-serum/anchor": "^0.20.1", "@solana/spl-token": "^0.1.8", - "decimal.js": "^10.3.1" + "decimal.js": "^10.3.1", + "tiny-invariant": "^1.2.0" }, "devDependencies": { "@types/decimal.js": "^7.4.0", "@types/mocha": "^9.0.0", + "@types/jest": "^26.0.24", "@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/parser": "^4.26.0", "chai": "^4.3.4", "eslint-config-prettier": "^8.3.0", "mocha": "^9.0.3", + "jest": "^27.0.6", + "ts-jest": "^27.0.3", "prettier": "^2.3.2", "typescript": "^4.5.5" }, @@ -26,7 +31,9 @@ "build": "tsc -p src", "watch": "tsc -w -p src", "prepublishOnly": "yarn build", - "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write" + "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write", + "test": "jest", + "docs": "npx typedoc --excludePrivate --categorizeByGroup false --tsconfig src/tsconfig.json" }, "lint-staged": { "*.{ts,md}": "yarn run prettier-format" diff --git a/sdk/src/client.ts b/sdk/src/client.ts deleted file mode 100644 index 0455946..0000000 --- a/sdk/src/client.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { WhirlpoolContext } from "./context"; -import { PublicKey } from "@solana/web3.js"; -import { WhirlpoolConfigAccount } from "./types/public/account-types"; -import { TransactionBuilder } from "./utils/transactions/transactions-builder"; -import { buildInitializeConfigIx } from "./instructions/initialize-config-ix"; -import { - ClosePositionParams, - CollectFeesParams, - CollectProtocolFeesParams, - CollectRewardParams, - InitConfigParams, - InitializeRewardParams, - InitPoolParams, - InitTickArrayParams, - OpenPositionParams, - SetCollectProtocolFeesAuthorityParams, - SetFeeAuthorityParams, - SetRewardAuthorityBySuperAuthorityParams, - parsePosition, - parseTickArray, - parseWhirlpool, - parseWhirlpoolsConfig, - SetRewardAuthorityParams, - SetRewardEmissionsParams, - SetRewardEmissionsSuperAuthorityParams, - SwapParams, - UpdateFeesAndRewardsParams, - SetFeeRateParams, - SetDefaultProtocolFeeRateParams, - SetProtocolFeeRateParams, - SetDefaultFeeRateParams, - DecreaseLiquidityParams, - IncreaseLiquidityParams, - InitFeeTierParams, -} from "."; -import { buildInitPoolIx } from "./instructions/initialize-pool-ix"; -import { - FeeTierData, - PositionData, - TickArrayData, - WhirlpoolData, -} from "./types/public/anchor-types"; -import { - buildOpenPositionIx, - buildOpenPositionWithMetadataIx, -} from "./instructions/open-position-ix"; -import { buildInitTickArrayIx } from "./instructions/initialize-tick-array-ix"; -import { buildIncreaseLiquidityIx } from "./instructions/increase-liquidity-ix"; -import { buildCollectFeesIx } from "./instructions/collect-fees-ix"; -import { buildCollectRewardIx } from "./instructions/collect-reward-ix"; -import { buildSwapIx } from "./instructions/swap-ix"; -import { buildInitializeRewardIx } from "./instructions/initialize-reward-ix"; -import { buildSetRewardEmissionsSuperAuthorityIx } from "./instructions/set-reward-emissions-super-authority-ix"; -import { buildSetRewardAuthorityIx } from "./instructions/set-reward-authority-ix"; -import { buildSetRewardEmissionsIx } from "./instructions/set-reward-emissions-ix"; -import { buildClosePositionIx } from "./instructions/close-position-ix"; -import { buildSetRewardAuthorityBySuperAuthorityIx } from "./instructions/set-reward-authority-by-super-authority-ix"; -import { buildSetFeeAuthorityIx } from "./instructions/set-fee-authority-ix"; -import { buildSetCollectProtocolFeesAuthorityIx } from "./instructions/set-collect-protocol-fees-authority-ix"; -import { buildUpdateFeesAndRewardsIx } from "./instructions/update-fees-and-rewards-ix"; -import { buildCollectProtocolFeesIx } from "./instructions/collect-protocol-fees-ix"; -import { buildDecreaseLiquidityIx } from "./instructions/decrease-liquidity-ix"; -import { buildSetFeeRateIx } from "./instructions/set-fee-rate-ix"; -import { buildSetDefaultProtocolFeeRateIx } from "./instructions/set-default-protocol-fee-rate-ix"; -import { buildSetDefaultFeeRateIx } from "./instructions/set-default-fee-rate-ix"; -import { buildSetProtocolFeeRateIx } from "./instructions/set-protocol-fee-rate-ix"; -import { buildInitializeFeeTier } from "./instructions/initialize-fee-tier"; -import { Decimal } from "decimal.js"; - -// Global rules for Decimals -// - 40 digits of precision for the largest number -// - 20 digits of precision for the smallest number -// - Always round towards 0 to mirror smart contract rules -Decimal.set({ precision: 40, toExpPos: 40, toExpNeg: -20, rounding: 1 }); - -/** - * WhirlpoolClient provides a portal to perform admin-type tasks on the Whirlpool protocol. - */ -export class WhirlpoolClient { - readonly context: WhirlpoolContext; - - public constructor(context: WhirlpoolContext) { - this.context = context; - } - - /** - * Construct a TransactionBuilder to initialize a WhirlpoolConfig account with the provided parameters. - * @param params Parameters to configure the initialized WhirlpoolConfig account - * @returns A TransactionBuilder to initialize a WhirlpoolConfig account with the provided parameters. - */ - public initConfigTx(params: InitConfigParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildInitializeConfigIx(this.context, params) - ); - } - - /** - * Fetches and parses a WhirlpoolConfig account. - * @param poolPubKey A public key of a WhirlpoolConfig account - * @returns A WhirlpoolConfig type containing the parameters stored on the account - */ - public async getConfig(configPubKey: PublicKey): Promise { - const program = this.context.program; - const account = await program.account.whirlpoolsConfig.fetch(configPubKey); - // TODO: If we feel nice we can build a builder or something instead of casting - return account as WhirlpoolConfigAccount; - } - - /** - * Parses a WhirlpoolConfig account. - * @param data A buffer containing data fetched from an account - * @returns A WhirlpoolConfig type containing the parameters stored on the account - */ - public parseConfig(data: Buffer): WhirlpoolConfigAccount | null { - return parseWhirlpoolsConfig(data); - } - - /** - * Construct a TransactionBuilder to initialize a FeeTier account with the provided parameters. - * @param params Parameters to configure the initialized FeeTier account - * @returns A TransactionBuilder to initialize a FeeTier account with the provided parameters. - */ - public initFeeTierTx(params: InitFeeTierParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildInitializeFeeTier(this.context, params) - ); - } - - /** - * Fetches and parses a FeeTier account. - * @param feeTierKey A public key of a FeeTier account - * @returns A FeeTier type containing the parameters stored on the account - */ - public async getFeeTier(feeTierKey: PublicKey): Promise { - const program = this.context.program; - - const feeTierAccount = await program.account.feeTier.fetch(feeTierKey); - return feeTierAccount as unknown as FeeTierData; - } - - /** - * Construct a TransactionBuilder to initialize a Whirlpool account with the provided parameters. - * @param params Parameters to configure the initialized Whirlpool account - * @returns A TransactionBuilder to initialize a Whirlpool account with the provided parameters. - */ - public initPoolTx(params: InitPoolParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildInitPoolIx(this.context, params) - ); - } - - /** - * Fetches and parses a Whirlpool account. - * @param poolPubKey A public key of a Whirlpool account - * @returns A Whirlpool type containing the parameters stored on the account - */ - public async getPool(poolKey: PublicKey): Promise { - const program = this.context.program; - - const whirlpoolAccount = await program.account.whirlpool.fetch(poolKey); - return whirlpoolAccount as unknown as WhirlpoolData; - } - - /** - * Parses a Whirlpool account. - * @param data A buffer containing data fetched from an account - * @returns A Whirlpool type containing the parameters stored on the account - */ - public parsePool(data: Buffer): WhirlpoolData | null { - return parseWhirlpool(data); - } - - /** - * Construct a TransactionBuilder to open a Position account. - * @param params Parameters to configure the initialized Position account. - * @returns A TransactionBuilder to initialize a Position account with the provided parameters. - */ - public openPositionTx(params: OpenPositionParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildOpenPositionIx(this.context, params) - ); - } - - /** - * Construct a TransactionBuilder to open a Position account with metadata. - * @param params Parameters to configure the initialized Position account. - * @returns A TransactionBuilder to initialize a Position account with the provided parameters. - */ - public openPositionWithMetadataTx(params: Required): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildOpenPositionWithMetadataIx(this.context, params) - ); - } - - public closePositionTx(params: ClosePositionParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildClosePositionIx(this.context, params) - ); - } - - /** - * Fetches a Position account. - * @param positionKey The public key of the Position account - * @returns A Position type containing the parameters stored on the account - */ - public async getPosition(positionKey: PublicKey): Promise { - const positionAccount = await this.context.program.account.position.fetch(positionKey); - return positionAccount as unknown as PositionData; - } - - /** - * Parses a Position account. - * @param data A buffer containing data fetched from an account - * @returns A Position type containing the parameters stored on the account - */ - public parsePosition(data: Buffer): PositionData | null { - return parsePosition(data); - } - - /* - * Construct a TransactionBuilder to initialize a TickArray account with the provided parameters. - * @param params Parameters to configure the initialized TickArray account - * @returns A TransactionBuilder to initialize a TickArray account with the provided parameters. - */ - public initTickArrayTx(params: InitTickArrayParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildInitTickArrayIx(this.context, params) - ); - } - - /** - * Fetches and parses a TickArray account. Account is used to store Ticks for a Whirlpool. - * @param arrayPubKey A public key of a TickArray account - * @returns A TickArrayData type containing the parameters stored on the account - */ - public async getTickArray(arrayPubKey: PublicKey): Promise { - const program = this.context.program; - const tickArrayAccount = await program.account.tickArray.fetch(arrayPubKey); - return tickArrayAccount as unknown as TickArrayData; - } - - /** - * Parses a TickArray account. - * @param data A buffer containing data fetched from an account - * @returns A Position type containing the parameters stored on the account - */ - public parseTickArray(data: Buffer): TickArrayData | null { - return parseTickArray(data); - } - - public initializeRewardTx(params: InitializeRewardParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildInitializeRewardIx(this.context, params) - ); - } - - public setRewardEmissionsTx(params: SetRewardEmissionsParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetRewardEmissionsIx(this.context, params) - ); - } - - /** - * Construct a TransactionBuilder to increase the liquidity of a Position. - * @param params Parameters to configure the increase liquidity instruction - * @returns A TransactionBuilder containing one increase liquidity instruction - */ - public increaseLiquidityTx(params: IncreaseLiquidityParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildIncreaseLiquidityIx(this.context, params) - ); - } - - public decreaseLiquidityTx(params: DecreaseLiquidityParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildDecreaseLiquidityIx(this.context, params) - ); - } - - public updateFeesAndRewards(params: UpdateFeesAndRewardsParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildUpdateFeesAndRewardsIx(this.context, params) - ); - } - - /** - * Construct a TransactionBuilder to collect the fees for a Position. - * @param params Parameters to configure the collect fees instruction - * @returns A TransactionBuilder containing one collect fees instruction - */ - public collectFeesTx(params: CollectFeesParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildCollectFeesIx(this.context, params) - ); - } - - /** - * Construct a TransactionBuilder to collect a reward at the specified index for a Position. - * @param params Parameters to configure the collect reward instruction - * @returns A TransactionBuilder containing one collect reward instruction - */ - public collectRewardTx(params: CollectRewardParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildCollectRewardIx(this.context, params) - ); - } - - public collectProtocolFeesTx(params: CollectProtocolFeesParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildCollectProtocolFeesIx(this.context, params) - ); - } - - public swapTx(params: SwapParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSwapIx(this.context, params) - ); - } - - public setRewardEmissionsSuperAuthorityTx( - params: SetRewardEmissionsSuperAuthorityParams - ): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetRewardEmissionsSuperAuthorityIx(this.context, params) - ); - } - - public setRewardAuthorityTx(params: SetRewardAuthorityParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetRewardAuthorityIx(this.context, params) - ); - } - - public setRewardAuthorityBySuperAuthorityTx( - params: SetRewardAuthorityBySuperAuthorityParams - ): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetRewardAuthorityBySuperAuthorityIx(this.context, params) - ); - } - - public setFeeAuthorityTx(params: SetFeeAuthorityParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetFeeAuthorityIx(this.context, params) - ); - } - - public setCollectProtocolFeesAuthorityTx( - params: SetCollectProtocolFeesAuthorityParams - ): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetCollectProtocolFeesAuthorityIx(this.context, params) - ); - } - - public setFeeRateIx(params: SetFeeRateParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetFeeRateIx(this.context, params) - ); - } - - public setProtocolFeeRateIx(params: SetProtocolFeeRateParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetProtocolFeeRateIx(this.context, params) - ); - } - - public setDefaultFeeRateIx(params: SetDefaultFeeRateParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetDefaultFeeRateIx(this.context, params) - ); - } - - public setDefaultProtocolFeeRateIx(params: SetDefaultProtocolFeeRateParams): TransactionBuilder { - return new TransactionBuilder(this.context.provider).addInstruction( - buildSetDefaultProtocolFeeRateIx(this.context, params) - ); - } -} diff --git a/sdk/src/context.ts b/sdk/src/context.ts index b09c6bd..b65ae81 100644 --- a/sdk/src/context.ts +++ b/sdk/src/context.ts @@ -4,6 +4,9 @@ import WhirlpoolIDL from "./artifacts/whirlpool.json"; import { Whirlpool } from "./artifacts/whirlpool"; import { Wallet } from "@project-serum/anchor/dist/cjs/provider"; +/** + * @category Core + */ export class WhirlpoolContext { readonly connection: Connection; readonly wallet: Wallet; diff --git a/sdk/src/impl/position-impl.ts b/sdk/src/impl/position-impl.ts new file mode 100644 index 0000000..d340312 --- /dev/null +++ b/sdk/src/impl/position-impl.ts @@ -0,0 +1,129 @@ +import { AddressUtil, deriveATA, TransactionBuilder } from "@orca-so/common-sdk"; +import { Address } from "@project-serum/anchor"; +import { WhirlpoolContext } from "../context"; +import { + IncreaseLiquidityInput, + DecreaseLiquidityInput, + increaseLiquidityIx, + decreaseLiquidityIx, +} from "../instructions"; +import { PositionData } from "../types/public"; +import { Position } from "../whirlpool-client"; +import { PublicKey, Signer } from "@solana/web3.js"; +import { AccountFetcher } from "../network/public"; +import { PDAUtil, TickUtil } from "../utils/public"; +import { toTx } from "../utils/instructions-util"; + +export class PositionImpl implements Position { + private data: PositionData; + constructor( + readonly ctx: WhirlpoolContext, + readonly fetcher: AccountFetcher, + readonly address: PublicKey, + data: PositionData + ) { + this.data = data; + } + + getData(): PositionData { + return this.data; + } + + async refreshData() { + await this.refresh(); + return this.data; + } + + async increaseLiquidity( + liquidityInput: IncreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address + ) { + const sourceWalletKey = sourceWallet + ? AddressUtil.toPubKey(sourceWallet) + : this.ctx.wallet.publicKey; + const positionWalletKey = positionWallet + ? AddressUtil.toPubKey(positionWallet) + : this.ctx.wallet.publicKey; + + const whirlpool = await this.fetcher.getPool(this.data.whirlpool, true); + if (!whirlpool) { + throw new Error("Unable to fetch whirlpool for this position."); + } + + return toTx( + this.ctx, + increaseLiquidityIx(this.ctx.program, { + ...liquidityInput, + whirlpool: this.data.whirlpool, + position: this.address, + positionTokenAccount: await deriveATA(positionWalletKey, this.data.positionMint), + tokenOwnerAccountA: await deriveATA(sourceWalletKey, whirlpool.tokenMintA), + tokenOwnerAccountB: await deriveATA(sourceWalletKey, whirlpool.tokenMintB), + tokenVaultA: whirlpool.tokenVaultA, + tokenVaultB: whirlpool.tokenVaultB, + tickArrayLower: PDAUtil.getTickArray( + this.ctx.program.programId, + this.data.whirlpool, + TickUtil.getStartTickIndex(this.data.tickLowerIndex, whirlpool.tickSpacing) + ).publicKey, + tickArrayUpper: PDAUtil.getTickArray( + this.ctx.program.programId, + this.data.whirlpool, + TickUtil.getStartTickIndex(this.data.tickUpperIndex, whirlpool.tickSpacing) + ).publicKey, + positionAuthority: positionWalletKey, + }) + ); + } + + async decreaseLiquidity( + liquidityInput: DecreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address + ) { + const sourceWalletKey = sourceWallet + ? AddressUtil.toPubKey(sourceWallet) + : this.ctx.wallet.publicKey; + const positionWalletKey = positionWallet + ? AddressUtil.toPubKey(positionWallet) + : this.ctx.wallet.publicKey; + const whirlpool = await this.fetcher.getPool(this.data.whirlpool, true); + + if (!whirlpool) { + throw new Error("Unable to fetch whirlpool for this position."); + } + + return toTx( + this.ctx, + decreaseLiquidityIx(this.ctx.program, { + ...liquidityInput, + whirlpool: this.data.whirlpool, + position: this.address, + positionTokenAccount: await deriveATA(positionWalletKey, this.data.positionMint), + tokenOwnerAccountA: await deriveATA(sourceWalletKey, whirlpool.tokenMintA), + tokenOwnerAccountB: await deriveATA(sourceWalletKey, whirlpool.tokenMintB), + tokenVaultA: whirlpool.tokenVaultA, + tokenVaultB: whirlpool.tokenVaultB, + tickArrayLower: PDAUtil.getTickArray( + this.ctx.program.programId, + this.data.whirlpool, + TickUtil.getStartTickIndex(this.data.tickLowerIndex, whirlpool.tickSpacing) + ).publicKey, + tickArrayUpper: PDAUtil.getTickArray( + this.ctx.program.programId, + this.data.whirlpool, + TickUtil.getStartTickIndex(this.data.tickUpperIndex, whirlpool.tickSpacing) + ).publicKey, + positionAuthority: positionWalletKey, + }) + ); + } + + private async refresh() { + const account = await this.fetcher.getPosition(this.address, true); + if (!!account) { + this.data = account; + } + } +} diff --git a/sdk/src/impl/whirlpool-client-impl.ts b/sdk/src/impl/whirlpool-client-impl.ts new file mode 100644 index 0000000..fb6ed53 --- /dev/null +++ b/sdk/src/impl/whirlpool-client-impl.ts @@ -0,0 +1,53 @@ +import { AddressUtil } from "@orca-so/common-sdk"; +import { Address } from "@project-serum/anchor"; +import { WhirlpoolContext } from "../context"; +import { AccountFetcher } from "../network/public"; +import { WhirlpoolData, TokenInfo } from "../types/public"; +import { WhirlpoolClient, Whirlpool, Position } from "../whirlpool-client"; +import { PositionImpl } from "./position-impl"; +import { WhirlpoolImpl } from "./whirlpool-impl"; + +export class WhirlpoolClientImpl implements WhirlpoolClient { + constructor(readonly ctx: WhirlpoolContext, readonly fetcher: AccountFetcher) {} + + public async getPool(poolAddress: Address): Promise { + const account = await this.fetcher.getPool(poolAddress, true); + if (!account) { + throw new Error(`Unable to fetch Whirlpool at address at ${poolAddress}`); + } + const tokenInfos = await getTokenInfos(this.fetcher, account); + return new WhirlpoolImpl( + this.ctx, + this.fetcher, + AddressUtil.toPubKey(poolAddress), + tokenInfos[0], + tokenInfos[1], + account + ); + } + + public async getPosition(positionAddress: Address): Promise { + const account = await this.fetcher.getPosition(positionAddress, true); + if (!account) { + throw new Error(`Unable to fetch Position at address at ${positionAddress}`); + } + return new PositionImpl(this.ctx, this.fetcher, AddressUtil.toPubKey(positionAddress), account); + } +} + +async function getTokenInfos(fetcher: AccountFetcher, data: WhirlpoolData): Promise { + const mintA = data.tokenMintA; + const infoA = await fetcher.getMintInfo(mintA); + if (!infoA) { + throw new Error(`Unable to fetch MintInfo for mint - ${mintA}`); + } + const mintB = data.tokenMintB; + const infoB = await fetcher.getMintInfo(mintA); + if (!infoB) { + throw new Error(`Unable to fetch MintInfo for mint - ${mintB}`); + } + return [ + { mint: mintA, ...infoA }, + { mint: mintB, ...infoB }, + ]; +} diff --git a/sdk/src/impl/whirlpool-impl.ts b/sdk/src/impl/whirlpool-impl.ts new file mode 100644 index 0000000..1d5f8e4 --- /dev/null +++ b/sdk/src/impl/whirlpool-impl.ts @@ -0,0 +1,517 @@ +import { + AddressUtil, + deriveATA, + Percentage, + resolveOrCreateATA, + TransactionBuilder, + ZERO, +} from "@orca-so/common-sdk"; +import { Address, BN, translateAddress } from "@project-serum/anchor"; +import { WhirlpoolContext } from "../context"; +import { + IncreaseLiquidityInput, + openPositionIx, + openPositionWithMetadataIx, + initTickArrayIx, + increaseLiquidityIx, + decreaseLiquidityIx, + closePositionIx, + swapIx, +} from "../instructions"; +import { MAX_SQRT_PRICE, MIN_SQRT_PRICE, TokenInfo, WhirlpoolData } from "../types/public"; +import { Whirlpool } from "../whirlpool-client"; +import { PublicKey, Keypair } from "@solana/web3.js"; +import { u64 } from "@solana/spl-token"; +import { AccountFetcher } from "../network/public"; +import invariant from "tiny-invariant"; +import { PDAUtil, PriceMath, TickUtil } from "../utils/public"; +import { decreaseLiquidityQuoteByLiquidityWithParams, SwapQuote } from "../quotes/public"; + +export class WhirlpoolImpl implements Whirlpool { + private data: WhirlpoolData; + constructor( + readonly ctx: WhirlpoolContext, + readonly fetcher: AccountFetcher, + readonly address: PublicKey, + readonly tokenAInfo: TokenInfo, + readonly tokenBInfo: TokenInfo, + data: WhirlpoolData + ) { + this.data = data; + } + + getData(): WhirlpoolData { + return this.data; + } + + getTokenAInfo(): TokenInfo { + return this.tokenAInfo; + } + + getTokenBInfo(): TokenInfo { + return this.tokenBInfo; + } + + async refreshData() { + await this.refresh(); + return this.data; + } + + async openPosition( + tickLower: number, + tickUpper: number, + liquidityInput: IncreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address, + funder?: Address + ) { + await this.refresh(); + return this.getOpenPositionWithOptMetadataTx( + tickLower, + tickUpper, + liquidityInput, + !!sourceWallet ? AddressUtil.toPubKey(sourceWallet) : this.ctx.wallet.publicKey, + !!positionWallet ? AddressUtil.toPubKey(positionWallet) : this.ctx.wallet.publicKey, + !!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey + ); + } + + async openPositionWithMetadata( + tickLower: number, + tickUpper: number, + liquidityInput: IncreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address, + funder?: Address + ) { + await this.refresh(); + return this.getOpenPositionWithOptMetadataTx( + tickLower, + tickUpper, + liquidityInput, + !!sourceWallet ? AddressUtil.toPubKey(sourceWallet) : this.ctx.wallet.publicKey, + !!positionWallet ? AddressUtil.toPubKey(positionWallet) : this.ctx.wallet.publicKey, + !!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey, + true + ); + } + + initTickArrayForTicks(ticks: number[], funder?: Address) { + const startTicks = ticks.map((tick) => TickUtil.getStartTickIndex(tick, this.data.tickSpacing)); + const tx = new TransactionBuilder(this.ctx.provider); + const initializedArrayTicks: number[] = []; + + startTicks.forEach((startTick) => { + if (initializedArrayTicks.includes(startTick)) { + return; + } + initializedArrayTicks.push(startTick); + + const tickArrayPda = PDAUtil.getTickArray( + this.ctx.program.programId, + this.address, + startTick + ); + + tx.addInstruction( + initTickArrayIx(this.ctx.program, { + startTick, + tickArrayPda, + whirlpool: this.address, + funder: !!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey, + }) + ); + }); + return tx; + } + + async closePosition( + positionAddress: Address, + slippageTolerance: Percentage, + destinationWallet?: Address, + positionWallet?: Address + ) { + await this.refresh(); + const positionWalletKey = positionWallet + ? AddressUtil.toPubKey(positionWallet) + : this.ctx.wallet.publicKey; + const destinationWalletKey = destinationWallet + ? AddressUtil.toPubKey(destinationWallet) + : this.ctx.wallet.publicKey; + return this.getClosePositionIx( + AddressUtil.toPubKey(positionAddress), + slippageTolerance, + destinationWalletKey, + positionWalletKey + ); + } + + async swap(quote: SwapQuote, sourceWallet?: Address) { + const sourceWalletKey = sourceWallet + ? AddressUtil.toPubKey(sourceWallet) + : this.ctx.wallet.publicKey; + return this.getSwapTx(quote, sourceWalletKey); + } + + /** + * Construct a transaction for opening an new position with optional metadata + */ + async getOpenPositionWithOptMetadataTx( + tickLower: number, + tickUpper: number, + liquidityInput: IncreaseLiquidityInput, + sourceWallet: PublicKey, + positionWallet: PublicKey, + funder: PublicKey, + withMetadata: boolean = false + ): Promise<{ positionMint: PublicKey; tx: TransactionBuilder }> { + invariant(TickUtil.checkTickInBounds(tickLower), "tickLower is out of bounds."); + invariant(TickUtil.checkTickInBounds(tickUpper), "tickUpper is out of bounds."); + + const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput; + + invariant(liquidity.gt(new u64(0)), "liquidity must be greater than zero"); + + const whirlpool = await this.fetcher.getPool(this.address, false); + if (!whirlpool) { + throw new Error(`Whirlpool not found: ${translateAddress(this.address).toBase58()}`); + } + + invariant( + TickUtil.isTickInitializable(tickLower, whirlpool.tickSpacing), + `lower tick ${tickLower} is not an initializable tick for tick-spacing ${whirlpool.tickSpacing}` + ); + invariant( + TickUtil.isTickInitializable(tickUpper, whirlpool.tickSpacing), + `upper tick ${tickUpper} is not an initializable tick for tick-spacing ${whirlpool.tickSpacing}` + ); + + const positionMintKeypair = Keypair.generate(); + const positionPda = PDAUtil.getPosition( + this.ctx.program.programId, + positionMintKeypair.publicKey + ); + const metadataPda = PDAUtil.getPositionMetadata(positionMintKeypair.publicKey); + const positionTokenAccountAddress = await deriveATA( + positionWallet, + positionMintKeypair.publicKey + ); + + const txBuilder = new TransactionBuilder(this.ctx.provider); + + const positionIx = (withMetadata ? openPositionWithMetadataIx : openPositionIx)( + this.ctx.program, + { + funder, + owner: positionWallet, + positionPda, + metadataPda, + positionMintAddress: positionMintKeypair.publicKey, + positionTokenAccount: positionTokenAccountAddress, + whirlpool: this.address, + tickLowerIndex: tickLower, + tickUpperIndex: tickUpper, + } + ); + txBuilder.addInstruction(positionIx).addSigner(positionMintKeypair); + + const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA( + this.ctx.connection, + sourceWallet, + whirlpool.tokenMintA, + () => this.fetcher.getAccountRentExempt(), + tokenMaxA + ); + const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA( + this.ctx.connection, + sourceWallet, + whirlpool.tokenMintB, + () => this.fetcher.getAccountRentExempt(), + tokenMaxB + ); + txBuilder.addInstruction(tokenOwnerAccountAIx); + txBuilder.addInstruction(tokenOwnerAccountBIx); + + const tickArrayLowerPda = PDAUtil.getTickArrayFromTickIndex( + tickLower, + this.data.tickSpacing, + this.address, + this.ctx.program.programId + ); + const tickArrayUpperPda = PDAUtil.getTickArrayFromTickIndex( + tickUpper, + this.data.tickSpacing, + this.address, + this.ctx.program.programId + ); + + const [tickArrayLower, tickArrayUpper] = await this.fetcher.listTickArrays( + [tickArrayLowerPda.publicKey, tickArrayUpperPda.publicKey], + true + ); + + invariant(!!tickArrayLower, "tickArray for the tickLower has not been initialized"); + invariant(!!tickArrayUpper, "tickArray for the tickUpper has not been initialized"); + + const liquidityIx = increaseLiquidityIx(this.ctx.program, { + liquidityAmount: liquidity, + tokenMaxA, + tokenMaxB, + whirlpool: this.address, + positionAuthority: positionWallet, + position: positionPda.publicKey, + positionTokenAccount: positionTokenAccountAddress, + tokenOwnerAccountA, + tokenOwnerAccountB, + tokenVaultA: whirlpool.tokenVaultA, + tokenVaultB: whirlpool.tokenVaultB, + tickArrayLower: tickArrayLowerPda.publicKey, + tickArrayUpper: tickArrayUpperPda.publicKey, + }); + txBuilder.addInstruction(liquidityIx); + + return { + positionMint: positionMintKeypair.publicKey, + tx: txBuilder, + }; + } + + async getClosePositionIx( + positionAddress: PublicKey, + slippageTolerance: Percentage, + destinationWallet: PublicKey, + positionWallet: PublicKey + ): Promise { + const position = await this.fetcher.getPosition(positionAddress, true); + if (!position) { + throw new Error(`Position not found: ${positionAddress.toBase58()}`); + } + const whirlpool = this.data; + + invariant( + position.whirlpool.equals(this.address), + `Position ${positionAddress.toBase58()} is not a position for Whirlpool ${this.address.toBase58()}` + ); + + const tickArrayLower = PDAUtil.getTickArrayFromTickIndex( + position.tickLowerIndex, + whirlpool.tickSpacing, + position.whirlpool, + this.ctx.program.programId + ).publicKey; + const tickArrayUpper = PDAUtil.getTickArrayFromTickIndex( + position.tickUpperIndex, + whirlpool.tickSpacing, + position.whirlpool, + this.ctx.program.programId + ).publicKey; + + const positionTokenAccount = await deriveATA(positionWallet, position.positionMint); + + const txBuilder = new TransactionBuilder(this.ctx.provider); + + const resolvedAssociatedTokenAddresses: Record = {}; + const { address: tokenOwnerAccountA, ...createTokenOwnerAccountAIx } = await resolveOrCreateATA( + this.ctx.connection, + destinationWallet, + whirlpool.tokenMintA, + () => this.fetcher.getAccountRentExempt() + ); + const { address: tokenOwnerAccountB, ...createTokenOwnerAccountBIx } = await resolveOrCreateATA( + this.ctx.connection, + destinationWallet, + whirlpool.tokenMintB, + () => this.fetcher.getAccountRentExempt() + ); + txBuilder.addInstruction(createTokenOwnerAccountAIx).addInstruction(createTokenOwnerAccountBIx); + resolvedAssociatedTokenAddresses[whirlpool.tokenMintA.toBase58()] = tokenOwnerAccountA; + resolvedAssociatedTokenAddresses[whirlpool.tokenMintB.toBase58()] = tokenOwnerAccountB; + + // TODO: Collect all Fees and rewards for the position. + // TODO: Optimize - no need to call updateFee if we call decreaseLiquidity first. + // const collectTx = await buildCollectFeesAndRewardsTx(this.dal, { + // provider, + // positionAddress: positionAddress, + // resolvedAssociatedTokenAddresses, + // }); + // txBuilder.addInstruction(collectTx.compressIx(false)); + + /* Remove all liquidity remaining in the position */ + if (position.liquidity.gt(new u64(0))) { + const decreaseLiqQuote = decreaseLiquidityQuoteByLiquidityWithParams({ + liquidity: position.liquidity, + slippageTolerance, + sqrtPrice: whirlpool.sqrtPrice, + tickCurrentIndex: whirlpool.tickCurrentIndex, + tickLowerIndex: position.tickLowerIndex, + tickUpperIndex: position.tickUpperIndex, + }); + + const liquidityIx = decreaseLiquidityIx(this.ctx.program, { + liquidityAmount: decreaseLiqQuote.liquidityAmount, + tokenMinA: decreaseLiqQuote.tokenMinA, + tokenMinB: decreaseLiqQuote.tokenMinB, + whirlpool: position.whirlpool, + positionAuthority: positionWallet, + position: positionAddress, + positionTokenAccount, + tokenOwnerAccountA, + tokenOwnerAccountB, + tokenVaultA: whirlpool.tokenVaultA, + tokenVaultB: whirlpool.tokenVaultB, + tickArrayLower, + tickArrayUpper, + }); + txBuilder.addInstruction(liquidityIx); + } + + /* Close position */ + const positionIx = closePositionIx(this.ctx.program, { + positionAuthority: this.ctx.wallet.publicKey, + receiver: this.ctx.wallet.publicKey, + positionTokenAccount, + position: positionAddress, + positionMint: position.positionMint, + }); + txBuilder.addInstruction(positionIx); + + return txBuilder; + } + + private async getSwapTx(quote: SwapQuote, wallet: PublicKey): Promise { + const { + sqrtPriceLimit, + otherAmountThreshold, + estimatedAmountIn, + estimatedAmountOut, + aToB, + amountSpecifiedIsInput, + } = quote; + const whirlpool = this.data; + const txBuilder = new TransactionBuilder(this.ctx.provider); + + const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = await resolveOrCreateATA( + this.ctx.connection, + wallet, + whirlpool.tokenMintA, + () => this.fetcher.getAccountRentExempt(), + aToB ? estimatedAmountIn : ZERO + ); + txBuilder.addInstruction(tokenOwnerAccountAIx); + + const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = await resolveOrCreateATA( + this.ctx.connection, + wallet, + whirlpool.tokenMintB, + () => this.fetcher.getAccountRentExempt(), + !aToB ? estimatedAmountIn : ZERO + ); + txBuilder.addInstruction(tokenOwnerAccountBIx); + + const targetSqrtPriceLimitX64 = sqrtPriceLimit || this.getDefaultSqrtPriceLimit(aToB); + + const tickArrayAddresses = await this.getTickArrayPublicKeysForSwap( + whirlpool.tickCurrentIndex, + targetSqrtPriceLimitX64, + whirlpool.tickSpacing, + this.address, + this.ctx.program.programId, + aToB + ); + + const oraclePda = PDAUtil.getOracle(this.ctx.program.programId, this.address); + + txBuilder.addInstruction( + swapIx(this.ctx.program, { + amount: amountSpecifiedIsInput ? estimatedAmountIn : estimatedAmountOut, + otherAmountThreshold, + sqrtPriceLimit: targetSqrtPriceLimitX64, + amountSpecifiedIsInput, + aToB, + whirlpool: this.address, + tokenAuthority: wallet, + tokenOwnerAccountA, + tokenVaultA: whirlpool.tokenVaultA, + tokenOwnerAccountB, + tokenVaultB: whirlpool.tokenVaultB, + tickArray0: tickArrayAddresses[0], + tickArray1: tickArrayAddresses[1], + tickArray2: tickArrayAddresses[2], + oracle: oraclePda.publicKey, + }) + ); + + return txBuilder; + } + + private async getTickArrayPublicKeysForSwap( + tickCurrentIndex: number, + targetSqrtPriceX64: BN, + tickSpacing: number, + poolAddress: PublicKey, + programId: PublicKey, + aToB: boolean + ): Promise<[PublicKey, PublicKey, PublicKey]> { + // TODO: fix directionality + const nextInitializableTickIndex = ( + aToB ? TickUtil.getPrevInitializableTickIndex : TickUtil.getNextInitializableTickIndex + )(tickCurrentIndex, tickSpacing); + const targetTickIndex = PriceMath.sqrtPriceX64ToTickIndex(targetSqrtPriceX64); + + let currentStartTickIndex = TickUtil.getStartTickIndex(nextInitializableTickIndex, tickSpacing); + const targetStartTickIndex = TickUtil.getStartTickIndex(targetTickIndex, tickSpacing); + + const offset = nextInitializableTickIndex < targetTickIndex ? 1 : -1; + + let count = 1; + const tickArrayAddresses: [PublicKey, PublicKey, PublicKey] = [ + PDAUtil.getTickArray(programId, poolAddress, currentStartTickIndex).publicKey, + PublicKey.default, + PublicKey.default, + ]; + + while (currentStartTickIndex !== targetStartTickIndex && count < 3) { + const nextStartTickIndex = TickUtil.getStartTickIndex( + nextInitializableTickIndex, + tickSpacing, + offset * count + ); + const nextTickArrayAddress = PDAUtil.getTickArray( + programId, + poolAddress, + nextStartTickIndex + ).publicKey; + + const nextTickArray = await this.fetcher.getTickArray(nextTickArrayAddress, false); + if (!nextTickArray) { + break; + } + + tickArrayAddresses[count] = nextTickArrayAddress; + count++; + currentStartTickIndex = nextStartTickIndex; + } + + while (count < 3) { + tickArrayAddresses[count] = PDAUtil.getTickArray( + programId, + poolAddress, + currentStartTickIndex + ).publicKey; + count++; + } + + return tickArrayAddresses; + } + + private getDefaultSqrtPriceLimit(aToB: boolean): BN { + return new BN(aToB ? MIN_SQRT_PRICE : MAX_SQRT_PRICE); + } + + private async refresh() { + const account = await this.fetcher.getPool(this.address, true); + if (!!account) { + this.data = account; + } + } +} diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 7f10f76..9858470 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -1,7 +1,17 @@ -export * from "./client"; +import Decimal from "decimal.js"; + export * from "./context"; export * from "./types/public"; export * from "./utils/public"; +export * from "./network/public"; +export * from "./quotes/public"; +export * from "./ix"; +export * from "./whirlpool-client"; export * from "./types/public/anchor-types"; -export * from "./utils/transactions/transactions-builder"; + +// Global rules for Decimals +// - 40 digits of precision for the largest number +// - 20 digits of precision for the smallest number +// - Always round towards 0 to mirror smart contract rules +Decimal.set({ precision: 40, toExpPos: 40, toExpNeg: -20, rounding: 1 }); diff --git a/sdk/src/instructions/close-position-ix.ts b/sdk/src/instructions/close-position-ix.ts index 58a8345..a9caf06 100644 --- a/sdk/src/instructions/close-position-ix.ts +++ b/sdk/src/instructions/close-position-ix.ts @@ -1,15 +1,48 @@ import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { ClosePositionParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; -export function buildClosePositionIx( - context: WhirlpoolContext, +/** + * Parameters to close a position in a Whirlpool. + * + * @category Instruction Types + * @param receiver - PublicKey for the wallet that will receive the rented lamports. + * @param position - PublicKey for the position. + * @param positionMint - PublicKey for the mint token for the Position token. + * @param positionTokenAccount - The associated token address for the position token in the owners wallet. + * @param positionAuthority - Authority that owns the position token. + */ +export type ClosePositionParams = { + receiver: PublicKey; + position: PublicKey; + positionMint: PublicKey; + positionTokenAccount: PublicKey; + positionAuthority: PublicKey; +}; + +/** + * Close a position in a Whirlpool. Burns the position token in the owner's wallet. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - ClosePositionParams object + * @returns - Instruction to perform the action. + */ +export function closePositionIx( + program: Program, params: ClosePositionParams ): Instruction { - const { positionAuthority, receiver, position, positionMint, positionTokenAccount } = params; + const { + positionAuthority, + receiver: receiver, + position: position, + positionMint: positionMint, + positionTokenAccount, + } = params; - const ix = context.program.instruction.closePosition({ + const ix = program.instruction.closePosition({ accounts: { positionAuthority, receiver, diff --git a/sdk/src/instructions/collect-fees-ix.ts b/sdk/src/instructions/collect-fees-ix.ts index ab78334..d1937d2 100644 --- a/sdk/src/instructions/collect-fees-ix.ts +++ b/sdk/src/instructions/collect-fees-ix.ts @@ -1,12 +1,44 @@ -import { WhirlpoolContext } from "../context"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { CollectFeesParams } from ".."; +import { PublicKey } from "@solana/web3.js"; -export function buildCollectFeesIx( - context: WhirlpoolContext, - params: CollectFeesParams -): Instruction { +import { Instruction } from "@orca-so/common-sdk"; + +/** + * Parameters to collect fees from a position. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param position - PublicKey for the position will be opened for. + * @param positionTokenAccount - PublicKey for the position token's associated token address. + * @param tokenOwnerAccountA - PublicKey for the token A account that will be withdrawed from. + * @param tokenOwnerAccountB - PublicKey for the token B account that will be withdrawed from. + * @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool. + * @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool. + * @param positionAuthority - authority that owns the token corresponding to this desired position. + */ +export type CollectFeesParams = { + whirlpool: PublicKey; + position: PublicKey; + positionTokenAccount: PublicKey; + tokenOwnerAccountA: PublicKey; + tokenOwnerAccountB: PublicKey; + tokenVaultA: PublicKey; + tokenVaultB: PublicKey; + positionAuthority: PublicKey; +}; + +/** + * Collect fees accrued for this position. + * Call updateFeesAndRewards before this to update the position to the newest accrued values. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - CollectFeesParams object + * @returns - Instruction to perform the action. + */ +export function collectFeesIx(program: Program, params: CollectFeesParams): Instruction { const { whirlpool, positionAuthority, @@ -18,7 +50,7 @@ export function buildCollectFeesIx( tokenVaultB, } = params; - const ix = context.program.instruction.collectFees({ + const ix = program.instruction.collectFees({ accounts: { whirlpool, positionAuthority, diff --git a/sdk/src/instructions/collect-protocol-fees-ix.ts b/sdk/src/instructions/collect-protocol-fees-ix.ts index ff14d21..eca51fc 100644 --- a/sdk/src/instructions/collect-protocol-fees-ix.ts +++ b/sdk/src/instructions/collect-protocol-fees-ix.ts @@ -1,10 +1,41 @@ -import { WhirlpoolContext } from "../context"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { CollectProtocolFeesParams } from ".."; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildCollectProtocolFeesIx( - context: WhirlpoolContext, +/** + * Parameters to collect protocol fees for a Whirlpool + * + * @category Instruction Types + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool. + * @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool. + * @param tokenOwnerAccountA - PublicKey for the associated token account for tokenA in the collection wallet + * @param tokenOwnerAccountB - PublicKey for the associated token account for tokenA in the collection wallet + * @param collectProtocolFeesAuthority - assigned authority in the WhirlpoolsConfig that can collect protocol fees + */ +export type CollectProtocolFeesParams = { + whirlpoolsConfig: PublicKey; + whirlpool: PublicKey; + tokenVaultA: PublicKey; + tokenVaultB: PublicKey; + tokenOwnerAccountA: PublicKey; + tokenOwnerAccountB: PublicKey; + collectProtocolFeesAuthority: PublicKey; +}; + +/** + * Collect protocol fees accrued in this Whirlpool. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - CollectProtocolFeesParams object + * @returns - Instruction to perform the action. + */ +export function collectProtocolFeesIx( + program: Program, params: CollectProtocolFeesParams ): Instruction { const { @@ -13,11 +44,11 @@ export function buildCollectProtocolFeesIx( collectProtocolFeesAuthority, tokenVaultA, tokenVaultB, - tokenDestinationA, - tokenDestinationB, + tokenOwnerAccountA: tokenDestinationA, + tokenOwnerAccountB: tokenDestinationB, } = params; - const ix = context.program.instruction.collectProtocolFees({ + const ix = program.instruction.collectProtocolFees({ accounts: { whirlpoolsConfig, whirlpool, diff --git a/sdk/src/instructions/collect-reward-ix.ts b/sdk/src/instructions/collect-reward-ix.ts index 9e279c4..67d0078 100644 --- a/sdk/src/instructions/collect-reward-ix.ts +++ b/sdk/src/instructions/collect-reward-ix.ts @@ -1,10 +1,42 @@ -import { WhirlpoolContext } from "../context"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { CollectRewardParams } from ".."; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildCollectRewardIx( - context: WhirlpoolContext, +/** + * Parameters to collect rewards from a reward index in a position. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param position - PublicKey for the position will be opened for. + * @param positionTokenAccount - PublicKey for the position token's associated token address. + * @param rewardIndex - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS). + * @param rewardOwnerAccount - PublicKey for the reward token account that the reward will deposit into. + * @param rewardVault - PublicKey of the vault account that reward will be withdrawn from. + * @param positionAuthority - authority that owns the token corresponding to this desired position. + */ +export type CollectRewardParams = { + whirlpool: PublicKey; + position: PublicKey; + positionTokenAccount: PublicKey; + rewardIndex: number; + rewardOwnerAccount: PublicKey; + rewardVault: PublicKey; + positionAuthority: PublicKey; +}; + +/** + * Collect rewards accrued for this reward index in a position. + * Call updateFeesAndRewards before this to update the position to the newest accrued values. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - CollectRewardParams object + * @returns - Instruction to perform the action. + */ +export function collectRewardIx( + program: Program, params: CollectRewardParams ): Instruction { const { @@ -17,7 +49,7 @@ export function buildCollectRewardIx( rewardIndex, } = params; - const ix = context.program.instruction.collectReward(rewardIndex, { + const ix = program.instruction.collectReward(rewardIndex, { accounts: { whirlpool, positionAuthority, diff --git a/sdk/src/instructions/decrease-liquidity-ix.ts b/sdk/src/instructions/decrease-liquidity-ix.ts index bb87756..3883331 100644 --- a/sdk/src/instructions/decrease-liquidity-ix.ts +++ b/sdk/src/instructions/decrease-liquidity-ix.ts @@ -1,10 +1,65 @@ -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { DecreaseLiquidityParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; +import { BN } from "@project-serum/anchor"; -export function buildDecreaseLiquidityIx( - context: WhirlpoolContext, +/** + * Parameters to remove liquidity from a position. + * + * @category Instruction Types + * @param liquidityAmount - The total amount of Liquidity the user is withdrawing + * @param tokenMinA - The minimum amount of token A to remove from the position. + * @param tokenMinB - The minimum amount of token B to remove from the position. + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param position - PublicKey for the position will be opened for. + * @param positionTokenAccount - PublicKey for the position token's associated token address. + * @param tokenOwnerAccountA - PublicKey for the token A account that will be withdrawed from. + * @param tokenOwnerAccountB - PublicKey for the token B account that will be withdrawed from. + * @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool. + * @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool. + * @param tickArrayLower - PublicKey for the tick-array account that hosts the tick at the lower tick index. + * @param tickArrayUpper - PublicKey for the tick-array account that hosts the tick at the upper tick index. + * @param positionAuthority - authority that owns the token corresponding to this desired position. + */ +export type DecreaseLiquidityParams = { + whirlpool: PublicKey; + position: PublicKey; + positionTokenAccount: PublicKey; + tokenOwnerAccountA: PublicKey; + tokenOwnerAccountB: PublicKey; + tokenVaultA: PublicKey; + tokenVaultB: PublicKey; + tickArrayLower: PublicKey; + tickArrayUpper: PublicKey; + positionAuthority: PublicKey; +} & DecreaseLiquidityInput; + +/** + * @category Instruction Types + */ +export type DecreaseLiquidityInput = { + tokenMinA: BN; + tokenMinB: BN; + liquidityAmount: BN; +}; + +/** + * Remove liquidity to a position in the Whirlpool. + * + * #### Special Errors + * - `LiquidityZero` - Provided liquidity amount is zero. + * - `LiquidityTooHigh` - Provided liquidity exceeds u128::max. + * - `TokenMinSubceeded` - The required token to perform this operation subceeds the user defined amount. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - DecreaseLiquidityParams object + * @returns - Instruction to perform the action. + */ +export function decreaseLiquidityIx( + program: Program, params: DecreaseLiquidityParams ): Instruction { const { @@ -23,7 +78,7 @@ export function buildDecreaseLiquidityIx( tickArrayUpper, } = params; - const ix = context.program.instruction.decreaseLiquidity(liquidityAmount, tokenMinA, tokenMinB, { + const ix = program.instruction.decreaseLiquidity(liquidityAmount, tokenMinA, tokenMinB, { accounts: { whirlpool, tokenProgram: TOKEN_PROGRAM_ID, diff --git a/sdk/src/instructions/increase-liquidity-ix.ts b/sdk/src/instructions/increase-liquidity-ix.ts index 2415402..f5aafd0 100644 --- a/sdk/src/instructions/increase-liquidity-ix.ts +++ b/sdk/src/instructions/increase-liquidity-ix.ts @@ -1,10 +1,73 @@ -import { WhirlpoolContext } from "../context"; -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { IncreaseLiquidityParams } from ".."; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; -export function buildIncreaseLiquidityIx( - context: WhirlpoolContext, +import { Instruction } from "@orca-so/common-sdk"; + +/** + * Parameters to increase liquidity for a position. + * + * @category Instruction Types + * @param liquidityAmount - The total amount of Liquidity the user is willing to deposit. + * @param tokenMaxA - The maximum amount of token A to add to the position. + * @param tokenMaxB - The maximum amount of token B to add to the position. + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param position - PublicKey for the position will be opened for. + * @param positionTokenAccount - PublicKey for the position token's associated token address. + * @param tokenOwnerAccountA - PublicKey for the token A account that will be withdrawed from. + * @param tokenOwnerAccountB - PublicKey for the token B account that will be withdrawed from. + * @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool. + * @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool. + * @param tickArrayLower - PublicKey for the tick-array account that hosts the tick at the lower tick index. + * @param tickArrayUpper - PublicKey for the tick-array account that hosts the tick at the upper tick index. + * @param positionAuthority - authority that owns the token corresponding to this desired position. + */ +export type IncreaseLiquidityParams = { + whirlpool: PublicKey; + position: PublicKey; + positionTokenAccount: PublicKey; + tokenOwnerAccountA: PublicKey; + tokenOwnerAccountB: PublicKey; + tokenVaultA: PublicKey; + tokenVaultB: PublicKey; + tickArrayLower: PublicKey; + tickArrayUpper: PublicKey; + positionAuthority: PublicKey; +} & IncreaseLiquidityInput; + +/** + * Input parameters to deposit liquidity into a position. + * + * This type is usually generated by a quote class to estimate the amount of tokens required to + * deposit a certain amount of liquidity into a position. + * + * @category Instruction Types + * @param tokenMaxA - the maximum amount of tokenA allowed to withdraw from the source wallet. + * @param tokenMaxB - the maximum amount of tokenB allowed to withdraw from the source wallet. + * @param liquidityAmount - the desired amount of liquidity to deposit into the position/ + */ +export type IncreaseLiquidityInput = { + tokenMaxA: u64; + tokenMaxB: u64; + liquidityAmount: u64; +}; + +/** + * Add liquidity to a position in the Whirlpool. + * + * #### Special Errors + * `LiquidityZero` - Provided liquidity amount is zero. + * `LiquidityTooHigh` - Provided liquidity exceeds u128::max. + * `TokenMaxExceeded` - The required token to perform this operation exceeds the user defined amount. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - IncreaseLiquidityParams object + * @returns - Instruction to perform the action. + */ +export function increaseLiquidityIx( + program: Program, params: IncreaseLiquidityParams ): Instruction { const { @@ -23,7 +86,7 @@ export function buildIncreaseLiquidityIx( tickArrayUpper, } = params; - const ix = context.program.instruction.increaseLiquidity(liquidityAmount, tokenMaxA, tokenMaxB, { + const ix = program.instruction.increaseLiquidity(liquidityAmount, tokenMaxA, tokenMaxB, { accounts: { whirlpool, tokenProgram: TOKEN_PROGRAM_ID, diff --git a/sdk/src/instructions/index.ts b/sdk/src/instructions/index.ts new file mode 100644 index 0000000..25f6baf --- /dev/null +++ b/sdk/src/instructions/index.ts @@ -0,0 +1,24 @@ +export * from "./close-position-ix"; +export * from "./collect-fees-ix"; +export * from "./collect-protocol-fees-ix"; +export * from "./collect-reward-ix"; +export * from "./decrease-liquidity-ix"; +export * from "./increase-liquidity-ix"; +export * from "./initialize-config-ix"; +export * from "./initialize-fee-tier-ix"; +export * from "./initialize-pool-ix"; +export * from "./initialize-reward-ix"; +export * from "./initialize-tick-array-ix"; +export * from "./open-position-ix"; +export * from "./set-collect-protocol-fees-authority-ix"; +export * from "./set-default-fee-rate-ix"; +export * from "./set-default-protocol-fee-rate-ix"; +export * from "./set-fee-authority-ix"; +export * from "./set-fee-rate-ix"; +export * from "./set-protocol-fee-rate-ix"; +export * from "./set-reward-authority-by-super-authority-ix"; +export * from "./set-reward-authority-ix"; +export * from "./set-reward-emissions-ix"; +export * from "./set-reward-emissions-super-authority-ix"; +export * from "./swap-ix"; +export * from "./update-fees-and-rewards-ix"; diff --git a/sdk/src/instructions/initialize-config-ix.ts b/sdk/src/instructions/initialize-config-ix.ts index 1f25f62..cab3834 100644 --- a/sdk/src/instructions/initialize-config-ix.ts +++ b/sdk/src/instructions/initialize-config-ix.ts @@ -1,10 +1,40 @@ -import { SystemProgram } from "@solana/web3.js"; -import { WhirlpoolContext } from "../context"; -import { InitConfigParams } from "../types/public/ix-types"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { SystemProgram, Keypair, PublicKey } from "@solana/web3.js"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; -export function buildInitializeConfigIx( - context: WhirlpoolContext, +import { Instruction } from "@orca-so/common-sdk"; + +/** + * Parameters to initialize a WhirlpoolsConfig account. + * + * @category Instruction Types + * @param whirlpoolsConfigKeypair - Generated keypair for the WhirlpoolsConfig. + * @param feeAuthority - Authority authorized to initialize fee-tiers and set customs fees. + * @param collect_protocol_fees_authority - Authority authorized to collect protocol fees. + * @param rewardEmissionsSuperAuthority - Authority authorized to set reward authorities in pools. + * @param defaultProtocolFeeRate - The default protocol fee rate. Stored as a basis point of the total fees collected by feeRate. + * @param funder - The account that would fund the creation of this account + */ +export type InitConfigParams = { + whirlpoolsConfigKeypair: Keypair; + feeAuthority: PublicKey; + collectProtocolFeesAuthority: PublicKey; + rewardEmissionsSuperAuthority: PublicKey; + defaultProtocolFeeRate: number; + funder: PublicKey; +}; + +/** + * Initializes a WhirlpoolsConfig account that hosts info & authorities + * required to govern a set of Whirlpools. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - InitConfigParams object + * @returns - Instruction to perform the action. + */ +export function initializeConfigIx( + program: Program, params: InitConfigParams ): Instruction { const { @@ -15,14 +45,14 @@ export function buildInitializeConfigIx( funder, } = params; - const ix = context.program.instruction.initializeConfig( + const ix = program.instruction.initializeConfig( feeAuthority, collectProtocolFeesAuthority, rewardEmissionsSuperAuthority, defaultProtocolFeeRate, { accounts: { - config: params.whirlpoolConfigKeypair.publicKey, + config: params.whirlpoolsConfigKeypair.publicKey, funder, systemProgram: SystemProgram.programId, }, @@ -32,6 +62,6 @@ export function buildInitializeConfigIx( return { instructions: [ix], cleanupInstructions: [], - signers: [params.whirlpoolConfigKeypair], + signers: [params.whirlpoolsConfigKeypair], }; } diff --git a/sdk/src/instructions/initialize-fee-tier-ix.ts b/sdk/src/instructions/initialize-fee-tier-ix.ts new file mode 100644 index 0000000..d5e060d --- /dev/null +++ b/sdk/src/instructions/initialize-fee-tier-ix.ts @@ -0,0 +1,61 @@ +import { SystemProgram, PublicKey } from "@solana/web3.js"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { PDA } from "@orca-so/common-sdk"; + +import { Instruction } from "@orca-so/common-sdk"; + +/** + * Parameters to initialize a FeeTier account. + * + * @category Instruction Types + * @param whirlpoolsConfig - PublicKey for the whirlpool config space that the fee-tier will be initialized for. + * @param feeTierPda - PDA for the fee-tier account that will be initialized + * @param tickSpacing - The tick spacing this fee tier recommends its default fee rate for. + * @param defaultFeeRate - The default fee rate for this fee-tier. Stored as a hundredths of a basis point. + * @param feeAuthority - Authority authorized to initialize fee-tiers and set customs fees. + * @param funder - The account that would fund the creation of this account + */ +export type InitFeeTierParams = { + whirlpoolsConfig: PublicKey; + feeTierPda: PDA; + tickSpacing: number; + defaultFeeRate: number; + feeAuthority: PublicKey; + funder: PublicKey; +}; + +/** + * Initializes a fee tier account usable by Whirlpools in this WhirlpoolsConfig space. + * + * Special Errors + * `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - InitFeeTierParams object + * @returns - Instruction to perform the action. + */ +export function initializeFeeTierIx( + program: Program, + params: InitFeeTierParams +): Instruction { + const { feeTierPda, whirlpoolsConfig, tickSpacing, feeAuthority, defaultFeeRate, funder } = + params; + + const ix = program.instruction.initializeFeeTier(tickSpacing, defaultFeeRate, { + accounts: { + config: whirlpoolsConfig, + feeTier: feeTierPda.publicKey, + feeAuthority, + funder, + systemProgram: SystemProgram.programId, + }, + }); + + return { + instructions: [ix], + cleanupInstructions: [], + signers: [], + }; +} diff --git a/sdk/src/instructions/initialize-fee-tier.ts b/sdk/src/instructions/initialize-fee-tier.ts deleted file mode 100644 index 7d9b06a..0000000 --- a/sdk/src/instructions/initialize-fee-tier.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { SystemProgram } from "@solana/web3.js"; -import { getFeeTierPda } from ".."; -import { WhirlpoolContext } from "../context"; -import { InitFeeTierParams } from "../types/public/ix-types"; -import { Instruction } from "../utils/transactions/transactions-builder"; - -export function buildInitializeFeeTier( - context: WhirlpoolContext, - params: InitFeeTierParams -): Instruction { - const { feeTierPda, whirlpoolConfigKey, tickSpacing, feeAuthority, defaultFeeRate, funder } = - params; - - const ix = context.program.instruction.initializeFeeTier(tickSpacing, defaultFeeRate, { - accounts: { - config: whirlpoolConfigKey, - feeTier: feeTierPda.publicKey, - feeAuthority, - funder, - systemProgram: SystemProgram.programId, - }, - }); - - return { - instructions: [ix], - cleanupInstructions: [], - signers: [], - }; -} diff --git a/sdk/src/instructions/initialize-pool-ix.ts b/sdk/src/instructions/initialize-pool-ix.ts index 0637ed0..73b4c16 100644 --- a/sdk/src/instructions/initialize-pool-ix.ts +++ b/sdk/src/instructions/initialize-pool-ix.ts @@ -1,18 +1,58 @@ -import { WhirlpoolContext } from "../context"; -import { SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { InitPoolParams } from ".."; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { SystemProgram, SYSVAR_RENT_PUBKEY, PublicKey, Keypair } from "@solana/web3.js"; +import { Instruction } from "@orca-so/common-sdk"; import { WhirlpoolBumpsData } from "../types/public/anchor-types"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { BN } from "@project-serum/anchor"; +import { PDA } from "@orca-so/common-sdk"; -export function buildInitPoolIx(context: WhirlpoolContext, params: InitPoolParams): Instruction { - const program = context.program; +/** + * Parameters to initialize a Whirlpool account. + * + * @category Instruction Types + * @param initSqrtPrice - The desired initial sqrt-price for this pool + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param whirlpoolPda - PDA for the whirlpool account that would be initialized + * @param tokenMintA - Mint public key for token A + * @param tokenMintB - Mint public key for token B + * @param tokenVaultAKeypair - Keypair of the token A vault for this pool + * @param tokenVaultBKeypair - Keypair of the token B vault for this pool + * @param feeTierKey - PublicKey of the fee-tier account that this pool would use for the fee-rate + * @param tickSpacing - The desired tick spacing for this pool. + * @param funder - The account that would fund the creation of this account + */ +export type InitPoolParams = { + initSqrtPrice: BN; + whirlpoolsConfig: PublicKey; + whirlpoolPda: PDA; + tokenMintA: PublicKey; + tokenMintB: PublicKey; + tokenVaultAKeypair: Keypair; + tokenVaultBKeypair: Keypair; + feeTierKey: PublicKey; + tickSpacing: number; + funder: PublicKey; +}; +/** + * Initializes a tick_array account to represent a tick-range in a Whirlpool. + * + * Special Errors + * `InvalidTokenMintOrder` - The order of mints have to be ordered by + * `SqrtPriceOutOfBounds` - provided initial_sqrt_price is not between 2^-64 to 2^64 + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - InitPoolParams object + * @returns - Instruction to perform the action. + */ +export function initializePoolIx(program: Program, params: InitPoolParams): Instruction { const { initSqrtPrice, tokenMintA, tokenMintB, - whirlpoolConfigKey, + whirlpoolsConfig, whirlpoolPda, feeTierKey, tokenVaultAKeypair, @@ -27,9 +67,9 @@ export function buildInitPoolIx(context: WhirlpoolContext, params: InitPoolParam const ix = program.instruction.initializePool(whirlpoolBumps, tickSpacing, initSqrtPrice, { accounts: { - whirlpoolsConfig: whirlpoolConfigKey, - tokenMintA: tokenMintA, - tokenMintB: tokenMintB, + whirlpoolsConfig, + tokenMintA, + tokenMintB, funder, whirlpool: whirlpoolPda.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, diff --git a/sdk/src/instructions/initialize-reward-ix.ts b/sdk/src/instructions/initialize-reward-ix.ts index 6b487ae..9197615 100644 --- a/sdk/src/instructions/initialize-reward-ix.ts +++ b/sdk/src/instructions/initialize-reward-ix.ts @@ -1,18 +1,52 @@ import * as anchor from "@project-serum/anchor"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { SystemProgram } from "@solana/web3.js"; -import { InitializeRewardParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { SystemProgram, Keypair, PublicKey } from "@solana/web3.js"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; -export function buildInitializeRewardIx( - context: WhirlpoolContext, +import { Instruction } from "@orca-so/common-sdk"; + +/** + * Parameters to initialize a rewards for a Whirlpool + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool config space that the fee-tier will be initialized for. + * @param rewardIndex - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS). + * @param rewardMint - PublicKey for the reward mint that we'd use for the reward index. + * @param rewardVaultKeypair - Keypair of the vault for this reward index. + * @param rewardAuthority - Assigned authority by the reward_super_authority for the specified reward-index in this Whirlpool + * @param funder - The account that would fund the creation of this account + */ +export type InitializeRewardParams = { + whirlpool: PublicKey; + rewardIndex: number; + rewardMint: PublicKey; + rewardVaultKeypair: Keypair; + rewardAuthority: PublicKey; + funder: PublicKey; +}; + +/** + * Initialize reward for a Whirlpool. A pool can only support up to a set number of rewards. + * The initial emissionsPerSecond is set to 0. + * + * #### Special Errors + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS, or all reward slots for this pool has been initialized. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - InitializeRewardParams object + * @returns - Instruction to perform the action. + */ +export function initializeRewardIx( + program: Program, params: InitializeRewardParams ): Instruction { const { rewardAuthority, funder, whirlpool, rewardMint, rewardVaultKeypair, rewardIndex } = params; - const ix = context.program.instruction.initializeReward(rewardIndex, { + const ix = program.instruction.initializeReward(rewardIndex, { accounts: { rewardAuthority, funder, diff --git a/sdk/src/instructions/initialize-tick-array-ix.ts b/sdk/src/instructions/initialize-tick-array-ix.ts index ca5eecf..8e20552 100644 --- a/sdk/src/instructions/initialize-tick-array-ix.ts +++ b/sdk/src/instructions/initialize-tick-array-ix.ts @@ -1,14 +1,41 @@ -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { InitTickArrayParams } from ".."; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; import * as anchor from "@project-serum/anchor"; +import { PublicKey } from "@solana/web3.js"; +import { PDA } from "@orca-so/common-sdk"; -export function buildInitTickArrayIx( - context: WhirlpoolContext, +/** + * Parameters to initialize a TickArray account. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool that the initialized tick-array will host ticks for. + * @param tickArrayPda - PDA for the tick array account that will be initialized + * @param startTick - The starting tick index for this tick-array. Has to be a multiple of TickArray size & the tick spacing of this pool. + * @param funder - The account that would fund the creation of this account + */ +export type InitTickArrayParams = { + whirlpool: PublicKey; + tickArrayPda: PDA; + startTick: number; + funder: PublicKey; +}; + +/** + * Initializes a TickArray account. + * + * #### Special Errors + * `InvalidStartTick` - if the provided start tick is out of bounds or is not a multiple of TICK_ARRAY_SIZE * tick spacing. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - InitTickArrayParams object + * @returns - Instruction to perform the action. + */ +export function initTickArrayIx( + program: Program, params: InitTickArrayParams ): Instruction { - const program = context.program; - const { whirlpool, funder, tickArrayPda } = params; const ix = program.instruction.initializeTickArray(params.startTick, { diff --git a/sdk/src/instructions/open-position-ix.ts b/sdk/src/instructions/open-position-ix.ts index caa7891..868e00e 100644 --- a/sdk/src/instructions/open-position-ix.ts +++ b/sdk/src/instructions/open-position-ix.ts @@ -1,17 +1,52 @@ -import { WhirlpoolContext } from "../context"; -import { PublicKey, SystemProgram } from "@solana/web3.js"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { OpenPositionParams, PDA } from ".."; -import * as anchor from "@project-serum/anchor"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { PublicKey } from "@solana/web3.js"; +import { PDA, Instruction } from "@orca-so/common-sdk"; +import { METADATA_PROGRAM_ADDRESS } from ".."; import { OpenPositionBumpsData, OpenPositionWithMetadataBumpsData, } from "../types/public/anchor-types"; -import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { METADATA_PROGRAM_ADDRESS } from "../utils/public"; +import { openPositionAccounts } from "../utils/instructions-util"; -export function buildOpenPositionIx( - context: WhirlpoolContext, +/** + * Parameters to open a position in a Whirlpool. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param ownerKey - PublicKey for the wallet that will host the minted position token. + * @param positionPda - PDA for the derived position address. + * @param positionMintAddress - PublicKey for the mint token for the Position token. + * @param positionTokenAccount - The associated token address for the position token in the owners wallet. + * @param tickLowerIndex - The tick specifying the lower end of the position range. + * @param tickUpperIndex - The tick specifying the upper end of the position range. + * @param funder - The account that would fund the creation of this account + */ +export type OpenPositionParams = { + whirlpool: PublicKey; + owner: PublicKey; + positionPda: PDA; + positionMintAddress: PublicKey; + positionTokenAccount: PublicKey; + tickLowerIndex: number; + tickUpperIndex: number; + funder: PublicKey; +}; + +/** + * Open a position in a Whirlpool. A unique token will be minted to represent the position in the users wallet. + * The position will start off with 0 liquidity. + * + * #### Special Errors + * `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - OpenPositionParams object + * @returns - Instruction to perform the action. + */ +export function openPositionIx( + program: Program, params: OpenPositionParams ): Instruction { const { positionPda, tickLowerIndex, tickUpperIndex } = params; @@ -20,10 +55,11 @@ export function buildOpenPositionIx( positionBump: positionPda.bump, }; - const ix = context.program.instruction.openPosition(bumps, tickLowerIndex, tickUpperIndex, { + const ix = program.instruction.openPosition(bumps, tickLowerIndex, tickUpperIndex, { accounts: openPositionAccounts(params), }); + // TODO: Require Keypair and auto sign this ix return { instructions: [ix], cleanupInstructions: [], @@ -31,8 +67,21 @@ export function buildOpenPositionIx( }; } -export function buildOpenPositionWithMetadataIx( - context: WhirlpoolContext, +/** + * Open a position in a Whirlpool. A unique token will be minted to represent the position + * in the users wallet. Additional Metaplex metadata is appended to identify the token. + * The position will start off with 0 liquidity. + * + * #### Special Errors + * `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - OpenPositionParams object and a derived PDA that hosts the position's metadata. + * @returns - Instruction to perform the action. + */ +export function openPositionWithMetadataIx( + program: Program, params: OpenPositionParams & { metadataPda: PDA } ): Instruction { const { positionPda, metadataPda, tickLowerIndex, tickUpperIndex } = params; @@ -42,46 +91,19 @@ export function buildOpenPositionWithMetadataIx( metadataBump: metadataPda.bump, }; - const ix = context.program.instruction.openPositionWithMetadata( - bumps, - tickLowerIndex, - tickUpperIndex, - { - accounts: { - ...openPositionAccounts(params), - positionMetadataAccount: metadataPda.publicKey, - metadataProgram: METADATA_PROGRAM_ADDRESS, - metadataUpdateAuth: new PublicKey("3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr"), - }, - } - ); + const ix = program.instruction.openPositionWithMetadata(bumps, tickLowerIndex, tickUpperIndex, { + accounts: { + ...openPositionAccounts(params), + positionMetadataAccount: metadataPda.publicKey, + metadataProgram: METADATA_PROGRAM_ADDRESS, + metadataUpdateAuth: new PublicKey("3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr"), + }, + }); + // TODO: Require Keypair and auto sign this ix return { instructions: [ix], cleanupInstructions: [], signers: [], }; } - -export function openPositionAccounts(params: OpenPositionParams) { - const { - funder, - ownerKey, - positionPda, - positionMintAddress, - positionTokenAccountAddress, - whirlpoolKey, - } = params; - return { - funder: funder, - owner: ownerKey, - position: positionPda.publicKey, - positionMint: positionMintAddress, - positionTokenAccount: positionTokenAccountAddress, - whirlpool: whirlpoolKey, - tokenProgram: TOKEN_PROGRAM_ID, - systemProgram: SystemProgram.programId, - rent: anchor.web3.SYSVAR_RENT_PUBKEY, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, - }; -} diff --git a/sdk/src/instructions/set-collect-protocol-fees-authority-ix.ts b/sdk/src/instructions/set-collect-protocol-fees-authority-ix.ts index 2b318ca..8a6abad 100644 --- a/sdk/src/instructions/set-collect-protocol-fees-authority-ix.ts +++ b/sdk/src/instructions/set-collect-protocol-fees-authority-ix.ts @@ -1,15 +1,39 @@ -import { SetCollectProtocolFeesAuthorityParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetCollectProtocolFeesAuthorityIx( - context: WhirlpoolContext, +/** + * Parameters to set the collect fee authority in a WhirlpoolsConfig + * + * @category Instruction Types + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param collectProtocolFeesAuthority - The current collectProtocolFeesAuthority in the WhirlpoolsConfig + * @param newCollectProtocolFeesAuthority - The new collectProtocolFeesAuthority in the WhirlpoolsConfig + */ +export type SetCollectProtocolFeesAuthorityParams = { + whirlpoolsConfig: PublicKey; + collectProtocolFeesAuthority: PublicKey; + newCollectProtocolFeesAuthority: PublicKey; +}; + +/** + * Sets the fee authority to collect protocol fees for a WhirlpoolsConfig. + * Only the current collect protocol fee authority has permission to invoke this instruction. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetCollectProtocolFeesAuthorityParams object + * @returns - Instruction to perform the action. + */ +export function setCollectProtocolFeesAuthorityIx( + program: Program, params: SetCollectProtocolFeesAuthorityParams ): Instruction { const { whirlpoolsConfig, collectProtocolFeesAuthority, newCollectProtocolFeesAuthority } = params; - const ix = context.program.instruction.setCollectProtocolFeesAuthority({ + const ix = program.instruction.setCollectProtocolFeesAuthority({ accounts: { whirlpoolsConfig, collectProtocolFeesAuthority, diff --git a/sdk/src/instructions/set-default-fee-rate-ix.ts b/sdk/src/instructions/set-default-fee-rate-ix.ts index 89f9dd5..d354186 100644 --- a/sdk/src/instructions/set-default-fee-rate-ix.ts +++ b/sdk/src/instructions/set-default-fee-rate-ix.ts @@ -1,16 +1,47 @@ -import { getFeeTierPda, SetDefaultFeeRateParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetDefaultFeeRateIx( - context: WhirlpoolContext, +import { PDAUtil } from "../utils/public"; + +/** + * Parameters to set the default fee rate for a FeeTier. + * + * @category Instruction Types + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this fee-tier is initialized in + * @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates. + * @param tickSpacing - The tick spacing of the fee-tier that we would like to update. + * @param defaultFeeRate - The new default fee rate for this fee-tier. Stored as a hundredths of a basis point. + */ +export type SetDefaultFeeRateParams = { + whirlpoolsConfig: PublicKey; + feeAuthority: PublicKey; + tickSpacing: number; + defaultFeeRate: number; +}; + +/** + * Updates a fee tier account with a new default fee rate. The new rate will not retroactively update + * initialized pools. + * + * #### Special Errors + * - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetDefaultFeeRateParams object + * @returns - Instruction to perform the action. + */ +export function setDefaultFeeRateIx( + program: Program, params: SetDefaultFeeRateParams ): Instruction { const { whirlpoolsConfig, feeAuthority, tickSpacing, defaultFeeRate } = params; - const feeTierPda = getFeeTierPda(context.program.programId, whirlpoolsConfig, tickSpacing); + const feeTierPda = PDAUtil.getFeeTier(program.programId, whirlpoolsConfig, tickSpacing); - const ix = context.program.instruction.setDefaultFeeRate(defaultFeeRate, { + const ix = program.instruction.setDefaultFeeRate(defaultFeeRate, { accounts: { whirlpoolsConfig, feeTier: feeTierPda.publicKey, diff --git a/sdk/src/instructions/set-default-protocol-fee-rate-ix.ts b/sdk/src/instructions/set-default-protocol-fee-rate-ix.ts index 55b364a..f53074e 100644 --- a/sdk/src/instructions/set-default-protocol-fee-rate-ix.ts +++ b/sdk/src/instructions/set-default-protocol-fee-rate-ix.ts @@ -1,14 +1,41 @@ -import { SetDefaultProtocolFeeRateParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetDefaultProtocolFeeRateIx( - context: WhirlpoolContext, +/** + * Parameters to set the default fee rate for a FeeTier. + * + * @category Instruction Types + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates. + * @param defaultProtocolFeeRate - The new default protocol fee rate for this config. Stored as a basis point of the total fees collected by feeRate. + */ +export type SetDefaultProtocolFeeRateParams = { + whirlpoolsConfig: PublicKey; + feeAuthority: PublicKey; + defaultProtocolFeeRate: number; +}; + +/** + * Updates a WhirlpoolsConfig with a new default protocol fee rate. The new rate will not retroactively update + * initialized pools. + * + * #### Special Errors + * - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetDefaultFeeRateParams object + * @returns - Instruction to perform the action. + */ +export function setDefaultProtocolFeeRateIx( + program: Program, params: SetDefaultProtocolFeeRateParams ): Instruction { const { whirlpoolsConfig, feeAuthority, defaultProtocolFeeRate } = params; - const ix = context.program.instruction.setDefaultProtocolFeeRate(defaultProtocolFeeRate, { + const ix = program.instruction.setDefaultProtocolFeeRate(defaultProtocolFeeRate, { accounts: { whirlpoolsConfig, feeAuthority, diff --git a/sdk/src/instructions/set-fee-authority-ix.ts b/sdk/src/instructions/set-fee-authority-ix.ts index ed7ccc7..5883ef7 100644 --- a/sdk/src/instructions/set-fee-authority-ix.ts +++ b/sdk/src/instructions/set-fee-authority-ix.ts @@ -1,14 +1,39 @@ -import { SetFeeAuthorityParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetFeeAuthorityIx( - context: WhirlpoolContext, +/** + * Parameters to set the fee authority in a WhirlpoolsConfig + * + * @category Instruction Types + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param feeAuthority - The current feeAuthority in the WhirlpoolsConfig + * @param newFeeAuthority - The new feeAuthority in the WhirlpoolsConfig + */ +export type SetFeeAuthorityParams = { + whirlpoolsConfig: PublicKey; + feeAuthority: PublicKey; + newFeeAuthority: PublicKey; +}; + +/** + * Sets the fee authority for a WhirlpoolsConfig. + * The fee authority can set the fee & protocol fee rate for individual pools or set the default fee rate for newly minted pools. + * Only the current fee authority has permission to invoke this instruction. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetFeeAuthorityParams object + * @returns - Instruction to perform the action. + */ +export function setFeeAuthorityIx( + program: Program, params: SetFeeAuthorityParams ): Instruction { const { whirlpoolsConfig, feeAuthority, newFeeAuthority } = params; - const ix = context.program.instruction.setFeeAuthority({ + const ix = program.instruction.setFeeAuthority({ accounts: { whirlpoolsConfig, feeAuthority, diff --git a/sdk/src/instructions/set-fee-rate-ix.ts b/sdk/src/instructions/set-fee-rate-ix.ts index 6e88568..a77b21f 100644 --- a/sdk/src/instructions/set-fee-rate-ix.ts +++ b/sdk/src/instructions/set-fee-rate-ix.ts @@ -1,14 +1,40 @@ -import { SetFeeRateParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetFeeRateIx( - context: WhirlpoolContext, - params: SetFeeRateParams -): Instruction { +/** + * Parameters to set fee rate for a Whirlpool. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool to update. This whirlpool has to be part of the provided WhirlpoolsConfig space. + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates. + * @param feeRate - The new fee rate for this fee-tier. Stored as a hundredths of a basis point. + */ +export type SetFeeRateParams = { + whirlpool: PublicKey; + whirlpoolsConfig: PublicKey; + feeAuthority: PublicKey; + feeRate: number; +}; + +/** + * Sets the fee rate for a Whirlpool. + * Only the current fee authority has permission to invoke this instruction. + * + * #### Special Errors + * - `FeeRateMaxExceeded` - If the provided fee_rate exceeds MAX_FEE_RATE. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetFeeRateParams object + * @returns - Instruction to perform the action. + */ +export function setFeeRateIx(program: Program, params: SetFeeRateParams): Instruction { const { whirlpoolsConfig, whirlpool, feeAuthority, feeRate } = params; - const ix = context.program.instruction.setFeeRate(feeRate, { + const ix = program.instruction.setFeeRate(feeRate, { accounts: { whirlpoolsConfig, whirlpool, diff --git a/sdk/src/instructions/set-protocol-fee-rate-ix.ts b/sdk/src/instructions/set-protocol-fee-rate-ix.ts index d33af13..86ea41d 100644 --- a/sdk/src/instructions/set-protocol-fee-rate-ix.ts +++ b/sdk/src/instructions/set-protocol-fee-rate-ix.ts @@ -1,14 +1,43 @@ -import { SetProtocolFeeRateParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetProtocolFeeRateIx( - context: WhirlpoolContext, +/** + * Parameters to set fee rate for a Whirlpool. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool to update. This whirlpool has to be part of the provided WhirlpoolsConfig space. + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param feeAuthority - Authority authorized in the WhirlpoolsConfig to set default fee rates. + * @param protocolFeeRate - The new default protocol fee rate for this pool. Stored as a basis point of the total fees collected by feeRate. + */ +export type SetProtocolFeeRateParams = { + whirlpool: PublicKey; + whirlpoolsConfig: PublicKey; + feeAuthority: PublicKey; + protocolFeeRate: number; +}; + +/** + * Sets the protocol fee rate for a Whirlpool. + * Only the current fee authority has permission to invoke this instruction. + * + * #### Special Errors + * - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetFeeRateParams object + * @returns - Instruction to perform the action. + */ +export function setProtocolFeeRateIx( + program: Program, params: SetProtocolFeeRateParams ): Instruction { const { whirlpoolsConfig, whirlpool, feeAuthority, protocolFeeRate } = params; - const ix = context.program.instruction.setProtocolFeeRate(protocolFeeRate, { + const ix = program.instruction.setProtocolFeeRate(protocolFeeRate, { accounts: { whirlpoolsConfig, whirlpool, diff --git a/sdk/src/instructions/set-reward-authority-by-super-authority-ix.ts b/sdk/src/instructions/set-reward-authority-by-super-authority-ix.ts index 134dafb..3189d61 100644 --- a/sdk/src/instructions/set-reward-authority-by-super-authority-ix.ts +++ b/sdk/src/instructions/set-reward-authority-by-super-authority-ix.ts @@ -1,9 +1,41 @@ -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { SetRewardAuthorityBySuperAuthorityParams } from ".."; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetRewardAuthorityBySuperAuthorityIx( - context: WhirlpoolContext, +/** + * Parameters to update the reward authority at a particular rewardIndex on a Whirlpool. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool to update. This whirlpool has to be part of the provided WhirlpoolsConfig space. + * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in + * @param rewardIndex - The reward index that we'd like to update. (0 <= index <= NUM_REWARDS). + * @param rewardEmissionsSuperAuthority - The current rewardEmissionsSuperAuthority in the WhirlpoolsConfig + * @param newRewardAuthority - The new rewardAuthority in the Whirlpool at the rewardIndex + */ +export type SetRewardAuthorityBySuperAuthorityParams = { + whirlpool: PublicKey; + whirlpoolsConfig: PublicKey; + rewardIndex: number; + rewardEmissionsSuperAuthority: PublicKey; + newRewardAuthority: PublicKey; +}; + +/** + * Set the whirlpool reward authority at the provided `reward_index`. + * Only the current reward super authority has permission to invoke this instruction. + * + * #### Special Errors + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetRewardAuthorityParams object + * @returns - Instruction to perform the action. + */ +export function setRewardAuthorityBySuperAuthorityIx( + program: Program, params: SetRewardAuthorityBySuperAuthorityParams ): Instruction { const { @@ -14,7 +46,7 @@ export function buildSetRewardAuthorityBySuperAuthorityIx( rewardIndex, } = params; - const ix = context.program.instruction.setRewardAuthorityBySuperAuthority(rewardIndex, { + const ix = program.instruction.setRewardAuthorityBySuperAuthority(rewardIndex, { accounts: { whirlpoolsConfig, whirlpool, diff --git a/sdk/src/instructions/set-reward-authority-ix.ts b/sdk/src/instructions/set-reward-authority-ix.ts index b3c35d2..c37f5d1 100644 --- a/sdk/src/instructions/set-reward-authority-ix.ts +++ b/sdk/src/instructions/set-reward-authority-ix.ts @@ -1,13 +1,43 @@ -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { SetRewardAuthorityParams } from ".."; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetRewardAuthorityIx( - context: WhirlpoolContext, +/** + * Parameters to update the reward authority at a particular rewardIndex on a Whirlpool. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool to update. + * @param rewardIndex - The reward index that we'd like to update. (0 <= index <= NUM_REWARDS). + * @param rewardAuthority - The current rewardAuthority in the Whirlpool at the rewardIndex + * @param newRewardAuthority - The new rewardAuthority in the Whirlpool at the rewardIndex + */ +export type SetRewardAuthorityParams = { + whirlpool: PublicKey; + rewardIndex: number; + rewardAuthority: PublicKey; + newRewardAuthority: PublicKey; +}; + +/** + * Set the whirlpool reward authority at the provided `reward_index`. + * Only the current reward authority for this reward index has permission to invoke this instruction. + * + * #### Special Errors + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetRewardAuthorityParams object + * @returns - Instruction to perform the action. + */ +export function setRewardAuthorityIx( + program: Program, params: SetRewardAuthorityParams ): Instruction { const { whirlpool, rewardAuthority, newRewardAuthority, rewardIndex } = params; - const ix = context.program.instruction.setRewardAuthority(rewardIndex, { + const ix = program.instruction.setRewardAuthority(rewardIndex, { accounts: { whirlpool, rewardAuthority, diff --git a/sdk/src/instructions/set-reward-emissions-ix.ts b/sdk/src/instructions/set-reward-emissions-ix.ts index aa3dd93..8f8fb37 100644 --- a/sdk/src/instructions/set-reward-emissions-ix.ts +++ b/sdk/src/instructions/set-reward-emissions-ix.ts @@ -1,14 +1,54 @@ -import { SetRewardEmissionsParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; +import { BN } from "@project-serum/anchor"; -export function buildSetRewardEmissionsIx( - context: WhirlpoolContext, +/** + * Parameters to set rewards emissions for a reward in a Whirlpool + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool which the reward resides in. + * @param rewardIndex - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS). + * @param rewardVaultKey - PublicKey of the vault for this reward index. + * @param rewardAuthority - Assigned authority by the reward_super_authority for the specified reward-index in this Whirlpool + * @param emissionsPerSecondX64 - The new emissions per second to set for this reward. + */ +export type SetRewardEmissionsParams = { + whirlpool: PublicKey; + rewardIndex: number; + rewardVaultKey: PublicKey; + rewardAuthority: PublicKey; + emissionsPerSecondX64: BN; +}; + +/** + * Set the reward emissions for a reward in a Whirlpool. + * + * #### Special Errors + * - `RewardVaultAmountInsufficient` - The amount of rewards in the reward vault cannot emit more than a day of desired emissions. + * - `InvalidTimestamp` - Provided timestamp is not in order with the previous timestamp. + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetRewardEmissionsParams object + * @returns - Instruction to perform the action. + */ +export function setRewardEmissionsIx( + program: Program, params: SetRewardEmissionsParams ): Instruction { - const { rewardAuthority, whirlpool, rewardIndex, rewardVault, emissionsPerSecondX64 } = params; + const { + rewardAuthority, + whirlpool, + rewardIndex, + rewardVaultKey: rewardVault, + emissionsPerSecondX64, + } = params; - const ix = context.program.instruction.setRewardEmissions(rewardIndex, emissionsPerSecondX64, { + const ix = program.instruction.setRewardEmissions(rewardIndex, emissionsPerSecondX64, { accounts: { rewardAuthority, whirlpool, diff --git a/sdk/src/instructions/set-reward-emissions-super-authority-ix.ts b/sdk/src/instructions/set-reward-emissions-super-authority-ix.ts index a59167d..166192b 100644 --- a/sdk/src/instructions/set-reward-emissions-super-authority-ix.ts +++ b/sdk/src/instructions/set-reward-emissions-super-authority-ix.ts @@ -1,15 +1,40 @@ -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; -import { SetRewardEmissionsSuperAuthorityParams } from ".."; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; -export function buildSetRewardEmissionsSuperAuthorityIx( - context: WhirlpoolContext, +/** + * Parameters to set rewards emissions for a reward in a Whirlpool + * + * @category Instruction Types + * @param whirlpoolsConfig - PublicKey for the WhirlpoolsConfig that we want to update. + * @param rewardEmissionsSuperAuthority - Current reward emission super authority in this WhirlpoolsConfig + * @param newRewardEmissionsSuperAuthority - New reward emission super authority for this WhirlpoolsConfig + */ +export type SetRewardEmissionsSuperAuthorityParams = { + whirlpoolsConfig: PublicKey; + rewardEmissionsSuperAuthority: PublicKey; + newRewardEmissionsSuperAuthority: PublicKey; +}; + +/** + * Set the whirlpool reward super authority for a WhirlpoolsConfig + * Only the current reward super authority has permission to invoke this instruction. + * This instruction will not change the authority on any `WhirlpoolRewardInfo` whirlpool rewards. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SetRewardEmissionsSuperAuthorityParams object + * @returns - Instruction to perform the action. + */ +export function setRewardEmissionsSuperAuthorityIx( + program: Program, params: SetRewardEmissionsSuperAuthorityParams ): Instruction { const { whirlpoolsConfig, rewardEmissionsSuperAuthority, newRewardEmissionsSuperAuthority } = params; - const ix = context.program.instruction.setRewardEmissionsSuperAuthority({ + const ix = program.instruction.setRewardEmissionsSuperAuthority({ accounts: { whirlpoolsConfig, rewardEmissionsSuperAuthority: rewardEmissionsSuperAuthority, diff --git a/sdk/src/instructions/swap-ix.ts b/sdk/src/instructions/swap-ix.ts index 9fc7ff5..e813162 100644 --- a/sdk/src/instructions/swap-ix.ts +++ b/sdk/src/instructions/swap-ix.ts @@ -1,9 +1,86 @@ -import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { SwapParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { Instruction } from "@orca-so/common-sdk"; +import { PublicKey } from "@solana/web3.js"; +import { BN } from "@project-serum/anchor"; -export function buildSwapIx(context: WhirlpoolContext, params: SwapParams): Instruction { +/** + * Parameters and accounts to swap on a Whirlpool + * + * @category Instruction Types + * @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A. + * @param amountSpecifiedIsInput - Specifies the token the parameter `amount`represents. If true, the amount represents + * the input token of the swap. + * @param amount - The amount of input or output token to swap from (depending on amountSpecifiedIsInput). + * @param otherAmountThreshold - The maximum/minimum of input/output token to swap into (depending on amountSpecifiedIsInput). + * @param sqrtPriceLimit - The maximum/minimum price the swap will swap to. + * @param tickArray0 - PublicKey of the tick-array where the Whirlpool's currentTickIndex resides in + * @param tickArray1 - The next tick-array in the swap direction. If the swap will not reach the next tick-aray, input the same array as tickArray0. + * @param tickArray2 - The next tick-array in the swap direction after tickArray2. If the swap will not reach the next tick-aray, input the same array as tickArray1. + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param tokenOwnerAccountA - PublicKey for the associated token account for tokenA in the collection wallet + * @param tokenOwnerAccountB - PublicKey for the associated token account for tokenB in the collection wallet + * @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool. + * @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool. + * @param oracle - PublicKey for the oracle account for this Whirlpool. + * @param tokenAuthority - authority to withdraw tokens from the input token account + */ +export type SwapParams = SwapInput & { + whirlpool: PublicKey; + tokenOwnerAccountA: PublicKey; + tokenOwnerAccountB: PublicKey; + tokenVaultA: PublicKey; + tokenVaultB: PublicKey; + oracle: PublicKey; + tokenAuthority: PublicKey; +}; + +/** + * Parameters to swap on a Whirlpool + * + * @category Instruction Types + * @param aToB - The direction of the swap. True if swapping from A to B. False if swapping from B to A. + * @param amountSpecifiedIsInput - Specifies the token the parameter `amount`represents. If true, the amount represents + * the input token of the swap. + * @param amount - The amount of input or output token to swap from (depending on amountSpecifiedIsInput). + * @param otherAmountThreshold - The maximum/minimum of input/output token to swap into (depending on amountSpecifiedIsInput). + * @param sqrtPriceLimit - The maximum/minimum price the swap will swap to. + * @param tickArray0 - PublicKey of the tick-array where the Whirlpool's currentTickIndex resides in + * @param tickArray1 - The next tick-array in the swap direction. If the swap will not reach the next tick-aray, input the same array as tickArray0. + * @param tickArray2 - The next tick-array in the swap direction after tickArray2. If the swap will not reach the next tick-aray, input the same array as tickArray1. + */ +export type SwapInput = { + amount: u64; + otherAmountThreshold: u64; + sqrtPriceLimit: BN; + amountSpecifiedIsInput: boolean; + aToB: boolean; + tickArray0: PublicKey; + tickArray1: PublicKey; + tickArray2: PublicKey; +}; + +/** + * Perform a swap in this Whirlpool + * + * #### Special Errors + * - `ZeroTradableAmount` - User provided parameter `amount` is 0. + * - `InvalidSqrtPriceLimitDirection` - User provided parameter `sqrt_price_limit` does not match the direction of the trade. + * - `SqrtPriceOutOfBounds` - User provided parameter `sqrt_price_limit` is over Whirlppool's max/min bounds for sqrt-price. + * - `InvalidTickArraySequence` - User provided tick-arrays are not in sequential order required to proceed in this trade direction. + * - `TickArraySequenceInvalidIndex` - The swap loop attempted to access an invalid array index during the query of the next initialized tick. + * - `TickArrayIndexOutofBounds` - The swap loop attempted to access an invalid array index during tick crossing. + * - `LiquidityOverflow` - Liquidity value overflowed 128bits during tick crossing. + * - `InvalidTickSpacing` - The swap pool was initialized with tick-spacing of 0. + * + * ### Parameters + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - SwapParams object + * @returns - Instruction to perform the action. + */ +export function swapIx(program: Program, params: SwapParams): Instruction { const { amount, otherAmountThreshold, @@ -22,7 +99,7 @@ export function buildSwapIx(context: WhirlpoolContext, params: SwapParams): Inst oracle, } = params; - const ix = context.program.instruction.swap( + const ix = program.instruction.swap( amount, otherAmountThreshold, sqrtPriceLimit, diff --git a/sdk/src/instructions/update-fees-and-rewards-ix.ts b/sdk/src/instructions/update-fees-and-rewards-ix.ts index e97527a..b2df112 100644 --- a/sdk/src/instructions/update-fees-and-rewards-ix.ts +++ b/sdk/src/instructions/update-fees-and-rewards-ix.ts @@ -1,14 +1,44 @@ -import { UpdateFeesAndRewardsParams } from ".."; -import { WhirlpoolContext } from "../context"; -import { Instruction } from "../utils/transactions/transactions-builder"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "../artifacts/whirlpool"; +import { PublicKey } from "@solana/web3.js"; -export function buildUpdateFeesAndRewardsIx( - context: WhirlpoolContext, +import { Instruction } from "@orca-so/common-sdk"; + +/** + * Parameters to update fees and reward values for a position. + * + * @category Instruction Types + * @param whirlpool - PublicKey for the whirlpool that the position will be opened for. + * @param position - PublicKey for the position will be opened for. + * @param tickArrayLower - PublicKey for the tick-array account that hosts the tick at the lower tick index. + * @param tickArrayUpper - PublicKey for the tick-array account that hosts the tick at the upper tick index. + */ +export type UpdateFeesAndRewardsParams = { + whirlpool: PublicKey; + position: PublicKey; + tickArrayLower: PublicKey; + tickArrayUpper: PublicKey; +}; + +/** + * Update the accrued fees and rewards for a position. + * + * #### Special Errors + * `TickNotFound` - Provided tick array account does not contain the tick for this position. + * `LiquidityZero` - Position has zero liquidity and therefore already has the most updated fees and reward values. + * + * @category Instructions + * @param context - Context object containing services required to generate the instruction + * @param params - UpdateFeesAndRewardsParams object + * @returns - Instruction to perform the action. + */ +export function updateFeesAndRewardsIx( + program: Program, params: UpdateFeesAndRewardsParams ): Instruction { const { whirlpool, position, tickArrayLower, tickArrayUpper } = params; - const ix = context.program.instruction.updateFeesAndRewards({ + const ix = program.instruction.updateFeesAndRewards({ accounts: { whirlpool, position, diff --git a/sdk/src/ix.ts b/sdk/src/ix.ts new file mode 100644 index 0000000..2a9de1d --- /dev/null +++ b/sdk/src/ix.ts @@ -0,0 +1,418 @@ +import { PDA } from "@orca-so/common-sdk"; +import { Program } from "@project-serum/anchor"; +import { Whirlpool } from "./artifacts/whirlpool"; +import * as ix from "./instructions"; + +/** + * Instruction set for the Whirlpools program. + * + * @category Core + */ +export class WhirlpoolIx { + /** + * Initializes a WhirlpoolsConfig account that hosts info & authorities + * required to govern a set of Whirlpools. + * + * @param program - program object containing services required to generate the instruction + * @param params - InitConfigParams object + * @returns - Instruction to perform the action. + */ + public static initializeConfigIx(program: Program, params: ix.InitConfigParams) { + return ix.initializeConfigIx(program, params); + } + + /** + * Initializes a fee tier account usable by Whirlpools in this WhirlpoolsConfig space. + * + * Special Errors + * `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE. + * + * @param program - program object containing services required to generate the instruction + * @param params - InitFeeTierParams object + * @returns - Instruction to perform the action. + */ + public static initializeFeeTierIx(program: Program, params: ix.InitFeeTierParams) { + return ix.initializeFeeTierIx(program, params); + } + + /** + * Initializes a tick_array account to represent a tick-range in a Whirlpool. + * + * Special Errors + * `InvalidTokenMintOrder` - The order of mints have to be ordered by + * `SqrtPriceOutOfBounds` - provided initial_sqrt_price is not between 2^-64 to 2^64 + * + * @param program - program object containing services required to generate the instruction + * @param params - InitPoolParams object + * @returns - Instruction to perform the action. + */ + public static initializePoolIx(program: Program, params: ix.InitPoolParams) { + return ix.initializePoolIx(program, params); + } + + /** + * Initialize reward for a Whirlpool. A pool can only support up to a set number of rewards. + * The initial emissionsPerSecond is set to 0. + * + * #### Special Errors + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS, or all reward slots for this pool has been initialized. + * + * @param program - program object containing services required to generate the instruction + * @param params - InitializeRewardParams object + * @returns - Instruction to perform the action. + */ + public static initializeRewardIx(program: Program, params: ix.InitializeRewardParams) { + return ix.initializeRewardIx(program, params); + } + + /** + * Initializes a TickArray account. + * + * #### Special Errors + * `InvalidStartTick` - if the provided start tick is out of bounds or is not a multiple of TICK_ARRAY_SIZE * tick spacing. + * + * @param program - program object containing services required to generate the instruction + * @param params - InitTickArrayParams object + * @returns - Instruction to perform the action. + */ + public static initTickArrayIx(program: Program, params: ix.InitTickArrayParams) { + return ix.initTickArrayIx(program, params); + } + + /** + * Open a position in a Whirlpool. A unique token will be minted to represent the position in the users wallet. + * The position will start off with 0 liquidity. + * + * #### Special Errors + * `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool. + * + + * @param program - program object containing services required to generate the instruction + * @param params - OpenPositionParams object + * @returns - Instruction to perform the action. + */ + public static openPositionIx(program: Program, params: ix.OpenPositionParams) { + return ix.openPositionIx(program, params); + } + + /** + * Open a position in a Whirlpool. A unique token will be minted to represent the position + * in the users wallet. Additional Metaplex metadata is appended to identify the token. + * The position will start off with 0 liquidity. + * + * #### Special Errors + * `InvalidTickIndex` - If a provided tick is out of bounds, out of order or not a multiple of the tick-spacing in this pool. + * + + * @param program - program object containing services required to generate the instruction + * @param params - OpenPositionParams object and a derived PDA that hosts the position's metadata. + * @returns - Instruction to perform the action. + */ + public static openPositionWithMetadataIx( + program: Program, + params: ix.OpenPositionParams & { metadataPda: PDA } + ) { + return ix.openPositionWithMetadataIx(program, params); + } + + /** + * Add liquidity to a position in the Whirlpool. This call also updates the position's accrued fees and rewards. + * + * #### Special Errors + * `LiquidityZero` - Provided liquidity amount is zero. + * `LiquidityTooHigh` - Provided liquidity exceeds u128::max. + * `TokenMaxExceeded` - The required token to perform this operation exceeds the user defined amount. + * + * @param program - program object containing services required to generate the instruction + * @param params - IncreaseLiquidityParams object + * @returns - Instruction to perform the action. + */ + public static increaseLiquidityIx( + program: Program, + params: ix.IncreaseLiquidityParams + ) { + return ix.increaseLiquidityIx(program, params); + } + + /** + * Remove liquidity to a position in the Whirlpool. This call also updates the position's accrued fees and rewards. + * + * #### Special Errors + * - `LiquidityZero` - Provided liquidity amount is zero. + * - `LiquidityTooHigh` - Provided liquidity exceeds u128::max. + * - `TokenMinSubceeded` - The required token to perform this operation subceeds the user defined amount. + * + * @param program - program object containing services required to generate the instruction + * @param params - DecreaseLiquidityParams object + * @returns - Instruction to perform the action. + */ + public static decreaseLiquidityIx( + program: Program, + params: ix.DecreaseLiquidityParams + ) { + return ix.decreaseLiquidityIx(program, params); + } + + /** + * Close a position in a Whirlpool. Burns the position token in the owner's wallet. + * + + * @param program - program object containing services required to generate the instruction + * @param params - ClosePositionParams object + * @returns - Instruction to perform the action. + */ + public static closePositionIx(program: Program, params: ix.ClosePositionParams) { + return ix.closePositionIx(program, params); + } + + /** + * Perform a swap in this Whirlpool + * + * #### Special Errors + * - `ZeroTradableAmount` - User provided parameter `amount` is 0. + * - `InvalidSqrtPriceLimitDirection` - User provided parameter `sqrt_price_limit` does not match the direction of the trade. + * - `SqrtPriceOutOfBounds` - User provided parameter `sqrt_price_limit` is over Whirlppool's max/min bounds for sqrt-price. + * - `InvalidTickArraySequence` - User provided tick-arrays are not in sequential order required to proceed in this trade direction. + * - `TickArraySequenceInvalidIndex` - The swap loop attempted to access an invalid array index during the query of the next initialized tick. + * - `TickArrayIndexOutofBounds` - The swap loop attempted to access an invalid array index during tick crossing. + * - `LiquidityOverflow` - Liquidity value overflowed 128bits during tick crossing. + * - `InvalidTickSpacing` - The swap pool was initialized with tick-spacing of 0. + * + * ### Parameters + * @param program - program object containing services required to generate the instruction + * @param params - SwapParams object + * @returns - Instruction to perform the action. + */ + public static swapIx(program: Program, params: ix.SwapParams) { + return ix.swapIx(program, params); + } + + /** + * Update the accrued fees and rewards for a position. + * + * #### Special Errors + * `TickNotFound` - Provided tick array account does not contain the tick for this position. + * `LiquidityZero` - Position has zero liquidity and therefore already has the most updated fees and reward values. + * + * @param program - program object containing services required to generate the instruction + * @param params - UpdateFeesAndRewardsParams object + * @returns - Instruction to perform the action. + */ + public static updateFeesAndRewardsIx( + program: Program, + params: ix.UpdateFeesAndRewardsParams + ) { + return ix.updateFeesAndRewardsIx(program, params); + } + + /** + * Collect fees accrued for this position. + * Call updateFeesAndRewards before this to update the position to the newest accrued values. + * + * @param program - program object containing services required to generate the instruction + * @param params - CollectFeesParams object + * @returns - Instruction to perform the action. + */ + public static collectFeesIx(program: Program, params: ix.CollectFeesParams) { + return ix.collectFeesIx(program, params); + } + + /** + * Collect protocol fees accrued in this Whirlpool. + * + * @param program - program object containing services required to generate the instruction + * @param params - CollectProtocolFeesParams object + * @returns - Instruction to perform the action. + */ + public static collectProtocolFeesIx( + program: Program, + params: ix.CollectProtocolFeesParams + ) { + return ix.collectProtocolFeesIx(program, params); + } + + /** + * Collect rewards accrued for this reward index in a position. + * Call updateFeesAndRewards before this to update the position to the newest accrued values. + * + + * @param program - program object containing services required to generate the instruction + * @param params - CollectRewardParams object + * @returns - Instruction to perform the action. + */ + public static collectRewardIx(program: Program, params: ix.CollectRewardParams) { + return ix.collectRewardIx(program, params); + } + + /** + * Sets the fee authority to collect protocol fees for a WhirlpoolsConfig. + * Only the current collect protocol fee authority has permission to invoke this instruction. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetCollectProtocolFeesAuthorityParams object + * @returns - Instruction to perform the action. + */ + public static setCollectProtocolFeesAuthorityIx( + program: Program, + params: ix.SetCollectProtocolFeesAuthorityParams + ) { + return ix.setCollectProtocolFeesAuthorityIx(program, params); + } + + /** + * Updates a fee tier account with a new default fee rate. The new rate will not retroactively update + * initialized pools. + * + * #### Special Errors + * - `FeeRateMaxExceeded` - If the provided default_fee_rate exceeds MAX_FEE_RATE. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetDefaultFeeRateParams object + * @returns - Instruction to perform the action. + */ + public static setDefaultFeeRateIx( + program: Program, + params: ix.SetDefaultFeeRateParams + ) { + return ix.setDefaultFeeRateIx(program, params); + } + + /** + * Updates a WhirlpoolsConfig with a new default protocol fee rate. The new rate will not retroactively update + * initialized pools. + * + * #### Special Errors + * - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetDefaultFeeRateParams object + * @returns - Instruction to perform the action. + */ + public static setDefaultProtocolFeeRateIx( + program: Program, + params: ix.SetDefaultProtocolFeeRateParams + ) { + return ix.setDefaultProtocolFeeRateIx(program, params); + } + + /** + * Sets the fee authority for a WhirlpoolsConfig. + * The fee authority can set the fee & protocol fee rate for individual pools or set the default fee rate for newly minted pools. + * Only the current fee authority has permission to invoke this instruction. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetFeeAuthorityParams object + * @returns - Instruction to perform the action. + */ + public static setFeeAuthorityIx(program: Program, params: ix.SetFeeAuthorityParams) { + return ix.setFeeAuthorityIx(program, params); + } + + /** + * Sets the fee rate for a Whirlpool. + * Only the current fee authority has permission to invoke this instruction. + * + * #### Special Errors + * - `FeeRateMaxExceeded` - If the provided fee_rate exceeds MAX_FEE_RATE. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetFeeRateParams object + * @returns - Instruction to perform the action. + */ + public static setFeeRateIx(program: Program, params: ix.SetFeeRateParams) { + return ix.setFeeRateIx(program, params); + } + + /** + * Sets the protocol fee rate for a Whirlpool. + * Only the current fee authority has permission to invoke this instruction. + * + * #### Special Errors + * - `ProtocolFeeRateMaxExceeded` - If the provided default_protocol_fee_rate exceeds MAX_PROTOCOL_FEE_RATE. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetFeeRateParams object + * @returns - Instruction to perform the action. + */ + public static setProtocolFeeRateIx( + program: Program, + params: ix.SetProtocolFeeRateParams + ) { + return ix.setProtocolFeeRateIx(program, params); + } + + /** + * Set the whirlpool reward authority at the provided `reward_index`. + * Only the current reward super authority has permission to invoke this instruction. + * + * #### Special Errors + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetRewardAuthorityParams object + * @returns - Instruction to perform the action. + */ + public static setRewardAuthorityBySuperAuthorityIx( + program: Program, + params: ix.SetRewardAuthorityBySuperAuthorityParams + ) { + return ix.setRewardAuthorityBySuperAuthorityIx(program, params); + } + + /** + * Set the whirlpool reward authority at the provided `reward_index`. + * Only the current reward authority for this reward index has permission to invoke this instruction. + * + * #### Special Errors + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetRewardAuthorityParams object + * @returns - Instruction to perform the action. + */ + public static setRewardAuthorityIx( + program: Program, + params: ix.SetRewardAuthorityParams + ) { + return ix.setRewardAuthorityIx(program, params); + } + + /** + * Set the reward emissions for a reward in a Whirlpool. + * + * #### Special Errors + * - `RewardVaultAmountInsufficient` - The amount of rewards in the reward vault cannot emit more than a day of desired emissions. + * - `InvalidTimestamp` - Provided timestamp is not in order with the previous timestamp. + * - `InvalidRewardIndex` - If the provided reward index doesn't match the lowest uninitialized index in this pool, + * or exceeds NUM_REWARDS. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetRewardEmissionsParams object + * @returns - Instruction to perform the action. + */ + public static setRewardEmissionsIx( + program: Program, + params: ix.SetRewardEmissionsParams + ) { + return ix.setRewardEmissionsIx(program, params); + } + + /** + * Set the whirlpool reward super authority for a WhirlpoolsConfig + * Only the current reward super authority has permission to invoke this instruction. + * This instruction will not change the authority on any `WhirlpoolRewardInfo` whirlpool rewards. + * + * @param program - program object containing services required to generate the instruction + * @param params - SetRewardEmissionsSuperAuthorityParams object + * @returns - Instruction to perform the action. + */ + public static setRewardEmissionsSuperAuthorityIx( + program: Program, + params: ix.SetRewardEmissionsSuperAuthorityParams + ) { + return ix.setRewardEmissionsSuperAuthorityIx(program, params); + } +} diff --git a/sdk/src/network/public/fetcher.ts b/sdk/src/network/public/fetcher.ts new file mode 100644 index 0000000..8f3750b --- /dev/null +++ b/sdk/src/network/public/fetcher.ts @@ -0,0 +1,343 @@ +import { Connection, PublicKey } from "@solana/web3.js"; +import invariant from "tiny-invariant"; +import { AccountInfo, AccountLayout, MintInfo } from "@solana/spl-token"; +import { + ParsableEntity, + ParsableFeeTier, + ParsableMintInfo, + ParsablePosition, + ParsableTickArray, + ParsableTokenInfo, + ParsableWhirlpool, + ParsableWhirlpoolsConfig, +} from "./parsing"; +import { Address } from "@project-serum/anchor"; +import { PositionData, TickArrayData, WhirlpoolsConfigData, WhirlpoolData } from "../.."; +import { FeeTierData } from "../../types/public"; +import { AddressUtil } from "@orca-so/common-sdk"; + +/** + * Supported accounts + */ +type CachedValue = + | WhirlpoolsConfigData + | WhirlpoolData + | PositionData + | TickArrayData + | FeeTierData + | AccountInfo + | MintInfo; + +/** + * Include both the entity (i.e. type) of the stored value, and the value itself + */ +interface CachedContent { + entity: ParsableEntity; + value: CachedValue | null; +} + +/** + * Type for rpc batch request response + */ +type GetMultipleAccountsResponse = { + error?: string; + result?: { + value?: ({ data: [string, string] } | null)[]; + }; +}; + +/** + * Data access layer to access Whirlpool related accounts + * Includes internal cache that can be refreshed by the client. + * + * @category Core + */ +export class AccountFetcher { + private readonly connection: Connection; + private readonly _cache: Record> = {}; + private _accountRentExempt: number | undefined; + + constructor(connection: Connection, cache?: Record>) { + this.connection = connection; + this._cache = cache ?? {}; + } + + /*** Public Methods ***/ + + /** + * Retrieve minimum balance for rent exemption of a Token Account; + * + * @param refresh force refresh of account rent exemption + * @returns minimum balance for rent exemption + */ + public async getAccountRentExempt(refresh: boolean = false) { + // This value should be relatively static or at least not break according to spec + // https://docs.solana.com/developing/programming-model/accounts#rent-exemption + if (!this._accountRentExempt || refresh) { + this._accountRentExempt = await this.connection.getMinimumBalanceForRentExemption( + AccountLayout.span + ); + } + return this._accountRentExempt; + } + + /** + * Retrieve a cached whirlpool account. Fetch from rpc on cache miss. + * + * @param address whirlpool address + * @param refresh force cache refresh + * @returns whirlpool account + */ + public async getPool(address: Address, refresh = false): Promise { + return this.get(AddressUtil.toPubKey(address), ParsableWhirlpool, refresh); + } + + /** + * Retrieve a cached position account. Fetch from rpc on cache miss. + * + * @param address position address + * @param refresh force cache refresh + * @returns position account + */ + public async getPosition(address: Address, refresh = false): Promise { + return this.get(AddressUtil.toPubKey(address), ParsablePosition, refresh); + } + + /** + * Retrieve a cached tick array account. Fetch from rpc on cache miss. + * + * @param address tick array address + * @param refresh force cache refresh + * @returns tick array account + */ + public async getTickArray(address: Address, refresh = false): Promise { + return this.get(AddressUtil.toPubKey(address), ParsableTickArray, refresh); + } + + /** + * Retrieve a cached fee tier account. Fetch from rpc on cache miss. + * + * @param address fee tier address + * @param refresh force cache refresh + * @returns fee tier account + */ + public async getFeeTier(address: Address, refresh = false): Promise { + return this.get(AddressUtil.toPubKey(address), ParsableFeeTier, refresh); + } + + /** + * Retrieve a cached token info account. Fetch from rpc on cache miss. + * + * @param address token info address + * @param refresh force cache refresh + * @returns token info account + */ + public async getTokenInfo(address: Address, refresh = false): Promise { + return this.get(AddressUtil.toPubKey(address), ParsableTokenInfo, refresh); + } + + /** + * Retrieve a cached mint info account. Fetch from rpc on cache miss. + * + * @param address mint info address + * @param refresh force cache refresh + * @returns mint info account + */ + public async getMintInfo(address: Address, refresh = false): Promise { + return this.get(AddressUtil.toPubKey(address), ParsableMintInfo, refresh); + } + + /** + * Retrieve a cached whirlpool config account. Fetch from rpc on cache miss. + * + * @param address whirlpool config address + * @param refresh force cache refresh + * @returns whirlpool config account + */ + public async getConfig(address: Address, refresh = false): Promise { + return this.get(AddressUtil.toPubKey(address), ParsableWhirlpoolsConfig, refresh); + } + + /** + * Retrieve a list of cached whirlpool accounts. Fetch from rpc for cache misses. + * + * @param addresses whirlpool addresses + * @param refresh force cache refresh + * @returns whirlpool accounts + */ + public async listPools( + addresses: Address[], + refresh: boolean + ): Promise<(WhirlpoolData | null)[]> { + return this.list(AddressUtil.toPubKeys(addresses), ParsableWhirlpool, refresh); + } + + /** + * Retrieve a list of cached position accounts. Fetch from rpc for cache misses. + * + * @param addresses position addresses + * @param refresh force cache refresh + * @returns position accounts + */ + public async listPositions( + addresses: Address[], + refresh: boolean + ): Promise<(PositionData | null)[]> { + return this.list(AddressUtil.toPubKeys(addresses), ParsablePosition, refresh); + } + + /** + * Retrieve a list of cached tick array accounts. Fetch from rpc for cache misses. + * + * @param addresses tick array addresses + * @param refresh force cache refresh + * @returns tick array accounts + */ + public async listTickArrays( + addresses: Address[], + refresh: boolean + ): Promise<(TickArrayData | null)[]> { + return this.list(AddressUtil.toPubKeys(addresses), ParsableTickArray, refresh); + } + + /** + * Retrieve a list of cached token info accounts. Fetch from rpc for cache misses. + * + * @param addresses token info addresses + * @param refresh force cache refresh + * @returns token info accounts + */ + public async listTokenInfos( + addresses: Address[], + refresh: boolean + ): Promise<(AccountInfo | null)[]> { + return this.list(AddressUtil.toPubKeys(addresses), ParsableTokenInfo, refresh); + } + + /** + * Retrieve a list of cached mint info accounts. Fetch from rpc for cache misses. + * + * @param addresses mint info addresses + * @param refresh force cache refresh + * @returns mint info accounts + */ + public async listMintInfos(addresses: Address[], refresh: boolean): Promise<(MintInfo | null)[]> { + return this.list(AddressUtil.toPubKeys(addresses), ParsableMintInfo, refresh); + } + + /** + * Update the cached value of all entities currently in the cache. + * Uses batched rpc request for network efficient fetch. + */ + public async refreshAll(): Promise { + const addresses: string[] = Object.keys(this._cache); + const data = await this.bulkRequest(addresses); + + for (const [idx, [key, cachedContent]] of Object.entries(this._cache).entries()) { + const entity = cachedContent.entity; + const value = entity.parse(data[idx]); + + this._cache[key] = { entity, value }; + } + } + + /*** Private Methods ***/ + + /** + * Retrieve from cache or fetch from rpc, an account + */ + private async get( + address: PublicKey, + entity: ParsableEntity, + refresh: boolean + ): Promise { + const key = address.toBase58(); + const cachedValue: CachedValue | null | undefined = this._cache[key]?.value; + + if (cachedValue !== undefined && !refresh) { + return cachedValue as T | null; + } + + const accountInfo = await this.connection.getAccountInfo(address); + const accountData = accountInfo?.data; + const value = entity.parse(accountData); + this._cache[key] = { entity, value }; + + return value; + } + + /** + * Retrieve from cache or fetch from rpc, a list of accounts + */ + private async list( + addresses: PublicKey[], + entity: ParsableEntity, + refresh: boolean + ): Promise<(T | null)[]> { + const keys = addresses.map((address) => address.toBase58()); + const cachedValues: [string, CachedValue | null | undefined][] = keys.map((key) => [ + key, + refresh ? undefined : this._cache[key]?.value, + ]); + + /* Look for accounts not found in cache */ + const undefinedAccounts: { cacheIndex: number; key: string }[] = []; + cachedValues.forEach(([key, value], cacheIndex) => { + if (value === undefined) { + undefinedAccounts.push({ cacheIndex, key }); + } + }); + + /* Fetch accounts not found in cache */ + if (undefinedAccounts.length > 0) { + const data = await this.bulkRequest(undefinedAccounts.map((account) => account.key)); + undefinedAccounts.forEach(({ cacheIndex, key }, dataIndex) => { + const value = entity.parse(data[dataIndex]); + invariant(cachedValues[cacheIndex]?.[1] === undefined, "unexpected non-undefined value"); + cachedValues[cacheIndex] = [key, value]; + this._cache[key] = { entity, value }; + }); + } + + const result = cachedValues + .map(([_, value]) => value) + .filter((value): value is T | null => value !== undefined); + invariant(result.length === addresses.length, "not enough results fetched"); + return result; + } + + /** + * Make batch rpc request + */ + private async bulkRequest(addresses: string[]): Promise<(Buffer | null)[]> { + const responses: Promise[] = []; + const chunk = 100; // getMultipleAccounts has limitation of 100 accounts per request + + for (let i = 0; i < addresses.length; i += chunk) { + const addressesSubset = addresses.slice(i, i + chunk); + const res = (this.connection as any)._rpcRequest("getMultipleAccounts", [ + addressesSubset, + { commitment: this.connection.commitment }, + ]); + responses.push(res); + } + + const combinedResult: (Buffer | null)[] = []; + + (await Promise.all(responses)).forEach((res) => { + invariant(!res.error, `bulkRequest result error: ${res.error}`); + invariant(!!res.result?.value, "bulkRequest no value"); + + res.result.value.forEach((account) => { + if (!account || account.data[1] !== "base64") { + combinedResult.push(null); + } else { + combinedResult.push(Buffer.from(account.data[0], account.data[1])); + } + }); + }); + + invariant(combinedResult.length === addresses.length, "bulkRequest not enough results"); + return combinedResult; + } +} diff --git a/sdk/src/network/public/index.ts b/sdk/src/network/public/index.ts new file mode 100644 index 0000000..4c47a86 --- /dev/null +++ b/sdk/src/network/public/index.ts @@ -0,0 +1,2 @@ +export * from "./fetcher"; +export * from "./parsing"; diff --git a/sdk/src/network/public/parsing.ts b/sdk/src/network/public/parsing.ts new file mode 100644 index 0000000..75ff748 --- /dev/null +++ b/sdk/src/network/public/parsing.ts @@ -0,0 +1,212 @@ +import { AccountInfo, MintInfo, MintLayout, u64 } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; +import { + WhirlpoolsConfigData, + WhirlpoolData, + PositionData, + TickArrayData, + AccountName, + FeeTierData, +} from "../../types/public"; +import { AccountsCoder, Coder, Idl } from "@project-serum/anchor"; +import * as WhirlpoolIDL from "../../artifacts/whirlpool.json"; +import { TokenUtil } from "@orca-so/common-sdk"; + +/** + * Static abstract class definition to parse entities. + * @category Parsables + */ +export interface ParsableEntity { + /** + * Parse account data + * + * @param accountData Buffer data for the entity + * @returns Parsed entity + */ + parse: (accountData: Buffer | undefined | null) => T | null; +} + +/** + * @category Parsables + */ +@staticImplements>() +export class ParsableWhirlpoolsConfig { + private constructor() {} + + public static parse(data: Buffer | undefined | null): WhirlpoolsConfigData | null { + if (!data) { + return null; + } + + try { + return parseAnchorAccount(AccountName.WhirlpoolsConfig, data); + } catch (e) { + console.error(`error while parsing WhirlpoolsConfig: ${e}`); + return null; + } + } +} + +/** + * @category Parsables + */ +@staticImplements>() +export class ParsableWhirlpool { + private constructor() {} + + public static parse(data: Buffer | undefined | null): WhirlpoolData | null { + if (!data) { + return null; + } + + try { + return parseAnchorAccount(AccountName.Whirlpool, data); + } catch (e) { + console.error(`error while parsing Whirlpool: ${e}`); + return null; + } + } +} + +/** + * @category Parsables + */ +@staticImplements>() +export class ParsablePosition { + private constructor() {} + + public static parse(data: Buffer | undefined | null): PositionData | null { + if (!data) { + return null; + } + + try { + return parseAnchorAccount(AccountName.Position, data); + } catch (e) { + console.error(`error while parsing Position: ${e}`); + return null; + } + } +} + +/** + * @category Parsables + */ +@staticImplements>() +export class ParsableTickArray { + private constructor() {} + + public static parse(data: Buffer | undefined | null): TickArrayData | null { + if (!data) { + return null; + } + + try { + return parseAnchorAccount(AccountName.TickArray, data); + } catch (e) { + console.error(`error while parsing TickArray: ${e}`); + return null; + } + } +} + +/** + * @category Parsables + */ +@staticImplements>() +export class ParsableFeeTier { + private constructor() {} + + public static parse(data: Buffer | undefined | null): FeeTierData | null { + if (!data) { + return null; + } + + try { + return parseAnchorAccount(AccountName.FeeTier, data); + } catch (e) { + console.error(`error while parsing FeeTier: ${e}`); + return null; + } + } +} + +/** + * @category Parsables + */ +@staticImplements>() +export class ParsableTokenInfo { + private constructor() {} + + public static parse(data: Buffer | undefined | null): AccountInfo | null { + if (!data) { + return null; + } + + try { + return TokenUtil.deserializeTokenAccount(data); + } catch (e) { + console.error(`error while parsing TokenAccount: ${e}`); + return null; + } + } +} + +/** + * @category Parsables + */ +@staticImplements>() +export class ParsableMintInfo { + private constructor() {} + + public static parse(data: Buffer | undefined | null): MintInfo | null { + if (!data) { + return null; + } + + try { + const buffer = MintLayout.decode(data); + const mintInfo: MintInfo = { + mintAuthority: + buffer.mintAuthorityOption === 0 ? null : new PublicKey(buffer.mintAuthority), + supply: u64.fromBuffer(buffer.supply), + decimals: buffer.decimals, + isInitialized: buffer.isInitialized !== 0, + freezeAuthority: + buffer.freezeAuthority === 0 ? null : new PublicKey(buffer.freezeAuthority), + }; + + return mintInfo; + } catch (e) { + console.error(`error while parsing MintInfo: ${e}`); + return null; + } + } +} + +/** + * Class decorator to define an interface with static methods + * Reference: https://github.com/Microsoft/TypeScript/issues/13462#issuecomment-295685298 + */ +function staticImplements() { + return (constructor: U) => { + constructor; + }; +} + +const WhirlpoolCoder = new Coder(WhirlpoolIDL as Idl); + +function parseAnchorAccount(accountName: AccountName, data: Buffer) { + const discriminator = AccountsCoder.accountDiscriminator(accountName); + if (discriminator.compare(data.slice(0, 8))) { + console.error("incorrect account name during parsing"); + return null; + } + + try { + return WhirlpoolCoder.accounts.decode(accountName, data); + } catch (_e) { + console.error("unknown account name during parsing"); + return null; + } +} diff --git a/sdk/src/quotes/public/collect-fees-quote.ts b/sdk/src/quotes/public/collect-fees-quote.ts new file mode 100644 index 0000000..dfbc9f7 --- /dev/null +++ b/sdk/src/quotes/public/collect-fees-quote.ts @@ -0,0 +1,116 @@ +import { MathUtil } from "@orca-so/common-sdk"; +import { BN } from "@project-serum/anchor"; +import { PositionData, TickData, WhirlpoolData } from "../../types/public"; + +/** + * @category Quotes + */ +export type CollectFeesQuoteParam = { + whirlpool: WhirlpoolData; + position: PositionData; + tickLower: TickData; + tickUpper: TickData; +}; + +/** + * @category Quotes + */ +export type CollectFeesQuote = { + feeOwedA: BN; + feeOwedB: BN; +}; + +/** + * Get a quote on the outstanding fees owed to a position. + * + * @category Quotes + * @param param A collection of fetched Whirlpool accounts to faciliate the quote. + * @returns A quote object containing the fees owed for each token in the pool. + */ +export function collectFeesQuote(param: CollectFeesQuoteParam): CollectFeesQuote { + const { whirlpool, position, tickLower, tickUpper } = param; + + const { + tickCurrentIndex, + feeGrowthGlobalA: feeGrowthGlobalAX64, + feeGrowthGlobalB: feeGrowthGlobalBX64, + } = whirlpool; + const { + tickLowerIndex, + tickUpperIndex, + liquidity, + feeOwedA, + feeOwedB, + feeGrowthCheckpointA: feeGrowthCheckpointAX64, + feeGrowthCheckpointB: feeGrowthCheckpointBX64, + } = position; + const { + feeGrowthOutsideA: tickLowerFeeGrowthOutsideAX64, + feeGrowthOutsideB: tickLowerFeeGrowthOutsideBX64, + } = tickLower; + const { + feeGrowthOutsideA: tickUpperFeeGrowthOutsideAX64, + feeGrowthOutsideB: tickUpperFeeGrowthOutsideBX64, + } = tickUpper; + + // Calculate the fee growths inside the position + + let feeGrowthBelowAX64: BN | null = null; + let feeGrowthBelowBX64: BN | null = null; + + if (tickCurrentIndex < tickLowerIndex) { + feeGrowthBelowAX64 = MathUtil.subUnderflowU128( + feeGrowthGlobalAX64, + tickLowerFeeGrowthOutsideAX64 + ); + feeGrowthBelowBX64 = MathUtil.subUnderflowU128( + feeGrowthGlobalBX64, + tickLowerFeeGrowthOutsideBX64 + ); + } else { + feeGrowthBelowAX64 = tickLowerFeeGrowthOutsideAX64; + feeGrowthBelowBX64 = tickLowerFeeGrowthOutsideBX64; + } + + let feeGrowthAboveAX64: BN | null = null; + let feeGrowthAboveBX64: BN | null = null; + + if (tickCurrentIndex < tickUpperIndex) { + feeGrowthAboveAX64 = tickUpperFeeGrowthOutsideAX64; + feeGrowthAboveBX64 = tickUpperFeeGrowthOutsideBX64; + } else { + feeGrowthAboveAX64 = MathUtil.subUnderflowU128( + feeGrowthGlobalAX64, + tickUpperFeeGrowthOutsideAX64 + ); + feeGrowthAboveBX64 = MathUtil.subUnderflowU128( + feeGrowthGlobalBX64, + tickUpperFeeGrowthOutsideBX64 + ); + } + + const feeGrowthInsideAX64 = MathUtil.subUnderflowU128( + MathUtil.subUnderflowU128(feeGrowthGlobalAX64, feeGrowthBelowAX64), + feeGrowthAboveAX64 + ); + const feeGrowthInsideBX64 = MathUtil.subUnderflowU128( + MathUtil.subUnderflowU128(feeGrowthGlobalBX64, feeGrowthBelowBX64), + feeGrowthAboveBX64 + ); + + // Calculate the updated fees owed + const feeOwedADelta = MathUtil.subUnderflowU128(feeGrowthInsideAX64, feeGrowthCheckpointAX64) + .mul(liquidity) + .shrn(64); + const feeOwedBDelta = MathUtil.subUnderflowU128(feeGrowthInsideBX64, feeGrowthCheckpointBX64) + .mul(liquidity) + .shrn(64); + + const updatedFeeOwedA = feeOwedA.add(feeOwedADelta); + const updatedFeeOwedB = feeOwedB.add(feeOwedBDelta); + + return { + feeOwedA: updatedFeeOwedA, + feeOwedB: updatedFeeOwedB, + }; +} diff --git a/sdk/src/quotes/public/collect-rewards-quote.ts b/sdk/src/quotes/public/collect-rewards-quote.ts new file mode 100644 index 0000000..c49f9db --- /dev/null +++ b/sdk/src/quotes/public/collect-rewards-quote.ts @@ -0,0 +1,125 @@ +import { MathUtil } from "@orca-so/common-sdk"; +import { BN } from "@project-serum/anchor"; +import invariant from "tiny-invariant"; +import { NUM_REWARDS, PositionData, TickData, WhirlpoolData } from "../../types/public"; +import { PoolUtil } from "../../utils/public/pool-utils"; + +/** + * @category Quotes + */ +export type CollectRewardsQuoteParam = { + whirlpool: WhirlpoolData; + position: PositionData; + tickLower: TickData; + tickUpper: TickData; +}; + +/** + * @category Quotes + */ +export type CollectRewardsQuote = [BN | undefined, BN | undefined, BN | undefined]; + +/** + * Get a quote on the outstanding rewards owed to a position. + * + * @category Quotes + * @param param A collection of fetched Whirlpool accounts to faciliate the quote. + * @returns A quote object containing the rewards owed for each reward in the pool. + */ +export function collectRewardsQuote(param: CollectRewardsQuoteParam): CollectRewardsQuote { + const { whirlpool, position, tickLower, tickUpper } = param; + + const { tickCurrentIndex, rewardInfos: whirlpoolRewardsInfos } = whirlpool; + const { tickLowerIndex, tickUpperIndex, liquidity, rewardInfos } = position; + + // Calculate the reward growths inside the position + + const range = [...Array(NUM_REWARDS).keys()]; + const rewardGrowthsBelowX64: BN[] = range.map(() => new BN(0)); + const rewardGrowthsAboveX64: BN[] = range.map(() => new BN(0)); + + for (const i of range) { + const rewardInfo = whirlpoolRewardsInfos[i]; + invariant(!!rewardInfo, "whirlpoolRewardsInfos cannot be undefined"); + + const growthGlobalX64 = rewardInfo.growthGlobalX64; + const lowerRewardGrowthsOutside = tickLower.rewardGrowthsOutside[i]; + const upperRewardGrowthsOutside = tickUpper.rewardGrowthsOutside[i]; + invariant(!!lowerRewardGrowthsOutside, "lowerRewardGrowthsOutside cannot be undefined"); + invariant(!!upperRewardGrowthsOutside, "upperRewardGrowthsOutside cannot be undefined"); + + if (tickCurrentIndex < tickLowerIndex) { + rewardGrowthsBelowX64[i] = MathUtil.subUnderflowU128( + growthGlobalX64, + lowerRewardGrowthsOutside + ); + } else { + rewardGrowthsBelowX64[i] = lowerRewardGrowthsOutside; + } + + if (tickCurrentIndex < tickUpperIndex) { + rewardGrowthsAboveX64[i] = upperRewardGrowthsOutside; + } else { + rewardGrowthsAboveX64[i] = MathUtil.subUnderflowU128( + growthGlobalX64, + upperRewardGrowthsOutside + ); + } + } + + const rewardGrowthsInsideX64: [BN, boolean][] = range.map(() => [new BN(0), false]); + + for (const i of range) { + const rewardInfo = whirlpoolRewardsInfos[i]; + invariant(!!rewardInfo, "whirlpoolRewardsInfos cannot be undefined"); + + const isRewardInitialized = PoolUtil.isRewardInitialized(rewardInfo); + + if (isRewardInitialized) { + const growthBelowX64 = rewardGrowthsBelowX64[i]; + const growthAboveX64 = rewardGrowthsAboveX64[i]; + invariant(!!growthBelowX64, "growthBelowX64 cannot be undefined"); + invariant(!!growthAboveX64, "growthAboveX64 cannot be undefined"); + + const growthInsde = MathUtil.subUnderflowU128( + MathUtil.subUnderflowU128(rewardInfo.growthGlobalX64, growthBelowX64), + growthAboveX64 + ); + rewardGrowthsInsideX64[i] = [growthInsde, true]; + } + } + + // Calculate the updated rewards owed + + const updatedRewardInfosX64: BN[] = range.map(() => new BN(0)); + + for (const i of range) { + const growthInsideX64 = rewardGrowthsInsideX64[i]; + invariant(!!growthInsideX64, "growthInsideX64 cannot be undefined"); + + const [rewardGrowthInsideX64, isRewardInitialized] = growthInsideX64; + + if (isRewardInitialized) { + const rewardInfo = rewardInfos[i]; + invariant(!!rewardInfo, "rewardInfo cannot be undefined"); + + const amountOwedX64 = rewardInfo.amountOwed.shln(64); + const growthInsideCheckpointX64 = rewardInfo.growthInsideCheckpoint; + updatedRewardInfosX64[i] = amountOwedX64.add( + MathUtil.subUnderflowU128(rewardGrowthInsideX64, growthInsideCheckpointX64).mul(liquidity) + ); + } + } + + invariant(rewardGrowthsInsideX64.length >= 3, "rewards length is less than 3"); + + const rewardExistsA = rewardGrowthsInsideX64[0]?.[1]; + const rewardExistsB = rewardGrowthsInsideX64[1]?.[1]; + const rewardExistsC = rewardGrowthsInsideX64[2]?.[1]; + + const rewardOwedA = rewardExistsA ? updatedRewardInfosX64[0]?.shrn(64) : undefined; + const rewardOwedB = rewardExistsB ? updatedRewardInfosX64[1]?.shrn(64) : undefined; + const rewardOwedC = rewardExistsC ? updatedRewardInfosX64[2]?.shrn(64) : undefined; + + return [rewardOwedA, rewardOwedB, rewardOwedC]; +} diff --git a/sdk/src/quotes/public/decrease-liquidity-quote.ts b/sdk/src/quotes/public/decrease-liquidity-quote.ts new file mode 100644 index 0000000..f9eb91a --- /dev/null +++ b/sdk/src/quotes/public/decrease-liquidity-quote.ts @@ -0,0 +1,155 @@ +import { Percentage } from "@orca-so/common-sdk"; +import { ZERO } from "@orca-so/sdk"; +import { BN } from "@project-serum/anchor"; +import { u64 } from "@solana/spl-token"; +import invariant from "tiny-invariant"; +import { DecreaseLiquidityInput } from "../../instructions"; +import { + PositionUtil, + PositionStatus, + adjustForSlippage, + getTokenAFromLiquidity, + getTokenBFromLiquidity, +} from "../../utils/position-util"; +import { PriceMath, TickUtil } from "../../utils/public"; +import { Position, Whirlpool } from "../../whirlpool-client"; + +/** + * @category Quotes + * @param liquidity - The desired liquidity to withdraw from the Whirlpool + * @param tickCurrentIndex - The Whirlpool's current tickIndex + * @param sqrtPrice - The Whirlpool's current sqrtPrice + * @param tickLowerIndex - The lower index of the position that we are withdrawing from. + * @param tickUpperIndex - The upper index of the position that we are withdrawing from. + * @param slippageTolerance - The maximum slippage allowed when calculating the minimum tokens received. + */ +export type DecreaseLiquidityQuoteParam = { + liquidity: u64; + tickCurrentIndex: number; + sqrtPrice: BN; + tickLowerIndex: number; + tickUpperIndex: number; + slippageTolerance: Percentage; +}; + +export async function decreaseLiquidityQuoteByLiquidity( + liquidity: u64, + slippageTolerance: Percentage, + position: Position, + whirlpool: Whirlpool +) { + const positionData = position.getData(); + const whirlpoolData = whirlpool.getData(); + + invariant( + liquidity.lte(positionData.liquidity), + "Quote liquidity is more than the position liquidity." + ); + + return decreaseLiquidityQuoteByLiquidityWithParams({ + liquidity, + slippageTolerance, + tickLowerIndex: positionData.tickLowerIndex, + tickUpperIndex: positionData.tickUpperIndex, + sqrtPrice: whirlpoolData.sqrtPrice, + tickCurrentIndex: whirlpoolData.tickCurrentIndex, + }); +} + +/** + * Get an estimated quote on the minimum tokens receivable based on the desired withdraw liquidity value. + * + * @category Quotes + * @param param DecreaseLiquidityQuoteParam + * @returns An DecreaseLiquidityInput object detailing the tokenMin & liquidity values to use when calling decrease-liquidity-ix. + */ +export function decreaseLiquidityQuoteByLiquidityWithParams( + param: DecreaseLiquidityQuoteParam +): DecreaseLiquidityInput { + invariant(TickUtil.checkTickInBounds(param.tickLowerIndex), "tickLowerIndex is out of bounds."); + invariant(TickUtil.checkTickInBounds(param.tickUpperIndex), "tickUpperIndex is out of bounds."); + invariant( + TickUtil.checkTickInBounds(param.tickCurrentIndex), + "tickCurrentIndex is out of bounds." + ); + + const positionStatus = PositionUtil.getPositionStatus( + param.tickCurrentIndex, + param.tickLowerIndex, + param.tickUpperIndex + ); + + switch (positionStatus) { + case PositionStatus.BelowRange: + return quotePositionBelowRange(param); + case PositionStatus.InRange: + return quotePositionInRange(param); + case PositionStatus.AboveRange: + return quotePositionAboveRange(param); + default: + throw new Error(`type ${positionStatus} is an unknown PositionStatus`); + } +} + +function quotePositionBelowRange(param: DecreaseLiquidityQuoteParam): DecreaseLiquidityInput { + const { tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance } = param; + + const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); + const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); + + const minTokenA = adjustForSlippage( + getTokenAFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false), + slippageTolerance, + false + ); + + return { + tokenMinA: minTokenA, + tokenMinB: ZERO, + liquidityAmount: liquidity, + }; +} + +function quotePositionInRange(param: DecreaseLiquidityQuoteParam): DecreaseLiquidityInput { + const { sqrtPrice, tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance } = param; + + const sqrtPriceX64 = sqrtPrice; + const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); + const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); + + const minTokenA = adjustForSlippage( + getTokenAFromLiquidity(liquidity, sqrtPriceX64, sqrtPriceUpperX64, false), + slippageTolerance, + false + ); + const minTokenB = adjustForSlippage( + getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceX64, false), + slippageTolerance, + false + ); + + return { + tokenMinA: minTokenA, + tokenMinB: minTokenB, + liquidityAmount: liquidity, + }; +} + +function quotePositionAboveRange(param: DecreaseLiquidityQuoteParam): DecreaseLiquidityInput { + const { tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance: slippageTolerance } = param; + + const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); + const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); + + const minTokenB = adjustForSlippage( + getTokenBFromLiquidity(liquidity, sqrtPriceLowerX64, sqrtPriceUpperX64, false), + slippageTolerance, + false + ); + + return { + tokenMinA: ZERO, + tokenMinB: minTokenB, + liquidityAmount: liquidity, + }; +} diff --git a/sdk/src/quotes/public/increase-liquidity-quote.ts b/sdk/src/quotes/public/increase-liquidity-quote.ts new file mode 100644 index 0000000..61d3da6 --- /dev/null +++ b/sdk/src/quotes/public/increase-liquidity-quote.ts @@ -0,0 +1,230 @@ +import { DecimalUtil, Percentage, ZERO } from "@orca-so/common-sdk"; +import { BN } from "@project-serum/anchor"; +import { u64 } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; +import Decimal from "decimal.js"; +import invariant from "tiny-invariant"; +import { IncreaseLiquidityInput } from "../../instructions"; +import { TokenInfo, WhirlpoolData } from "../../types/public"; +import { + PositionUtil, + PositionStatus, + getLiquidityFromTokenA, + adjustForSlippage, + getTokenAFromLiquidity, + getTokenBFromLiquidity, + getLiquidityFromTokenB, +} from "../../utils/position-util"; +import { PriceMath, TickUtil } from "../../utils/public"; +import { Whirlpool } from "../../whirlpool-client"; + +/** + * @category Quotes + * @param inputTokenAmount - The amount of input tokens to deposit. + * @param inputTokenMint - The mint of the input token the user would like to deposit. + * @param tokenMintA - The mint of tokenA in the Whirlpool the user is depositing into. + * @param tokenMintB -The mint of tokenB in the Whirlpool the user is depositing into. + * @param tickCurrentIndex - The Whirlpool's current tickIndex + * @param sqrtPrice - The Whirlpool's current sqrtPrice + * @param tickLowerIndex - The lower index of the position that we are withdrawing from. + * @param tickUpperIndex - The upper index of the position that we are withdrawing from. + * @param slippageTolerance - The maximum slippage allowed when calculating the minimum tokens received. + */ +export type IncreaseLiquidityQuoteParam = { + inputTokenAmount: u64; + inputTokenMint: PublicKey; + tokenMintA: PublicKey; + tokenMintB: PublicKey; + tickCurrentIndex: number; + sqrtPrice: BN; + tickLowerIndex: number; + tickUpperIndex: number; + slippageTolerance: Percentage; +}; + +export function increaseLiquidityQuoteByInputToken( + inputTokenMint: PublicKey, + inputTokenAmount: Decimal, + tickLower: number, + tickUpper: number, + slippageTolerance: Percentage, + whirlpool: Whirlpool +) { + const data = whirlpool.getData(); + const tokenAInfo = whirlpool.getTokenAInfo(); + const tokenBInfo = whirlpool.getTokenBInfo(); + + const inputTokenInfo = inputTokenMint.equals(tokenAInfo.mint) ? tokenAInfo : tokenBInfo; + + return increaseLiquidityQuoteByInputTokenWithParams({ + inputTokenMint: inputTokenMint, + inputTokenAmount: DecimalUtil.toU64(inputTokenAmount, inputTokenInfo.decimals), + tickLowerIndex: TickUtil.getInitializableTickIndex(tickLower, data.tickSpacing), + tickUpperIndex: TickUtil.getInitializableTickIndex(tickUpper, data.tickSpacing), + slippageTolerance, + ...data, + }); +} + +/** + * Get an estimated quote on the maximum tokens required to deposit based on a specified input token amount. + * + * @category Quotes + * @param param IncreaseLiquidityQuoteParam + * @returns An IncreaseLiquidityInput object detailing the required token amounts & liquidity values to use when calling increase-liquidity-ix. + */ +export function increaseLiquidityQuoteByInputTokenWithParams( + param: IncreaseLiquidityQuoteParam +): IncreaseLiquidityInput { + invariant(TickUtil.checkTickInBounds(param.tickLowerIndex), "tickLowerIndex is out of bounds."); + invariant(TickUtil.checkTickInBounds(param.tickUpperIndex), "tickUpperIndex is out of bounds."); + invariant( + param.inputTokenMint.equals(param.tokenMintA) || param.inputTokenMint.equals(param.tokenMintB), + `input token mint ${param.inputTokenMint.toBase58()} does not match any tokens in the provided pool.` + ); + + const positionStatus = PositionUtil.getPositionStatus( + param.tickCurrentIndex, + param.tickLowerIndex, + param.tickUpperIndex + ); + + switch (positionStatus) { + case PositionStatus.BelowRange: + return quotePositionBelowRange(param); + case PositionStatus.InRange: + return quotePositionInRange(param); + case PositionStatus.AboveRange: + return quotePositionAboveRange(param); + default: + throw new Error(`type ${positionStatus} is an unknown PositionStatus`); + } +} + +/*** Private ***/ + +function quotePositionBelowRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityInput { + const { + tokenMintA, + inputTokenMint, + inputTokenAmount, + tickLowerIndex, + tickUpperIndex, + slippageTolerance, + } = param; + + if (!tokenMintA.equals(inputTokenMint)) { + return { + tokenMaxA: ZERO, + tokenMaxB: ZERO, + liquidityAmount: ZERO, + }; + } + + const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); + const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); + + const liquidityAmount = getLiquidityFromTokenA( + inputTokenAmount, + sqrtPriceLowerX64, + sqrtPriceUpperX64, + false + ); + + const maxTokenA = adjustForSlippage( + getTokenAFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, true), + slippageTolerance, + true + ); + const maxTokenB = ZERO; + + return { + tokenMaxA: maxTokenA, + tokenMaxB: maxTokenB, + liquidityAmount, + }; +} + +function quotePositionInRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityInput { + const { + tokenMintA, + sqrtPrice, + inputTokenMint, + inputTokenAmount, + tickLowerIndex, + tickUpperIndex, + slippageTolerance, + } = param; + + const sqrtPriceX64 = sqrtPrice; + const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); + const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); + + let [tokenAmountA, tokenAmountB] = tokenMintA.equals(inputTokenMint) + ? [inputTokenAmount, undefined] + : [undefined, inputTokenAmount]; + + let liquidityAmount: BN; + + if (tokenAmountA) { + liquidityAmount = getLiquidityFromTokenA(tokenAmountA, sqrtPriceX64, sqrtPriceUpperX64, false); + tokenAmountA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true); + tokenAmountB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true); + } else if (tokenAmountB) { + liquidityAmount = getLiquidityFromTokenB(tokenAmountB, sqrtPriceLowerX64, sqrtPriceX64, false); + tokenAmountA = getTokenAFromLiquidity(liquidityAmount, sqrtPriceX64, sqrtPriceUpperX64, true); + tokenAmountB = getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceX64, true); + } else { + throw new Error("invariant violation"); + } + + const maxTokenA = adjustForSlippage(tokenAmountA, slippageTolerance, true); + const maxTokenB = adjustForSlippage(tokenAmountB, slippageTolerance, true); + + return { + tokenMaxA: maxTokenA, + tokenMaxB: maxTokenB, + liquidityAmount, + }; +} + +function quotePositionAboveRange(param: IncreaseLiquidityQuoteParam): IncreaseLiquidityInput { + const { + tokenMintB, + inputTokenMint, + inputTokenAmount, + tickLowerIndex, + tickUpperIndex, + slippageTolerance, + } = param; + + if (!tokenMintB.equals(inputTokenMint)) { + return { + tokenMaxA: ZERO, + tokenMaxB: ZERO, + liquidityAmount: ZERO, + }; + } + + const sqrtPriceLowerX64 = PriceMath.tickIndexToSqrtPriceX64(tickLowerIndex); + const sqrtPriceUpperX64 = PriceMath.tickIndexToSqrtPriceX64(tickUpperIndex); + const liquidityAmount = getLiquidityFromTokenB( + inputTokenAmount, + sqrtPriceLowerX64, + sqrtPriceUpperX64, + false + ); + + const maxTokenA = ZERO; + const maxTokenB = adjustForSlippage( + getTokenBFromLiquidity(liquidityAmount, sqrtPriceLowerX64, sqrtPriceUpperX64, true), + slippageTolerance, + true + ); + + return { + tokenMaxA: maxTokenA, + tokenMaxB: maxTokenB, + liquidityAmount, + }; +} diff --git a/sdk/src/quotes/public/index.ts b/sdk/src/quotes/public/index.ts new file mode 100644 index 0000000..ccf3d6b --- /dev/null +++ b/sdk/src/quotes/public/index.ts @@ -0,0 +1,5 @@ +export * from "./increase-liquidity-quote"; +export * from "./decrease-liquidity-quote"; +export * from "./collect-fees-quote"; +export * from "./collect-rewards-quote"; +export * from "./swap-quote"; diff --git a/sdk/src/quotes/public/swap-quote.ts b/sdk/src/quotes/public/swap-quote.ts new file mode 100644 index 0000000..3028d33 --- /dev/null +++ b/sdk/src/quotes/public/swap-quote.ts @@ -0,0 +1,439 @@ +import { Address, BN } from "@project-serum/anchor"; +import { PublicKey } from "@solana/web3.js"; +import { u64 } from "@solana/spl-token"; +import invariant from "tiny-invariant"; +import { PoolUtil } from "../../utils/public/pool-utils"; +import { + SwapDirection, + AmountSpecified, + adjustAmountForSlippage, + getAmountFixedDelta, + getNextSqrtPrice, + getAmountUnfixedDelta, +} from "../../utils/position-util"; +import { SwapInput } from "../../instructions"; +import { + WhirlpoolData, + TickArrayData, + MAX_TICK_ARRAY_CROSSINGS, + MIN_SQRT_PRICE, + MAX_SQRT_PRICE, + TICK_ARRAY_SIZE, +} from "../../types/public"; +import { AddressUtil, MathUtil, Percentage, ZERO } from "@orca-so/common-sdk"; +import { PriceMath, TickArrayUtil, TickUtil } from "../../utils/public"; + +/** + * @category Quotes + */ +export type SwapQuoteParam = { + whirlpoolAddress: Address; + swapTokenMint: Address; + whirlpoolData: WhirlpoolData; + tokenAmount: u64; + amountSpecifiedIsInput: boolean; + slippageTolerance: Percentage; + tickArrayAddresses: PublicKey[]; + tickArrays: (TickArrayData | null)[]; +}; + +/** + * @category Quotes + */ +export type SwapQuote = { + estimatedAmountIn: u64; + estimatedAmountOut: u64; +} & SwapInput; + +/** + * TODO: Bug - The quote swap loop will ignore the first initialized tick of the next array on array traversal + * if the tick is on offset 0. + * TODO: Build out a comprhensive integ test-suite for all tick traversal. The test suite would confirm + * edge cases for both smart-contract and quote (they must equal). The original test-cases in SDK can be ported + * over in this effort. + * TODO: Think about other types of quote that we want to write (ex. limit by price to get trade amount etc) + * + * Get an estimated quote of a swap + * + * @category Quotes + * @param param a SwapQuoteParam object detailing parameters of the swap + * @return a SwapQuote on the estimated amountIn & amountOut of the swap and a SwapInput to use on the swap instruction. + */ +export function swapQuoteByInputToken(param: SwapQuoteParam): SwapQuote { + const { + whirlpoolAddress, + swapTokenMint, + tokenAmount, + whirlpoolData, + amountSpecifiedIsInput, + slippageTolerance, + tickArrays, + tickArrayAddresses, + } = param; + + const swapDirection = + AddressUtil.toPubKey(swapTokenMint).equals(whirlpoolData.tokenMintA) === amountSpecifiedIsInput + ? SwapDirection.AtoB + : SwapDirection.BtoA; + const amountSpecified = amountSpecifiedIsInput ? AmountSpecified.Input : AmountSpecified.Output; + + const { amountIn, amountOut, sqrtPriceLimitX64, tickArraysCrossed } = simulateSwap( + { + whirlpoolAddress, + whirlpoolData, + amountSpecified, + swapDirection, + tickArrays, + }, + { + amount: tokenAmount, + currentSqrtPriceX64: whirlpoolData.sqrtPrice, + currentTickIndex: whirlpoolData.tickCurrentIndex, + currentLiquidity: whirlpoolData.liquidity, + } + ); + + const otherAmountThreshold = adjustAmountForSlippage( + amountIn, + amountOut, + slippageTolerance, + amountSpecified + ); + + // Compute the traversed set of tick-arrays. Set the remaining slots to + //the last traversed array. + const traversedTickArrays = tickArrayAddresses.map((addr, index) => { + if (index < tickArraysCrossed) { + return addr; + } + return tickArrayAddresses[Math.max(tickArraysCrossed - 1, 0)]; + }); + + return { + amount: tokenAmount, + otherAmountThreshold, + sqrtPriceLimit: sqrtPriceLimitX64, + estimatedAmountIn: amountIn, + estimatedAmountOut: amountOut, + aToB: swapDirection === SwapDirection.AtoB, + amountSpecifiedIsInput, + tickArray0: traversedTickArrays[0], + tickArray1: traversedTickArrays[1], + tickArray2: traversedTickArrays[2], + }; +} + +type SwapSimulationBaseInput = { + whirlpoolAddress: Address; + whirlpoolData: WhirlpoolData; + amountSpecified: AmountSpecified; + swapDirection: SwapDirection; + tickArrays: (TickArrayData | null)[]; +}; + +type SwapSimulationInput = { + amount: BN; + currentSqrtPriceX64: BN; + currentTickIndex: number; + currentLiquidity: BN; +}; + +type SwapSimulationOutput = { + amountIn: BN; + amountOut: BN; + sqrtPriceLimitX64: BN; + tickArraysCrossed: number; +}; + +type SwapStepSimulationInput = { + sqrtPriceX64: BN; + tickIndex: number; + liquidity: BN; + amountRemaining: u64; + tickArraysCrossed: number; +}; + +type SwapStepSimulationOutput = { + nextSqrtPriceX64: BN; + nextTickIndex: number; + input: BN; + output: BN; + tickArraysCrossed: number; + hasReachedNextTick: boolean; +}; + +function simulateSwap( + baseInput: SwapSimulationBaseInput, + input: SwapSimulationInput +): SwapSimulationOutput { + const { amountSpecified, swapDirection } = baseInput; + + let { + currentTickIndex, + currentLiquidity, + amount: specifiedAmountLeft, + currentSqrtPriceX64, + } = input; + + invariant(!specifiedAmountLeft.eq(ZERO), "amount must be nonzero"); + + let otherAmountCalculated = ZERO; + + let tickArraysCrossed = 0; + let sqrtPriceLimitX64; + + while (specifiedAmountLeft.gt(ZERO)) { + if (tickArraysCrossed > MAX_TICK_ARRAY_CROSSINGS) { + throw Error("Crossed the maximum number of tick arrays"); + } + + const swapStepSimulationOutput: SwapStepSimulationOutput = simulateSwapStep(baseInput, { + sqrtPriceX64: currentSqrtPriceX64, + amountRemaining: specifiedAmountLeft, + tickIndex: currentTickIndex, + liquidity: currentLiquidity, + tickArraysCrossed, + }); + + const { input, output, nextSqrtPriceX64, nextTickIndex, hasReachedNextTick } = + swapStepSimulationOutput; + + const [specifiedAmountUsed, otherAmount] = resolveTokenAmounts(input, output, amountSpecified); + + specifiedAmountLeft = specifiedAmountLeft.sub(specifiedAmountUsed); + otherAmountCalculated = otherAmountCalculated.add(otherAmount); + + if (hasReachedNextTick) { + const nextTick = fetchTick(baseInput, nextTickIndex); + + currentLiquidity = calculateNewLiquidity( + currentLiquidity, + nextTick.liquidityNet, + swapDirection + ); + + currentTickIndex = swapDirection == SwapDirection.AtoB ? nextTickIndex - 1 : nextTickIndex; + } + + currentSqrtPriceX64 = nextSqrtPriceX64; + tickArraysCrossed = swapStepSimulationOutput.tickArraysCrossed; + + if (tickArraysCrossed > MAX_TICK_ARRAY_CROSSINGS) { + sqrtPriceLimitX64 = PriceMath.tickIndexToSqrtPriceX64(nextTickIndex); + } + } + + const [inputAmount, outputAmount] = resolveTokenAmounts( + input.amount.sub(specifiedAmountLeft), + otherAmountCalculated, + amountSpecified + ); + + if (!sqrtPriceLimitX64) { + if (swapDirection === SwapDirection.AtoB) { + sqrtPriceLimitX64 = new BN(MIN_SQRT_PRICE); + } else { + sqrtPriceLimitX64 = new BN(MAX_SQRT_PRICE); + } + } + + // Return sqrtPriceLimit if 3 tick arrays crossed + return { + amountIn: inputAmount, + amountOut: outputAmount, + sqrtPriceLimitX64, + tickArraysCrossed, + }; +} + +function simulateSwapStep( + baseInput: SwapSimulationBaseInput, + input: SwapStepSimulationInput +): SwapStepSimulationOutput { + const { whirlpoolData, amountSpecified, swapDirection } = baseInput; + + const { feeRate } = whirlpoolData; + + const feeRatePercentage = PoolUtil.getFeeRate(feeRate); + + const { amountRemaining, liquidity, sqrtPriceX64, tickIndex, tickArraysCrossed } = input; + + const { tickIndex: nextTickIndex, tickArraysCrossed: tickArraysCrossedUpdate } = + // Return last tick in tick array if max tick arrays crossed + // Error out of this gets called for another iteration + getNextInitializedTickIndex(baseInput, tickIndex, tickArraysCrossed); + + const targetSqrtPriceX64 = PriceMath.tickIndexToSqrtPriceX64(nextTickIndex); + + let fixedDelta = getAmountFixedDelta( + sqrtPriceX64, + targetSqrtPriceX64, + liquidity, + amountSpecified, + swapDirection + ); + + let amountCalculated = amountRemaining; + if (amountSpecified == AmountSpecified.Input) { + amountCalculated = calculateAmountAfterFees(amountRemaining, feeRatePercentage); + } + + const nextSqrtPriceX64 = amountCalculated.gte(fixedDelta) + ? targetSqrtPriceX64 // Fully utilize liquidity till upcoming (next/prev depending on swap type) initialized tick + : getNextSqrtPrice(sqrtPriceX64, liquidity, amountCalculated, amountSpecified, swapDirection); + + const hasReachedNextTick = nextSqrtPriceX64.eq(targetSqrtPriceX64); + + const unfixedDelta = getAmountUnfixedDelta( + sqrtPriceX64, + nextSqrtPriceX64, + liquidity, + amountSpecified, + swapDirection + ); + + if (!hasReachedNextTick) { + fixedDelta = getAmountFixedDelta( + sqrtPriceX64, + nextSqrtPriceX64, + liquidity, + amountSpecified, + swapDirection + ); + } + + let [inputDelta, outputDelta] = resolveTokenAmounts(fixedDelta, unfixedDelta, amountSpecified); + + // Cap output if output specified + if (amountSpecified == AmountSpecified.Output && outputDelta.gt(amountRemaining)) { + outputDelta = amountRemaining; + } + + if (amountSpecified == AmountSpecified.Input && !hasReachedNextTick) { + inputDelta = amountRemaining; + } else { + inputDelta = inputDelta.add(calculateFeesFromAmount(inputDelta, feeRatePercentage)); + } + + return { + nextTickIndex, + nextSqrtPriceX64, + input: inputDelta, + output: outputDelta, + tickArraysCrossed: tickArraysCrossedUpdate, + hasReachedNextTick, + }; +} + +function calculateAmountAfterFees(amount: u64, feeRate: Percentage): BN { + return amount.mul(feeRate.denominator.sub(feeRate.numerator)).div(feeRate.denominator); +} + +function calculateFeesFromAmount(amount: u64, feeRate: Percentage): BN { + return MathUtil.divRoundUp( + amount.mul(feeRate.numerator), + feeRate.denominator.sub(feeRate.numerator) + ); +} + +function calculateNewLiquidity(liquidity: BN, nextLiquidityNet: BN, swapDirection: SwapDirection) { + if (swapDirection == SwapDirection.AtoB) { + nextLiquidityNet = nextLiquidityNet.neg(); + } + + return liquidity.add(nextLiquidityNet); +} + +function resolveTokenAmounts( + specifiedTokenAmount: BN, + otherTokenAmount: BN, + amountSpecified: AmountSpecified +): [BN, BN] { + if (amountSpecified == AmountSpecified.Input) { + return [specifiedTokenAmount, otherTokenAmount]; + } else { + return [otherTokenAmount, specifiedTokenAmount]; + } +} + +function fetchTickArray(baseInput: SwapSimulationBaseInput, tickIndex: number) { + const { + tickArrays, + whirlpoolData: { tickSpacing }, + } = baseInput; + + const startTickArray = tickArrays[0]; + invariant(!!startTickArray, `tickArray is null at index 0`); + const sequenceStartIndex = startTickArray.startTickIndex; + const expectedArrayIndex = Math.abs( + Math.floor((tickIndex - sequenceStartIndex) / tickSpacing / TICK_ARRAY_SIZE) + ); + + invariant( + expectedArrayIndex > 0 || expectedArrayIndex < tickArrays.length, + `tickIndex ${tickIndex} assumes array-index of ${expectedArrayIndex} and is out of bounds for sequence` + ); + + const tickArray = tickArrays[expectedArrayIndex]; + invariant(!!tickArray, `tickArray is null at array-index ${expectedArrayIndex}`); + + return tickArray; +} + +function fetchTick(baseInput: SwapSimulationBaseInput, tickIndex: number) { + const tickArray = fetchTickArray(baseInput, tickIndex); + const { + whirlpoolData: { tickSpacing }, + } = baseInput; + return TickArrayUtil.getTickFromArray(tickArray, tickIndex, tickSpacing); +} + +function getNextInitializedTickIndex( + baseInput: SwapSimulationBaseInput, + currentTickIndex: number, + tickArraysCrossed: number +) { + const { + whirlpoolData: { tickSpacing }, + swapDirection, + } = baseInput; + let nextInitializedTickIndex: number | undefined = undefined; + + while (nextInitializedTickIndex === undefined) { + const currentTickArray = fetchTickArray(baseInput, currentTickIndex); + + let temp; + if (swapDirection == SwapDirection.AtoB) { + temp = TickUtil.findPreviousInitializedTickIndex( + currentTickArray, + currentTickIndex, + tickSpacing + ); + } else { + temp = TickUtil.findNextInitializedTickIndex(currentTickArray, currentTickIndex, tickSpacing); + } + + if (temp) { + nextInitializedTickIndex = temp; + } else if (tickArraysCrossed === MAX_TICK_ARRAY_CROSSINGS) { + if (swapDirection === SwapDirection.AtoB) { + nextInitializedTickIndex = currentTickArray.startTickIndex; + } else { + nextInitializedTickIndex = currentTickArray.startTickIndex + TICK_ARRAY_SIZE * tickSpacing; + } + tickArraysCrossed++; + } else { + if (swapDirection === SwapDirection.AtoB) { + currentTickIndex = currentTickArray.startTickIndex - 1; + } else { + currentTickIndex = currentTickArray.startTickIndex + TICK_ARRAY_SIZE * tickSpacing - 1; + } + tickArraysCrossed++; + } + } + + return { + tickIndex: nextInitializedTickIndex, + tickArraysCrossed, + }; +} diff --git a/sdk/src/tsconfig.json b/sdk/src/tsconfig.json index 4515aa4..71541f2 100644 --- a/sdk/src/tsconfig.json +++ b/sdk/src/tsconfig.json @@ -1,10 +1,11 @@ { "extends": "../tsconfig-base.json", "compilerOptions": { - "composite": true, + "composite": true }, - "include": [ - "./**/*", - "./artifacts/**/*.json" - ] + "include": ["./**/*", "./artifacts/**/*.json"], + "typedocOptions": { + "entryPoints": ["index.ts"], + "out": "../../target/typedocs" + } } diff --git a/sdk/src/types/public/account-types.ts b/sdk/src/types/public/account-types.ts deleted file mode 100644 index 3e85836..0000000 --- a/sdk/src/types/public/account-types.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; - -/** - * This file contains the types that the client exposes to SDK users. - * - * TODO: This file may or may not exist pending SDK's approach on parsing - * the Whirlpool Accounts. - * - */ -export interface WhirlpoolConfigAccount { - feeAuthority: PublicKey; - collectProtocolFeesAuthority: PublicKey; - rewardEmissionsSuperAuthority: PublicKey; - defaultFeeRate: number; - defaultProtocolFeeRate: number; -} diff --git a/sdk/src/types/public/anchor-types.ts b/sdk/src/types/public/anchor-types.ts index 178b0ae..a18ea8b 100644 --- a/sdk/src/types/public/anchor-types.ts +++ b/sdk/src/types/public/anchor-types.ts @@ -9,18 +9,32 @@ import { PublicKey } from "@solana/web3.js"; * be hard-casted to fit the type. */ +/** + * Supported parasable account names from the Whirlpool contract. + * @category Parsables + */ export enum AccountName { WhirlpoolsConfig = "WhirlpoolsConfig", Position = "Position", TickArray = "TickArray", Whirlpool = "Whirlpool", + FeeTier = "FeeTier", } -export type TickSpacingData = { - stable?: {}; - standard?: {}; +/** + * @category Solana Accounts + */ +export type WhirlpoolsConfigData = { + feeAuthority: PublicKey; + collectProtocolFeesAuthority: PublicKey; + rewardEmissionsSuperAuthority: PublicKey; + defaultFeeRate: number; + defaultProtocolFeeRate: number; }; +/** + * @category Solana Accounts + */ export type WhirlpoolRewardInfoData = { mint: PublicKey; vault: PublicKey; @@ -29,10 +43,16 @@ export type WhirlpoolRewardInfoData = { growthGlobalX64: BN; }; +/** + * @category Solana Accounts + */ export type WhirlpoolBumpsData = { whirlpoolBump: number; }; +/** + * @category Solana Accounts + */ export type WhirlpoolData = { whirlpoolsConfig: PublicKey; whirlpoolBump: number[]; @@ -54,12 +74,18 @@ export type WhirlpoolData = { tickSpacing: number; }; +/** + * @category Solana Accounts + */ export type TickArrayData = { whirlpool: PublicKey; startTickIndex: number; ticks: TickData[]; }; +/** + * @category Solana Accounts + */ export type TickData = { initialized: boolean; liquidityNet: BN; @@ -69,20 +95,32 @@ export type TickData = { rewardGrowthsOutside: BN[]; }; +/** + * @category Solana Accounts + */ export type PositionRewardInfoData = { growthInsideCheckpoint: BN; amountOwed: BN; }; +/** + * @category Solana Accounts + */ export type OpenPositionBumpsData = { positionBump: number; }; +/** + * @category Solana Accounts + */ export type OpenPositionWithMetadataBumpsData = { positionBump: number; metadataBump: number; }; +/** + * @category Solana Accounts + */ export type PositionData = { whirlpool: PublicKey; positionMint: PublicKey; @@ -96,6 +134,9 @@ export type PositionData = { rewardInfos: PositionRewardInfoData[]; }; +/** + * @category Solana Accounts + */ export type FeeTierData = { whirlpoolsConfig: PublicKey; tickSpacing: number; diff --git a/sdk/src/types/public/client-types.ts b/sdk/src/types/public/client-types.ts new file mode 100644 index 0000000..c4642ba --- /dev/null +++ b/sdk/src/types/public/client-types.ts @@ -0,0 +1,4 @@ +import { PublicKey } from "@solana/web3.js"; +import { MintInfo } from "@solana/spl-token"; + +export type TokenInfo = MintInfo & { mint: PublicKey }; diff --git a/sdk/src/types/public/constants.ts b/sdk/src/types/public/constants.ts index 689dc2d..2b59f6d 100644 --- a/sdk/src/types/public/constants.ts +++ b/sdk/src/types/public/constants.ts @@ -1,2 +1,44 @@ -// The number of rewards supported by this whirlpool. +import { PublicKey } from "@solana/web3.js"; + +/** + * The number of rewards supported by this whirlpool. + * @category Constants + */ export const NUM_REWARDS = 3; + +/** + * @category Constants + */ +export const MAX_TICK_ARRAY_CROSSINGS = 2; + +/** + * @category Constants + */ +export const MAX_TICK_INDEX = 443636; + +/** + * @category Constants + */ +export const MIN_TICK_INDEX = -443636; + +/** + * @category Constants + */ +export const TICK_ARRAY_SIZE = 88; + +/** + * @category Constants + */ +export const MAX_SQRT_PRICE = "79226673515401279992447579055"; + +/** + * @category Constants + */ +export const MIN_SQRT_PRICE = "4295048016"; + +/** + * @category Constants + */ +export const METADATA_PROGRAM_ADDRESS = new PublicKey( + "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" +); diff --git a/sdk/src/types/public/helper-types.ts b/sdk/src/types/public/helper-types.ts deleted file mode 100644 index a7a6410..0000000 --- a/sdk/src/types/public/helper-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; - -export type PDA = { publicKey: PublicKey; bump: number }; diff --git a/sdk/src/types/public/index.ts b/sdk/src/types/public/index.ts index f5c7abd..f49701e 100644 --- a/sdk/src/types/public/index.ts +++ b/sdk/src/types/public/index.ts @@ -1,5 +1,4 @@ -export * from "./ix-types"; -export * from "./account-types"; export * from "./constants"; -export * from "./helper-types"; export * from "./anchor-types"; +export * from "./ix-types"; +export * from "./client-types"; diff --git a/sdk/src/types/public/ix-types.ts b/sdk/src/types/public/ix-types.ts index 6bebed0..0873081 100644 --- a/sdk/src/types/public/ix-types.ts +++ b/sdk/src/types/public/ix-types.ts @@ -1,227 +1,29 @@ -import { u64 } from "@solana/spl-token"; -import { Keypair, PublicKey } from "@solana/web3.js"; -import BN from "bn.js"; -import { PDA } from "./helper-types"; - -export type InitConfigParams = { - whirlpoolConfigKeypair: Keypair; - feeAuthority: PublicKey; - collectProtocolFeesAuthority: PublicKey; - rewardEmissionsSuperAuthority: PublicKey; - defaultProtocolFeeRate: number; - funder: PublicKey; -}; - -export type InitFeeTierParams = { - feeTierPda: PDA; - feeAuthority: PublicKey; - whirlpoolConfigKey: PublicKey; - tickSpacing: number; - defaultFeeRate: number; - funder: PublicKey; -}; - -export type InitPoolParams = { - initSqrtPrice: BN; - whirlpoolConfigKey: PublicKey; - tokenMintA: PublicKey; - tokenMintB: PublicKey; - whirlpoolPda: PDA; - feeTierKey: PublicKey; - tokenVaultAKeypair: Keypair; - tokenVaultBKeypair: Keypair; - tickSpacing: number; - funder: PublicKey; -}; - -export type InitTickArrayParams = { - whirlpool: PublicKey; - tickArrayPda: PDA; - startTick: number; - funder: PublicKey; -}; - -export type InitializeRewardParams = { - rewardAuthority: PublicKey; - funder: PublicKey; - whirlpool: PublicKey; - rewardMint: PublicKey; - rewardVaultKeypair: Keypair; - rewardIndex: number; -}; - -export type SetRewardEmissionsParams = { - rewardAuthority: PublicKey; - whirlpool: PublicKey; - rewardIndex: number; - rewardVault: PublicKey; - emissionsPerSecondX64: BN; -}; - -export type OpenPositionParams = { - funder: PublicKey; - ownerKey: PublicKey; - positionPda: PDA; - metadataPda?: PDA; - positionMintAddress: PublicKey; - positionTokenAccountAddress: PublicKey; - whirlpoolKey: PublicKey; - tickLowerIndex: number; - tickUpperIndex: number; -}; - -export type ClosePositionParams = { - positionAuthority: PublicKey; - receiver: PublicKey; - position: PublicKey; - positionMint: PublicKey; - positionTokenAccount: PublicKey; -}; - -export type IncreaseLiquidityParams = { - liquidityAmount: BN; - tokenMaxA: u64; - tokenMaxB: u64; - whirlpool: PublicKey; - positionAuthority: PublicKey; - position: PublicKey; - positionTokenAccount: PublicKey; - tokenOwnerAccountA: PublicKey; - tokenOwnerAccountB: PublicKey; - tokenVaultA: PublicKey; - tokenVaultB: PublicKey; - tickArrayLower: PublicKey; - tickArrayUpper: PublicKey; -}; - -export type DecreaseLiquidityParams = { - liquidityAmount: BN; - tokenMinA: u64; - tokenMinB: u64; - whirlpool: PublicKey; - positionAuthority: PublicKey; - position: PublicKey; - positionTokenAccount: PublicKey; - tokenOwnerAccountA: PublicKey; - tokenOwnerAccountB: PublicKey; - tokenVaultA: PublicKey; - tokenVaultB: PublicKey; - tickArrayLower: PublicKey; - tickArrayUpper: PublicKey; -}; - -export type UpdateFeesAndRewardsParams = { - whirlpool: PublicKey; - position: PublicKey; - tickArrayLower: PublicKey; - tickArrayUpper: PublicKey; -}; - -export type CollectFeesParams = { - whirlpool: PublicKey; - positionAuthority: PublicKey; - position: PublicKey; - positionTokenAccount: PublicKey; - tokenOwnerAccountA: PublicKey; - tokenOwnerAccountB: PublicKey; - tokenVaultA: PublicKey; - tokenVaultB: PublicKey; -}; - -export type CollectRewardParams = { - whirlpool: PublicKey; - positionAuthority: PublicKey; - position: PublicKey; - positionTokenAccount: PublicKey; - rewardOwnerAccount: PublicKey; - rewardVault: PublicKey; - rewardIndex: number; -}; - -export type CollectProtocolFeesParams = { - whirlpoolsConfig: PublicKey; - whirlpool: PublicKey; - collectProtocolFeesAuthority: PublicKey; - tokenVaultA: PublicKey; - tokenVaultB: PublicKey; - tokenDestinationA: PublicKey; - tokenDestinationB: PublicKey; -}; - -export type SwapParams = { - amount: u64; - otherAmountThreshold: u64; - sqrtPriceLimit: BN; - amountSpecifiedIsInput: boolean; - aToB: boolean; - whirlpool: PublicKey; - tokenAuthority: PublicKey; - tokenOwnerAccountA: PublicKey; - tokenVaultA: PublicKey; - tokenOwnerAccountB: PublicKey; - tokenVaultB: PublicKey; - tickArray0: PublicKey; - tickArray1: PublicKey; - tickArray2: PublicKey; - oracle: PublicKey; -}; - -export type SetRewardEmissionsSuperAuthorityParams = { - whirlpoolsConfig: PublicKey; - rewardEmissionsSuperAuthority: PublicKey; - newRewardEmissionsSuperAuthority: PublicKey; -}; - -export type SetRewardAuthorityParams = { - whirlpool: PublicKey; - rewardAuthority: PublicKey; - newRewardAuthority: PublicKey; - rewardIndex: number; -}; - -export type SetRewardAuthorityBySuperAuthorityParams = { - whirlpoolsConfig: PublicKey; - whirlpool: PublicKey; - rewardEmissionsSuperAuthority: PublicKey; - newRewardAuthority: PublicKey; - rewardIndex: number; -}; - -export type SetFeeAuthorityParams = { - whirlpoolsConfig: PublicKey; - feeAuthority: PublicKey; - newFeeAuthority: PublicKey; -}; - -export type SetCollectProtocolFeesAuthorityParams = { - whirlpoolsConfig: PublicKey; - collectProtocolFeesAuthority: PublicKey; - newCollectProtocolFeesAuthority: PublicKey; -}; - -export type SetFeeRateParams = { - whirlpool: PublicKey; - whirlpoolsConfig: PublicKey; - feeAuthority: PublicKey; - feeRate: number; -}; - -export type SetProtocolFeeRateParams = { - whirlpool: PublicKey; - whirlpoolsConfig: PublicKey; - feeAuthority: PublicKey; - protocolFeeRate: number; -}; - -export type SetDefaultFeeRateParams = { - whirlpoolsConfig: PublicKey; - feeAuthority: PublicKey; - tickSpacing: number; - defaultFeeRate: number; -}; - -export type SetDefaultProtocolFeeRateParams = { - whirlpoolsConfig: PublicKey; - feeAuthority: PublicKey; - defaultProtocolFeeRate: number; -}; +export { + CollectFeesParams, + ClosePositionParams, + CollectProtocolFeesParams, + CollectRewardParams, + SetCollectProtocolFeesAuthorityParams, + SetDefaultFeeRateParams, + SetDefaultProtocolFeeRateParams, + SetFeeAuthorityParams, + SetFeeRateParams, + SetProtocolFeeRateParams, + SetRewardAuthorityBySuperAuthorityParams, + SetRewardAuthorityParams, + SetRewardEmissionsParams, + SetRewardEmissionsSuperAuthorityParams, + IncreaseLiquidityParams, + IncreaseLiquidityInput, + DecreaseLiquidityInput, + DecreaseLiquidityParams, + OpenPositionParams, + SwapInput, + SwapParams, + UpdateFeesAndRewardsParams, + InitConfigParams, + InitFeeTierParams, + InitPoolParams, + InitTickArrayParams, + InitializeRewardParams, +} from "../../instructions/"; diff --git a/sdk/src/utils/find-program-address.ts b/sdk/src/utils/find-program-address.ts deleted file mode 100644 index 6327f81..0000000 --- a/sdk/src/utils/find-program-address.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { utils } from "@project-serum/anchor"; -import { PublicKey } from "@solana/web3.js"; -import { PDA } from "../types/public/helper-types"; - -export function findProgramAddress(seeds: (Uint8Array | Buffer)[], programId: PublicKey): PDA { - const [publicKey, bump] = utils.publicKey.findProgramAddressSync(seeds, programId); - return { publicKey, bump }; -} diff --git a/sdk/src/utils/instructions-util.ts b/sdk/src/utils/instructions-util.ts new file mode 100644 index 0000000..a3200c9 --- /dev/null +++ b/sdk/src/utils/instructions-util.ts @@ -0,0 +1,33 @@ +import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { OpenPositionParams } from "../instructions"; +import * as anchor from "@project-serum/anchor"; +import { SystemProgram } from "@solana/web3.js"; +import { WhirlpoolContext } from "../context"; +import { Instruction, TransactionBuilder } from "@orca-so/common-sdk"; + +export function toTx(ctx: WhirlpoolContext, ix: Instruction): TransactionBuilder { + return new TransactionBuilder(ctx.provider).addInstruction(ix); +} + +export function openPositionAccounts(params: OpenPositionParams) { + const { + funder, + owner, + positionPda, + positionMintAddress, + positionTokenAccount: positionTokenAccountAddress, + whirlpool: whirlpoolKey, + } = params; + return { + funder: funder, + owner, + position: positionPda.publicKey, + positionMint: positionMintAddress, + positionTokenAccount: positionTokenAccountAddress, + whirlpool: whirlpoolKey, + tokenProgram: TOKEN_PROGRAM_ID, + systemProgram: SystemProgram.programId, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }; +} diff --git a/sdk/src/utils/position-util.ts b/sdk/src/utils/position-util.ts new file mode 100644 index 0000000..15cd3cc --- /dev/null +++ b/sdk/src/utils/position-util.ts @@ -0,0 +1,206 @@ +import { MathUtil, Percentage } from "@orca-so/common-sdk"; +import { BN } from "@project-serum/anchor"; +import { + getLowerSqrtPriceFromTokenA, + getUpperSqrtPriceFromTokenA, + getUpperSqrtPriceFromTokenB, + getLowerSqrtPriceFromTokenB, +} from "./swap-utils"; + +export enum SwapDirection { + AtoB = "Swap A to B", + BtoA = "Swap B to A", +} + +export enum AmountSpecified { + Input = "Specified input amount", + Output = "Specified output amount", +} + +export enum PositionStatus { + BelowRange, + InRange, + AboveRange, +} + +export class PositionUtil { + private constructor() {} + + public static getPositionStatus( + tickCurrentIndex: number, + tickLowerIndex: number, + tickUpperIndex: number + ): PositionStatus { + if (tickCurrentIndex < tickLowerIndex) { + return PositionStatus.BelowRange; + } else if (tickCurrentIndex < tickUpperIndex) { + return PositionStatus.InRange; + } else { + return PositionStatus.AboveRange; + } + } +} + +export function adjustForSlippage( + n: BN, + { numerator, denominator }: Percentage, + adjustUp: boolean +): BN { + if (adjustUp) { + return n.mul(denominator.add(numerator)).div(denominator); + } else { + return n.mul(denominator).div(denominator.add(numerator)); + } +} + +export function adjustAmountForSlippage( + amountIn: BN, + amountOut: BN, + { numerator, denominator }: Percentage, + amountSpecified: AmountSpecified +): BN { + if (amountSpecified === AmountSpecified.Input) { + return amountOut.mul(denominator).div(denominator.add(numerator)); + } else { + return amountIn.mul(denominator.add(numerator)).div(denominator); + } +} + +export function getLiquidityFromTokenA( + amount: BN, + sqrtPriceLowerX64: BN, + sqrtPriceUpperX64: BN, + roundUp: boolean +) { + const result = amount + .mul(sqrtPriceLowerX64) + .mul(sqrtPriceUpperX64) + .div(sqrtPriceUpperX64.sub(sqrtPriceLowerX64)); + if (roundUp) { + return MathUtil.shiftRightRoundUp(result); + } else { + return result.shrn(64); + } +} + +export function getLiquidityFromTokenB( + amount: BN, + sqrtPriceLowerX64: BN, + sqrtPriceUpperX64: BN, + roundUp: boolean +) { + const numerator = amount.shln(64); + const denominator = sqrtPriceUpperX64.sub(sqrtPriceLowerX64); + if (roundUp) { + return MathUtil.divRoundUp(numerator, denominator); + } else { + return numerator.div(denominator); + } +} + +function orderSqrtPrice(sqrtPrice0X64: BN, sqrtPrice1X64: BN): [BN, BN] { + if (sqrtPrice0X64.lt(sqrtPrice1X64)) { + return [sqrtPrice0X64, sqrtPrice1X64]; + } else { + return [sqrtPrice1X64, sqrtPrice0X64]; + } +} + +export function getTokenAFromLiquidity( + liquidity: BN, + sqrtPrice0X64: BN, + sqrtPrice1X64: BN, + roundUp: boolean +) { + const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64); + + const numerator = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64)).shln(64); + const denominator = sqrtPriceUpperX64.mul(sqrtPriceLowerX64); + if (roundUp) { + return MathUtil.divRoundUp(numerator, denominator); + } else { + return numerator.div(denominator); + } +} + +export function getTokenBFromLiquidity( + liquidity: BN, + sqrtPrice0X64: BN, + sqrtPrice1X64: BN, + roundUp: boolean +) { + const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64); + + const result = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64)); + if (roundUp) { + return MathUtil.shiftRightRoundUp(result); + } else { + return result.shrn(64); + } +} + +export function getAmountFixedDelta( + currentSqrtPriceX64: BN, + targetSqrtPriceX64: BN, + liquidity: BN, + amountSpecified: AmountSpecified, + swapDirection: SwapDirection +) { + if ((amountSpecified == AmountSpecified.Input) == (swapDirection == SwapDirection.AtoB)) { + return getTokenAFromLiquidity( + liquidity, + currentSqrtPriceX64, + targetSqrtPriceX64, + amountSpecified == AmountSpecified.Input + ); + } else { + return getTokenBFromLiquidity( + liquidity, + currentSqrtPriceX64, + targetSqrtPriceX64, + amountSpecified == AmountSpecified.Input + ); + } +} + +export function getAmountUnfixedDelta( + currentSqrtPriceX64: BN, + targetSqrtPriceX64: BN, + liquidity: BN, + amountSpecified: AmountSpecified, + swapDirection: SwapDirection +) { + if ((amountSpecified == AmountSpecified.Input) == (swapDirection == SwapDirection.AtoB)) { + return getTokenBFromLiquidity( + liquidity, + currentSqrtPriceX64, + targetSqrtPriceX64, + amountSpecified == AmountSpecified.Output + ); + } else { + return getTokenAFromLiquidity( + liquidity, + currentSqrtPriceX64, + targetSqrtPriceX64, + amountSpecified == AmountSpecified.Output + ); + } +} + +export function getNextSqrtPrice( + sqrtPriceX64: BN, + liquidity: BN, + amount: BN, + amountSpecified: AmountSpecified, + swapDirection: SwapDirection +) { + if (amountSpecified === AmountSpecified.Input && swapDirection === SwapDirection.AtoB) { + return getLowerSqrtPriceFromTokenA(amount, liquidity, sqrtPriceX64); + } else if (amountSpecified === AmountSpecified.Output && swapDirection === SwapDirection.BtoA) { + return getUpperSqrtPriceFromTokenA(amount, liquidity, sqrtPriceX64); + } else if (amountSpecified === AmountSpecified.Input && swapDirection === SwapDirection.BtoA) { + return getUpperSqrtPriceFromTokenB(amount, liquidity, sqrtPriceX64); + } else { + return getLowerSqrtPriceFromTokenB(amount, liquidity, sqrtPriceX64); + } +} diff --git a/sdk/src/utils/public/addresses.ts b/sdk/src/utils/public/addresses.ts deleted file mode 100644 index dd37df8..0000000 --- a/sdk/src/utils/public/addresses.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { findProgramAddress } from "../find-program-address"; -import { BN } from "@project-serum/anchor"; - -const PDA_WHIRLPOOL_SEED = "whirlpool"; -const PDA_VAULT_A_SEED = "token_vault_a"; -const PDA_VAULT_B_SEED = "token_vault_b"; -const PDA_POSITION_SEED = "position"; -const PDA_METADATA_SEED = "metadata"; -const PDA_TICK_ARRAY_SEED = "tick_array"; -const PDA_FEE_TIER_SEED = "fee_tier"; -const PDA_ORACLE_SEED = "oracle"; - -export function getWhirlpoolPda( - programId: PublicKey, - whirlpoolConfigKey: PublicKey, - tokenMintAKey: PublicKey, - tokenMintBKey: PublicKey, - tickSpacing: number -) { - return findProgramAddress( - [ - Buffer.from(PDA_WHIRLPOOL_SEED), - whirlpoolConfigKey.toBuffer(), - tokenMintAKey.toBuffer(), - tokenMintBKey.toBuffer(), - new BN(tickSpacing).toArrayLike(Buffer, "le", 2), - ], - programId - ); -} - -export function getWhirlpoolVaultAPda( - programId: PublicKey, - whirlpoolKey: PublicKey, - tokenMintAKey: PublicKey -) { - return findProgramAddress( - [Buffer.from(PDA_VAULT_A_SEED), whirlpoolKey.toBuffer(), tokenMintAKey.toBuffer()], - programId - ); -} - -export function getWhirlpoolVaultBPda( - programId: PublicKey, - whirlpoolKey: PublicKey, - tokenMintBKey: PublicKey -) { - return findProgramAddress( - [Buffer.from(PDA_VAULT_B_SEED), whirlpoolKey.toBuffer(), tokenMintBKey.toBuffer()], - programId - ); -} - -export function getPositionPda(programId: PublicKey, positionMintKey: PublicKey) { - return findProgramAddress( - [Buffer.from(PDA_POSITION_SEED), positionMintKey.toBuffer()], - programId - ); -} - -export const METADATA_PROGRAM_ADDRESS = new PublicKey( - "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" -); -export function getPositionMetadataPda(positionMintKey: PublicKey) { - return findProgramAddress( - [ - Buffer.from(PDA_METADATA_SEED), - METADATA_PROGRAM_ADDRESS.toBuffer(), - positionMintKey.toBuffer(), - ], - METADATA_PROGRAM_ADDRESS - ); -} - -export function getTickArrayPda( - programId: PublicKey, - whirlpoolAddress: PublicKey, - startTick: number -) { - return findProgramAddress( - [ - Buffer.from(PDA_TICK_ARRAY_SEED), - whirlpoolAddress.toBuffer(), - Buffer.from(startTick.toString()), - ], - programId - ); -} - -export function getFeeTierPda( - programId: PublicKey, - whirlpoolsConfigAddress: PublicKey, - tickSpacing: number -) { - return findProgramAddress( - [ - Buffer.from(PDA_FEE_TIER_SEED), - whirlpoolsConfigAddress.toBuffer(), - new BN(tickSpacing).toArrayLike(Buffer, "le", 2), - ], - programId - ); -} - -export function getOraclePda(programId: PublicKey, whirlpoolAddress: PublicKey) { - return findProgramAddress([Buffer.from(PDA_ORACLE_SEED), whirlpoolAddress.toBuffer()], programId); -} diff --git a/sdk/src/utils/public/index.ts b/sdk/src/utils/public/index.ts index 49949d3..2b22c8a 100644 --- a/sdk/src/utils/public/index.ts +++ b/sdk/src/utils/public/index.ts @@ -1,5 +1,4 @@ -export * from "./addresses"; -export * from "./math"; -export * from "./parse"; +export * from "./pda-utils"; +export * from "./price-math"; export * from "./tick-utils"; -export * from "./liquidity"; +export * from "./pool-utils"; diff --git a/sdk/src/utils/public/liquidity.ts b/sdk/src/utils/public/liquidity.ts deleted file mode 100644 index 5d0d9c0..0000000 --- a/sdk/src/utils/public/liquidity.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { BN } from "@project-serum/anchor"; -import { u64 } from "@solana/spl-token"; -import { min } from "bn.js"; -import Decimal from "decimal.js"; -import { - fromX64_BN, - fromX64_Decimal, - tickIndexToSqrtPriceX64, - toX64, - toX64_BN, - toX64_Decimal, -} from "."; - -export type TokenAmounts = { - tokenA: u64; - tokenB: u64; -}; - -export function toTokenAmount(a: number, b: number): TokenAmounts { - return { - tokenA: new u64(a.toString()), - tokenB: new u64(b.toString()), - }; -} - -/** - * Estimate the liquidity amount required to increase/decrease liquidity. - * - * // TODO: At the top end of the price range, tick calcuation is off therefore the results can be off - * - * @param currTick - Whirlpool's current tick index (aka price) - * @param lowerTick - Position lower tick index - * @param upperTick - Position upper tick index - * @param tokenAmount - The desired amount of tokens to deposit/withdraw - * @returns An estimated amount of liquidity needed to deposit/withdraw the desired amount of tokens. - */ -export function estimateLiquidityFromTokenAmounts( - currTick: number, - lowerTick: number, - upperTick: number, - tokenAmount: TokenAmounts -): BN { - if (upperTick < lowerTick) { - throw new Error("upper tick cannot be lower than the lower tick"); - } - - const currSqrtPrice = tickIndexToSqrtPriceX64(currTick); - const lowerSqrtPrice = tickIndexToSqrtPriceX64(lowerTick); - const upperSqrtPrice = tickIndexToSqrtPriceX64(upperTick); - - if (currTick >= upperTick) { - return estLiquidityForTokenB(upperSqrtPrice, lowerSqrtPrice, tokenAmount.tokenB); - } else if (currTick < lowerTick) { - return estLiquidityForTokenA(lowerSqrtPrice, upperSqrtPrice, tokenAmount.tokenA); - } else { - const estLiquidityAmountA = estLiquidityForTokenA( - currSqrtPrice, - upperSqrtPrice, - tokenAmount.tokenA - ); - const estLiquidityAmountB = estLiquidityForTokenB( - currSqrtPrice, - lowerSqrtPrice, - tokenAmount.tokenB - ); - return BN.min(estLiquidityAmountA, estLiquidityAmountB); - } -} - -export function getTokenAmountsFromLiquidity( - liquidity: u64, - currentPrice: u64, - lowerPrice: u64, - upperPrice: u64, - round_up: boolean -): TokenAmounts { - const _liquidity = new Decimal(liquidity.toString()); - const _currentPrice = new Decimal(currentPrice.toString()); - const _lowerPrice = new Decimal(lowerPrice.toString()); - const _upperPrice = new Decimal(upperPrice.toString()); - let tokenA, tokenB; - if (currentPrice.lt(lowerPrice)) { - // x = L * (pb - pa) / (pa * pb) - tokenA = toX64_Decimal(_liquidity) - .mul(_upperPrice.sub(_lowerPrice)) - .div(_lowerPrice.mul(_upperPrice)); - tokenB = new Decimal(0); - } else if (currentPrice.lt(upperPrice)) { - // x = L * (pb - p) / (p * pb) - // y = L * (p - pa) - tokenA = toX64_Decimal(_liquidity) - .mul(_upperPrice.sub(_currentPrice)) - .div(_currentPrice.mul(_upperPrice)); - tokenB = fromX64_Decimal(_liquidity.mul(_currentPrice.sub(_lowerPrice))); - } else { - // y = L * (pb - pa) - tokenA = new Decimal(0); - tokenB = fromX64_Decimal(_liquidity.mul(_upperPrice.sub(_lowerPrice))); - } - - // TODO: round up - if (round_up) { - return { - tokenA: new u64(tokenA.ceil().toString()), - tokenB: new u64(tokenB.ceil().toString()), - }; - } else { - return { - tokenA: new u64(tokenA.floor().toString()), - tokenB: new u64(tokenB.floor().toString()), - }; - } -} - -// Convert this function based on Delta A = Delta L * (1/sqrt(lower) - 1/sqrt(upper)) -function estLiquidityForTokenA(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) { - const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2); - const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2); - - const num = fromX64_BN(tokenAmount.mul(upperSqrtPriceX64).mul(lowerSqrtPriceX64)); - const dem = upperSqrtPriceX64.sub(lowerSqrtPriceX64); - - return num.div(dem); -} - -// Convert this function based on Delta B = Delta L * (sqrt_price(upper) - sqrt_price(lower)) -function estLiquidityForTokenB(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) { - const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2); - const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2); - - const delta = upperSqrtPriceX64.sub(lowerSqrtPriceX64); - - return toX64_BN(tokenAmount).div(delta); -} diff --git a/sdk/src/utils/public/math.ts b/sdk/src/utils/public/math.ts deleted file mode 100644 index 96683ba..0000000 --- a/sdk/src/utils/public/math.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { BN } from "@project-serum/anchor"; -import Decimal from "decimal.js"; - -export function toX64_BN(num: BN): BN { - return num.mul(new BN(2).pow(new BN(64))); -} - -export function toX64_Decimal(num: Decimal): Decimal { - return num.mul(Decimal.pow(2, 64)); -} - -export function toX64(num: Decimal): BN { - return new BN(num.mul(Decimal.pow(2, 64)).floor().toFixed()); -} - -export function fromX64(num: BN): Decimal { - return new Decimal(num.toString()).mul(Decimal.pow(2, -64)); -} - -export function fromX64_Decimal(num: Decimal): Decimal { - return num.mul(Decimal.pow(2, -64)); -} - -export function fromX64_BN(num: BN): BN { - return num.div(new BN(2).pow(new BN(64))); -} diff --git a/sdk/src/utils/public/parse.ts b/sdk/src/utils/public/parse.ts deleted file mode 100644 index e17f640..0000000 --- a/sdk/src/utils/public/parse.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { AccountsCoder, Coder, Idl } from "@project-serum/anchor"; -import { WhirlpoolConfigAccount } from "../.."; -import { - AccountName, - PositionData, - TickArrayData, - WhirlpoolData, -} from "../../types/public/anchor-types"; -import * as WhirlpoolIDL from "../../artifacts/whirlpool.json"; - -const WhirlpoolCoder = new Coder(WhirlpoolIDL as Idl); - -export function parseWhirlpoolsConfig(data: Buffer): WhirlpoolConfigAccount | null { - return parse(AccountName.WhirlpoolsConfig, data); -} - -export function parseWhirlpool(data: Buffer): WhirlpoolData | null { - return parse(AccountName.Whirlpool, data); -} - -export function parsePosition(data: Buffer): PositionData | null { - return parse(AccountName.Position, data); -} - -export function parseTickArray(data: Buffer): TickArrayData | null { - return parse(AccountName.TickArray, data); -} - -function parse(accountName: AccountName, data: Buffer) { - const discriminator = AccountsCoder.accountDiscriminator(accountName); - if (discriminator.compare(data.slice(0, 8))) { - console.error("incorrect account name during parsing"); - return null; - } - - try { - return WhirlpoolCoder.accounts.decode(accountName, data); - } catch (_e) { - console.error("unknown account name during parsing"); - return null; - } -} diff --git a/sdk/src/utils/public/pda-utils.ts b/sdk/src/utils/public/pda-utils.ts new file mode 100644 index 0000000..852ba87 --- /dev/null +++ b/sdk/src/utils/public/pda-utils.ts @@ -0,0 +1,171 @@ +import { AddressUtil } from "@orca-so/common-sdk"; +import { BN } from "@project-serum/anchor"; +import { PublicKey } from "@solana/web3.js"; +import { METADATA_PROGRAM_ADDRESS } from "../../types/public"; +import { PriceMath } from "./price-math"; +import { TickUtil } from "./tick-utils"; + +const PDA_WHIRLPOOL_SEED = "whirlpool"; +const PDA_POSITION_SEED = "position"; +const PDA_METADATA_SEED = "metadata"; +const PDA_TICK_ARRAY_SEED = "tick_array"; +const PDA_FEE_TIER_SEED = "fee_tier"; +const PDA_ORACLE_SEED = "oracle"; + +/** + * @category Program Derived Addresses + */ +export class PDAUtil { + /** + * + * @param programId + * @param whirlpoolsConfigKey + * @param tokenMintAKey + * @param tokenMintBKey + * @param tickSpacing + * @returns + */ + public static getWhirlpool( + programId: PublicKey, + whirlpoolsConfigKey: PublicKey, + tokenMintAKey: PublicKey, + tokenMintBKey: PublicKey, + tickSpacing: number + ) { + return AddressUtil.findProgramAddress( + [ + Buffer.from(PDA_WHIRLPOOL_SEED), + whirlpoolsConfigKey.toBuffer(), + tokenMintAKey.toBuffer(), + tokenMintBKey.toBuffer(), + new BN(tickSpacing).toArrayLike(Buffer, "le", 2), + ], + programId + ); + } + + /** + * @category Program Derived Addresses + * @param programId + * @param positionMintKey + * @returns + */ + public static getPosition(programId: PublicKey, positionMintKey: PublicKey) { + return AddressUtil.findProgramAddress( + [Buffer.from(PDA_POSITION_SEED), positionMintKey.toBuffer()], + programId + ); + } + + /** + * @category Program Derived Addresses + * @param positionMintKey + * @returns + */ + public static getPositionMetadata(positionMintKey: PublicKey) { + return AddressUtil.findProgramAddress( + [ + Buffer.from(PDA_METADATA_SEED), + METADATA_PROGRAM_ADDRESS.toBuffer(), + positionMintKey.toBuffer(), + ], + METADATA_PROGRAM_ADDRESS + ); + } + + /** + * @category Program Derived Addresses + * @param programId + * @param whirlpoolAddress + * @param startTick + * @returns + */ + public static getTickArray(programId: PublicKey, whirlpoolAddress: PublicKey, startTick: number) { + return AddressUtil.findProgramAddress( + [ + Buffer.from(PDA_TICK_ARRAY_SEED), + whirlpoolAddress.toBuffer(), + Buffer.from(startTick.toString()), + ], + programId + ); + } + + /** + * Get the PDA of the tick array containing tickIndex. + * tickArrayOffset can be used to get neighboring tick arrays. + * + * @param tickIndex + * @param tickSpacing + * @param whirlpool + * @param programId + * @param tickArrayOffset + * @returns + */ + public static getTickArrayFromTickIndex( + tickIndex: number, + tickSpacing: number, + whirlpool: PublicKey, + programId: PublicKey, + tickArrayOffset = 0 + ) { + const startIndex = TickUtil.getStartTickIndex(tickIndex, tickSpacing, tickArrayOffset); + return PDAUtil.getTickArray( + AddressUtil.toPubKey(programId), + AddressUtil.toPubKey(whirlpool), + startIndex + ); + } + + public static getTickArrayFromSqrtPrice( + sqrtPriceX64: BN, + tickSpacing: number, + whirlpool: PublicKey, + programId: PublicKey, + tickArrayOffset = 0 + ) { + const tickIndex = PriceMath.sqrtPriceX64ToTickIndex(sqrtPriceX64); + return PDAUtil.getTickArrayFromTickIndex( + tickIndex, + tickSpacing, + whirlpool, + programId, + tickArrayOffset + ); + } + + /** + * @category Program Derived Addresses + * @param programId + * @param whirlpoolsConfigAddress + * @param tickSpacing + * @returns + */ + public static getFeeTier( + programId: PublicKey, + whirlpoolsConfigAddress: PublicKey, + tickSpacing: number + ) { + return AddressUtil.findProgramAddress( + [ + Buffer.from(PDA_FEE_TIER_SEED), + whirlpoolsConfigAddress.toBuffer(), + new BN(tickSpacing).toArrayLike(Buffer, "le", 2), + ], + programId + ); + } + + /** + * @category Program Derived Addresses + * @param programId + * @param whirlpoolAddress + * @returns + */ + public static getOracle(programId: PublicKey, whirlpoolAddress: PublicKey) { + return AddressUtil.findProgramAddress( + [Buffer.from(PDA_ORACLE_SEED), whirlpoolAddress.toBuffer()], + programId + ); + } +} diff --git a/sdk/src/utils/public/pool-utils.ts b/sdk/src/utils/public/pool-utils.ts new file mode 100644 index 0000000..b8af283 --- /dev/null +++ b/sdk/src/utils/public/pool-utils.ts @@ -0,0 +1,231 @@ +import { AddressUtil, MathUtil, Percentage } from "@orca-so/common-sdk"; +import { Address, BN } from "@project-serum/anchor"; +import { u64 } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; +import Decimal from "decimal.js"; +import { MAX_TICK_ARRAY_CROSSINGS, WhirlpoolRewardInfoData } from "../../types/public"; +import { PDAUtil } from "./pda-utils"; +import { PriceMath } from "./price-math"; +import { TickUtil } from "./tick-utils"; + +/** + * @category Whirlpool Utils + */ +export class PoolUtil { + private constructor() {} + + public static isRewardInitialized(rewardInfo: WhirlpoolRewardInfoData): boolean { + return ( + !PublicKey.default.equals(rewardInfo.mint) && !PublicKey.default.equals(rewardInfo.vault) + ); + } + + public static getFeeRate(feeRate: number): Percentage { + /** + * Smart Contract comment: https://github.com/orca-so/whirlpool/blob/main/programs/whirlpool/src/state/whirlpool.rs#L9-L11 + * // Stored as hundredths of a basis point + * // u16::MAX corresponds to ~6.5% + * pub fee_rate: u16, + */ + return Percentage.fromFraction(feeRate, 1e6); // TODO + } + + public static getProtocolFeeRate(protocolFeeRate: number): Percentage { + /** + * Smart Contract comment: https://github.com/orca-so/whirlpool/blob/main/programs/whirlpool/src/state/whirlpool.rs#L13-L14 + * // Stored as a basis point + * pub protocol_fee_rate: u16, + */ + return Percentage.fromFraction(protocolFeeRate, 1e4); // TODO + } + + public static orderMints(mintX: Address, mintY: Address): [Address, Address] { + let mintA, mintB; + if ( + Buffer.compare( + AddressUtil.toPubKey(mintX).toBuffer(), + AddressUtil.toPubKey(mintY).toBuffer() + ) < 0 + ) { + mintA = mintX; + mintB = mintY; + } else { + mintA = mintY; + mintB = mintX; + } + + return [mintA, mintB]; + } + + /** + * @category Whirlpool Utils + * @param liquidity + * @param currentPrice + * @param lowerPrice + * @param upperPrice + * @param round_up + * @returns + */ + public static getTokenAmountsFromLiquidity( + liquidity: u64, + currentPrice: u64, + lowerPrice: u64, + upperPrice: u64, + round_up: boolean + ): TokenAmounts { + const _liquidity = new Decimal(liquidity.toString()); + const _currentPrice = new Decimal(currentPrice.toString()); + const _lowerPrice = new Decimal(lowerPrice.toString()); + const _upperPrice = new Decimal(upperPrice.toString()); + let tokenA, tokenB; + if (currentPrice.lt(lowerPrice)) { + // x = L * (pb - pa) / (pa * pb) + tokenA = MathUtil.toX64_Decimal(_liquidity) + .mul(_upperPrice.sub(_lowerPrice)) + .div(_lowerPrice.mul(_upperPrice)); + tokenB = new Decimal(0); + } else if (currentPrice.lt(upperPrice)) { + // x = L * (pb - p) / (p * pb) + // y = L * (p - pa) + tokenA = MathUtil.toX64_Decimal(_liquidity) + .mul(_upperPrice.sub(_currentPrice)) + .div(_currentPrice.mul(_upperPrice)); + tokenB = MathUtil.fromX64_Decimal(_liquidity.mul(_currentPrice.sub(_lowerPrice))); + } else { + // y = L * (pb - pa) + tokenA = new Decimal(0); + tokenB = MathUtil.fromX64_Decimal(_liquidity.mul(_upperPrice.sub(_lowerPrice))); + } + + // TODO: round up + if (round_up) { + return { + tokenA: new u64(tokenA.ceil().toString()), + tokenB: new u64(tokenB.ceil().toString()), + }; + } else { + return { + tokenA: new u64(tokenA.floor().toString()), + tokenB: new u64(tokenB.floor().toString()), + }; + } + } + + /** + * Estimate the liquidity amount required to increase/decrease liquidity. + * + * // TODO: At the top end of the price range, tick calcuation is off therefore the results can be off + * + * @category Whirlpool Utils + * @param currTick - Whirlpool's current tick index (aka price) + * @param lowerTick - Position lower tick index + * @param upperTick - Position upper tick index + * @param tokenAmount - The desired amount of tokens to deposit/withdraw + * @returns An estimated amount of liquidity needed to deposit/withdraw the desired amount of tokens. + */ + public static estimateLiquidityFromTokenAmounts( + currTick: number, + lowerTick: number, + upperTick: number, + tokenAmount: TokenAmounts + ): BN { + if (upperTick < lowerTick) { + throw new Error("upper tick cannot be lower than the lower tick"); + } + + const currSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(currTick); + const lowerSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(lowerTick); + const upperSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(upperTick); + + if (currTick >= upperTick) { + return estLiquidityForTokenB(upperSqrtPrice, lowerSqrtPrice, tokenAmount.tokenB); + } else if (currTick < lowerTick) { + return estLiquidityForTokenA(lowerSqrtPrice, upperSqrtPrice, tokenAmount.tokenA); + } else { + const estLiquidityAmountA = estLiquidityForTokenA( + currSqrtPrice, + upperSqrtPrice, + tokenAmount.tokenA + ); + const estLiquidityAmountB = estLiquidityForTokenB( + currSqrtPrice, + lowerSqrtPrice, + tokenAmount.tokenB + ); + return BN.min(estLiquidityAmountA, estLiquidityAmountB); + } + } + + /** + * Given the current tick-index, returns the dervied PDA and fetched data + * for the tick-arrays that this swap may traverse across. + * + * TODO: Handle the edge case where the first tick-array may be the previous array of + * expected start-index due to slippage. + * + * @category Whirlpool Utils + * @param tickCurrentIndex - The current tickIndex for the Whirlpool to swap on. + * @param tickSpacing - The tickSpacing for the Whirlpool. + * @param aToB - The direction of the trade. + * @param programId - The Whirlpool programId which the Whirlpool lives on. + * @param whirlpoolAddress - PublicKey of the whirlpool to swap on. + * @returns An array of PublicKey[] for the tickArray accounts that this swap may traverse across. + */ + public static getTickArrayPublicKeysForSwap( + tickCurrentIndex: number, + tickSpacing: number, + aToB: boolean, + programId: PublicKey, + whirlpoolAddress: PublicKey + ) { + let offset = 0; + let tickArrayAddresses: PublicKey[] = []; + for (let i = 0; i <= MAX_TICK_ARRAY_CROSSINGS; i++) { + const startIndex = TickUtil.getStartTickIndex(tickCurrentIndex, tickSpacing, offset); + const pda = PDAUtil.getTickArray(programId, whirlpoolAddress, startIndex); + tickArrayAddresses.push(pda.publicKey); + offset = aToB ? offset - 1 : offset + 1; + } + + return tickArrayAddresses; + } +} + +/** + * @category Whirlpool Utils + */ +export type TokenAmounts = { + tokenA: u64; + tokenB: u64; +}; + +/** + * @category Whirlpool Utils + */ +export function toTokenAmount(a: number, b: number): TokenAmounts { + return { + tokenA: new u64(a.toString()), + tokenB: new u64(b.toString()), + }; +} + +// Convert this function based on Delta A = Delta L * (1/sqrt(lower) - 1/sqrt(upper)) +function estLiquidityForTokenA(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) { + const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2); + const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2); + + const num = MathUtil.fromX64_BN(tokenAmount.mul(upperSqrtPriceX64).mul(lowerSqrtPriceX64)); + const dem = upperSqrtPriceX64.sub(lowerSqrtPriceX64); + + return num.div(dem); +} + +// Convert this function based on Delta B = Delta L * (sqrt_price(upper) - sqrt_price(lower)) +function estLiquidityForTokenB(sqrtPrice1: BN, sqrtPrice2: BN, tokenAmount: u64) { + const lowerSqrtPriceX64 = BN.min(sqrtPrice1, sqrtPrice2); + const upperSqrtPriceX64 = BN.max(sqrtPrice1, sqrtPrice2); + + const delta = upperSqrtPriceX64.sub(lowerSqrtPriceX64); + + return MathUtil.toX64_BN(tokenAmount).div(delta); +} diff --git a/sdk/src/utils/public/price-math.ts b/sdk/src/utils/public/price-math.ts new file mode 100644 index 0000000..829383c --- /dev/null +++ b/sdk/src/utils/public/price-math.ts @@ -0,0 +1,279 @@ +import { MathUtil } from "@orca-so/common-sdk"; +import { BN } from "@project-serum/anchor"; +import Decimal from "decimal.js"; +import { MAX_TICK_INDEX, MIN_TICK_INDEX, MAX_SQRT_PRICE, MIN_SQRT_PRICE } from "../../types/public"; +import { TickUtil } from "./tick-utils"; + +const BIT_PRECISION = 14; +const LOG_B_2_X32 = "59543866431248"; +const LOG_B_P_ERR_MARGIN_LOWER_X64 = "184467440737095516"; +const LOG_B_P_ERR_MARGIN_UPPER_X64 = "15793534762490258745"; + +/** + * A collection of utility functions to convert between price, tickIndex and sqrtPrice. + * + * @category Whirlpool Utils + */ +export class PriceMath { + public static priceToSqrtPriceX64(price: Decimal, decimalsA: number, decimalsB: number): BN { + return MathUtil.toX64(price.mul(Decimal.pow(10, decimalsB - decimalsA)).sqrt()); + } + + public static sqrtPriceX64ToPrice( + sqrtPriceX64: BN, + decimalsA: number, + decimalsB: number + ): Decimal { + return MathUtil.fromX64(sqrtPriceX64) + .pow(2) + .mul(Decimal.pow(10, decimalsA - decimalsB)); + } + + /** + * @param tickIndex + * @returns + */ + public static tickIndexToSqrtPriceX64(tickIndex: number): BN { + if (tickIndex > MAX_TICK_INDEX || tickIndex < MIN_TICK_INDEX) { + throw new Error("Provided tick index does not fit within supported tick index range."); + } + + if (tickIndex > 0) { + return new BN(tickIndexToSqrtPricePositive(tickIndex)); + } else { + return new BN(tickIndexToSqrtPriceNegative(tickIndex)); + } + } + + /** + * + * @param sqrtPriceX64 + * @returns + */ + public static sqrtPriceX64ToTickIndex(sqrtPriceX64: BN): number { + if (sqrtPriceX64.gt(new BN(MAX_SQRT_PRICE)) || sqrtPriceX64.lt(new BN(MIN_SQRT_PRICE))) { + throw new Error("Provided sqrtPrice is not within the supported sqrtPrice range."); + } + + const msb = sqrtPriceX64.bitLength() - 1; + const adjustedMsb = new BN(msb - 64); + const log2pIntegerX32 = signedShiftLeft(adjustedMsb, 32, 128); + + let bit = new BN("8000000000000000", "hex"); + let precision = 0; + let log2pFractionX64 = new BN(0); + + let r = msb >= 64 ? sqrtPriceX64.shrn(msb - 63) : sqrtPriceX64.shln(63 - msb); + + while (bit.gt(new BN(0)) && precision < BIT_PRECISION) { + r = r.mul(r); + let rMoreThanTwo = r.shrn(127); + r = r.shrn(63 + rMoreThanTwo.toNumber()); + log2pFractionX64 = log2pFractionX64.add(bit.mul(rMoreThanTwo)); + bit = bit.shrn(1); + precision += 1; + } + + const log2pFractionX32 = log2pFractionX64.shrn(32); + + const log2pX32 = log2pIntegerX32.add(log2pFractionX32); + const logbpX64 = log2pX32.mul(new BN(LOG_B_2_X32)); + + const tickLow = signedShiftRight( + logbpX64.sub(new BN(LOG_B_P_ERR_MARGIN_LOWER_X64)), + 64, + 128 + ).toNumber(); + const tickHigh = signedShiftRight( + logbpX64.add(new BN(LOG_B_P_ERR_MARGIN_UPPER_X64)), + 64, + 128 + ).toNumber(); + + if (tickLow == tickHigh) { + return tickLow; + } else { + const derivedTickHighSqrtPriceX64 = PriceMath.tickIndexToSqrtPriceX64(tickHigh); + if (derivedTickHighSqrtPriceX64.lte(sqrtPriceX64)) { + return tickHigh; + } else { + return tickLow; + } + } + } + + public static tickIndexToPrice(tickIndex: number, decimalsA: number, decimalsB: number): Decimal { + return PriceMath.sqrtPriceX64ToPrice( + PriceMath.tickIndexToSqrtPriceX64(tickIndex), + decimalsA, + decimalsB + ); + } + + public static priceToTickIndex(price: Decimal, decimalsA: number, decimalsB: number): number { + return PriceMath.sqrtPriceX64ToTickIndex( + PriceMath.priceToSqrtPriceX64(price, decimalsA, decimalsB) + ); + } + + public static priceToInitializableTickIndex( + price: Decimal, + decimalsA: number, + decimalsB: number, + tickSpacing: number + ): number { + return TickUtil.getInitializableTickIndex( + PriceMath.priceToTickIndex(price, decimalsA, decimalsB), + tickSpacing + ); + } +} + +// Private Functions + +function tickIndexToSqrtPricePositive(tick: number) { + let ratio: BN; + + if ((tick & 1) != 0) { + ratio = new BN("79232123823359799118286999567"); + } else { + ratio = new BN("79228162514264337593543950336"); + } + + if ((tick & 2) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("79236085330515764027303304731")), 96, 256); + } + if ((tick & 4) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("79244008939048815603706035061")), 96, 256); + } + if ((tick & 8) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("79259858533276714757314932305")), 96, 256); + } + if ((tick & 16) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("79291567232598584799939703904")), 96, 256); + } + if ((tick & 32) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("79355022692464371645785046466")), 96, 256); + } + if ((tick & 64) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("79482085999252804386437311141")), 96, 256); + } + if ((tick & 128) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("79736823300114093921829183326")), 96, 256); + } + if ((tick & 256) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("80248749790819932309965073892")), 96, 256); + } + if ((tick & 512) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("81282483887344747381513967011")), 96, 256); + } + if ((tick & 1024) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("83390072131320151908154831281")), 96, 256); + } + if ((tick & 2048) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("87770609709833776024991924138")), 96, 256); + } + if ((tick & 4096) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("97234110755111693312479820773")), 96, 256); + } + if ((tick & 8192) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("119332217159966728226237229890")), 96, 256); + } + if ((tick & 16384) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("179736315981702064433883588727")), 96, 256); + } + if ((tick & 32768) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("407748233172238350107850275304")), 96, 256); + } + if ((tick & 65536) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("2098478828474011932436660412517")), 96, 256); + } + if ((tick & 131072) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("55581415166113811149459800483533")), 96, 256); + } + if ((tick & 262144) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("38992368544603139932233054999993551")), 96, 256); + } + + return signedShiftRight(ratio, 32, 256); +} + +function tickIndexToSqrtPriceNegative(tickIndex: number) { + let tick = Math.abs(tickIndex); + let ratio: BN; + + if ((tick & 1) != 0) { + ratio = new BN("18445821805675392311"); + } else { + ratio = new BN("18446744073709551616"); + } + + if ((tick & 2) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18444899583751176498")), 64, 256); + } + if ((tick & 4) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18443055278223354162")), 64, 256); + } + if ((tick & 8) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18439367220385604838")), 64, 256); + } + if ((tick & 16) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18431993317065449817")), 64, 256); + } + if ((tick & 32) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18417254355718160513")), 64, 256); + } + if ((tick & 64) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18387811781193591352")), 64, 256); + } + if ((tick & 128) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18329067761203520168")), 64, 256); + } + if ((tick & 256) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("18212142134806087854")), 64, 256); + } + if ((tick & 512) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("17980523815641551639")), 64, 256); + } + if ((tick & 1024) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("17526086738831147013")), 64, 256); + } + if ((tick & 2048) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("16651378430235024244")), 64, 256); + } + if ((tick & 4096) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("15030750278693429944")), 64, 256); + } + if ((tick & 8192) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("12247334978882834399")), 64, 256); + } + if ((tick & 16384) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("8131365268884726200")), 64, 256); + } + if ((tick & 32768) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("3584323654723342297")), 64, 256); + } + if ((tick & 65536) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("696457651847595233")), 64, 256); + } + if ((tick & 131072) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("26294789957452057")), 64, 256); + } + if ((tick & 262144) != 0) { + ratio = signedShiftRight(ratio.mul(new BN("37481735321082")), 64, 256); + } + + return ratio; +} + +function signedShiftLeft(n0: BN, shiftBy: number, bitWidth: number) { + let twosN0 = n0.toTwos(bitWidth).shln(shiftBy); + twosN0.imaskn(bitWidth + 1); + return twosN0.fromTwos(bitWidth); +} + +function signedShiftRight(n0: BN, shiftBy: number, bitWidth: number) { + let twoN0 = n0.toTwos(bitWidth).shrn(shiftBy); + twoN0.imaskn(bitWidth - shiftBy + 1); + return twoN0.fromTwos(bitWidth - shiftBy); +} diff --git a/sdk/src/utils/public/tick-utils.ts b/sdk/src/utils/public/tick-utils.ts index 43bdbed..0a5f5e1 100644 --- a/sdk/src/utils/public/tick-utils.ts +++ b/sdk/src/utils/public/tick-utils.ts @@ -1,229 +1,203 @@ -import { BN } from "@project-serum/anchor"; +import invariant from "tiny-invariant"; +import { Address, BN } from "@project-serum/anchor"; +import { PublicKey } from "@solana/web3.js"; +import { + MAX_TICK_INDEX, + MIN_TICK_INDEX, + TickArrayData, + TickData, + TICK_ARRAY_SIZE, +} from "../../types/public"; +import { PDAUtil } from "./pda-utils"; -export const MAX_TICK_INDEX = 443636; -export const MIN_TICK_INDEX = -443636; -export const TICK_ARRAY_SIZE = 88; - -export const MAX_SQRT_PRICE = "79226673515401279992447579055"; -export const MIN_SQRT_PRICE = "4295048016"; - -const BIT_PRECISION = 14; -const LOG_B_2_X32 = "59543866431248"; -const LOG_B_P_ERR_MARGIN_LOWER_X64 = "184467440737095516"; -const LOG_B_P_ERR_MARGIN_UPPER_X64 = "15793534762490258745"; - -export function tickIndexToSqrtPriceX64(tickIndex: number): BN { - if (tickIndex > MAX_TICK_INDEX || tickIndex < MIN_TICK_INDEX) { - throw new Error("Provided tick index does not fit within supported tick index range."); - } - - if (tickIndex > 0) { - return new BN(tickIndexToSqrtPricePositive(tickIndex)); - } else { - return new BN(tickIndexToSqrtPriceNegative(tickIndex)); - } +enum TickSearchDirection { + Left, + Right, } -export function sqrtPriceX64ToTickIndex(sqrtPriceX64: BN): number { - if (sqrtPriceX64.gt(new BN(MAX_SQRT_PRICE)) || sqrtPriceX64.lt(new BN(MIN_SQRT_PRICE))) { - throw new Error("Provided sqrtPrice is not within the supported sqrtPrice range."); +/** + * A collection of utility functions when interacting with Ticks. + * @category Whirlpool Utils + */ +export class TickUtil { + private constructor() {} + + /** + * Get the startIndex of the tick array containing tickIndex. + * + * @param tickIndex + * @param tickSpacing + * @param offset can be used to get neighboring tick array startIndex. + * @returns + */ + public static getStartTickIndex(tickIndex: number, tickSpacing: number, offset = 0): number { + const realIndex = Math.floor(tickIndex / tickSpacing / TICK_ARRAY_SIZE); + const startTickIndex = (realIndex + offset) * tickSpacing * TICK_ARRAY_SIZE; + + const ticksInArray = TICK_ARRAY_SIZE * tickSpacing; + const minTickIndex = MIN_TICK_INDEX - ((MIN_TICK_INDEX % ticksInArray) + ticksInArray); + invariant(startTickIndex >= minTickIndex, "startTickIndex is too small"); + invariant(startTickIndex <= MAX_TICK_INDEX, "startTickIndex is too large"); + return startTickIndex; } - const msb = sqrtPriceX64.bitLength() - 1; - const adjustedMsb = new BN(msb - 64); - const log2pIntegerX32 = signedShiftLeft(adjustedMsb, 32, 128); - - let bit = new BN("8000000000000000", "hex"); - let precision = 0; - let log2pFractionX64 = new BN(0); - - let r = msb >= 64 ? sqrtPriceX64.shrn(msb - 63) : sqrtPriceX64.shln(63 - msb); - - while (bit.gt(new BN(0)) && precision < BIT_PRECISION) { - r = r.mul(r); - let rMoreThanTwo = r.shrn(127); - r = r.shrn(63 + rMoreThanTwo.toNumber()); - log2pFractionX64 = log2pFractionX64.add(bit.mul(rMoreThanTwo)); - bit = bit.shrn(1); - precision += 1; + /** + * Get the nearest (rounding down) valid tick index from the tickIndex. + * A valid tick index is a point on the tick spacing grid line. + */ + public static getInitializableTickIndex(tickIndex: number, tickSpacing: number): number { + return tickIndex - (tickIndex % tickSpacing); } - const log2pFractionX32 = log2pFractionX64.shrn(32); + public static getNextInitializableTickIndex(tickIndex: number, tickSpacing: number) { + return TickUtil.getInitializableTickIndex(tickIndex, tickSpacing) + tickSpacing; + } - const log2pX32 = log2pIntegerX32.add(log2pFractionX32); - const logbpX64 = log2pX32.mul(new BN(LOG_B_2_X32)); + public static getPrevInitializableTickIndex(tickIndex: number, tickSpacing: number) { + return TickUtil.getInitializableTickIndex(tickIndex, tickSpacing) - tickSpacing; + } - const tickLow = signedShiftRight( - logbpX64.sub(new BN(LOG_B_P_ERR_MARGIN_LOWER_X64)), - 64, - 128 - ).toNumber(); - const tickHigh = signedShiftRight( - logbpX64.add(new BN(LOG_B_P_ERR_MARGIN_UPPER_X64)), - 64, - 128 - ).toNumber(); + /** + * Get the previous initialized tick index within the same tick array. + * + * @param account + * @param currentTickIndex + * @param tickSpacing + * @returns + */ + public static findPreviousInitializedTickIndex( + account: TickArrayData, + currentTickIndex: number, + tickSpacing: number + ): number | null { + return TickUtil.findInitializedTick( + account, + currentTickIndex, + tickSpacing, + TickSearchDirection.Left + ); + } - if (tickLow == tickHigh) { - return tickLow; - } else { - const derivedTickHighSqrtPriceX64 = tickIndexToSqrtPriceX64(tickHigh); - if (derivedTickHighSqrtPriceX64.lte(sqrtPriceX64)) { - return tickHigh; - } else { - return tickLow; + /** + * Get the next initialized tick index within the same tick array. + * @param account + * @param currentTickIndex + * @param tickSpacing + * @returns + */ + public static findNextInitializedTickIndex( + account: TickArrayData, + currentTickIndex: number, + tickSpacing: number + ): number | null { + return TickUtil.findInitializedTick( + account, + currentTickIndex, + tickSpacing, + TickSearchDirection.Right + ); + } + + private static findInitializedTick( + account: TickArrayData, + currentTickIndex: number, + tickSpacing: number, + searchDirection: TickSearchDirection + ): number | null { + const currentTickArrayIndex = tickIndexToInnerIndex( + account.startTickIndex, + currentTickIndex, + tickSpacing + ); + + const increment = searchDirection === TickSearchDirection.Right ? 1 : -1; + + let stepInitializedTickArrayIndex = + searchDirection === TickSearchDirection.Right + ? currentTickArrayIndex + increment + : currentTickArrayIndex; + while ( + stepInitializedTickArrayIndex >= 0 && + stepInitializedTickArrayIndex < account.ticks.length + ) { + if (account.ticks[stepInitializedTickArrayIndex]?.initialized) { + return innerIndexToTickIndex( + account.startTickIndex, + stepInitializedTickArrayIndex, + tickSpacing + ); + } + + stepInitializedTickArrayIndex += increment; } + + return null; + } + + public static checkTickInBounds(tick: number) { + return tick <= MAX_TICK_INDEX && tick >= MIN_TICK_INDEX; + } + + public static isTickInitializable(tick: number, tickSpacing: number) { + return tick % tickSpacing === 0; } } -export function getStartTickIndex(tickIndex: number, tickSpacing: number): number { - const ticksInArray = tickSpacing * TICK_ARRAY_SIZE; - return Math.floor(tickIndex / ticksInArray) * ticksInArray; +export class TickArrayUtil { + /** + * Get the tick from tickArray with a global tickIndex. + */ + public static getTickFromArray( + tickArray: TickArrayData, + tickIndex: number, + tickSpacing: number + ): TickData { + const realIndex = tickIndexToInnerIndex(tickArray.startTickIndex, tickIndex, tickSpacing); + const tick = tickArray.ticks[realIndex]; + invariant( + !!tick, + `tick realIndex out of range - start - ${tickArray.startTickIndex} index - ${tickIndex}, realIndex - ${realIndex}` + ); + return tick; + } + + /** + * + * @param tickLowerIndex + * @param tickUpperIndex + * @param tickSpacing + * @param whirlpool + * @param programId + * @returns + */ + public static getAdjacentTickArrays( + tickLowerIndex: number, + tickUpperIndex: number, + tickSpacing: number, + whirlpool: PublicKey, + programId: PublicKey + ): [PublicKey, PublicKey] { + return [ + PDAUtil.getTickArrayFromTickIndex(tickLowerIndex, tickSpacing, whirlpool, programId) + .publicKey, + PDAUtil.getTickArrayFromTickIndex(tickUpperIndex, tickSpacing, whirlpool, programId) + .publicKey, + ]; + } } -function tickIndexToSqrtPricePositive(tick: number) { - let ratio: BN; - - if ((tick & 1) != 0) { - ratio = new BN("79232123823359799118286999567"); - } else { - ratio = new BN("79228162514264337593543950336"); - } - - if ((tick & 2) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("79236085330515764027303304731")), 96, 256); - } - if ((tick & 4) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("79244008939048815603706035061")), 96, 256); - } - if ((tick & 8) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("79259858533276714757314932305")), 96, 256); - } - if ((tick & 16) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("79291567232598584799939703904")), 96, 256); - } - if ((tick & 32) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("79355022692464371645785046466")), 96, 256); - } - if ((tick & 64) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("79482085999252804386437311141")), 96, 256); - } - if ((tick & 128) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("79736823300114093921829183326")), 96, 256); - } - if ((tick & 256) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("80248749790819932309965073892")), 96, 256); - } - if ((tick & 512) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("81282483887344747381513967011")), 96, 256); - } - if ((tick & 1024) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("83390072131320151908154831281")), 96, 256); - } - if ((tick & 2048) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("87770609709833776024991924138")), 96, 256); - } - if ((tick & 4096) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("97234110755111693312479820773")), 96, 256); - } - if ((tick & 8192) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("119332217159966728226237229890")), 96, 256); - } - if ((tick & 16384) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("179736315981702064433883588727")), 96, 256); - } - if ((tick & 32768) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("407748233172238350107850275304")), 96, 256); - } - if ((tick & 65536) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("2098478828474011932436660412517")), 96, 256); - } - if ((tick & 131072) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("55581415166113811149459800483533")), 96, 256); - } - if ((tick & 262144) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("38992368544603139932233054999993551")), 96, 256); - } - - return signedShiftRight(ratio, 32, 256); +function tickIndexToInnerIndex( + startTickIndex: number, + tickIndex: number, + tickSpacing: number +): number { + return Math.floor((tickIndex - startTickIndex) / tickSpacing); } -function tickIndexToSqrtPriceNegative(tickIndex: number) { - let tick = Math.abs(tickIndex); - let ratio: BN; - - if ((tick & 1) != 0) { - ratio = new BN("18445821805675392311"); - } else { - ratio = new BN("18446744073709551616"); - } - - if ((tick & 2) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18444899583751176498")), 64, 256); - } - if ((tick & 4) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18443055278223354162")), 64, 256); - } - if ((tick & 8) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18439367220385604838")), 64, 256); - } - if ((tick & 16) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18431993317065449817")), 64, 256); - } - if ((tick & 32) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18417254355718160513")), 64, 256); - } - if ((tick & 64) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18387811781193591352")), 64, 256); - } - if ((tick & 128) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18329067761203520168")), 64, 256); - } - if ((tick & 256) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("18212142134806087854")), 64, 256); - } - if ((tick & 512) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("17980523815641551639")), 64, 256); - } - if ((tick & 1024) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("17526086738831147013")), 64, 256); - } - if ((tick & 2048) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("16651378430235024244")), 64, 256); - } - if ((tick & 4096) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("15030750278693429944")), 64, 256); - } - if ((tick & 8192) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("12247334978882834399")), 64, 256); - } - if ((tick & 16384) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("8131365268884726200")), 64, 256); - } - if ((tick & 32768) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("3584323654723342297")), 64, 256); - } - if ((tick & 65536) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("696457651847595233")), 64, 256); - } - if ((tick & 131072) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("26294789957452057")), 64, 256); - } - if ((tick & 262144) != 0) { - ratio = signedShiftRight(ratio.mul(new BN("37481735321082")), 64, 256); - } - - return ratio; -} - -function signedShiftLeft(n0: BN, shiftBy: number, bitWidth: number) { - let twosN0 = n0.toTwos(bitWidth).shln(shiftBy); - twosN0.imaskn(bitWidth + 1); - return twosN0.fromTwos(bitWidth); -} - -function signedShiftRight(n0: BN, shiftBy: number, bitWidth: number) { - let twoN0 = n0.toTwos(bitWidth).shrn(shiftBy); - twoN0.imaskn(bitWidth - shiftBy + 1); - return twoN0.fromTwos(bitWidth - shiftBy); +function innerIndexToTickIndex( + startTickIndex: number, + tickArrayIndex: number, + tickSpacing: number +): number { + return startTickIndex + tickArrayIndex * tickSpacing; } diff --git a/sdk/src/utils/swap-utils.ts b/sdk/src/utils/swap-utils.ts new file mode 100644 index 0000000..3088ed0 --- /dev/null +++ b/sdk/src/utils/swap-utils.ts @@ -0,0 +1,28 @@ +import { MathUtil } from "@orca-so/common-sdk"; +import BN from "bn.js"; + +export function getLowerSqrtPriceFromTokenA(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN { + const numerator = liquidity.mul(sqrtPriceX64).shln(64); + const denominator = liquidity.shln(64).add(amount.mul(sqrtPriceX64)); + + // always round up + return MathUtil.divRoundUp(numerator, denominator); +} + +export function getUpperSqrtPriceFromTokenA(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN { + const numerator = liquidity.mul(sqrtPriceX64).shln(64); + const denominator = liquidity.shln(64).sub(amount.mul(sqrtPriceX64)); + + // always round up + return MathUtil.divRoundUp(numerator, denominator); +} + +export function getLowerSqrtPriceFromTokenB(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN { + // always round down + return sqrtPriceX64.sub(MathUtil.divRoundUp(amount.shln(64), liquidity)); +} + +export function getUpperSqrtPriceFromTokenB(amount: BN, liquidity: BN, sqrtPriceX64: BN): BN { + // always round down (rounding up a negative number) + return sqrtPriceX64.add(amount.shln(64).div(liquidity)); +} diff --git a/sdk/src/utils/transactions/transactions-builder.ts b/sdk/src/utils/transactions/transactions-builder.ts deleted file mode 100644 index 1cc8588..0000000 --- a/sdk/src/utils/transactions/transactions-builder.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Provider } from "@project-serum/anchor"; -import { Transaction, Signer, TransactionInstruction } from "@solana/web3.js"; - -export const EMPTY_INSTRUCTION: Instruction = { - instructions: [], - cleanupInstructions: [], - signers: [], -}; - -export type Instruction = { - instructions: TransactionInstruction[]; - cleanupInstructions: TransactionInstruction[]; - signers: Signer[]; -}; - -export type TransactionPayload = { - transaction: Transaction; - signers: Signer[]; -}; - -export class TransactionBuilder { - private provider: Provider; - private instructions: Instruction[]; - private signers: Signer[]; - - constructor(provider: Provider) { - this.provider = provider; - this.instructions = []; - this.signers = []; - } - - addInstruction(instruction: Instruction): TransactionBuilder { - this.instructions.push(instruction); - return this; - } - - addSigner(signer: Signer): TransactionBuilder { - this.signers.push(signer); - return this; - } - - isEmpty(): boolean { - return this.instructions.length == 0; - } - - /** - * Compresses all instructions & signers in this builder - * into one single instruction - * @param compressPost Compress all post instructions into the instructions field - * @returns Instruction object containing all - */ - compressIx(compressPost: boolean): Instruction { - let instructions: TransactionInstruction[] = []; - let cleanupInstructions: TransactionInstruction[] = []; - let signers: Signer[] = []; - this.instructions.forEach((curr) => { - instructions = instructions.concat(curr.instructions); - // Cleanup instructions should execute in reverse order - cleanupInstructions = curr.cleanupInstructions.concat(cleanupInstructions); - signers = signers.concat(curr.signers); - }); - - if (compressPost) { - instructions = instructions.concat(cleanupInstructions); - cleanupInstructions = []; - } - - return { - instructions: [...instructions], - cleanupInstructions: [...cleanupInstructions], - signers, - }; - } - - /** - * Constructs a transaction payload with the gathered instructions - * @returns a TransactionPayload object that can be excuted or agregated into other transactions - */ - async build(): Promise { - const recentBlockHash = (await this.provider.connection.getRecentBlockhash("singleGossip")) - .blockhash; - - const transaction = new Transaction({ - recentBlockhash: recentBlockHash, - feePayer: this.provider.wallet.publicKey, - }); - - const ix = this.compressIx(true); - - transaction.add(...ix.instructions); - transaction.feePayer = this.provider.wallet.publicKey; - - return { - transaction: transaction, - signers: ix.signers.concat(this.signers), - }; - } - - /** - * Constructs a transaction payload with the gathered instructions, sign it with the provider and send it out - * @returns the txId of the transaction - */ - async buildAndExecute(): Promise { - const tx = await this.build(); - return this.provider.send(tx.transaction, tx.signers, { commitment: "confirmed" }); - } -} diff --git a/sdk/src/whirlpool-client.ts b/sdk/src/whirlpool-client.ts new file mode 100644 index 0000000..c66b5fd --- /dev/null +++ b/sdk/src/whirlpool-client.ts @@ -0,0 +1,211 @@ +import { Percentage, TransactionBuilder } from "@orca-so/common-sdk"; +import { Address } from "@project-serum/anchor"; +import { WhirlpoolContext } from "./context"; +import { AccountFetcher } from "./network/public"; +import { + WhirlpoolData, + PositionData, + IncreaseLiquidityInput, + DecreaseLiquidityInput, +} from "./types/public"; +import { TokenInfo } from "./types/public/client-types"; +import { PublicKey } from "@solana/web3.js"; +import { SwapQuote } from "./quotes/public"; +import { WhirlpoolClientImpl } from "./impl/whirlpool-client-impl"; + +export interface WhirlpoolClient { + /** + * Get a Whirlpool object to interact with the Whirlpool account at the given address. + * @param poolAddress the address of the Whirlpool account + * @return a Whirlpool object to interact with + */ + getPool: (poolAddress: Address) => Promise; + + /** + * Get a Position object to interact with the Position account at the given address. + * @param positionAddress the address of the Position account + * @return a Position object to interact with + */ + getPosition: (positionAddress: Address) => Promise; +} + +/** + * Construct a WhirlpoolClient instance to help interact with Whirlpools accounts with. + * + * @param ctx - WhirlpoolContext object + * @param fetcher - AccountFetcher instance to help fetch data with. + * @returns a WhirlpoolClient instance to help with interacting with Whirlpools accounts. + */ +export function buildWhirlpoolClient(ctx: WhirlpoolContext, fetcher: AccountFetcher) { + return new WhirlpoolClientImpl(ctx, fetcher); +} + +/** + * Helper class to interact with a Whirlpool account and build complex transactions. + */ +export interface Whirlpool { + /** + * Return the most recently fetched Whirlpool account data. + * @return most recently fetched WhirlpoolData for this address. + */ + getData: () => WhirlpoolData; + + /** + * Fetch and return the most recently fetched Whirlpool account data. + * @return the most up to date WhirlpoolData for this address. + */ + refreshData: () => Promise; + + /** + * Get the TokenInfo for token A of this pool. + * @return TokenInfo for token A + */ + getTokenAInfo: () => TokenInfo; + + /** + * Get the TokenInfo for token B of this pool. + * @return TokenInfo for token B + */ + getTokenBInfo: () => TokenInfo; + + // TODO: add functionality to check whether tick arrays exists + /** + * Initialize a set of tick-arrays to support the provided ticks. + * + * This function does not ensure the provided tick index are within range and not initialized. + * + * If `funder` is provided, the funder wallet has to sign this transaction. + * + * @param ticks - A group of ticks that define the desired tick-arrays to initialize + * @param funder - optional - the wallet that will fund the cost needed to initialize the position. If null, the WhirlpoolContext wallet is used. + * @return a transaction that will initialize the defined tick-arrays if executed. + */ + initTickArrayForTicks: (ticks: number[], funder?: Address) => TransactionBuilder; + + /** + * Open and fund a position on this Whirlpool. + * + * User has to ensure the TickArray for tickLower and tickUpper has been initialized prior to calling this function. + * + * If `funder` is provided, the funder wallet has to sign this transaction. + * + * @param tickLower - the tick index for the lower bound of this position + * @param tickUpper - the tick index for the upper bound of this position + * @param liquidityInput - an InputLiquidityInput type to define the desired liquidity amount to deposit + * @param sourceWallet - optional - the wallet to withdraw tokens to deposit into the position. If null, the WhirlpoolContext wallet is used. + * @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used. + * @param funder - optional - the wallet that will fund the cost needed to initialize the position. If null, the WhirlpoolContext wallet is used. + * @return `positionMint` - the position to be created. `tx` - The transaction containing the instructions to perform the operation on chain. + */ + openPosition: ( + tickLower: number, + tickUpper: number, + liquidityInput: IncreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address, + funder?: Address + ) => Promise<{ positionMint: PublicKey; tx: TransactionBuilder }>; + + /** + * Open and fund a position with meta-data on this Whirlpool. + * + * User has to ensure the TickArray for tickLower and tickUpper has been initialized prior to calling this function. + * + * If `sourceWallet`, `positionWallet` or `funder` is provided, the wallet owners have to sign this transaction. + * + * @param tickLower - the tick index for the lower bound of this position + * @param tickUpper - the tick index for the upper bound of this position + * @param liquidityInput - input that defines the desired liquidity amount and maximum tokens willing to be to deposited. + * @param sourceWallet - optional - the wallet to withdraw tokens to deposit into the position. If null, the WhirlpoolContext wallet is used. + * @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used. + * @param funder - optional - the wallet that will fund the cost needed to initialize the position. If null, the WhirlpoolContext wallet is used. + * @return `positionMint` - the position to be created. `tx` - The transaction containing the instructions to perform the operation on chain. + */ + openPositionWithMetadata: ( + tickLower: number, + tickUpper: number, + liquidityInput: IncreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address, + funder?: Address + ) => Promise<{ positionMint: PublicKey; tx: TransactionBuilder }>; + + /** + * Withdraw all tokens from a position, close the account and burn the position token. + * + * Users have to collect all fees and rewards from this position prior to closing the account. + * + * If `positionWallet` is provided, the wallet owner has to sign this transaction. + * + * @param positionAddress - The address of the position account. + * @param slippageTolerance - The amount of slippage the caller is willing to accept when withdrawing liquidity. + * @param destinationWallet - optional - The wallet that the tokens withdrawn will be sent to. If null, the WhirlpoolContext wallet is used. + * @param positionWallet - optional - The wallet that houses the position token that corresponds to this position address. If null, the WhirlpoolContext wallet is used. + */ + closePosition: ( + positionAddress: Address, + slippageTolerance: Percentage, + destinationWallet?: Address, + positionWallet?: Address + ) => Promise; + + /** + * Perform a swap between tokenA and tokenB on this pool. + * + * @param quote - A quote on the desired tokenIn and tokenOut for this swap. Use @link {swapQuote} to generate this object. + * @param wallet - The wallet that tokens will be withdrawn and deposit into. If null, the WhirlpoolContext wallet is used. + * @return a transaction that will perform the swap once executed. + */ + swap: (quote: SwapQuote, wallet?: PublicKey) => Promise; +} + +/** + * Helper class to interact with a Position account and build complex transactions. + */ +export interface Position { + /** + * Return the most recently fetched Position account data. + * @return most recently fetched PositionData for this address. + */ + getData: () => PositionData; + + /** + * Fetch and return the most recently fetched Position account data. + * @return the most up to date PositionData for this address. + */ + refreshData: () => Promise; + + /** + * Deposit additional tokens into this postiion. + * + * If `sourceWallet`, `positionWallet` is provided, the wallet owners have to sign this transaction. + * + * @param liquidityInput - input that defines the desired liquidity amount and maximum tokens willing to be to deposited. + * @param sourceWallet - optional - the wallet to withdraw tokens to deposit into the position. If null, the WhirlpoolContext wallet is used. + * @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used. + * @return the transaction that will deposit the tokens into the position when executed. + */ + increaseLiquidity: ( + liquidityInput: IncreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address + ) => Promise; + + /** + * Withdraw liquidity from this position. + * + * If `positionWallet` is provided, the wallet owners have to sign this transaction. + * + * @param liquidityInput - input that defines the desired liquidity amount and minimum tokens willing to be to withdrawn from the position. + * @param sourceWallet - optional - the wallet to deposit tokens into when withdrawing from the position. If null, the WhirlpoolContext wallet is used. + * @param positionWallet - optional - the wallet to that houses the position token. If null, the WhirlpoolContext wallet is used. + * @return the transaction that will deposit the tokens into the position when executed. + */ + decreaseLiquidity: ( + liquidityInput: DecreaseLiquidityInput, + sourceWallet?: Address, + positionWallet?: Address + ) => Promise; + + // TODO: Implement Collect fees +} diff --git a/sdk/tests/initialize_config.ts b/sdk/tests/initialize_config.ts deleted file mode 100644 index 0651e6c..0000000 --- a/sdk/tests/initialize_config.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as assert from "assert"; -import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { InitConfigParams } from "../src/types/public/ix-types"; -import { generateDefaultConfigParams } from "./utils/test-builders"; -import { ONE_SOL, systemTransferTx } from "./utils"; - -describe("initialize_config", () => { - const provider = anchor.Provider.local(); - anchor.setProvider(anchor.Provider.env()); - const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); - - let initializedConfigInfo: InitConfigParams; - - it("successfully init a WhirlpoolsConfig account", async () => { - const { configInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); - - const configAccount = await client.getConfig(configInitInfo.whirlpoolConfigKeypair.publicKey); - - assert.ok( - configAccount.collectProtocolFeesAuthority.equals(configInitInfo.collectProtocolFeesAuthority) - ); - - assert.ok(configAccount.feeAuthority.equals(configInitInfo.feeAuthority)); - - assert.ok( - configAccount.rewardEmissionsSuperAuthority.equals( - configInitInfo.rewardEmissionsSuperAuthority - ) - ); - - assert.equal(configAccount.defaultProtocolFeeRate, configInitInfo.defaultProtocolFeeRate); - - initializedConfigInfo = configInitInfo; - }); - - it("fail on passing in already initialized whirlpool account", async () => { - let infoWithDupeConfigKey = { - ...generateDefaultConfigParams(context).configInitInfo, - whirlpoolConfigKeypair: initializedConfigInfo.whirlpoolConfigKeypair, - }; - await assert.rejects(client.initConfigTx(infoWithDupeConfigKey).buildAndExecute(), /0x0/); - }); - - it("succeeds when funder is different than account paying for transaction fee", async () => { - const funderKeypair = anchor.web3.Keypair.generate(); - await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); - const { configInitInfo } = generateDefaultConfigParams(context, funderKeypair.publicKey); - await client.initConfigTx(configInitInfo).addSigner(funderKeypair).buildAndExecute(); - }); -}); diff --git a/sdk/tests/close_position.ts b/sdk/tests/integration/close_position.test.ts similarity index 70% rename from sdk/tests/close_position.ts rename to sdk/tests/integration/close_position.test.ts index 23917b4..c00546f 100644 --- a/sdk/tests/close_position.ts +++ b/sdk/tests/integration/close_position.test.ts @@ -1,8 +1,7 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initTestPool, initTestPoolWithLiquidity, openPosition } from "./utils/init-utils"; +import { WhirlpoolContext } from "../../src/context"; +import { initTestPool, initTestPoolWithLiquidity, openPosition } from "../utils/init-utils"; import { approveToken, createAndMintToTokenAccount, @@ -11,40 +10,39 @@ import { TickSpacing, transfer, ZERO_BN, -} from "./utils"; -import { WhirlpoolTestFixture } from "./utils/fixture"; +} from "../utils"; +import { WhirlpoolTestFixture } from "../utils/fixture"; +import { WhirlpoolIx } from "../../src"; +import { toTx } from "../../src/utils/instructions-util"; describe("close_position", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); it("successfully closes an open position", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); - const { params } = await openPosition(client, poolInitInfo.whirlpoolPda.publicKey, 0, 128); + const { params } = await openPosition(ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128); const receiverKeypair = anchor.web3.Keypair.generate(); - await client - .closePositionTx({ + await toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: provider.wallet.publicKey, receiver: receiverKeypair.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) - .buildAndExecute(); + ).buildAndExecute(); const supplyResponse = await provider.connection.getTokenSupply(params.positionMintAddress); assert.equal(supplyResponse.value.uiAmount, 0); assert.equal(await provider.connection.getAccountInfo(params.positionPda.publicKey), undefined); - assert.equal( - await provider.connection.getAccountInfo(params.positionTokenAccountAddress), - undefined - ); + assert.equal(await provider.connection.getAccountInfo(params.positionTokenAccount), undefined); const receiverAccount = await provider.connection.getAccountInfo(receiverKeypair.publicKey); const lamports = receiverAccount?.lamports; @@ -52,82 +50,74 @@ describe("close_position", () => { }); it("succeeds if the position is delegated", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const owner = anchor.web3.Keypair.generate(); const delegate = anchor.web3.Keypair.generate(); const { params } = await openPosition( - client, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, owner.publicKey ); - await approveToken( - client.context.provider, - params.positionTokenAccountAddress, - delegate.publicKey, - 1, - owner - ); + await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner); await setAuthority( - client.context.provider, - params.positionTokenAccountAddress, + ctx.provider, + params.positionTokenAccount, delegate.publicKey, "CloseAccount", owner ); - await client - .closePositionTx({ + await toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: delegate.publicKey, receiver: owner.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) + ) .addSigner(delegate) .buildAndExecute(); }); it("succeeds with the owner's signature even if the token is delegated", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const owner = anchor.web3.Keypair.generate(); const delegate = anchor.web3.Keypair.generate(); const { params } = await openPosition( - client, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, owner.publicKey ); - await approveToken( - client.context.provider, - params.positionTokenAccountAddress, - delegate.publicKey, - 1, - owner - ); + await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner); - await client - .closePositionTx({ + await toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: owner.publicKey, receiver: owner.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) + ) .addSigner(owner) .buildAndExecute(); }); it("succeeds with position token that was transferred to new owner", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }], }); @@ -142,44 +132,47 @@ describe("close_position", () => { await transfer(provider, position.tokenAccount, newOwnerPositionTokenAccount, 1); - await client - .closePositionTx({ + await toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: newOwner.publicKey, receiver: newOwner.publicKey, position: position.publicKey, positionMint: position.mintKeypair.publicKey, positionTokenAccount: newOwnerPositionTokenAccount, }) + ) .addSigner(newOwner) .buildAndExecute(); }); it("fails to close a position with liquidity", async () => { - const { positionInfo } = await initTestPoolWithLiquidity(client); + const { positionInfo } = await initTestPoolWithLiquidity(ctx); const receiverKeypair = anchor.web3.Keypair.generate(); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: provider.wallet.publicKey, receiver: receiverKeypair.publicKey, position: positionInfo.positionPda.publicKey, positionMint: positionInfo.positionMintAddress, - positionTokenAccount: positionInfo.positionTokenAccountAddress, + positionTokenAccount: positionInfo.positionTokenAccount, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1775/ // ClosePositionNotEmpty ); }); it("fails if owner is not signer", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const owner = anchor.web3.Keypair.generate(); const { params } = await openPosition( - client, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, @@ -187,70 +180,66 @@ describe("close_position", () => { ); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: owner.publicKey, receiver: owner.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails if delegate is not signer", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const owner = anchor.web3.Keypair.generate(); const delegate = anchor.web3.Keypair.generate(); const { params } = await openPosition( - client, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, owner.publicKey ); - await approveToken( - client.context.provider, - params.positionTokenAccountAddress, - delegate.publicKey, - 1, - owner - ); + await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner); await setAuthority( - client.context.provider, - params.positionTokenAccountAddress, + ctx.provider, + params.positionTokenAccount, delegate.publicKey, "CloseAccount", owner ); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: delegate.publicKey, receiver: owner.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails if the authority does not match", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const owner = anchor.web3.Keypair.generate(); const fakeOwner = anchor.web3.Keypair.generate(); const { params } = await openPosition( - client, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, @@ -258,14 +247,16 @@ describe("close_position", () => { ); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: fakeOwner.publicKey, receiver: owner.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) + ) .addSigner(fakeOwner) .buildAndExecute(), /0x1783/ // MissingOrInvalidDelegate @@ -273,7 +264,7 @@ describe("close_position", () => { }); it("fails if position token account does not contain exactly one token", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }], }); @@ -286,57 +277,54 @@ describe("close_position", () => { ); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: provider.wallet.publicKey, receiver: provider.wallet.publicKey, position: position.publicKey, positionMint: position.mintKeypair.publicKey, positionTokenAccount: fakePositionTokenAccount, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails if delegated amount is 0", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const owner = anchor.web3.Keypair.generate(); const delegate = anchor.web3.Keypair.generate(); const { params } = await openPosition( - client, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, owner.publicKey ); - await approveToken( - client.context.provider, - params.positionTokenAccountAddress, - delegate.publicKey, - 0, - owner - ); + await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 0, owner); await setAuthority( - client.context.provider, - params.positionTokenAccountAddress, + ctx.provider, + params.positionTokenAccount, delegate.publicKey, "CloseAccount", owner ); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: delegate.publicKey, receiver: owner.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1784/ // InvalidPositionTokenAmount @@ -344,44 +332,40 @@ describe("close_position", () => { }); it("fails if positionAuthority does not match delegate", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const owner = anchor.web3.Keypair.generate(); const delegate = anchor.web3.Keypair.generate(); const fakeDelegate = anchor.web3.Keypair.generate(); const { params } = await openPosition( - client, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, owner.publicKey ); - await approveToken( - client.context.provider, - params.positionTokenAccountAddress, - delegate.publicKey, - 1, - owner - ); + await approveToken(ctx.provider, params.positionTokenAccount, delegate.publicKey, 1, owner); await setAuthority( - client.context.provider, - params.positionTokenAccountAddress, + ctx.provider, + params.positionTokenAccount, delegate.publicKey, "CloseAccount", owner ); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: fakeDelegate.publicKey, receiver: owner.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) + ) .addSigner(fakeDelegate) .buildAndExecute(), /0x1783/ // MissingOrInvalidDelegate @@ -389,7 +373,7 @@ describe("close_position", () => { }); it("fails if position token account mint does not match position mint", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }], }); @@ -402,21 +386,22 @@ describe("close_position", () => { const fakePositionTokenAccount = await createAndMintToTokenAccount(provider, tokenMintA, 1); await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: provider.wallet.publicKey, receiver: provider.wallet.publicKey, position: position.publicKey, positionMint: position.mintKeypair.publicKey, positionTokenAccount: fakePositionTokenAccount, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails if position_mint does not match position's position_mint field", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: ZERO_BN }], }); @@ -427,15 +412,16 @@ describe("close_position", () => { const position = positions[0]; await assert.rejects( - client - .closePositionTx({ + toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: provider.wallet.publicKey, receiver: provider.wallet.publicKey, position: position.publicKey, positionMint: tokenMintA, positionTokenAccount: position.tokenAccount, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // ConstraintAddress ); }); diff --git a/sdk/tests/collect_fees.ts b/sdk/tests/integration/collect_fees.test.ts similarity index 79% rename from sdk/tests/collect_fees.ts rename to sdk/tests/integration/collect_fees.test.ts index 929fd8d..d0000a0 100644 --- a/sdk/tests/collect_fees.ts +++ b/sdk/tests/integration/collect_fees.test.ts @@ -1,27 +1,38 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; +import { u64 } from "@solana/spl-token"; +import Decimal from "decimal.js"; import { - approveToken, + WhirlpoolContext, + AccountFetcher, + PositionData, + collectFeesQuote, + TickArrayData, + TickUtil, + WhirlpoolData, + WhirlpoolIx, + PDAUtil, + TickArrayUtil, +} from "../../src"; +import { + TickSpacing, + ZERO_BN, createTokenAccount, getTokenBalance, - TickSpacing, + approveToken, transfer, - ZERO_BN, -} from "./utils"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { getOraclePda, getPositionPda, getTickArrayPda, toX64 } from "../src"; -import { WhirlpoolTestFixture } from "./utils/fixture"; -import Decimal from "decimal.js"; -import { u64 } from "@solana/spl-token"; -import { initTestPool } from "./utils/init-utils"; +} from "../utils"; +import { WhirlpoolTestFixture } from "../utils/fixture"; +import { initTestPool } from "../utils/init-utils"; +import { MathUtil } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("collect_fees", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully collect fees", async () => { // In same tick array - start index 22528 @@ -29,7 +40,7 @@ describe("collect_fees", () => { const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }, // In range position @@ -49,23 +60,24 @@ describe("collect_fees", () => { positions, } = fixture.getInfos(); - const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528); - const positionBeforeSwap = await client.getPosition(positions[0].publicKey); + const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528); + const positionBeforeSwap = (await fetcher.getPosition(positions[0].publicKey)) as PositionData; assert.ok(positionBeforeSwap.feeOwedA.eq(ZERO_BN)); assert.ok(positionBeforeSwap.feeOwedB.eq(ZERO_BN)); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); // Accrue fees in token A - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(200_000), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -75,18 +87,19 @@ describe("collect_fees", () => { tickArray2: tickArrayPda.publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); // Accrue fees in token B - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(200_000), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(5)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(5)), amountSpecifiedIsInput: true, aToB: false, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -96,26 +109,44 @@ describe("collect_fees", () => { tickArray2: tickArrayPda.publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); - await client - .updateFeesAndRewards({ + await toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); - const positionBeforeCollect = await client.getPosition(positions[0].publicKey); + const positionBeforeCollect = (await fetcher.getPosition( + positions[0].publicKey, + true + )) as PositionData; assert.ok(positionBeforeCollect.feeOwedA.eq(new u64(581))); assert.ok(positionBeforeCollect.feeOwedB.eq(new u64(581))); const feeAccountA = await createTokenAccount(provider, tokenMintA, provider.wallet.publicKey); const feeAccountB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey); - await client - .collectFeesTx({ + // Generate collect fees expectation + const whirlpoolData = (await fetcher.getPool(whirlpoolPda.publicKey)) as WhirlpoolData; + const tickArrayData = (await fetcher.getTickArray(tickArrayPda.publicKey)) as TickArrayData; + const lowerTick = TickArrayUtil.getTickFromArray(tickArrayData, tickLowerIndex, tickSpacing); + const upperTick = TickArrayUtil.getTickFromArray(tickArrayData, tickUpperIndex, tickSpacing); + const expectation = collectFeesQuote({ + whirlpool: whirlpoolData, + position: positionBeforeCollect, + tickLower: lowerTick, + tickUpper: upperTick, + }); + + // Perform collect fees tx + await toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -125,32 +156,34 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(); - const positionAfter = await client.getPosition(positions[0].publicKey); + ).buildAndExecute(); + const positionAfter = (await fetcher.getPosition(positions[0].publicKey, true)) as PositionData; const feeBalanceA = await getTokenBalance(provider, feeAccountA); const feeBalanceB = await getTokenBalance(provider, feeAccountB); - assert.equal(feeBalanceA, "581"); - assert.equal(feeBalanceB, "581"); + + assert.equal(feeBalanceA, expectation.feeOwedA); + assert.equal(feeBalanceB, expectation.feeOwedB); assert.ok(positionAfter.feeOwedA.eq(ZERO_BN)); assert.ok(positionAfter.feeOwedB.eq(ZERO_BN)); // Assert out of range position values - await client - .updateFeesAndRewards({ + await toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[1].publicKey, tickArrayLower: positions[1].tickArrayLower, tickArrayUpper: positions[1].tickArrayUpper, }) - .buildAndExecute(); - const outOfRangePosition = await client.getPosition(positions[1].publicKey); - assert.ok(outOfRangePosition.feeOwedA.eq(ZERO_BN)); - assert.ok(outOfRangePosition.feeOwedB.eq(ZERO_BN)); + ).buildAndExecute(); + const outOfRangePosition = await fetcher.getPosition(positions[1].publicKey, true); + assert.ok(outOfRangePosition?.feeOwedA.eq(ZERO_BN)); + assert.ok(outOfRangePosition?.feeOwedB.eq(ZERO_BN)); }); it("successfully collect fees with approved delegate", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: new u64(10_000_000) }, // In range position @@ -167,8 +200,9 @@ describe("collect_fees", () => { const delegate = anchor.web3.Keypair.generate(); await approveToken(provider, position.tokenAccount, delegate.publicKey, 1); - await client - .collectFeesTx({ + await toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: position.publicKey, @@ -178,13 +212,14 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) + ) .addSigner(delegate) .buildAndExecute(); }); it("successfully collect fees with owner even if there is approved delegate", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: new u64(10_000_000) }, // In range position @@ -201,8 +236,9 @@ describe("collect_fees", () => { const delegate = anchor.web3.Keypair.generate(); await approveToken(provider, position.tokenAccount, delegate.publicKey, 1); - await client - .collectFeesTx({ + await toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: position.publicKey, @@ -212,12 +248,12 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); }); it("successfully collect fees with transferred position token", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 0, tickUpperIndex: 128, liquidityAmount: new u64(10_000_000) }, // In range position @@ -240,8 +276,9 @@ describe("collect_fees", () => { await transfer(provider, position.tokenAccount, newOwnerPositionTokenAccount, 1); - await client - .collectFeesTx({ + await toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: newOwner.publicKey, position: position.publicKey, @@ -251,6 +288,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) + ) .addSigner(newOwner) .buildAndExecute(); }); @@ -260,7 +298,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -273,11 +311,12 @@ describe("collect_fees", () => { const { poolInitInfo: { whirlpoolPda: whirlpoolPda2 }, - } = await initTestPool(client, tickSpacing); + } = await initTestPool(ctx, tickSpacing); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda2.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -287,7 +326,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d1/ // ConstraintHasOne ); }); @@ -297,7 +336,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -315,8 +354,9 @@ describe("collect_fees", () => { ); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -326,15 +366,16 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); await transfer(provider, positions[0].tokenAccount, positionTokenAccount2, 1); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -344,7 +385,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); @@ -354,7 +395,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -368,8 +409,9 @@ describe("collect_fees", () => { const delegate = anchor.web3.Keypair.generate(); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -379,6 +421,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1783/ // MissingOrInvalidDelegate @@ -390,7 +433,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -405,8 +448,9 @@ describe("collect_fees", () => { await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 2); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -416,6 +460,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1784/ // InvalidPositionTokenAmount @@ -427,7 +472,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -442,8 +487,9 @@ describe("collect_fees", () => { await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -453,7 +499,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); @@ -463,7 +509,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -481,8 +527,9 @@ describe("collect_fees", () => { ); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -492,7 +539,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); @@ -502,7 +549,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -523,8 +570,9 @@ describe("collect_fees", () => { const fakeVaultB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -534,13 +582,14 @@ describe("collect_fees", () => { tokenVaultA: fakeVaultA, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // ConstraintAddress ); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -550,7 +599,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: fakeVaultB, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // ConstraintAddress ); }); @@ -560,7 +609,7 @@ describe("collect_fees", () => { const tickLowerIndex = 29440; const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -589,8 +638,9 @@ describe("collect_fees", () => { ); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -600,13 +650,14 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); await assert.rejects( - client - .collectFeesTx({ + toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -616,7 +667,7 @@ describe("collect_fees", () => { tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); diff --git a/sdk/tests/collect_protocol_fees.ts b/sdk/tests/integration/collect_protocol_fees.test.ts similarity index 66% rename from sdk/tests/collect_protocol_fees.ts rename to sdk/tests/integration/collect_protocol_fees.test.ts index 64cf5ee..4d7beb7 100644 --- a/sdk/tests/collect_protocol_fees.ts +++ b/sdk/tests/integration/collect_protocol_fees.test.ts @@ -1,20 +1,20 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initTestPool } from "./utils/init-utils"; -import { getOraclePda, toX64 } from "../src"; -import { WhirlpoolTestFixture } from "./utils/fixture"; import { u64 } from "@solana/spl-token"; import Decimal from "decimal.js"; -import { createTokenAccount, getTokenBalance, TickSpacing, ZERO_BN } from "./utils"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx, PDAUtil } from "../../src"; +import { TickSpacing, ZERO_BN, createTokenAccount, getTokenBalance } from "../utils"; +import { WhirlpoolTestFixture } from "../utils/fixture"; +import { initTestPool } from "../utils/init-utils"; +import { MathUtil } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("collect_protocol_fees", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully collects fees", async () => { // In same tick array - start index 22528 @@ -22,7 +22,7 @@ describe("collect_protocol_fees", () => { const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(10_000_000) }], }); @@ -35,40 +35,43 @@ describe("collect_protocol_fees", () => { tokenMintB, }, configKeypairs: { feeAuthorityKeypair, collectProtocolFeesAuthorityKeypair }, - configInitInfo: { whirlpoolConfigKeypair }, + configInitInfo: { whirlpoolsConfigKeypair: whirlpoolsConfigKeypair }, tokenAccountA, tokenAccountB, positions, } = fixture.getInfos(); - await client - .setProtocolFeeRateIx({ + await toTx( + ctx, + WhirlpoolIx.setProtocolFeeRateIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey, feeAuthority: feeAuthorityKeypair.publicKey, protocolFeeRate: 2500, }) + ) .addSigner(feeAuthorityKeypair) .buildAndExecute(); - const poolBefore = await client.getPool(whirlpoolPda.publicKey); - assert.ok(poolBefore.protocolFeeOwedA.eq(ZERO_BN)); - assert.ok(poolBefore.protocolFeeOwedB.eq(ZERO_BN)); + const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; + assert.ok(poolBefore?.protocolFeeOwedA.eq(ZERO_BN)); + assert.ok(poolBefore?.protocolFeeOwedB.eq(ZERO_BN)); const tickArrayPda = positions[0].tickArrayLower; - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); // Accrue fees in token A - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(200_000), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -78,18 +81,19 @@ describe("collect_protocol_fees", () => { tickArray2: tickArrayPda, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); // Accrue fees in token B - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(200_000), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(5)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(5)), amountSpecifiedIsInput: true, aToB: false, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -99,25 +103,27 @@ describe("collect_protocol_fees", () => { tickArray2: tickArrayPda, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); - const poolAfter = await client.getPool(whirlpoolPda.publicKey); - assert.ok(poolAfter.protocolFeeOwedA.eq(new u64(150))); - assert.ok(poolAfter.protocolFeeOwedB.eq(new u64(150))); + const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; + assert.ok(poolAfter?.protocolFeeOwedA.eq(new u64(150))); + assert.ok(poolAfter?.protocolFeeOwedB.eq(new u64(150))); const destA = await createTokenAccount(provider, tokenMintA, provider.wallet.publicKey); const destB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey); - await client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + await toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolPda.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, - tokenDestinationA: destA, - tokenDestinationB: destB, + tokenOwnerAccountA: destA, + tokenOwnerAccountB: destB, }) + ) .addSigner(collectProtocolFeesAuthorityKeypair) .buildAndExecute(); @@ -125,13 +131,13 @@ describe("collect_protocol_fees", () => { const balanceDestB = await getTokenBalance(provider, destB); assert.equal(balanceDestA, "150"); assert.equal(balanceDestB, "150"); - assert.ok(poolBefore.protocolFeeOwedA.eq(ZERO_BN)); - assert.ok(poolBefore.protocolFeeOwedB.eq(ZERO_BN)); + assert.ok(poolBefore?.protocolFeeOwedA.eq(ZERO_BN)); + assert.ok(poolBefore?.protocolFeeOwedB.eq(ZERO_BN)); }); it("fails to collect fees without the authority's signature", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) }, @@ -140,30 +146,31 @@ describe("collect_protocol_fees", () => { const { poolInitInfo: { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair }, configKeypairs: { collectProtocolFeesAuthorityKeypair }, - configInitInfo: { whirlpoolConfigKeypair }, + configInitInfo: { whirlpoolsConfigKeypair }, tokenAccountA, tokenAccountB, } = fixture.getInfos(); await assert.rejects( - client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolPda.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, - tokenDestinationA: tokenAccountA, - tokenDestinationB: tokenAccountB, + tokenOwnerAccountA: tokenAccountA, + tokenOwnerAccountB: tokenAccountB, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails when collect_protocol_fees_authority is invalid", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) }, @@ -172,22 +179,24 @@ describe("collect_protocol_fees", () => { const { poolInitInfo: { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair }, configKeypairs: { rewardEmissionsSuperAuthorityKeypair }, - configInitInfo: { whirlpoolConfigKeypair }, + configInitInfo: { whirlpoolsConfigKeypair }, tokenAccountA, tokenAccountB, } = fixture.getInfos(); await assert.rejects( - client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolPda.publicKey, collectProtocolFeesAuthority: rewardEmissionsSuperAuthorityKeypair.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, - tokenDestinationA: tokenAccountA, - tokenDestinationB: tokenAccountB, + tokenOwnerAccountA: tokenAccountA, + tokenOwnerAccountB: tokenAccountB, }) + ) .addSigner(rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(), /0x7dc/ // ConstraintAddress @@ -196,7 +205,7 @@ describe("collect_protocol_fees", () => { it("fails when whirlpool does not match config", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) }, @@ -205,25 +214,27 @@ describe("collect_protocol_fees", () => { const { poolInitInfo: { tokenVaultAKeypair, tokenVaultBKeypair }, configKeypairs: { collectProtocolFeesAuthorityKeypair }, - configInitInfo: { whirlpoolConfigKeypair }, + configInitInfo: { whirlpoolsConfigKeypair }, tokenAccountA, tokenAccountB, } = fixture.getInfos(); const { poolInitInfo: { whirlpoolPda: whirlpoolPda2 }, - } = await initTestPool(client, tickSpacing); + } = await initTestPool(ctx, tickSpacing); await assert.rejects( - client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolPda2.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, - tokenDestinationA: tokenAccountA, - tokenDestinationB: tokenAccountB, + tokenOwnerAccountA: tokenAccountA, + tokenOwnerAccountB: tokenAccountB, }) + ) .addSigner(collectProtocolFeesAuthorityKeypair) .buildAndExecute(), /0x7d1/ // ConstraintHasOne @@ -232,7 +243,7 @@ describe("collect_protocol_fees", () => { it("fails when vaults do not match whirlpool vaults", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) }, @@ -247,7 +258,7 @@ describe("collect_protocol_fees", () => { tokenMintB, }, configKeypairs: { collectProtocolFeesAuthorityKeypair }, - configInitInfo: { whirlpoolConfigKeypair }, + configInitInfo: { whirlpoolsConfigKeypair: whirlpoolsConfigKeypair }, tokenAccountA, tokenAccountB, } = fixture.getInfos(); @@ -256,32 +267,36 @@ describe("collect_protocol_fees", () => { const fakeVaultB = await createTokenAccount(provider, tokenMintB, provider.wallet.publicKey); await assert.rejects( - client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolPda.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: fakeVaultA, tokenVaultB: tokenVaultBKeypair.publicKey, - tokenDestinationA: tokenAccountA, - tokenDestinationB: tokenAccountB, + tokenOwnerAccountA: tokenAccountA, + tokenOwnerAccountB: tokenAccountB, }) + ) .addSigner(collectProtocolFeesAuthorityKeypair) .buildAndExecute(), /0x7dc/ // ConstraintAddress ); await assert.rejects( - client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolPda.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: fakeVaultB, - tokenDestinationA: tokenAccountA, - tokenDestinationB: tokenAccountB, + tokenOwnerAccountA: tokenAccountA, + tokenOwnerAccountB: tokenAccountB, }) + ) .addSigner(collectProtocolFeesAuthorityKeypair) .buildAndExecute(), /0x7dc/ // ConstraintAddress @@ -290,7 +305,7 @@ describe("collect_protocol_fees", () => { it("fails when destination mints do not match whirlpool mints", async () => { const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [ { tickLowerIndex: 29440, tickUpperIndex: 33536, liquidityAmount: new u64(10_000_000) }, @@ -305,7 +320,7 @@ describe("collect_protocol_fees", () => { tokenMintB, }, configKeypairs: { collectProtocolFeesAuthorityKeypair }, - configInitInfo: { whirlpoolConfigKeypair }, + configInitInfo: { whirlpoolsConfigKeypair: whirlpoolsConfigKepair }, tokenAccountA, tokenAccountB, } = fixture.getInfos(); @@ -314,32 +329,36 @@ describe("collect_protocol_fees", () => { const invalidDestB = await createTokenAccount(provider, tokenMintA, provider.wallet.publicKey); await assert.rejects( - client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKepair.publicKey, whirlpool: whirlpoolPda.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, - tokenDestinationA: invalidDestA, - tokenDestinationB: tokenAccountB, + tokenOwnerAccountA: invalidDestA, + tokenOwnerAccountB: tokenAccountB, }) + ) .addSigner(collectProtocolFeesAuthorityKeypair) .buildAndExecute(), /0x7d3/ // ConstraintRaw ); await assert.rejects( - client - .collectProtocolFeesTx({ - whirlpoolsConfig: whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: whirlpoolsConfigKepair.publicKey, whirlpool: whirlpoolPda.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: tokenVaultAKeypair.publicKey, tokenVaultB: tokenVaultBKeypair.publicKey, - tokenDestinationA: tokenAccountA, - tokenDestinationB: invalidDestB, + tokenOwnerAccountA: tokenAccountA, + tokenOwnerAccountB: invalidDestB, }) + ) .addSigner(collectProtocolFeesAuthorityKeypair) .buildAndExecute(), /0x7d3/ // ConstraintRaw diff --git a/sdk/tests/collect_reward.ts b/sdk/tests/integration/collect_reward.test.ts similarity index 67% rename from sdk/tests/collect_reward.ts rename to sdk/tests/integration/collect_reward.test.ts index 441e063..ad21e32 100644 --- a/sdk/tests/collect_reward.ts +++ b/sdk/tests/integration/collect_reward.test.ts @@ -1,43 +1,70 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; +import { u64 } from "@solana/spl-token"; +import Decimal from "decimal.js"; import { - approveToken, - createAndMintToTokenAccount, - createMint, + WhirlpoolContext, + AccountFetcher, + NUM_REWARDS, + collectRewardsQuote, + WhirlpoolData, + PositionData, + TickArrayData, + TickUtil, + WhirlpoolIx, + TickArrayUtil, +} from "../../src"; +import { + TickSpacing, + sleep, createTokenAccount, getTokenBalance, - sleep, - TickSpacing, - transfer, ZERO_BN, -} from "./utils"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { u64 } from "@solana/spl-token"; -import { NUM_REWARDS, toX64 } from "../src"; -import Decimal from "decimal.js"; -import { WhirlpoolTestFixture } from "./utils/fixture"; -import { initTestPool } from "./utils/init-utils"; + approveToken, + transfer, + createMint, + createAndMintToTokenAccount, +} from "../utils"; +import { WhirlpoolTestFixture } from "../utils/fixture"; +import { initTestPool } from "../utils/init-utils"; +import { MathUtil } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("collect_reward", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully collect rewards", async () => { const vaultStartBalance = 1_000_000; - const fixture = await new WhirlpoolTestFixture(client).init({ - tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + const lowerTickIndex = -1280, + upperTickIndex = 1280, + tickSpacing = TickSpacing.Standard; + const fixture = await new WhirlpoolTestFixture(ctx).init({ + tickSpacing: tickSpacing, + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ - { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, + { + tickLowerIndex: lowerTickIndex, + tickUpperIndex: upperTickIndex, + liquidityAmount: new anchor.BN(1_000_000), + }, ], rewards: [ - { emissionsPerSecondX64: toX64(new Decimal(10)), vaultAmount: new u64(vaultStartBalance) }, - { emissionsPerSecondX64: toX64(new Decimal(10)), vaultAmount: new u64(vaultStartBalance) }, - { emissionsPerSecondX64: toX64(new Decimal(10)), vaultAmount: new u64(vaultStartBalance) }, + { + emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)), + vaultAmount: new u64(vaultStartBalance), + }, + { + emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)), + vaultAmount: new u64(vaultStartBalance), + }, + { + emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)), + vaultAmount: new u64(vaultStartBalance), + }, ], }); const { @@ -46,23 +73,52 @@ describe("collect_reward", () => { rewards, } = fixture.getInfos(); await sleep(500); - await client - .updateFeesAndRewards({ + await toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: positions[0].tickArrayLower, tickArrayUpper: positions[0].tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); + // Generate collect reward expectation + const pool = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; + const positionPreCollect = (await fetcher.getPosition( + positions[0].publicKey, + true + )) as PositionData; + const tickArrayLower = (await fetcher.getTickArray( + positions[0].tickArrayLower, + true + )) as TickArrayData; + + const tickArrayUpper = (await fetcher.getTickArray( + positions[0].tickArrayUpper, + true + )) as TickArrayData; + const lowerTick = TickArrayUtil.getTickFromArray(tickArrayLower, lowerTickIndex, tickSpacing); + const upperTick = TickArrayUtil.getTickFromArray(tickArrayUpper, upperTickIndex, tickSpacing); + + const expectation = collectRewardsQuote({ + whirlpool: pool, + position: positionPreCollect, + tickLower: lowerTick, + tickUpper: upperTick, + }); + + // Perform collect rewards tx for (let i = 0; i < NUM_REWARDS; i++) { const rewardOwnerAccount = await createTokenAccount( provider, rewards[i].rewardMint, provider.wallet.publicKey ); - await client - .collectRewardTx({ + + await toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -71,30 +127,33 @@ describe("collect_reward", () => { rewardVault: rewards[i].rewardVaultKeypair.publicKey, rewardIndex: i, }) - .buildAndExecute(); + ).buildAndExecute(); const collectedBalance = parseInt(await getTokenBalance(provider, rewardOwnerAccount)); - assert.ok(collectedBalance > 0); + assert.ok(collectedBalance === expectation[i]?.toNumber()); const vaultBalance = parseInt( await getTokenBalance(provider, rewards[i].rewardVaultKeypair.publicKey) ); assert.equal(vaultStartBalance - collectedBalance, vaultBalance); - const position = await client.getPosition(positions[0].publicKey); - assert.equal(position.rewardInfos[i].amountOwed, 0); - assert.ok(position.rewardInfos[i].growthInsideCheckpoint.gte(ZERO_BN)); + const position = await fetcher.getPosition(positions[0].publicKey, true); + assert.equal(position?.rewardInfos[i].amountOwed, 0); + assert.ok(position?.rewardInfos[i].growthInsideCheckpoint.gte(ZERO_BN)); } }); it("successfully collect reward with a position authority delegate", async () => { const vaultStartBalance = 1_000_000; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], rewards: [ - { emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(vaultStartBalance) }, + { + emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), + vaultAmount: new u64(vaultStartBalance), + }, ], }); const { @@ -108,20 +167,22 @@ describe("collect_reward", () => { provider.wallet.publicKey ); - await client - .updateFeesAndRewards({ + await toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: positions[0].tickArrayLower, tickArrayUpper: positions[0].tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); const delegate = anchor.web3.Keypair.generate(); await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1); - await client - .collectRewardTx({ + await toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -130,20 +191,24 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) + ) .addSigner(delegate) .buildAndExecute(); }); it("successfully collect reward with transferred position token", async () => { const vaultStartBalance = 1_000_000; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], rewards: [ - { emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(vaultStartBalance) }, + { + emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), + vaultAmount: new u64(vaultStartBalance), + }, ], }); const { @@ -165,17 +230,19 @@ describe("collect_reward", () => { ); await transfer(provider, positions[0].tokenAccount, delegatePositionAccount, 1); - await client - .updateFeesAndRewards({ + await toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: positions[0].tickArrayLower, tickArrayUpper: positions[0].tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); - await client - .collectRewardTx({ + await toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -184,20 +251,24 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) + ) .addSigner(delegate) .buildAndExecute(); }); it("successfully collect reward with owner even when there is a delegate", async () => { const vaultStartBalance = 1_000_000; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], rewards: [ - { emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(vaultStartBalance) }, + { + emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), + vaultAmount: new u64(vaultStartBalance), + }, ], }); const { @@ -211,20 +282,22 @@ describe("collect_reward", () => { provider.wallet.publicKey ); - await client - .updateFeesAndRewards({ + await toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: positions[0].tickArrayLower, tickArrayUpper: positions[0].tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); const delegate = anchor.web3.Keypair.generate(); await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1); - await client - .collectRewardTx({ + await toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -233,13 +306,13 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) - .buildAndExecute(); + ).buildAndExecute(); }); it("fails when reward index references an uninitialized reward", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], @@ -256,8 +329,9 @@ describe("collect_reward", () => { ); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -266,33 +340,36 @@ describe("collect_reward", () => { rewardVault: anchor.web3.PublicKey.default, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /0xbbf/ // AccountNotInitialized ); }); it("fails when position does not match whirlpool", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { positions, rewards } = fixture.getInfos(); const { poolInitInfo: { whirlpoolPda }, - } = await initTestPool(client, TickSpacing.Standard); + } = await initTestPool(ctx, TickSpacing.Standard); const rewardOwnerAccount = await createTokenAccount( provider, rewards[0].rewardMint, provider.wallet.publicKey ); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -301,19 +378,21 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d1/ // ConstraintHasOne ); }); it("fails when position token account does not have exactly one token", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda }, @@ -333,8 +412,9 @@ describe("collect_reward", () => { ); await transfer(provider, positions[0].tokenAccount, otherPositionAcount, 1); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -343,19 +423,21 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when position token account mint does not match position mint", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda, tokenMintA }, @@ -371,8 +453,9 @@ describe("collect_reward", () => { const fakePositionTokenAccount = await createAndMintToTokenAccount(provider, tokenMintA, 1); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -381,19 +464,21 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when position authority is not approved delegate for position token account", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda }, @@ -407,8 +492,9 @@ describe("collect_reward", () => { ); const delegate = anchor.web3.Keypair.generate(); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -417,6 +503,7 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1783/ // MissingOrInvalidDelegate @@ -424,13 +511,15 @@ describe("collect_reward", () => { }); it("fails when position authority is not authorized for exactly one token", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda }, @@ -445,8 +534,9 @@ describe("collect_reward", () => { const delegate = anchor.web3.Keypair.generate(); await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 2); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -455,6 +545,7 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1784/ // InvalidPositionTokenAmount @@ -462,13 +553,15 @@ describe("collect_reward", () => { }); it("fails when position authority was not a signer", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda }, @@ -483,8 +576,9 @@ describe("collect_reward", () => { const delegate = anchor.web3.Keypair.generate(); await approveToken(provider, positions[0].tokenAccount, delegate.publicKey, 1); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: delegate.publicKey, position: positions[0].publicKey, @@ -493,19 +587,21 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails when reward vault does not match whirlpool reward vault", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda }, @@ -518,8 +614,9 @@ describe("collect_reward", () => { provider.wallet.publicKey ); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -528,19 +625,21 @@ describe("collect_reward", () => { rewardVault: rewardOwnerAccount, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // ConstraintAddress ); }); it("fails when reward owner account mint does not match whirlpool reward mint", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda, tokenMintA }, @@ -553,8 +652,9 @@ describe("collect_reward", () => { provider.wallet.publicKey ); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -563,19 +663,21 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when reward index is out of bounds", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [ { tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: new anchor.BN(1_000_000) }, ], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda, tokenMintA }, @@ -588,8 +690,9 @@ describe("collect_reward", () => { provider.wallet.publicKey ); await assert.rejects( - client - .collectRewardTx({ + toTx( + ctx, + WhirlpoolIx.collectRewardIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -598,7 +701,7 @@ describe("collect_reward", () => { rewardVault: rewards[0].rewardVaultKeypair.publicKey, rewardIndex: 4, }) - .buildAndExecute(), + ).buildAndExecute(), /Program failed to complete/ // index out of bounds ); }); diff --git a/sdk/tests/decrease_liquidity.ts b/sdk/tests/integration/decrease_liquidity.test.ts similarity index 77% rename from sdk/tests/decrease_liquidity.ts rename to sdk/tests/integration/decrease_liquidity.test.ts index 141b8a7..05865fc 100644 --- a/sdk/tests/decrease_liquidity.ts +++ b/sdk/tests/integration/decrease_liquidity.test.ts @@ -1,47 +1,64 @@ import * as anchor from "@project-serum/anchor"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; import * as assert from "assert"; -import { initTestPool, initTickArray, openPosition } from "./utils/init-utils"; +import { u64 } from "@solana/spl-token"; +import Decimal from "decimal.js"; import { + WhirlpoolContext, + AccountFetcher, + WhirlpoolData, + TickArrayData, + PositionData, + WhirlpoolIx, +} from "../../src"; +import { + TickSpacing, + assertTick, approveToken, - createAndMintToTokenAccount, - createMint, createTokenAccount, transfer, -} from "./utils/token"; -import { u64 } from "@solana/spl-token"; -import { toX64 } from "../src"; -import Decimal from "decimal.js"; -import { assertTick, TickSpacing, ZERO_BN } from "./utils"; -import { WhirlpoolTestFixture } from "./utils/fixture"; + ZERO_BN, + createAndMintToTokenAccount, + createMint, +} from "../utils"; +import { WhirlpoolTestFixture } from "../utils/fixture"; +import { initTestPool, openPosition, initTickArray } from "../utils/init-utils"; +import { decreaseLiquidityQuoteByLiquidityWithParams } from "../../src/quotes/public/decrease-liquidity-quote"; +import { MathUtil, Percentage } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("decrease_liquidity", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); - - // TODO: Convert these test cases to take a non-zero min token amount to verify that the calculation is working. + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully decrease liquidity from position in one tick array", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const tickLower = 7168, + tickUpper = 8960; + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1.48)), - positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], + initialSqrtPrice: MathUtil.toX64(new Decimal(1.48)), + positions: [{ tickLowerIndex: tickLower, tickUpperIndex: tickUpper, liquidityAmount }], }); const { poolInitInfo, tokenAccountA, tokenAccountB, positions } = fixture.getInfos(); const { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair } = poolInitInfo; - const poolBefore = await client.getPool(whirlpoolPda.publicKey); + const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; - const removeAmount = new u64(1_000_000); - await client - .decreaseLiquidityTx({ - liquidityAmount: removeAmount, - tokenMinA: new u64(0), - tokenMinB: new u64(0), + const removalQuote = decreaseLiquidityQuoteByLiquidityWithParams({ + liquidity: new u64(1_000_000), + sqrtPrice: poolBefore.sqrtPrice, + slippageTolerance: Percentage.fromFraction(1, 100), + tickCurrentIndex: poolBefore.tickCurrentIndex, + tickLowerIndex: tickLower, + tickUpperIndex: tickUpper, + }); + + await toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { + ...removalQuote, whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: positions[0].publicKey, @@ -53,39 +70,51 @@ describe("decrease_liquidity", () => { tickArrayLower: positions[0].tickArrayLower, tickArrayUpper: positions[0].tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); - const remainingLiquidity = liquidityAmount.sub(removeAmount); - const poolAfter = await client.getPool(whirlpoolPda.publicKey); + const remainingLiquidity = liquidityAmount.sub(removalQuote.liquidityAmount); + const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp)); assert.ok(poolAfter.liquidity.eq(remainingLiquidity)); - const position = await client.getPosition(positions[0].publicKey); - assert.ok(position.liquidity.eq(remainingLiquidity)); + const position = await fetcher.getPosition(positions[0].publicKey, true); + assert.ok(position?.liquidity.eq(remainingLiquidity)); - const tickArray = await client.getTickArray(positions[0].tickArrayLower); + const tickArray = (await fetcher.getTickArray( + positions[0].tickArrayLower, + true + )) as TickArrayData; assertTick(tickArray.ticks[56], true, remainingLiquidity, remainingLiquidity); assertTick(tickArray.ticks[70], true, remainingLiquidity, remainingLiquidity.neg()); }); it("successfully decrease liquidity from position in two tick arrays", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const tickLower = -1280, + tickUpper = 1280; + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair } = poolInitInfo; const position = positions[0]; - const poolBefore = await client.getPool(whirlpoolPda.publicKey); + const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; - const removeAmount = new u64(1_000_000); - await client - .decreaseLiquidityTx({ - liquidityAmount: removeAmount, - tokenMinA: new u64(0), - tokenMinB: new u64(0), + const removalQuote = decreaseLiquidityQuoteByLiquidityWithParams({ + liquidity: new u64(1_000_000), + sqrtPrice: poolBefore.sqrtPrice, + slippageTolerance: Percentage.fromFraction(1, 100), + tickCurrentIndex: poolBefore.tickCurrentIndex, + tickLowerIndex: tickLower, + tickUpperIndex: tickUpper, + }); + + await toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { + ...removalQuote, whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: position.publicKey, @@ -97,28 +126,34 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); - const remainingLiquidity = liquidityAmount.sub(removeAmount); - const poolAfter = await client.getPool(whirlpoolPda.publicKey); + const remainingLiquidity = liquidityAmount.sub(removalQuote.liquidityAmount); + const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp)); assert.ok(poolAfter.liquidity.eq(remainingLiquidity)); - const positionAfter = await client.getPosition(position.publicKey); + const positionAfter = (await fetcher.getPosition(position.publicKey, true)) as PositionData; assert.ok(positionAfter.liquidity.eq(remainingLiquidity)); - const tickArrayLower = await client.getTickArray(position.tickArrayLower); + const tickArrayLower = (await fetcher.getTickArray( + position.tickArrayLower, + true + )) as TickArrayData; assertTick(tickArrayLower.ticks[78], true, remainingLiquidity, remainingLiquidity); - const tickArrayUpper = await client.getTickArray(position.tickArrayUpper); + const tickArrayUpper = (await fetcher.getTickArray( + position.tickArrayUpper, + true + )) as TickArrayData; assertTick(tickArrayUpper.ticks[10], true, remainingLiquidity, remainingLiquidity.neg()); }); it("successfully decrease liquidity with approved delegate", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -133,8 +168,9 @@ describe("decrease_liquidity", () => { const removeAmount = new u64(1_000_000); - await client - .decreaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount: removeAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -149,15 +185,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) + ) .addSigner(delegate) .buildAndExecute(); }); it("successfully decrease liquidity with owner even if there is approved delegate", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1.48)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1.48)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -172,8 +209,9 @@ describe("decrease_liquidity", () => { const removeAmount = new u64(1_000_000); - await client - .decreaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount: removeAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -188,14 +226,14 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); }); it("successfully decrease liquidity with transferred position token", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1.48)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1.48)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -211,8 +249,9 @@ describe("decrease_liquidity", () => { ); await transfer(provider, position.tokenAccount, newOwnerPositionTokenAccount, 1); - await client - .decreaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount: removeAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -227,15 +266,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) + ) .addSigner(newOwner) .buildAndExecute(); }); it("fails when liquidity amount is zero", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -243,8 +283,9 @@ describe("decrease_liquidity", () => { const position = positions[0]; await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount: new u64(0), tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -259,15 +300,15 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x177c/ // LiquidityZero ); }); it("fails when position has insufficient liquidity for the withdraw amount", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -275,8 +316,9 @@ describe("decrease_liquidity", () => { const position = positions[0]; await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount: new u64(1_000), tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -291,16 +333,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x177f/ // LiquidityUnderflow ); }); it("fails when token min a subceeded", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(0.005)), + initialSqrtPrice: MathUtil.toX64(new Decimal(0.005)), positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -308,8 +350,9 @@ describe("decrease_liquidity", () => { const position = positions[0]; await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(1_000_000), tokenMinB: new u64(0), @@ -324,24 +367,25 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1782/ // TokenMinSubceeded ); }); it("fails when token min b subceeded", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(5)), + initialSqrtPrice: MathUtil.toX64(new Decimal(5)), positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair } = poolInitInfo; const position = positions[0]; await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(1_000_000), @@ -356,16 +400,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1782/ // TokenMinSubceeded ); }); it("fails when position account does not have exactly 1 token", async () => { const liquidityAmount = new u64(1_250_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -380,8 +424,9 @@ describe("decrease_liquidity", () => { ); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -396,7 +441,7 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); @@ -404,8 +449,9 @@ describe("decrease_liquidity", () => { await transfer(provider, position.tokenAccount, newPositionTokenAccount, 1); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -420,16 +466,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when position token account mint does not match position mint", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -439,8 +485,9 @@ describe("decrease_liquidity", () => { const invalidPositionTokenAccount = await createAndMintToTokenAccount(provider, tokenMintA, 1); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -455,30 +502,31 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // A raw constraint was violated ); }); it("fails when position does not match whirlpool", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda } = poolInitInfo; const tickArray = positions[0].tickArrayLower; - const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard); const { - params: { positionPda, positionTokenAccountAddress }, - } = await openPosition(client, poolInitInfo2.whirlpoolPda.publicKey, 7168, 8960); + params: { positionPda, positionTokenAccount: positionTokenAccountAddress }, + } = await openPosition(ctx, poolInitInfo2.whirlpoolPda.publicKey, 7168, 8960); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -493,16 +541,16 @@ describe("decrease_liquidity", () => { tickArrayLower: tickArray, tickArrayUpper: tickArray, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d1/ // A has_one constraint was violated ); }); it("fails when token vaults do not match whirlpool vaults", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -513,8 +561,9 @@ describe("decrease_liquidity", () => { const fakeVaultB = await createAndMintToTokenAccount(provider, tokenMintB, 1_000); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -529,13 +578,14 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -550,16 +600,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when owner token account mint does not match whirlpool token mint", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -569,8 +619,9 @@ describe("decrease_liquidity", () => { const position = positions[0]; await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -585,13 +636,14 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -606,16 +658,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when position authority is not approved delegate for position token account", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -627,8 +679,9 @@ describe("decrease_liquidity", () => { await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -643,6 +696,7 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1783/ // MissingOrInvalidDelegate @@ -651,9 +705,9 @@ describe("decrease_liquidity", () => { it("fails when position authority is not authorized for exactly 1 token", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -666,8 +720,9 @@ describe("decrease_liquidity", () => { await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -682,6 +737,7 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1784/ // InvalidPositionTokenAmount @@ -690,9 +746,9 @@ describe("decrease_liquidity", () => { it("fails when position authority was not a signer", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -705,8 +761,9 @@ describe("decrease_liquidity", () => { await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(167_000), @@ -721,16 +778,16 @@ describe("decrease_liquidity", () => { tickArrayLower: position.tickArrayLower, tickArrayUpper: position.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails when tick arrays do not match the position", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -739,15 +796,16 @@ describe("decrease_liquidity", () => { const { params: { tickArrayPda: tickArrayLowerPda }, - } = await initTickArray(client, whirlpoolPda.publicKey, 11264); + } = await initTickArray(ctx, whirlpoolPda.publicKey, 11264); const { params: { tickArrayPda: tickArrayUpperPda }, - } = await initTickArray(client, whirlpoolPda.publicKey, 22528); + } = await initTickArray(ctx, whirlpoolPda.publicKey, 22528); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -762,35 +820,36 @@ describe("decrease_liquidity", () => { tickArrayLower: tickArrayLowerPda.publicKey, tickArrayUpper: tickArrayUpperPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1779/ // TicKNotFound ); }); it("fails when the tick arrays are for a different whirlpool", async () => { const liquidityAmount = new u64(6_500_000); - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(2.2)), + initialSqrtPrice: MathUtil.toX64(new Decimal(2.2)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda } = poolInitInfo; const position = positions[0]; - const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard); const { params: { tickArrayPda: tickArrayLowerPda }, - } = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, -11264); + } = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, -11264); const { params: { tickArrayPda: tickArrayUpperPda }, - } = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, 0); + } = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, 0); await assert.rejects( - client - .decreaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount, tokenMinA: new u64(0), tokenMinB: new u64(0), @@ -805,7 +864,7 @@ describe("decrease_liquidity", () => { tickArrayLower: tickArrayLowerPda.publicKey, tickArrayUpper: tickArrayUpperPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d1/ // A has one constraint was violated ); }); diff --git a/sdk/tests/increase_liquidity.ts b/sdk/tests/integration/increase_liquidity.test.ts similarity index 78% rename from sdk/tests/increase_liquidity.ts rename to sdk/tests/integration/increase_liquidity.test.ts index 179ddd9..117e1b2 100644 --- a/sdk/tests/increase_liquidity.ts +++ b/sdk/tests/integration/increase_liquidity.test.ts @@ -1,72 +1,72 @@ -import * as anchor from "@project-serum/anchor"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; import * as assert from "assert"; -import { initTestPool, initTickArray, openPosition } from "./utils/init-utils"; +import * as anchor from "@project-serum/anchor"; +import { u64 } from "@solana/spl-token"; +import Decimal from "decimal.js"; import { + WhirlpoolContext, + AccountFetcher, + WhirlpoolData, + PositionData, + TickArrayData, + TickUtil, + PriceMath, + WhirlpoolIx, + PDAUtil, +} from "../../src"; +import { + TickSpacing, + ZERO_BN, + getTokenBalance, + assertTick, approveToken, createAndMintToTokenAccount, - createMint, + MAX_U64, createTokenAccount, - getTokenBalance, transfer, -} from "./utils/token"; + createMint, +} from "../utils"; +import { WhirlpoolTestFixture } from "../utils/fixture"; +import { initTestPool, initTickArray, openPosition } from "../utils/init-utils"; import { - estimateLiquidityFromTokenAmounts, - getStartTickIndex, - getTickArrayPda, - tickIndexToSqrtPriceX64, - toTokenAmount, - toX64, - TransactionBuilder, -} from "../src"; -import Decimal from "decimal.js"; -import { u64 } from "@solana/spl-token"; -import { assertTick, MAX_U64, TickSpacing, ZERO_BN } from "./utils"; -import { WhirlpoolTestFixture } from "./utils/fixture"; -import { BN } from "@project-serum/anchor"; -import { buildIncreaseLiquidityIx } from "../src/instructions/increase-liquidity-ix"; -import { - buildOpenPositionIx, - buildOpenPositionWithMetadataIx, -} from "../src/instructions/open-position-ix"; -import { - generateDefaultInitTickArrayParams, generateDefaultOpenPositionParams, -} from "./utils/test-builders"; -import { buildInitTickArrayIx } from "../src/instructions/initialize-tick-array-ix"; + generateDefaultInitTickArrayParams, +} from "../utils/test-builders"; +import { PoolUtil, toTokenAmount } from "../../src/utils/public/pool-utils"; +import { MathUtil, TransactionBuilder } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("increase_liquidity", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("increase liquidity of a position spanning two tick arrays", async () => { const currTick = 0; const tickLowerIndex = -1280, tickUpperIndex = 1280; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }], - initialSqrtPrice: tickIndexToSqrtPriceX64(currTick), + initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick), }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda } = poolInitInfo; const positionInitInfo = positions[0]; - const poolBefore = await client.getPool(whirlpoolPda.publicKey); + const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; const tokenAmount = toTokenAmount(167_000, 167_000); - const liquidityAmount = estimateLiquidityFromTokenAmounts( + const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts( currTick, tickLowerIndex, tickUpperIndex, tokenAmount ); - await client - .increaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: tokenAmount.tokenA, tokenMaxB: tokenAmount.tokenB, @@ -81,12 +81,12 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); - const position = await client.getPosition(positionInitInfo.publicKey); + const position = (await fetcher.getPosition(positionInitInfo.publicKey, true)) as PositionData; assert.ok(position.liquidity.eq(liquidityAmount)); - const poolAfter = await client.getPool(whirlpoolPda.publicKey); + const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp)); assert.equal( await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey), @@ -96,11 +96,17 @@ describe("increase_liquidity", () => { await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey), tokenAmount.tokenB.toString() ); - assert.ok(poolAfter.liquidity.eq(new BN(liquidityAmount))); + assert.ok(poolAfter.liquidity.eq(new anchor.BN(liquidityAmount))); - const tickArrayLower = await client.getTickArray(positionInitInfo.tickArrayLower); + const tickArrayLower = (await fetcher.getTickArray( + positionInitInfo.tickArrayLower, + true + )) as TickArrayData; assertTick(tickArrayLower.ticks[78], true, liquidityAmount, liquidityAmount); - const tickArrayUpper = await client.getTickArray(positionInitInfo.tickArrayUpper); + const tickArrayUpper = (await fetcher.getTickArray( + positionInitInfo.tickArrayUpper, + true + )) as TickArrayData; assertTick(tickArrayUpper.ticks[10], true, liquidityAmount, liquidityAmount.neg()); }); @@ -108,26 +114,27 @@ describe("increase_liquidity", () => { const currTick = 500; const tickLowerIndex = 7168; const tickUpperIndex = 8960; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }], - initialSqrtPrice: tickIndexToSqrtPriceX64(currTick), + initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick), }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda } = poolInitInfo; const positionInitInfo = positions[0]; - const poolBefore = await client.getPool(whirlpoolPda.publicKey); + const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; const tokenAmount = toTokenAmount(1_000_000, 0); - const liquidityAmount = estimateLiquidityFromTokenAmounts( + const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts( currTick, tickLowerIndex, tickUpperIndex, tokenAmount ); - await client - .increaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: tokenAmount.tokenA, tokenMaxB: tokenAmount.tokenB, @@ -142,7 +149,7 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); assert.equal( await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey), @@ -155,15 +162,18 @@ describe("increase_liquidity", () => { ); const expectedLiquidity = new anchor.BN(liquidityAmount); - const position = await client.getPosition(positionInitInfo.publicKey); + const position = (await fetcher.getPosition(positionInitInfo.publicKey, true)) as PositionData; assert.ok(position.liquidity.eq(expectedLiquidity)); - const tickArray = await client.getTickArray(positionInitInfo.tickArrayLower); + const tickArray = (await fetcher.getTickArray( + positionInitInfo.tickArrayLower, + true + )) as TickArrayData; assertTick(tickArray.ticks[56], true, expectedLiquidity, expectedLiquidity); assertTick(tickArray.ticks[70], true, expectedLiquidity, expectedLiquidity.neg()); - const poolAfter = await client.getPool(whirlpoolPda.publicKey); + const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp)); assert.equal(poolAfter.liquidity, 0); }); @@ -172,16 +182,16 @@ describe("increase_liquidity", () => { const currTick = 500; const tickLowerIndex = 7168; const tickUpperIndex = 8960; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: tickIndexToSqrtPriceX64(currTick), + initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick), }); const { poolInitInfo, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda, tickSpacing } = poolInitInfo; - const poolBefore = await client.getPool(whirlpoolPda.publicKey); + const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; const tokenAmount = toTokenAmount(1_000_000, 0); - const liquidityAmount = estimateLiquidityFromTokenAmounts( + const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts( currTick, tickLowerIndex, tickUpperIndex, @@ -189,58 +199,58 @@ describe("increase_liquidity", () => { ); const { params, mint } = await generateDefaultOpenPositionParams( - client.context, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex, - context.wallet.publicKey + ctx.wallet.publicKey ); - const tickArrayLower = getTickArrayPda( - client.context.program.programId, + const tickArrayLower = PDAUtil.getTickArray( + ctx.program.programId, whirlpoolPda.publicKey, - getStartTickIndex(tickLowerIndex, tickSpacing) + TickUtil.getStartTickIndex(tickLowerIndex, tickSpacing) ).publicKey; - const tickArrayUpper = getTickArrayPda( - client.context.program.programId, + const tickArrayUpper = PDAUtil.getTickArray( + ctx.program.programId, whirlpoolPda.publicKey, - getStartTickIndex(tickUpperIndex, tickSpacing) + TickUtil.getStartTickIndex(tickUpperIndex, tickSpacing) ).publicKey; - await new TransactionBuilder(client.context.provider) + await new TransactionBuilder(ctx.provider) // TODO: create a ComputeBudgetInstruction to request more compute .addInstruction( - buildInitTickArrayIx( - client.context, + WhirlpoolIx.initTickArrayIx( + ctx.program, generateDefaultInitTickArrayParams( - client.context, + ctx, whirlpoolPda.publicKey, - getStartTickIndex(tickLowerIndex, tickSpacing) + TickUtil.getStartTickIndex(tickLowerIndex, tickSpacing) ) ) ) // .addInstruction( - // buildInitTickArrayIx(client.context, generateDefaultInitTickArrayParams( - // client.context, + // buildtoTx(ctx, WhirlpoolIx.initTickArrayIx(generateDefaultInitTickArrayParams( + // ctx, // whirlpoolPda.publicKey, // getStartTickIndex(pos[0].tickLowerIndex + TICK_ARRAY_SIZE * tickSpacing, tickSpacing), // )) // ) - .addInstruction(buildOpenPositionIx(client.context, params)) + .addInstruction(WhirlpoolIx.openPositionIx(ctx.program, params)) // .addInstruction( - // buildOpenPositionWithMetadataIx(client.context, params) + // buildWhirlpoolIx.openPositionWithMetadataIx(ctx.program, params) // ) .addSigner(mint) .addInstruction( - buildIncreaseLiquidityIx(client.context, { + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: tokenAmount.tokenA, tokenMaxB: tokenAmount.tokenB, whirlpool: whirlpoolPda.publicKey, positionAuthority: provider.wallet.publicKey, position: params.positionPda.publicKey, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, tokenOwnerAccountA: tokenAccountA, tokenOwnerAccountB: tokenAccountB, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, @@ -262,15 +272,18 @@ describe("increase_liquidity", () => { ); const expectedLiquidity = new anchor.BN(liquidityAmount); - const position = await client.getPosition(params.positionPda.publicKey); + const position = (await fetcher.getPosition( + params.positionPda.publicKey, + true + )) as PositionData; assert.ok(position.liquidity.eq(expectedLiquidity)); - const tickArray = await client.getTickArray(tickArrayLower); + const tickArray = (await fetcher.getTickArray(tickArrayLower, true)) as TickArrayData; assertTick(tickArray.ticks[56], true, expectedLiquidity, expectedLiquidity); assertTick(tickArray.ticks[70], true, expectedLiquidity, expectedLiquidity.neg()); - const poolAfter = await client.getPool(whirlpoolPda.publicKey); + const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp)); assert.equal(poolAfter.liquidity, 0); }); @@ -279,18 +292,18 @@ describe("increase_liquidity", () => { const currTick = 1300; const tickLowerIndex = -1280, tickUpperIndex = 1280; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }], - initialSqrtPrice: tickIndexToSqrtPriceX64(currTick), + initialSqrtPrice: PriceMath.tickIndexToSqrtPriceX64(currTick), }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda } = poolInitInfo; const positionInitInfo = positions[0]; - const poolBefore = await client.getPool(whirlpoolPda.publicKey); + const poolBefore = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; const tokenAmount = toTokenAmount(0, 167_000); - const liquidityAmount = estimateLiquidityFromTokenAmounts( + const liquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts( currTick, tickLowerIndex, tickUpperIndex, @@ -302,8 +315,9 @@ describe("increase_liquidity", () => { await approveToken(provider, tokenAccountA, delegate.publicKey, 1_000_000); await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000); - await client - .increaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: tokenAmount.tokenA, tokenMaxB: tokenAmount.tokenB, @@ -318,13 +332,14 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) + ) .addSigner(delegate) .buildAndExecute(); - const position = await client.getPosition(positionInitInfo.publicKey); + const position = (await fetcher.getPosition(positionInitInfo.publicKey, true)) as PositionData; assert.ok(position.liquidity.eq(liquidityAmount)); - const poolAfter = await client.getPool(whirlpoolPda.publicKey); + const poolAfter = (await fetcher.getPool(whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.ok(poolAfter.rewardLastUpdatedTimestamp.gte(poolBefore.rewardLastUpdatedTimestamp)); assert.equal( await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey), @@ -336,18 +351,24 @@ describe("increase_liquidity", () => { ); assert.equal(poolAfter.liquidity, 0); - const tickArrayLower = await client.getTickArray(positionInitInfo.tickArrayLower); + const tickArrayLower = (await fetcher.getTickArray( + positionInitInfo.tickArrayLower, + true + )) as TickArrayData; assertTick(tickArrayLower.ticks[78], true, liquidityAmount, liquidityAmount); - const tickArrayUpper = await client.getTickArray(positionInitInfo.tickArrayUpper); + const tickArrayUpper = (await fetcher.getTickArray( + positionInitInfo.tickArrayUpper, + true + )) as TickArrayData; assertTick(tickArrayUpper.ticks[10], true, liquidityAmount, liquidityAmount.neg()); }); it("add maximum amount of liquidity near minimum price", async () => { const currTick = -443621; const { poolInitInfo } = await initTestPool( - client, + ctx, TickSpacing.Stable, - tickIndexToSqrtPriceX64(currTick) + PriceMath.tickIndexToSqrtPriceX64(currTick) ); const { tokenMintA, tokenMintB, whirlpoolPda } = poolInitInfo; @@ -356,31 +377,32 @@ describe("increase_liquidity", () => { const { params: { tickArrayPda }, - } = await initTickArray(client, whirlpoolPda.publicKey, -444224); + } = await initTickArray(ctx, whirlpoolPda.publicKey, -444224); const tickLowerIndex = -443632; const tickUpperIndex = -443624; const positionInfo = await openPosition( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex ); - const { positionPda, positionTokenAccountAddress } = positionInfo.params; + const { positionPda, positionTokenAccount: positionTokenAccountAddress } = positionInfo.params; const tokenAmount = { tokenA: new u64(0), tokenB: MAX_U64, }; - const estLiquidityAmount = estimateLiquidityFromTokenAmounts( + const estLiquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts( currTick, tickLowerIndex, tickUpperIndex, tokenAmount ); - await client - .increaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount: estLiquidityAmount, tokenMaxA: tokenAmount.tokenA, tokenMaxB: tokenAmount.tokenB, @@ -395,18 +417,18 @@ describe("increase_liquidity", () => { tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); - const position = await client.getPosition(positionPda.publicKey); + const position = (await fetcher.getPosition(positionPda.publicKey, true)) as PositionData; assert.ok(position.liquidity.eq(estLiquidityAmount)); }); it("add maximum amount of liquidity near maximum price", async () => { const currTick = 443635; const { poolInitInfo } = await initTestPool( - client, + ctx, TickSpacing.Stable, - tickIndexToSqrtPriceX64(currTick) + PriceMath.tickIndexToSqrtPriceX64(currTick) ); const { tokenMintA, tokenMintB, whirlpoolPda } = poolInitInfo; @@ -415,31 +437,32 @@ describe("increase_liquidity", () => { const { params: { tickArrayPda }, - } = await initTickArray(client, whirlpoolPda.publicKey, 436480); + } = await initTickArray(ctx, whirlpoolPda.publicKey, 436480); const tickLowerIndex = 436488; const tickUpperIndex = 436496; const positionInfo = await openPosition( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex ); - const { positionPda, positionTokenAccountAddress } = positionInfo.params; + const { positionPda, positionTokenAccount: positionTokenAccountAddress } = positionInfo.params; const tokenAmount = { tokenA: new u64(0), tokenB: MAX_U64, }; - const estLiquidityAmount = estimateLiquidityFromTokenAmounts( + const estLiquidityAmount = PoolUtil.estimateLiquidityFromTokenAmounts( currTick, tickLowerIndex, tickUpperIndex, tokenAmount ); - await client - .increaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount: estLiquidityAmount, tokenMaxA: tokenAmount.tokenA, tokenMaxB: tokenAmount.tokenB, @@ -454,14 +477,14 @@ describe("increase_liquidity", () => { tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); - const position = await client.getPosition(positionPda.publicKey); + const position = (await fetcher.getPosition(positionPda.publicKey, true)) as PositionData; assert.ok(position.liquidity.eq(estLiquidityAmount)); }); it("fails with zero liquidity amount", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }], }); @@ -470,8 +493,9 @@ describe("increase_liquidity", () => { const positionInitInfo = positions[0]; await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount: ZERO_BN, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -486,15 +510,15 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x177c/ // LiquidityZero ); }); it("fails when token max a exceeded", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, - initialSqrtPrice: toX64(new Decimal(1)), + initialSqrtPrice: MathUtil.toX64(new Decimal(1)), positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }], }); const { poolInitInfo, positions, tokenAccountA, tokenAccountB } = fixture.getInfos(); @@ -504,8 +528,9 @@ describe("increase_liquidity", () => { const liquidityAmount = new u64(6_500_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(999_999_999), @@ -520,13 +545,13 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1781/ // TokenMaxExceeded ); }); it("fails when token max b exceeded", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }], }); @@ -537,8 +562,9 @@ describe("increase_liquidity", () => { const liquidityAmount = new u64(6_500_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(999_999_999), tokenMaxB: new u64(0), @@ -553,13 +579,13 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1781/ // TokenMaxExceeded ); }); it("fails when position account does not have exactly 1 token", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }], }); @@ -577,8 +603,9 @@ describe("increase_liquidity", () => { const liquidityAmount = new u64(6_500_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -593,7 +620,7 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); @@ -601,8 +628,9 @@ describe("increase_liquidity", () => { await transfer(provider, positionInitInfo.tokenAccount, newPositionTokenAccount, 1); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -617,13 +645,13 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when position token account mint does not match position mint", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }], }); @@ -637,8 +665,9 @@ describe("increase_liquidity", () => { const liquidityAmount = new u64(6_500_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -653,7 +682,7 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // A raw constraint was violated ); }); @@ -661,31 +690,33 @@ describe("increase_liquidity", () => { it("fails when position does not match whirlpool", async () => { const tickLowerIndex = 7168; const tickUpperIndex = 8960; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }], }); const { poolInitInfo, tokenAccountA, tokenAccountB } = fixture.getInfos(); const { whirlpoolPda } = poolInitInfo; - const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard); const positionInitInfo = await openPosition( - client, + ctx, poolInitInfo2.whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex ); - const { positionPda, positionTokenAccountAddress } = positionInitInfo.params; + const { positionPda, positionTokenAccount: positionTokenAccountAddress } = + positionInitInfo.params; const { params: { tickArrayPda }, - } = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, 0); + } = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, 0); const liquidityAmount = new u64(6_500_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -700,13 +731,13 @@ describe("increase_liquidity", () => { tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d1/ // A has_one constraint was violated ); }); it("fails when token vaults do not match whirlpool vaults", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }], }); @@ -719,8 +750,9 @@ describe("increase_liquidity", () => { const fakeVaultB = await createAndMintToTokenAccount(provider, tokenMintB, 1_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -735,13 +767,14 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -756,13 +789,13 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when owner token account mint does not match whirlpool token mint", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: 7168, tickUpperIndex: 8960, liquidityAmount: ZERO_BN }], }); @@ -775,8 +808,9 @@ describe("increase_liquidity", () => { const invalidTokenAccount = await createAndMintToTokenAccount(provider, invalidMint, 1_000_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -791,13 +825,14 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(1_000_000), @@ -812,13 +847,13 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails when position authority is not approved delegate for position token account", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }], }); @@ -834,8 +869,9 @@ describe("increase_liquidity", () => { await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(167_000), @@ -850,6 +886,7 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1783/ // MissingOrInvalidDelegate @@ -857,7 +894,7 @@ describe("increase_liquidity", () => { }); it("fails when position authority is not authorized for exactly 1 token", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }], }); @@ -874,8 +911,9 @@ describe("increase_liquidity", () => { await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(167_000), @@ -890,6 +928,7 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) + ) .addSigner(delegate) .buildAndExecute(), /0x1784/ // InvalidPositionTokenAmount @@ -897,7 +936,7 @@ describe("increase_liquidity", () => { }); it("fails when position authority was not a signer", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }], }); @@ -914,8 +953,9 @@ describe("increase_liquidity", () => { await approveToken(provider, tokenAccountB, delegate.publicKey, 1_000_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(167_000), @@ -930,13 +970,13 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails when position authority is not approved for token owner accounts", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }], }); @@ -951,8 +991,9 @@ describe("increase_liquidity", () => { await approveToken(provider, positionInitInfo.tokenAccount, delegate.publicKey, 1); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(167_000), @@ -967,6 +1008,7 @@ describe("increase_liquidity", () => { tickArrayLower: positionInitInfo.tickArrayLower, tickArrayUpper: positionInitInfo.tickArrayUpper, }) + ) .addSigner(delegate) .buildAndExecute(), /0x4/ // owner does not match @@ -974,7 +1016,7 @@ describe("increase_liquidity", () => { }); it("fails when tick arrays do not match the position", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }], }); @@ -984,17 +1026,18 @@ describe("increase_liquidity", () => { const { params: { tickArrayPda: tickArrayLowerPda }, - } = await initTickArray(client, whirlpoolPda.publicKey, 11264); + } = await initTickArray(ctx, whirlpoolPda.publicKey, 11264); const { params: { tickArrayPda: tickArrayUpperPda }, - } = await initTickArray(client, whirlpoolPda.publicKey, 22528); + } = await initTickArray(ctx, whirlpoolPda.publicKey, 22528); const liquidityAmount = new u64(1_250_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(167_000), @@ -1009,13 +1052,13 @@ describe("increase_liquidity", () => { tickArrayLower: tickArrayLowerPda.publicKey, tickArrayUpper: tickArrayUpperPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1779/ // TicKNotFound ); }); it("fails when the tick arrays are for a different whirlpool", async () => { - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing: TickSpacing.Standard, positions: [{ tickLowerIndex: -1280, tickUpperIndex: 1280, liquidityAmount: ZERO_BN }], }); @@ -1023,21 +1066,22 @@ describe("increase_liquidity", () => { const { whirlpoolPda } = poolInitInfo; const positionInitInfo = positions[0]; - const { poolInitInfo: poolInitInfo2 } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo: poolInitInfo2 } = await initTestPool(ctx, TickSpacing.Standard); const { params: { tickArrayPda: tickArrayLowerPda }, - } = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, -11264); + } = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, -11264); const { params: { tickArrayPda: tickArrayUpperPda }, - } = await initTickArray(client, poolInitInfo2.whirlpoolPda.publicKey, 0); + } = await initTickArray(ctx, poolInitInfo2.whirlpoolPda.publicKey, 0); const liquidityAmount = new u64(1_250_000); await assert.rejects( - client - .increaseLiquidityTx({ + toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount, tokenMaxA: new u64(0), tokenMaxB: new u64(167_000), @@ -1052,7 +1096,7 @@ describe("increase_liquidity", () => { tickArrayLower: tickArrayLowerPda.publicKey, tickArrayUpper: tickArrayUpperPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d1/ // A has one constraint was violated ); }); diff --git a/sdk/tests/integration/initialize_config.test.ts b/sdk/tests/integration/initialize_config.test.ts new file mode 100644 index 0000000..6f5632f --- /dev/null +++ b/sdk/tests/integration/initialize_config.test.ts @@ -0,0 +1,70 @@ +import * as assert from "assert"; +import * as anchor from "@project-serum/anchor"; +import { + WhirlpoolContext, + AccountFetcher, + WhirlpoolsConfigData, + WhirlpoolIx, + InitConfigParams, +} from "../../src"; +import { systemTransferTx, ONE_SOL } from "../utils"; +import { generateDefaultConfigParams } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; + +describe("initialize_config", () => { + const provider = anchor.Provider.local(); + anchor.setProvider(anchor.Provider.env()); + const program = anchor.workspace.Whirlpool; + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); + + let initializedConfigInfo: InitConfigParams; + + it("successfully init a WhirlpoolsConfig account", async () => { + const { configInitInfo } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); + + const configAccount = (await fetcher.getConfig( + configInitInfo.whirlpoolsConfigKeypair.publicKey + )) as WhirlpoolsConfigData; + + assert.ok( + configAccount.collectProtocolFeesAuthority.equals(configInitInfo.collectProtocolFeesAuthority) + ); + + assert.ok(configAccount.feeAuthority.equals(configInitInfo.feeAuthority)); + + assert.ok( + configAccount.rewardEmissionsSuperAuthority.equals( + configInitInfo.rewardEmissionsSuperAuthority + ) + ); + + assert.equal(configAccount.defaultProtocolFeeRate, configInitInfo.defaultProtocolFeeRate); + + initializedConfigInfo = configInitInfo; + }); + + it("fail on passing in already initialized whirlpool account", async () => { + let infoWithDupeConfigKey = { + ...generateDefaultConfigParams(ctx).configInitInfo, + whirlpoolsConfigKeypair: initializedConfigInfo.whirlpoolsConfigKeypair, + }; + await assert.rejects( + toTx( + ctx, + WhirlpoolIx.initializeConfigIx(ctx.program, infoWithDupeConfigKey) + ).buildAndExecute(), + /0x0/ + ); + }); + + it("succeeds when funder is different than account paying for transaction fee", async () => { + const funderKeypair = anchor.web3.Keypair.generate(); + await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); + const { configInitInfo } = generateDefaultConfigParams(ctx, funderKeypair.publicKey); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)) + .addSigner(funderKeypair) + .buildAndExecute(); + }); +}); diff --git a/sdk/tests/initialize_fee_tier.ts b/sdk/tests/integration/initialize_fee_tier.test.ts similarity index 59% rename from sdk/tests/initialize_fee_tier.ts rename to sdk/tests/integration/initialize_fee_tier.test.ts index aed0339..567b83e 100644 --- a/sdk/tests/initialize_fee_tier.ts +++ b/sdk/tests/integration/initialize_fee_tier.test.ts @@ -1,74 +1,73 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initFeeTier } from "./utils/init-utils"; +import { WhirlpoolContext, AccountFetcher, FeeTierData, WhirlpoolIx, PDAUtil } from "../../src"; +import { TickSpacing, systemTransferTx, ONE_SOL } from "../utils"; +import { initFeeTier } from "../utils/init-utils"; import { generateDefaultConfigParams, generateDefaultInitFeeTierParams, -} from "./utils/test-builders"; -import { getFeeTierPda } from "../src"; -import { systemTransferTx, ONE_SOL, TickSpacing } from "./utils"; +} from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; describe("initialize_fee_tier", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully init a FeeRate stable account", async () => { - const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const testTickSpacing = TickSpacing.Stable; const { params } = await initFeeTier( - client, + ctx, configInitInfo, configKeypairs.feeAuthorityKeypair, testTickSpacing, 800 ); - const generatedPda = getFeeTierPda( - client.context.program.programId, - configInitInfo.whirlpoolConfigKeypair.publicKey, + const generatedPda = PDAUtil.getFeeTier( + ctx.program.programId, + configInitInfo.whirlpoolsConfigKeypair.publicKey, testTickSpacing ); - const feeTierAccount = await client.getFeeTier(generatedPda.publicKey); + const feeTierAccount = (await fetcher.getFeeTier(generatedPda.publicKey)) as FeeTierData; assert.ok(feeTierAccount.tickSpacing == params.tickSpacing); assert.ok(feeTierAccount.defaultFeeRate == params.defaultFeeRate); }); it("successfully init a FeeRate standard account", async () => { - const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const testTickSpacing = TickSpacing.Standard; const { params } = await initFeeTier( - client, + ctx, configInitInfo, configKeypairs.feeAuthorityKeypair, testTickSpacing, 3000 ); - const feeTierAccount = await client.getFeeTier(params.feeTierPda.publicKey); + const feeTierAccount = (await fetcher.getFeeTier(params.feeTierPda.publicKey)) as FeeTierData; assert.ok(feeTierAccount.tickSpacing == params.tickSpacing); assert.ok(feeTierAccount.defaultFeeRate == params.defaultFeeRate); }); it("successfully init a FeeRate with another funder wallet", async () => { - const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const funderKeypair = anchor.web3.Keypair.generate(); await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); await initFeeTier( - client, + ctx, configInitInfo, configKeypairs.feeAuthorityKeypair, TickSpacing.Stable, @@ -78,12 +77,12 @@ describe("initialize_fee_tier", () => { }); it("fails when default fee rate exceeds max", async () => { - const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); await assert.rejects( initFeeTier( - client, + ctx, configInitInfo, configKeypairs.feeAuthorityKeypair, TickSpacing.Stable, @@ -94,41 +93,46 @@ describe("initialize_fee_tier", () => { }); it("fails when fee authority is not a signer", async () => { - const { configInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); await assert.rejects( - client - .initFeeTierTx( + toTx( + ctx, + WhirlpoolIx.initializeFeeTierIx( + ctx.program, generateDefaultInitFeeTierParams( - client.context, - configInitInfo.whirlpoolConfigKeypair.publicKey, + ctx, + configInitInfo.whirlpoolsConfigKeypair.publicKey, configInitInfo.feeAuthority, TickSpacing.Stable, 3000 ) ) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails when invalid fee authority provided", async () => { - const { configInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const fakeFeeAuthorityKeypair = anchor.web3.Keypair.generate(); await assert.rejects( - client - .initFeeTierTx( + toTx( + ctx, + WhirlpoolIx.initializeFeeTierIx( + ctx.program, generateDefaultInitFeeTierParams( - client.context, - configInitInfo.whirlpoolConfigKeypair.publicKey, + ctx, + configInitInfo.whirlpoolsConfigKeypair.publicKey, fakeFeeAuthorityKeypair.publicKey, TickSpacing.Stable, 3000 ) ) + ) .addSigner(fakeFeeAuthorityKeypair) .buildAndExecute(), /0x7dc/ // ConstraintAddress diff --git a/sdk/tests/initialize_pool.ts b/sdk/tests/integration/initialize_pool.test.ts similarity index 74% rename from sdk/tests/initialize_pool.ts rename to sdk/tests/integration/initialize_pool.test.ts index 97e2194..4593472 100644 --- a/sdk/tests/initialize_pool.ts +++ b/sdk/tests/integration/initialize_pool.test.ts @@ -1,45 +1,48 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; +import Decimal from "decimal.js"; import { - createMint, - asyncAssertTokenVault, - ZERO_BN, - systemTransferTx, - ONE_SOL, - TickSpacing, -} from "./utils"; -import { buildTestPoolParams, initTestPool } from "./utils/init-utils"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; -import { - getWhirlpoolPda, + WhirlpoolContext, + AccountFetcher, + WhirlpoolData, InitPoolParams, - toX64, - sqrtPriceX64ToTickIndex, MAX_SQRT_PRICE, MIN_SQRT_PRICE, -} from "../src"; -import Decimal from "decimal.js"; + PriceMath, + WhirlpoolIx, + PDAUtil, +} from "../../src"; +import { + TickSpacing, + ZERO_BN, + asyncAssertTokenVault, + systemTransferTx, + ONE_SOL, + createMint, +} from "../utils"; +import { initTestPool, buildTestPoolParams } from "../utils/init-utils"; +import { MathUtil } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("initialize_pool", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully init a Standard account", async () => { - const price = toX64(new Decimal(5)); + const price = MathUtil.toX64(new Decimal(5)); const { configInitInfo, poolInitInfo, feeTierParams } = await initTestPool( - client, + ctx, TickSpacing.Standard, price ); - const whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData; - const expectedWhirlpoolPda = getWhirlpoolPda( + const expectedWhirlpoolPda = PDAUtil.getWhirlpool( program.programId, - configInitInfo.whirlpoolConfigKeypair.publicKey, + configInitInfo.whirlpoolsConfigKeypair.publicKey, poolInitInfo.tokenMintA, poolInitInfo.tokenMintB, TickSpacing.Standard @@ -48,7 +51,7 @@ describe("initialize_pool", () => { assert.ok(poolInitInfo.whirlpoolPda.publicKey.equals(expectedWhirlpoolPda.publicKey)); assert.equal(expectedWhirlpoolPda.bump, whirlpool.whirlpoolBump[0]); - assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolConfigKey)); + assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolsConfig)); assert.ok(whirlpool.tokenMintA.equals(poolInitInfo.tokenMintA)); assert.ok(whirlpool.tokenVaultA.equals(poolInitInfo.tokenVaultAKeypair.publicKey)); @@ -61,7 +64,10 @@ describe("initialize_pool", () => { assert.ok(whirlpool.sqrtPrice.eq(new anchor.BN(poolInitInfo.initSqrtPrice.toString()))); assert.ok(whirlpool.liquidity.eq(ZERO_BN)); - assert.equal(whirlpool.tickCurrentIndex, sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice)); + assert.equal( + whirlpool.tickCurrentIndex, + PriceMath.sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice) + ); assert.ok(whirlpool.protocolFeeOwedA.eq(ZERO_BN)); assert.ok(whirlpool.protocolFeeOwedB.eq(ZERO_BN)); @@ -89,15 +95,15 @@ describe("initialize_pool", () => { }); it("successfully init a Stable account", async () => { - const price = toX64(new Decimal(5)); + const price = MathUtil.toX64(new Decimal(5)); const { configInitInfo, poolInitInfo, feeTierParams } = await initTestPool( - client, + ctx, TickSpacing.Stable, price ); - const whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData; - assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolConfigKey)); + assert.ok(whirlpool.whirlpoolsConfig.equals(poolInitInfo.whirlpoolsConfig)); assert.ok(whirlpool.tokenMintA.equals(poolInitInfo.tokenMintA)); assert.ok(whirlpool.tokenVaultA.equals(poolInitInfo.tokenVaultAKeypair.publicKey)); @@ -110,7 +116,10 @@ describe("initialize_pool", () => { assert.ok(whirlpool.sqrtPrice.eq(new anchor.BN(poolInitInfo.initSqrtPrice.toString()))); assert.ok(whirlpool.liquidity.eq(ZERO_BN)); - assert.equal(whirlpool.tickCurrentIndex, sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice)); + assert.equal( + whirlpool.tickCurrentIndex, + PriceMath.sqrtPriceX64ToTickIndex(poolInitInfo.initSqrtPrice) + ); assert.ok(whirlpool.protocolFeeOwedA.eq(ZERO_BN)); assert.ok(whirlpool.protocolFeeOwedB.eq(ZERO_BN)); @@ -140,11 +149,11 @@ describe("initialize_pool", () => { it("succeeds when funder is different than account paying for transaction fee", async () => { const funderKeypair = anchor.web3.Keypair.generate(); await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); - await initTestPool(client, TickSpacing.Standard, toX64(new Decimal(5)), funderKeypair); + await initTestPool(ctx, TickSpacing.Standard, MathUtil.toX64(new Decimal(5)), funderKeypair); }); it("fails when tokenVaultA mint does not match tokenA mint", async () => { - const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard); + const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard); const otherTokenPublicKey = await createMint(provider); const modifiedPoolInitInfo: InitPoolParams = { @@ -153,13 +162,13 @@ describe("initialize_pool", () => { }; await assert.rejects( - client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(), + toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(), /failed to complete|seeds|unauthorized/ ); }); it("fails when tokenVaultB mint does not match tokenB mint", async () => { - const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard); + const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard); const otherTokenPublicKey = await createMint(provider); const modifiedPoolInitInfo: InitPoolParams = { @@ -168,20 +177,17 @@ describe("initialize_pool", () => { }; await assert.rejects( - client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(), + toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(), /failed to complete|seeds|unauthorized/ ); }); it("fails when token mints are in the wrong order", async () => { - const { poolInitInfo, configInitInfo } = await buildTestPoolParams( - client, - TickSpacing.Standard - ); + const { poolInitInfo, configInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard); - const whirlpoolPda = getWhirlpoolPda( - context.program.programId, - configInitInfo.whirlpoolConfigKeypair.publicKey, + const whirlpoolPda = PDAUtil.getWhirlpool( + ctx.program.programId, + configInitInfo.whirlpoolsConfigKeypair.publicKey, poolInitInfo.tokenMintB, poolInitInfo.tokenMintA, TickSpacing.Stable @@ -196,20 +202,17 @@ describe("initialize_pool", () => { }; await assert.rejects( - client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(), + toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(), /custom program error: 0x1788/ // InvalidTokenMintOrder ); }); it("fails when the same token mint is passed in", async () => { - const { poolInitInfo, configInitInfo } = await buildTestPoolParams( - client, - TickSpacing.Standard - ); + const { poolInitInfo, configInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard); - const whirlpoolPda = getWhirlpoolPda( - context.program.programId, - configInitInfo.whirlpoolConfigKeypair.publicKey, + const whirlpoolPda = PDAUtil.getWhirlpool( + ctx.program.programId, + configInitInfo.whirlpoolsConfigKeypair.publicKey, poolInitInfo.tokenMintA, poolInitInfo.tokenMintA, TickSpacing.Stable @@ -223,13 +226,13 @@ describe("initialize_pool", () => { }; await assert.rejects( - client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(), + toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(), /custom program error: 0x1788/ // InvalidTokenMintOrder ); }); it("fails when sqrt-price exceeds max", async () => { - const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard); + const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard); const otherTokenPublicKey = await createMint(provider); const modifiedPoolInitInfo: InitPoolParams = { @@ -238,13 +241,13 @@ describe("initialize_pool", () => { }; await assert.rejects( - client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(), + toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(), /custom program error: 0x177b/ // SqrtPriceOutOfBounds ); }); it("fails when sqrt-price subceeds min", async () => { - const { poolInitInfo } = await buildTestPoolParams(client, TickSpacing.Standard); + const { poolInitInfo } = await buildTestPoolParams(ctx, TickSpacing.Standard); const modifiedPoolInitInfo: InitPoolParams = { ...poolInitInfo, @@ -252,7 +255,7 @@ describe("initialize_pool", () => { }; await assert.rejects( - client.initPoolTx(modifiedPoolInitInfo).buildAndExecute(), + toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, modifiedPoolInitInfo)).buildAndExecute(), /custom program error: 0x177b/ // SqrtPriceOutOfBounds ); }); diff --git a/sdk/tests/initialize_reward.ts b/sdk/tests/integration/initialize_reward.test.ts similarity index 77% rename from sdk/tests/initialize_reward.ts rename to sdk/tests/integration/initialize_reward.test.ts index ae863bf..d2664ff 100644 --- a/sdk/tests/initialize_reward.ts +++ b/sdk/tests/integration/initialize_reward.test.ts @@ -1,36 +1,38 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initializeReward, initTestPool } from "./utils/init-utils"; - -import { createMint, ONE_SOL, systemTransferTx, TickSpacing } from "./utils"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src"; +import { TickSpacing, systemTransferTx, ONE_SOL, createMint } from "../utils"; +import { initTestPool, initializeReward } from "../utils/init-utils"; +import { toTx } from "../../src/utils/instructions-util"; describe("initialize_reward", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully initializes reward at index 0", async () => { - const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); const { params } = await initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, 0 ); - const whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const whirlpool = (await fetcher.getPool( + poolInitInfo.whirlpoolPda.publicKey, + true + )) as WhirlpoolData; assert.ok(whirlpool.rewardInfos[0].mint.equals(params.rewardMint)); assert.ok(whirlpool.rewardInfos[0].vault.equals(params.rewardVaultKeypair.publicKey)); await assert.rejects( initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, 0 @@ -39,13 +41,16 @@ describe("initialize_reward", () => { ); const { params: params2 } = await initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, 1 ); - const whirlpool2 = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const whirlpool2 = (await fetcher.getPool( + poolInitInfo.whirlpoolPda.publicKey, + true + )) as WhirlpoolData; assert.ok(whirlpool2.rewardInfos[0].mint.equals(params.rewardMint)); assert.ok(whirlpool2.rewardInfos[0].vault.equals(params.rewardVaultKeypair.publicKey)); @@ -56,11 +61,11 @@ describe("initialize_reward", () => { }); it("succeeds when funder is different than account paying for transaction fee", async () => { - const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); const funderKeypair = anchor.web3.Keypair.generate(); await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); await initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, 0, @@ -69,11 +74,11 @@ describe("initialize_reward", () => { }); it("fails to initialize reward at index 1", async () => { - const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); await assert.rejects( initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, 1 @@ -83,11 +88,11 @@ describe("initialize_reward", () => { }); it("fails to initialize reward at out-of-bound index", async () => { - const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); await assert.rejects( initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, 3 @@ -96,11 +101,12 @@ describe("initialize_reward", () => { }); it("fails to initialize if authority signature is missing", async () => { - const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); await assert.rejects( - client - .initializeRewardTx({ + toTx( + ctx, + WhirlpoolIx.initializeRewardIx(ctx.program, { rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, funder: provider.wallet.publicKey, whirlpool: poolInitInfo.whirlpoolPda.publicKey, @@ -108,7 +114,7 @@ describe("initialize_reward", () => { rewardVaultKeypair: anchor.web3.Keypair.generate(), rewardIndex: 0, }) - .buildAndExecute() + ).buildAndExecute() ); }); }); diff --git a/sdk/tests/initialize_tick_array.ts b/sdk/tests/integration/initialize_tick_array.test.ts similarity index 54% rename from sdk/tests/initialize_tick_array.ts rename to sdk/tests/integration/initialize_tick_array.test.ts index e709145..dc6a32b 100644 --- a/sdk/tests/initialize_tick_array.ts +++ b/sdk/tests/integration/initialize_tick_array.test.ts @@ -1,75 +1,82 @@ import * as anchor from "@project-serum/anchor"; import * as assert from "assert"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initTestPool, initTickArray } from "./utils/init-utils"; -import { generateDefaultInitTickArrayParams } from "./utils/test-builders"; -import { InitPoolParams, InitTickArrayParams, TICK_ARRAY_SIZE } from "../src"; -import { ONE_SOL, systemTransferTx, TickSpacing } from "./utils"; +import { + WhirlpoolContext, + AccountFetcher, + TICK_ARRAY_SIZE, + InitTickArrayParams, + InitPoolParams, + TickArrayData, + WhirlpoolIx, +} from "../../src"; +import { toTx } from "../../src/utils/instructions-util"; +import { TickSpacing, systemTransferTx, ONE_SOL } from "../utils"; +import { initTestPool, initTickArray } from "../utils/init-utils"; +import { generateDefaultInitTickArrayParams } from "../utils/test-builders"; describe("initialize_tick_array", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully init a TickArray account", async () => { const tickSpacing = TickSpacing.Standard; - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); - await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); + await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey); const startTick = TICK_ARRAY_SIZE * tickSpacing * 2; const tickArrayInitInfo = generateDefaultInitTickArrayParams( - context, + ctx, poolInitInfo.whirlpoolPda.publicKey, startTick ); - await client.initTickArrayTx(tickArrayInitInfo).buildAndExecute(); - assertTickArrayInitialized(client, tickArrayInitInfo, poolInitInfo, startTick); + await toTx(ctx, WhirlpoolIx.initTickArrayIx(ctx.program, tickArrayInitInfo)).buildAndExecute(); + assertTickArrayInitialized(ctx, tickArrayInitInfo, poolInitInfo, startTick); }); it("successfully init a TickArray account with a negative index", async () => { const tickSpacing = TickSpacing.Standard; - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); - await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); + await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey); const startTick = TICK_ARRAY_SIZE * tickSpacing * -2; const tickArrayInitInfo = generateDefaultInitTickArrayParams( - context, + ctx, poolInitInfo.whirlpoolPda.publicKey, startTick ); - await client.initTickArrayTx(tickArrayInitInfo).buildAndExecute(); - assertTickArrayInitialized(client, tickArrayInitInfo, poolInitInfo, startTick); + await toTx(ctx, WhirlpoolIx.initTickArrayIx(ctx.program, tickArrayInitInfo)).buildAndExecute(); + assertTickArrayInitialized(ctx, tickArrayInitInfo, poolInitInfo, startTick); }); it("succeeds when funder is different than account paying for transaction fee", async () => { const tickSpacing = TickSpacing.Standard; - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const funderKeypair = anchor.web3.Keypair.generate(); await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); - await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey); const startTick = TICK_ARRAY_SIZE * tickSpacing * 3; - await initTickArray(client, poolInitInfo.whirlpoolPda.publicKey, startTick, funderKeypair); + await initTickArray(ctx, poolInitInfo.whirlpoolPda.publicKey, startTick, funderKeypair); }); it("fails when start tick index is not a valid start index", async () => { const tickSpacing = TickSpacing.Standard; - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); - await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); + await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey); const startTick = TICK_ARRAY_SIZE * tickSpacing * 2 + 1; const params = generateDefaultInitTickArrayParams( - context, + ctx, poolInitInfo.whirlpoolPda.publicKey, startTick ); try { - await client.initTickArrayTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.initTickArrayIx(ctx.program, params)).buildAndExecute(); assert.fail( "should fail if start-tick is not a multiple of tick spacing and num ticks in array" ); @@ -80,12 +87,14 @@ describe("initialize_tick_array", () => { }); async function assertTickArrayInitialized( - client: WhirlpoolClient, + ctx: WhirlpoolContext, tickArrayInitInfo: InitTickArrayParams, poolInitInfo: InitPoolParams, startTick: number ) { - let tickArrayData = await client.getTickArray(tickArrayInitInfo.tickArrayPda.publicKey); + let tickArrayData = (await fetcher.getTickArray( + tickArrayInitInfo.tickArrayPda.publicKey + )) as TickArrayData; assert.ok(tickArrayData.whirlpool.equals(poolInitInfo.whirlpoolPda.publicKey)); assert.ok(tickArrayData.startTickIndex == startTick); } diff --git a/sdk/tests/integration/position_management.ts b/sdk/tests/integration/multi-ix/position_management.test.ts similarity index 54% rename from sdk/tests/integration/position_management.ts rename to sdk/tests/integration/multi-ix/position_management.test.ts index 99f251d..49e411f 100644 --- a/sdk/tests/integration/position_management.ts +++ b/sdk/tests/integration/multi-ix/position_management.test.ts @@ -1,43 +1,45 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../../src/context"; -import { WhirlpoolClient } from "../../src/client"; -import { initTestPool, openPosition } from "../utils/init-utils"; -import { buildOpenPositionIx } from "../../src/instructions/open-position-ix"; -import { generateDefaultOpenPositionParams } from "../utils/test-builders"; -import { TickSpacing } from "../utils"; +import { WhirlpoolContext } from "../../../src/context"; +import { initTestPool, openPosition } from "../../utils/init-utils"; +import { generateDefaultOpenPositionParams } from "../../utils/test-builders"; +import { TickSpacing } from "../../utils"; +import { AccountFetcher, WhirlpoolIx } from "../../../src"; +import { toTx } from "../../../src/utils/instructions-util"; describe("position management tests", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully closes and opens a position in one transaction", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); - const { params } = await openPosition(client, poolInitInfo.whirlpoolPda.publicKey, 0, 128); + const { params } = await openPosition(ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128); const receiverKeypair = anchor.web3.Keypair.generate(); const { params: newParams, mint } = await generateDefaultOpenPositionParams( - context, + ctx, poolInitInfo.whirlpoolPda.publicKey, 0, 128, - context.wallet.publicKey, - context.wallet.publicKey + ctx.wallet.publicKey, + ctx.wallet.publicKey ); - await client - .closePositionTx({ + await toTx( + ctx, + WhirlpoolIx.closePositionIx(ctx.program, { positionAuthority: provider.wallet.publicKey, receiver: receiverKeypair.publicKey, position: params.positionPda.publicKey, positionMint: params.positionMintAddress, - positionTokenAccount: params.positionTokenAccountAddress, + positionTokenAccount: params.positionTokenAccount, }) - .addInstruction(buildOpenPositionIx(context, newParams)) + ) + .addInstruction(WhirlpoolIx.openPositionIx(ctx.program, newParams)) .addSigner(mint) .buildAndExecute(); diff --git a/sdk/tests/open_position.ts b/sdk/tests/integration/open_position.test.ts similarity index 80% rename from sdk/tests/open_position.ts rename to sdk/tests/integration/open_position.test.ts index 11b78fa..9cd640a 100644 --- a/sdk/tests/open_position.ts +++ b/sdk/tests/integration/open_position.test.ts @@ -2,36 +2,39 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; import { web3 } from "@project-serum/anchor"; import { Keypair } from "@solana/web3.js"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; -import { initTestPool, openPosition } from "./utils/init-utils"; -import { - getPositionPda, - InitPoolParams, - MAX_TICK_INDEX, - MIN_TICK_INDEX, - OpenPositionParams, - PDA, -} from "../src"; -import { - createMint, - createMintInstructions, - mintToByAuthority, - ONE_SOL, - systemTransferTx, - TickSpacing, - ZERO_BN, -} from "./utils"; import { ASSOCIATED_TOKEN_PROGRAM_ID, Token } from "@solana/spl-token"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { generateDefaultOpenPositionParams } from "./utils/test-builders"; +import { + WhirlpoolContext, + AccountFetcher, + OpenPositionParams, + InitPoolParams, + PositionData, + MAX_TICK_INDEX, + MIN_TICK_INDEX, + WhirlpoolIx, + PDAUtil, +} from "../../src"; +import { + TickSpacing, + systemTransferTx, + ONE_SOL, + ZERO_BN, + mintToByAuthority, + createMint, + createMintInstructions, +} from "../utils"; +import { initTestPool, openPosition } from "../utils/init-utils"; +import { generateDefaultOpenPositionParams } from "../utils/test-builders"; +import { PDA } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("open_position", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); let defaultParams: OpenPositionParams; let defaultMint: Keypair; @@ -42,11 +45,11 @@ describe("open_position", () => { const funderKeypair = anchor.web3.Keypair.generate(); before(async () => { - poolInitInfo = (await initTestPool(client, TickSpacing.Standard)).poolInitInfo; + poolInitInfo = (await initTestPool(ctx, TickSpacing.Standard)).poolInitInfo; whirlpoolPda = poolInitInfo.whirlpoolPda; const { params, mint } = await generateDefaultOpenPositionParams( - client.context, + ctx, whirlpoolPda.publicKey, 0, 128, @@ -59,14 +62,14 @@ describe("open_position", () => { it("successfully opens position and verify position address contents", async () => { const positionInitInfo = await openPosition( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex ); const { positionPda, positionMintAddress } = positionInitInfo.params; - const position = await client.getPosition(positionPda.publicKey); + const position = (await fetcher.getPosition(positionPda.publicKey)) as PositionData; assert.strictEqual(position.tickLowerIndex, tickLowerIndex); assert.strictEqual(position.tickUpperIndex, tickUpperIndex); @@ -83,7 +86,7 @@ describe("open_position", () => { it("succeeds when funder is different than account paying for transaction fee", async () => { await openPosition( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex, @@ -96,16 +99,17 @@ describe("open_position", () => { const newOwner = web3.Keypair.generate(); const positionInitInfo = await openPosition( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex, newOwner.publicKey ); - const { positionMintAddress, positionTokenAccountAddress } = positionInitInfo.params; + const { positionMintAddress, positionTokenAccount: positionTokenAccountAddress } = + positionInitInfo.params; const token = new Token( - context.connection, + ctx.connection, positionMintAddress, TOKEN_PROGRAM_ID, web3.Keypair.generate() @@ -127,12 +131,17 @@ describe("open_position", () => { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, anotherMintKey, - context.provider.wallet.publicKey + ctx.provider.wallet.publicKey ); await assert.rejects( - client - .openPositionTx({ ...defaultParams, positionTokenAccountAddress }) + toTx( + ctx, + WhirlpoolIx.openPositionIx(ctx.program, { + ...defaultParams, + positionTokenAccount: positionTokenAccountAddress, + }) + ) .addSigner(defaultMint) .buildAndExecute(), /An account required by the instruction is missing/ @@ -143,7 +152,7 @@ describe("open_position", () => { async function assertTicksFail(lowerTick: number, upperTick: number) { await assert.rejects( openPosition( - client, + ctx, whirlpoolPda.publicKey, lowerTick, upperTick, @@ -181,7 +190,7 @@ describe("open_position", () => { it("fail when position mint already exists", async () => { const positionMintKeypair = anchor.web3.Keypair.generate(); - const positionPda = getPositionPda(context.program.programId, positionMintKeypair.publicKey); + const positionPda = PDAUtil.getPosition(ctx.program.programId, positionMintKeypair.publicKey); const positionTokenAccountAddress = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, @@ -202,17 +211,19 @@ describe("open_position", () => { await provider.send(tx, [positionMintKeypair], { commitment: "confirmed" }); await assert.rejects( - client - .openPositionTx({ + toTx( + ctx, + WhirlpoolIx.openPositionIx(ctx.program, { funder: provider.wallet.publicKey, - ownerKey: provider.wallet.publicKey, + owner: provider.wallet.publicKey, positionPda, positionMintAddress: positionMintKeypair.publicKey, - positionTokenAccountAddress: positionTokenAccountAddress, - whirlpoolKey: whirlpoolPda.publicKey, + positionTokenAccount: positionTokenAccountAddress, + whirlpool: whirlpoolPda.publicKey, tickLowerIndex: 0, tickUpperIndex: 128, }) + ) .addSigner(positionMintKeypair) .buildAndExecute(), /0x0/ diff --git a/sdk/tests/open_position_with_metadata.ts b/sdk/tests/integration/open_position_with_metadata.test.ts similarity index 81% rename from sdk/tests/open_position_with_metadata.ts rename to sdk/tests/integration/open_position_with_metadata.test.ts index 515b6c3..873948c 100644 --- a/sdk/tests/open_position_with_metadata.ts +++ b/sdk/tests/integration/open_position_with_metadata.test.ts @@ -3,43 +3,43 @@ import * as anchor from "@project-serum/anchor"; import { Metadata } from "@metaplex-foundation/mpl-token-metadata"; import { web3 } from "@project-serum/anchor"; import { Keypair, PublicKey } from "@solana/web3.js"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; -import { initTestPool, openPositionWithMetadata } from "./utils/init-utils"; -import { - getPositionMetadataPda, - getPositionPda, - InitPoolParams, - MAX_TICK_INDEX, - METADATA_PROGRAM_ADDRESS, - MIN_TICK_INDEX, - OpenPositionParams, - OpenPositionWithMetadataBumpsData, - PDA, - TransactionBuilder, -} from "../src"; -import { openPositionAccounts } from "../src/instructions/open-position-ix"; -import { - createMint, - createMintInstructions, - mintToByAuthority, - ONE_SOL, - systemTransferTx, - TickSpacing, - ZERO_BN, -} from "./utils"; import { ASSOCIATED_TOKEN_PROGRAM_ID, Token } from "@solana/spl-token"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; -import { generateDefaultOpenPositionParams } from "./utils/test-builders"; +import { + WhirlpoolContext, + AccountFetcher, + OpenPositionParams, + InitPoolParams, + PositionData, + MAX_TICK_INDEX, + MIN_TICK_INDEX, + OpenPositionWithMetadataBumpsData, + METADATA_PROGRAM_ADDRESS, + WhirlpoolIx, + PDAUtil, +} from "../../src"; +import { + TickSpacing, + systemTransferTx, + ONE_SOL, + ZERO_BN, + mintToByAuthority, + createMint, + createMintInstructions, +} from "../utils"; +import { initTestPool, openPositionWithMetadata } from "../utils/init-utils"; +import { generateDefaultOpenPositionParams } from "../utils/test-builders"; +import { openPositionAccounts, toTx } from "../../src/utils/instructions-util"; +import { PDA, TransactionBuilder } from "@orca-so/common-sdk"; describe("open_position_with_metadata", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); - let defaultParams: Required; + let defaultParams: Required; let defaultMint: Keypair; const tickLowerIndex = 0; const tickUpperIndex = 128; @@ -48,11 +48,11 @@ describe("open_position_with_metadata", () => { const funderKeypair = anchor.web3.Keypair.generate(); before(async () => { - poolInitInfo = (await initTestPool(client, TickSpacing.Standard)).poolInitInfo; + poolInitInfo = (await initTestPool(ctx, TickSpacing.Standard)).poolInitInfo; whirlpoolPda = poolInitInfo.whirlpoolPda; const { params, mint } = await generateDefaultOpenPositionParams( - client.context, + ctx, whirlpoolPda.publicKey, 0, 128, @@ -75,13 +75,13 @@ describe("open_position_with_metadata", () => { it("successfully opens position and verify position address contents", async () => { const positionInitInfo = await openPositionWithMetadata( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex ); const { positionPda, metadataPda, positionMintAddress } = positionInitInfo.params; - const position = await client.getPosition(positionPda.publicKey); + const position = (await fetcher.getPosition(positionPda.publicKey)) as PositionData; assert.strictEqual(position.tickLowerIndex, tickLowerIndex); assert.strictEqual(position.tickUpperIndex, tickUpperIndex); @@ -99,7 +99,7 @@ describe("open_position_with_metadata", () => { it("succeeds when funder is different than account paying for transaction fee", async () => { const { params } = await openPositionWithMetadata( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex, @@ -114,17 +114,20 @@ describe("open_position_with_metadata", () => { const newOwner = web3.Keypair.generate(); const positionInitInfo = await openPositionWithMetadata( - client, + ctx, whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex, newOwner.publicKey ); - const { metadataPda, positionMintAddress, positionTokenAccountAddress } = - positionInitInfo.params; + const { + metadataPda, + positionMintAddress, + positionTokenAccount: positionTokenAccountAddress, + } = positionInitInfo.params; const token = new Token( - context.connection, + ctx.connection, positionMintAddress, TOKEN_PROGRAM_ID, web3.Keypair.generate() @@ -148,12 +151,17 @@ describe("open_position_with_metadata", () => { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, anotherMintKey, - context.provider.wallet.publicKey + ctx.provider.wallet.publicKey ); await assert.rejects( - client - .openPositionWithMetadataTx({ ...defaultParams, positionTokenAccountAddress }) + toTx( + ctx, + WhirlpoolIx.openPositionWithMetadataIx(ctx.program, { + ...defaultParams, + positionTokenAccount: positionTokenAccountAddress, + }) + ) .addSigner(defaultMint) .buildAndExecute(), /An account required by the instruction is missing/ @@ -164,7 +172,7 @@ describe("open_position_with_metadata", () => { async function assertTicksFail(lowerTick: number, upperTick: number) { await assert.rejects( openPositionWithMetadata( - client, + ctx, whirlpoolPda.publicKey, lowerTick, upperTick, @@ -202,8 +210,8 @@ describe("open_position_with_metadata", () => { it("fail when position mint already exists", async () => { const positionMintKeypair = anchor.web3.Keypair.generate(); - const positionPda = getPositionPda(context.program.programId, positionMintKeypair.publicKey); - const metadataPda = getPositionMetadataPda(positionMintKeypair.publicKey); + const positionPda = PDAUtil.getPosition(ctx.program.programId, positionMintKeypair.publicKey); + const metadataPda = PDAUtil.getPositionMetadata(positionMintKeypair.publicKey); const positionTokenAccountAddress = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, @@ -224,17 +232,19 @@ describe("open_position_with_metadata", () => { await provider.send(tx, [positionMintKeypair], { commitment: "confirmed" }); await assert.rejects( - client - .openPositionWithMetadataTx({ + toTx( + ctx, + WhirlpoolIx.openPositionWithMetadataIx(ctx.program, { ...defaultParams, positionPda, metadataPda, positionMintAddress: positionMintKeypair.publicKey, - positionTokenAccountAddress: positionTokenAccountAddress, - whirlpoolKey: whirlpoolPda.publicKey, + positionTokenAccount: positionTokenAccountAddress, + whirlpool: whirlpoolPda.publicKey, tickLowerIndex, tickUpperIndex, }) + ) .addSigner(positionMintKeypair) .buildAndExecute(), /0x0/ @@ -258,7 +268,7 @@ describe("open_position_with_metadata", () => { metadataBump: metadataPda.bump, }; - const ix = context.program.instruction.openPositionWithMetadata( + const ix = ctx.program.instruction.openPositionWithMetadata( bumps, tickLowerIndex, tickUpperIndex, @@ -284,11 +294,13 @@ describe("open_position_with_metadata", () => { const notMintKeypair = Keypair.generate(); const invalidParams = { ...defaultParams, - metadataPda: getPositionMetadataPda(notMintKeypair.publicKey), + metadataPda: PDAUtil.getPositionMetadata(notMintKeypair.publicKey), }; await assert.rejects( - client.openPositionWithMetadataTx(invalidParams).addSigner(defaultMint).buildAndExecute(), + toTx(ctx, WhirlpoolIx.openPositionWithMetadataIx(ctx.program, invalidParams)) + .addSigner(defaultMint) + .buildAndExecute(), // Invalid Metadata Key // https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/error.rs#L36 /0x5/ @@ -297,7 +309,7 @@ describe("open_position_with_metadata", () => { it("fails with non-program metadata program", async () => { const notMetadataProgram = Keypair.generate(); - const tx = new TransactionBuilder(context.provider).addInstruction( + const tx = new TransactionBuilder(ctx.provider).addInstruction( buildOpenWithAccountOverrides({ metadataProgram: notMetadataProgram.publicKey, }) @@ -312,7 +324,7 @@ describe("open_position_with_metadata", () => { }); it("fails with non-metadata program ", async () => { - const tx = new TransactionBuilder(context.provider).addInstruction( + const tx = new TransactionBuilder(ctx.provider).addInstruction( buildOpenWithAccountOverrides({ metadataProgram: TOKEN_PROGRAM_ID, }) @@ -328,7 +340,7 @@ describe("open_position_with_metadata", () => { it("fails with non-valid update_authority program", async () => { const notUpdateAuth = Keypair.generate(); - const tx = new TransactionBuilder(context.provider).addInstruction( + const tx = new TransactionBuilder(ctx.provider).addInstruction( buildOpenWithAccountOverrides({ metadataUpdateAuth: notUpdateAuth.publicKey, }) diff --git a/sdk/tests/set_collect_protocol_fee_authority.ts b/sdk/tests/integration/set_collect_protocol_fee_authority.test.ts similarity index 51% rename from sdk/tests/set_collect_protocol_fee_authority.ts rename to sdk/tests/integration/set_collect_protocol_fee_authority.test.ts index b0f2150..f09354e 100644 --- a/sdk/tests/set_collect_protocol_fee_authority.ts +++ b/sdk/tests/integration/set_collect_protocol_fee_authority.test.ts @@ -1,32 +1,36 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { generateDefaultConfigParams } from "./utils/test-builders"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolsConfigData, WhirlpoolIx } from "../../src"; +import { generateDefaultConfigParams } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_collect_protocol_fee_authority", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully set_collect_protocol_fee_authority", async () => { const { configInitInfo, configKeypairs: { collectProtocolFeesAuthorityKeypair }, - } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const newAuthorityKeypair = anchor.web3.Keypair.generate(); - await client - .setCollectProtocolFeesAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + await toTx( + ctx, + WhirlpoolIx.setCollectProtocolFeesAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, newCollectProtocolFeesAuthority: newAuthorityKeypair.publicKey, }) + ) .addSigner(collectProtocolFeesAuthorityKeypair) .buildAndExecute(); - const config = await client.getConfig(configInitInfo.whirlpoolConfigKeypair.publicKey); + const config = (await fetcher.getConfig( + configInitInfo.whirlpoolsConfigKeypair.publicKey + )) as WhirlpoolsConfigData; assert.ok(config.collectProtocolFeesAuthority.equals(newAuthorityKeypair.publicKey)); }); @@ -34,33 +38,35 @@ describe("set_collect_protocol_fee_authority", () => { const { configInitInfo, configKeypairs: { collectProtocolFeesAuthorityKeypair }, - } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); await assert.rejects( - client - .setCollectProtocolFeesAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setCollectProtocolFeesAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, collectProtocolFeesAuthority: collectProtocolFeesAuthorityKeypair.publicKey, newCollectProtocolFeesAuthority: provider.wallet.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails if invalid collect_protocol_fee_authority provided", async () => { - const { configInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); await assert.rejects( - client - .setCollectProtocolFeesAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setCollectProtocolFeesAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, collectProtocolFeesAuthority: provider.wallet.publicKey, newCollectProtocolFeesAuthority: provider.wallet.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // An address constraint was violated ); }); diff --git a/sdk/tests/set_default_fee_rate.ts b/sdk/tests/integration/set_default_fee_rate.test.ts similarity index 65% rename from sdk/tests/set_default_fee_rate.ts rename to sdk/tests/integration/set_default_fee_rate.test.ts index 4778f81..12fe9bd 100644 --- a/sdk/tests/set_default_fee_rate.ts +++ b/sdk/tests/integration/set_default_fee_rate.test.ts @@ -1,52 +1,60 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { getFeeTierPda, getWhirlpoolPda, InitPoolParams } from "../src"; -import { initTestPool } from "./utils/init-utils"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { createInOrderMints, generateDefaultConfigParams } from "./utils/test-builders"; -import { TickSpacing } from "./utils"; +import { + WhirlpoolContext, + AccountFetcher, + WhirlpoolData, + InitPoolParams, + WhirlpoolIx, + PDAUtil, +} from "../../src"; +import { TickSpacing } from "../utils"; +import { initTestPool } from "../utils/init-utils"; +import { createInOrderMints, generateDefaultConfigParams } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_default_fee_rate", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully set_default_fee_rate", async () => { const { poolInitInfo, configInitInfo, configKeypairs, feeTierParams } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newDefaultFeeRate = 45; // Fetch initial whirlpool and check it is default - let whirlpool_0 = await client.getPool(whirlpoolKey); + let whirlpool_0 = (await fetcher.getPool(whirlpoolKey)) as WhirlpoolData; assert.equal(whirlpool_0.feeRate, feeTierParams.defaultFeeRate); - await client - .setDefaultFeeRateIx({ + await toTx( + ctx, + WhirlpoolIx.setDefaultFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, feeAuthority: feeAuthorityKeypair.publicKey, tickSpacing: TickSpacing.Standard, defaultFeeRate: newDefaultFeeRate, }) + ) .addSigner(feeAuthorityKeypair) .buildAndExecute(); // Setting the default rate did not change existing whirlpool fee rate - whirlpool_0 = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + whirlpool_0 = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData; assert.equal(whirlpool_0.feeRate, feeTierParams.defaultFeeRate); // Newly initialized whirlpools have new default fee rate - const [tokenMintA, tokenMintB] = await createInOrderMints(context); - const whirlpoolPda = getWhirlpoolPda( - context.program.programId, + const [tokenMintA, tokenMintB] = await createInOrderMints(ctx); + const whirlpoolPda = PDAUtil.getWhirlpool( + ctx.program.programId, whirlpoolsConfigKey, tokenMintA, tokenMintB, @@ -64,26 +72,28 @@ describe("set_default_fee_rate", () => { tokenVaultBKeypair, tickSpacing: TickSpacing.Stable, }; - await client.initPoolTx(newPoolInitInfo).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, newPoolInitInfo)).buildAndExecute(); - const whirlpool_1 = await client.getPool(whirlpoolPda.publicKey); + const whirlpool_1 = (await fetcher.getPool(whirlpoolPda.publicKey)) as WhirlpoolData; assert.equal(whirlpool_1.feeRate, newDefaultFeeRate); }); it("fails when default fee rate exceeds max", async () => { - const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newDefaultFeeRate = 20_000; await assert.rejects( - client - .setDefaultFeeRateIx({ + toTx( + ctx, + WhirlpoolIx.setDefaultFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, feeAuthority: feeAuthorityKeypair.publicKey, tickSpacing: TickSpacing.Standard, defaultFeeRate: newDefaultFeeRate, }) + ) .addSigner(feeAuthorityKeypair) .buildAndExecute(), /0x178c/ // FeeRateMaxExceeded @@ -91,18 +101,20 @@ describe("set_default_fee_rate", () => { }); it("fails when fee tier account has not been initialized", async () => { - const { configInitInfo, configKeypairs } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; await assert.rejects( - client - .setDefaultFeeRateIx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setDefaultFeeRateIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, feeAuthority: feeAuthorityKeypair.publicKey, tickSpacing: TickSpacing.Standard, defaultFeeRate: 500, }) + ) .addSigner(feeAuthorityKeypair) .buildAndExecute(), /0xbc4/ // AccountNotInitialized @@ -110,12 +122,12 @@ describe("set_default_fee_rate", () => { }); it("fails when fee authority is not a signer", async () => { - const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; - const feeTierPda = getFeeTierPda( - context.program.programId, - configInitInfo.whirlpoolConfigKeypair.publicKey, + const feeTierPda = PDAUtil.getFeeTier( + ctx.program.programId, + configInitInfo.whirlpoolsConfigKeypair.publicKey, TickSpacing.Standard ); @@ -133,12 +145,12 @@ describe("set_default_fee_rate", () => { }); it("fails when invalid fee authority provided", async () => { - const { configInitInfo } = await initTestPool(client, TickSpacing.Standard); - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const { configInitInfo } = await initTestPool(ctx, TickSpacing.Standard); + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const fakeFeeAuthorityKeypair = anchor.web3.Keypair.generate(); - const feeTierPda = getFeeTierPda( - context.program.programId, - configInitInfo.whirlpoolConfigKeypair.publicKey, + const feeTierPda = PDAUtil.getFeeTier( + ctx.program.programId, + configInitInfo.whirlpoolsConfigKeypair.publicKey, TickSpacing.Standard ); diff --git a/sdk/tests/set_default_protocol_fee_rate.ts b/sdk/tests/integration/set_default_protocol_fee_rate.test.ts similarity index 67% rename from sdk/tests/set_default_protocol_fee_rate.ts rename to sdk/tests/integration/set_default_protocol_fee_rate.test.ts index eda1297..b6f531b 100644 --- a/sdk/tests/set_default_protocol_fee_rate.ts +++ b/sdk/tests/integration/set_default_protocol_fee_rate.test.ts @@ -1,50 +1,58 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { getWhirlpoolPda, InitPoolParams } from "../src"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initTestPool } from "./utils/init-utils"; -import { createInOrderMints } from "./utils/test-builders"; -import { TickSpacing } from "./utils"; +import { + WhirlpoolContext, + AccountFetcher, + WhirlpoolData, + InitPoolParams, + WhirlpoolIx, + PDAUtil, +} from "../../src"; +import { TickSpacing } from "../utils"; +import { initTestPool } from "../utils/init-utils"; +import { createInOrderMints } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_default_protocol_fee_rate", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully set_default_protocol_fee_rate", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newDefaultProtocolFeeRate = 45; // Fetch initial whirlpool and check it is default - let whirlpool_0 = await client.getPool(whirlpoolKey); + let whirlpool_0 = (await fetcher.getPool(whirlpoolKey)) as WhirlpoolData; assert.equal(whirlpool_0.protocolFeeRate, configInitInfo.defaultProtocolFeeRate); - await client - .setDefaultProtocolFeeRateIx({ + await toTx( + ctx, + WhirlpoolIx.setDefaultProtocolFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, feeAuthority: feeAuthorityKeypair.publicKey, defaultProtocolFeeRate: newDefaultProtocolFeeRate, }) + ) .addSigner(feeAuthorityKeypair) .buildAndExecute(); // Setting the default rate did not change existing whirlpool fee rate - whirlpool_0 = await client.getPool(whirlpoolKey); + whirlpool_0 = (await fetcher.getPool(whirlpoolKey)) as WhirlpoolData; assert.equal(whirlpool_0.protocolFeeRate, configInitInfo.defaultProtocolFeeRate); - const [tokenMintA, tokenMintB] = await createInOrderMints(context); - const whirlpoolPda = getWhirlpoolPda( - context.program.programId, + const [tokenMintA, tokenMintB] = await createInOrderMints(ctx); + const whirlpoolPda = PDAUtil.getWhirlpool( + ctx.program.programId, whirlpoolsConfigKey, tokenMintA, tokenMintB, @@ -62,25 +70,27 @@ describe("set_default_protocol_fee_rate", () => { tokenVaultBKeypair, tickSpacing: TickSpacing.Stable, }; - await client.initPoolTx(newPoolInitInfo).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, newPoolInitInfo)).buildAndExecute(); - const whirlpool_1 = await client.getPool(whirlpoolPda.publicKey); + const whirlpool_1 = (await fetcher.getPool(whirlpoolPda.publicKey)) as WhirlpoolData; assert.equal(whirlpool_1.protocolFeeRate, newDefaultProtocolFeeRate); }); it("fails when default fee rate exceeds max", async () => { - const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newDefaultProtocolFeeRate = 20_000; await assert.rejects( - client - .setDefaultProtocolFeeRateIx({ + toTx( + ctx, + WhirlpoolIx.setDefaultProtocolFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, feeAuthority: feeAuthorityKeypair.publicKey, defaultProtocolFeeRate: newDefaultProtocolFeeRate, }) + ) .addSigner(feeAuthorityKeypair) .buildAndExecute(), /0x178d/ // ProtocolFeeRateMaxExceeded @@ -88,8 +98,8 @@ describe("set_default_protocol_fee_rate", () => { }); it("fails when fee authority is not a signer", async () => { - const { configInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const { configInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newDefaultProtocolFeeRate = 1000; @@ -105,8 +115,8 @@ describe("set_default_protocol_fee_rate", () => { }); it("fails when invalid fee authority provided", async () => { - const { configInitInfo } = await initTestPool(client, TickSpacing.Standard); - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const { configInitInfo } = await initTestPool(ctx, TickSpacing.Standard); + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const fakeFeeAuthorityKeypair = anchor.web3.Keypair.generate(); const newDefaultProtocolFeeRate = 1000; diff --git a/sdk/tests/integration/set_fee_authority.test.ts b/sdk/tests/integration/set_fee_authority.test.ts new file mode 100644 index 0000000..e38e034 --- /dev/null +++ b/sdk/tests/integration/set_fee_authority.test.ts @@ -0,0 +1,73 @@ +import * as assert from "assert"; +import * as anchor from "@project-serum/anchor"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolsConfigData, WhirlpoolIx } from "../../src"; +import { generateDefaultConfigParams } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; + +describe("set_fee_authority", () => { + const provider = anchor.Provider.local(); + anchor.setProvider(anchor.Provider.env()); + const program = anchor.workspace.Whirlpool; + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); + + it("successfully set_fee_authority", async () => { + const { + configInitInfo, + configKeypairs: { feeAuthorityKeypair }, + } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); + const newAuthorityKeypair = anchor.web3.Keypair.generate(); + await toTx( + ctx, + WhirlpoolIx.setFeeAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, + feeAuthority: feeAuthorityKeypair.publicKey, + newFeeAuthority: newAuthorityKeypair.publicKey, + }) + ) + .addSigner(feeAuthorityKeypair) + .buildAndExecute(); + const config = (await fetcher.getConfig( + configInitInfo.whirlpoolsConfigKeypair.publicKey + )) as WhirlpoolsConfigData; + assert.ok(config.feeAuthority.equals(newAuthorityKeypair.publicKey)); + }); + + it("fails if current fee_authority is not a signer", async () => { + const { + configInitInfo, + configKeypairs: { feeAuthorityKeypair }, + } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); + + await assert.rejects( + toTx( + ctx, + WhirlpoolIx.setFeeAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, + feeAuthority: feeAuthorityKeypair.publicKey, + newFeeAuthority: provider.wallet.publicKey, + }) + ).buildAndExecute(), + /Signature verification failed/ + ); + }); + + it("fails if invalid fee_authority provided", async () => { + const { configInitInfo } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); + + await assert.rejects( + toTx( + ctx, + WhirlpoolIx.setFeeAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, + feeAuthority: provider.wallet.publicKey, + newFeeAuthority: provider.wallet.publicKey, + }) + ).buildAndExecute(), + /0x7dc/ // An address constraint was violated + ); + }); +}); diff --git a/sdk/tests/set_fee_rate.ts b/sdk/tests/integration/set_fee_rate.test.ts similarity index 69% rename from sdk/tests/set_fee_rate.ts rename to sdk/tests/integration/set_fee_rate.test.ts index da69af3..7092842 100644 --- a/sdk/tests/set_fee_rate.ts +++ b/sdk/tests/integration/set_fee_rate.test.ts @@ -1,31 +1,30 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; - -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initTestPool } from "./utils/init-utils"; -import { generateDefaultConfigParams } from "./utils/test-builders"; -import { TickSpacing } from "./utils"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src"; +import { TickSpacing } from "../utils"; +import { initTestPool } from "../utils/init-utils"; +import { generateDefaultConfigParams } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_fee_rate", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully sets_fee_rate", async () => { const { poolInitInfo, configInitInfo, configKeypairs, feeTierParams } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newFeeRate = 50; - let whirlpool = await client.getPool(whirlpoolKey); + let whirlpool = (await fetcher.getPool(whirlpoolKey, true)) as WhirlpoolData; assert.equal(whirlpool.feeRate, feeTierParams.defaultFeeRate); @@ -38,28 +37,30 @@ describe("set_fee_rate", () => { signers: [feeAuthorityKeypair], }); - whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.equal(whirlpool.feeRate, newFeeRate); }); it("fails when fee rate exceeds max", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newFeeRate = 20_000; await assert.rejects( - client - .setFeeRateIx({ + toTx( + ctx, + WhirlpoolIx.setFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, whirlpool: whirlpoolKey, feeAuthority: feeAuthorityKeypair.publicKey, feeRate: newFeeRate, }) + ) .addSigner(configKeypairs.feeAuthorityKeypair) .buildAndExecute(), /0x178c/ // FeeRateMaxExceeded @@ -68,43 +69,47 @@ describe("set_fee_rate", () => { it("fails when fee authority is not signer", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newFeeRate = 1000; await assert.rejects( - client - .setFeeRateIx({ + toTx( + ctx, + WhirlpoolIx.setFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, whirlpool: whirlpoolKey, feeAuthority: feeAuthorityKeypair.publicKey, feeRate: newFeeRate, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails when whirlpool and whirlpools config don't match", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; - const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(otherConfigInitInfo).buildAndExecute(); + const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(ctx); + await toTx( + ctx, + WhirlpoolIx.initializeConfigIx(ctx.program, otherConfigInitInfo) + ).buildAndExecute(); const newFeeRate = 1000; await assert.rejects( - context.program.rpc.setFeeRate(newFeeRate, { + ctx.program.rpc.setFeeRate(newFeeRate, { accounts: { - whirlpoolsConfig: otherConfigInitInfo.whirlpoolConfigKeypair.publicKey, + whirlpoolsConfig: otherConfigInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolKey, feeAuthority: feeAuthorityKeypair.publicKey, }, @@ -115,16 +120,16 @@ describe("set_fee_rate", () => { }); it("fails when fee authority is invalid", async () => { - const { poolInitInfo, configInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; const fakeAuthorityKeypair = anchor.web3.Keypair.generate(); const newFeeRate = 1000; await assert.rejects( - context.program.rpc.setFeeRate(newFeeRate, { + ctx.program.rpc.setFeeRate(newFeeRate, { accounts: { - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolKey, feeAuthority: fakeAuthorityKeypair.publicKey, }, diff --git a/sdk/tests/set_protocol_fee_rate.ts b/sdk/tests/integration/set_protocol_fee_rate.test.ts similarity index 69% rename from sdk/tests/set_protocol_fee_rate.ts rename to sdk/tests/integration/set_protocol_fee_rate.test.ts index 2e73719..e601054 100644 --- a/sdk/tests/set_protocol_fee_rate.ts +++ b/sdk/tests/integration/set_protocol_fee_rate.test.ts @@ -1,30 +1,30 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initTestPool } from "./utils/init-utils"; -import { generateDefaultConfigParams } from "./utils/test-builders"; -import { TickSpacing } from "./utils"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src"; +import { TickSpacing } from "../utils"; +import { initTestPool } from "../utils/init-utils"; +import { generateDefaultConfigParams } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_protocol_fee_rate", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully sets_protocol_fee_rate", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newProtocolFeeRate = 50; - let whirlpool = await client.getPool(whirlpoolKey); + let whirlpool = (await fetcher.getPool(whirlpoolKey, true)) as WhirlpoolData; assert.equal(whirlpool.protocolFeeRate, configInitInfo.defaultProtocolFeeRate); @@ -37,28 +37,30 @@ describe("set_protocol_fee_rate", () => { signers: [feeAuthorityKeypair], }); - whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.equal(whirlpool.protocolFeeRate, newProtocolFeeRate); }); it("fails when protocol fee rate exceeds max", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newProtocolFeeRate = 3_000; await assert.rejects( - client - .setProtocolFeeRateIx({ + toTx( + ctx, + WhirlpoolIx.setProtocolFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, whirlpool: whirlpoolKey, feeAuthority: feeAuthorityKeypair.publicKey, protocolFeeRate: newProtocolFeeRate, }) + ) .addSigner(configKeypairs.feeAuthorityKeypair) .buildAndExecute(), /0x178d/ // ProtocolFeeRateMaxExceeded @@ -67,40 +69,44 @@ describe("set_protocol_fee_rate", () => { it("fails when fee authority is not signer", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; - const whirlpoolsConfigKey = configInitInfo.whirlpoolConfigKeypair.publicKey; + const whirlpoolsConfigKey = configInitInfo.whirlpoolsConfigKeypair.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; const newProtocolFeeRate = 1000; await assert.rejects( - client - .setProtocolFeeRateIx({ + toTx( + ctx, + WhirlpoolIx.setProtocolFeeRateIx(ctx.program, { whirlpoolsConfig: whirlpoolsConfigKey, whirlpool: whirlpoolKey, feeAuthority: feeAuthorityKeypair.publicKey, protocolFeeRate: newProtocolFeeRate, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails when whirlpool and whirlpools config don't match", async () => { - const { poolInitInfo, configKeypairs } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configKeypairs } = await initTestPool(ctx, TickSpacing.Standard); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; const feeAuthorityKeypair = configKeypairs.feeAuthorityKeypair; - const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(otherConfigInitInfo).buildAndExecute(); + const { configInitInfo: otherConfigInitInfo } = generateDefaultConfigParams(ctx); + await toTx( + ctx, + WhirlpoolIx.initializeConfigIx(ctx.program, otherConfigInitInfo) + ).buildAndExecute(); const newProtocolFeeRate = 1000; await assert.rejects( - context.program.rpc.setProtocolFeeRate(newProtocolFeeRate, { + ctx.program.rpc.setProtocolFeeRate(newProtocolFeeRate, { accounts: { - whirlpoolsConfig: otherConfigInitInfo.whirlpoolConfigKeypair.publicKey, + whirlpoolsConfig: otherConfigInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolKey, feeAuthority: feeAuthorityKeypair.publicKey, }, @@ -111,15 +117,15 @@ describe("set_protocol_fee_rate", () => { }); it("fails when fee authority is invalid", async () => { - const { poolInitInfo, configInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; const fakeAuthorityKeypair = anchor.web3.Keypair.generate(); const newProtocolFeeRate = 1000; await assert.rejects( - context.program.rpc.setProtocolFeeRate(newProtocolFeeRate, { + ctx.program.rpc.setProtocolFeeRate(newProtocolFeeRate, { accounts: { - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: whirlpoolKey, feeAuthority: fakeAuthorityKeypair.publicKey, }, diff --git a/sdk/tests/set_reward_authority.ts b/sdk/tests/integration/set_reward_authority.test.ts similarity index 68% rename from sdk/tests/set_reward_authority.ts rename to sdk/tests/integration/set_reward_authority.test.ts index 5698a18..17f36fa 100644 --- a/sdk/tests/set_reward_authority.ts +++ b/sdk/tests/integration/set_reward_authority.test.ts @@ -1,28 +1,32 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { NUM_REWARDS } from "../src/types/public"; -import { TransactionBuilder } from "../src/utils/transactions/transactions-builder"; -import { buildSetRewardAuthorityIx } from "../src/instructions/set-reward-authority-ix"; -import { initTestPool } from "./utils/init-utils"; -import { TickSpacing } from "./utils"; +import { + WhirlpoolContext, + AccountFetcher, + NUM_REWARDS, + WhirlpoolData, + WhirlpoolIx, +} from "../../src"; +import { TickSpacing } from "../utils"; +import { initTestPool } from "../utils/init-utils"; +import { TransactionBuilder } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_reward_authority", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully set_reward_authority at every reward index", async () => { - const { configKeypairs, poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { configKeypairs, poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const newKeypairs = generateKeypairs(NUM_REWARDS); const txBuilder = new TransactionBuilder(provider); for (let i = 0; i < NUM_REWARDS; i++) { txBuilder.addInstruction( - buildSetRewardAuthorityIx(context, { + WhirlpoolIx.setRewardAuthorityIx(ctx.program, { whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: newKeypairs[i].publicKey, @@ -34,25 +38,27 @@ describe("set_reward_authority", () => { .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(); - const pool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const pool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData; for (let i = 0; i < NUM_REWARDS; i++) { assert.ok(pool.rewardInfos[i].authority.equals(newKeypairs[i].publicKey)); } }); it("fails when provided reward_authority does not match whirlpool reward authority", async () => { - const { poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const fakeAuthority = anchor.web3.Keypair.generate(); const newAuthority = anchor.web3.Keypair.generate(); await assert.rejects( - client - .setRewardAuthorityTx({ + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityIx(ctx.program, { whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardAuthority: fakeAuthority.publicKey, newRewardAuthority: newAuthority.publicKey, rewardIndex: 0, }) + ) .addSigner(fakeAuthority) .buildAndExecute(), /0x7dc/ // An address constraint was violated @@ -60,26 +66,31 @@ describe("set_reward_authority", () => { }); it("fails on invalid reward index", async () => { - const { configKeypairs, poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { configKeypairs, poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const newAuthority = anchor.web3.Keypair.generate(); assert.throws(() => { - client.setRewardAuthorityTx({ - whirlpool: poolInitInfo.whirlpoolPda.publicKey, - rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, - newRewardAuthority: newAuthority.publicKey, - rewardIndex: -1, - }); + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityIx(ctx.program, { + whirlpool: poolInitInfo.whirlpoolPda.publicKey, + rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, + newRewardAuthority: newAuthority.publicKey, + rewardIndex: -1, + }) + ).buildAndExecute(); }, /out of range/); await assert.rejects( - client - .setRewardAuthorityTx({ + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityIx(ctx.program, { whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: newAuthority.publicKey, rewardIndex: 255, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute() // /failed to send transaction/ @@ -87,18 +98,19 @@ describe("set_reward_authority", () => { }); it("fails when reward_authority is not a signer", async () => { - const { configKeypairs, poolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { configKeypairs, poolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const newAuthority = anchor.web3.Keypair.generate(); await assert.rejects( - client - .setRewardAuthorityTx({ + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityIx(ctx.program, { whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: newAuthority.publicKey, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); diff --git a/sdk/tests/set_reward_authority_by_super_authority.ts b/sdk/tests/integration/set_reward_authority_by_super_authority.test.ts similarity index 68% rename from sdk/tests/set_reward_authority_by_super_authority.ts rename to sdk/tests/integration/set_reward_authority_by_super_authority.test.ts index 32d798a..64ca073 100644 --- a/sdk/tests/set_reward_authority_by_super_authority.ts +++ b/sdk/tests/integration/set_reward_authority_by_super_authority.test.ts @@ -1,54 +1,58 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { initTestPool } from "./utils/init-utils"; -import { TickSpacing } from "./utils"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src"; +import { TickSpacing } from "../utils"; +import { initTestPool } from "../utils/init-utils"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_reward_authority_by_super_authority", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully set_reward_authority_by_super_authority", async () => { const { configKeypairs, poolInitInfo, configInitInfo } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const newAuthorityKeypair = anchor.web3.Keypair.generate(); - await client - .setRewardAuthorityBySuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + await toTx( + ctx, + WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardEmissionsSuperAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: newAuthorityKeypair.publicKey, rewardIndex: 0, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(); - const pool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const pool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey)) as WhirlpoolData; assert.ok(pool.rewardInfos[0].authority.equals(newAuthorityKeypair.publicKey)); }); it("fails if invalid whirlpool provided", async () => { - const { configKeypairs, configInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { configKeypairs, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const { poolInitInfo: { whirlpoolPda: invalidPool }, - } = await initTestPool(client, TickSpacing.Standard); + } = await initTestPool(ctx, TickSpacing.Standard); await assert.rejects( - client - .setRewardAuthorityBySuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: invalidPool.publicKey, rewardEmissionsSuperAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: provider.wallet.publicKey, rewardIndex: 0, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(), /0x7d1/ // A has_one constraint was violated @@ -56,18 +60,20 @@ describe("set_reward_authority_by_super_authority", () => { }); it("fails if invalid super authority provided", async () => { - const { poolInitInfo, configInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo, configInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const invalidSuperAuthorityKeypair = anchor.web3.Keypair.generate(); await assert.rejects( - client - .setRewardAuthorityBySuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardEmissionsSuperAuthority: invalidSuperAuthorityKeypair.publicKey, newRewardAuthority: provider.wallet.publicKey, rewardIndex: 0, }) + ) .addSigner(invalidSuperAuthorityKeypair) .buildAndExecute(), /0x7dc/ // An address constraint was violated @@ -76,55 +82,60 @@ describe("set_reward_authority_by_super_authority", () => { it("fails if super authority is not a signer", async () => { const { configKeypairs, poolInitInfo, configInitInfo } = await initTestPool( - client, + ctx, TickSpacing.Standard ); await assert.rejects( - client - .setRewardAuthorityBySuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardEmissionsSuperAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: provider.wallet.publicKey, rewardIndex: 0, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); it("fails on invalid reward index", async () => { const { configKeypairs, poolInitInfo, configInitInfo } = await initTestPool( - client, + ctx, TickSpacing.Standard ); assert.throws(() => { - client - .setRewardAuthorityBySuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardEmissionsSuperAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: provider.wallet.publicKey, rewardIndex: -1, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(); }, /out of range/); await assert.rejects( - client - .setRewardAuthorityBySuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setRewardAuthorityBySuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardEmissionsSuperAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardAuthority: provider.wallet.publicKey, rewardIndex: 200, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(), /0x178a/ // InvalidRewardIndex diff --git a/sdk/tests/set_reward_emissions.ts b/sdk/tests/integration/set_reward_emissions.test.ts similarity index 74% rename from sdk/tests/set_reward_emissions.ts rename to sdk/tests/integration/set_reward_emissions.test.ts index db71fd5..c82c49e 100644 --- a/sdk/tests/set_reward_emissions.ts +++ b/sdk/tests/integration/set_reward_emissions.test.ts @@ -1,22 +1,22 @@ import * as anchor from "@project-serum/anchor"; import * as assert from "assert"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; -import { createAndMintToTokenAccount, mintToByAuthority, TickSpacing, ZERO_BN } from "./utils"; -import { initializeReward, initTestPool } from "./utils/init-utils"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolData, WhirlpoolIx } from "../../src"; +import { toTx } from "../../src/utils/instructions-util"; +import { TickSpacing, mintToByAuthority, ZERO_BN, createAndMintToTokenAccount } from "../utils"; +import { initTestPool, initializeReward } from "../utils/init-utils"; describe("set_reward_emissions", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); const emissionsPerSecondX64 = new anchor.BN(10_000).shln(64).div(new anchor.BN(60 * 60 * 24)); it("successfully set_reward_emissions", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); @@ -25,7 +25,7 @@ describe("set_reward_emissions", () => { const { params: { rewardVaultKeypair, rewardMint }, } = await initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, rewardIndex @@ -33,39 +33,46 @@ describe("set_reward_emissions", () => { await mintToByAuthority(provider, rewardMint, rewardVaultKeypair.publicKey, 10000); - await client - .setRewardEmissionsTx({ + await toTx( + ctx, + WhirlpoolIx.setRewardEmissionsIx(ctx.program, { rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardIndex, - rewardVault: rewardVaultKeypair.publicKey, + rewardVaultKey: rewardVaultKeypair.publicKey, emissionsPerSecondX64, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(); - let whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + let whirlpool = (await fetcher.getPool( + poolInitInfo.whirlpoolPda.publicKey, + true + )) as WhirlpoolData; assert.ok(whirlpool.rewardInfos[0].emissionsPerSecondX64.eq(emissionsPerSecondX64)); // Successfuly set emissions back to zero - await client - .setRewardEmissionsTx({ + await toTx( + ctx, + WhirlpoolIx.setRewardEmissionsIx(ctx.program, { rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardIndex, - rewardVault: rewardVaultKeypair.publicKey, + rewardVaultKey: rewardVaultKeypair.publicKey, emissionsPerSecondX64: ZERO_BN, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(); - whirlpool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + whirlpool = (await fetcher.getPool(poolInitInfo.whirlpoolPda.publicKey, true)) as WhirlpoolData; assert.ok(whirlpool.rewardInfos[0].emissionsPerSecondX64.eq(ZERO_BN)); }); it("fails when token vault does not contain at least 1 day of emission runway", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); @@ -74,21 +81,23 @@ describe("set_reward_emissions", () => { const { params: { rewardVaultKeypair }, } = await initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, rewardIndex ); await assert.rejects( - client - .setRewardEmissionsTx({ + toTx( + ctx, + WhirlpoolIx.setRewardEmissionsIx(ctx.program, { rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardIndex, - rewardVault: rewardVaultKeypair.publicKey, + rewardVaultKey: rewardVaultKeypair.publicKey, emissionsPerSecondX64, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(), /0x178b/ // RewardVaultAmountInsufficient @@ -97,7 +106,7 @@ describe("set_reward_emissions", () => { it("fails if provided reward vault does not match whirlpool reward vault", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); @@ -105,7 +114,7 @@ describe("set_reward_emissions", () => { const { params: { rewardVaultKeypair, rewardMint }, } = await initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, rewardIndex @@ -114,14 +123,16 @@ describe("set_reward_emissions", () => { const fakeVault = await createAndMintToTokenAccount(provider, rewardMint, 10000); await assert.rejects( - client - .setRewardEmissionsTx({ + toTx( + ctx, + WhirlpoolIx.setRewardEmissionsIx(ctx.program, { whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority, - rewardVault: fakeVault, + rewardVaultKey: fakeVault, rewardIndex, emissionsPerSecondX64, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(), /0x7dc/ // An address constraint was violated @@ -130,21 +141,23 @@ describe("set_reward_emissions", () => { it("cannot set emission for an uninitialized reward", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const rewardIndex = 0; await assert.rejects( - client - .setRewardEmissionsTx({ + toTx( + ctx, + WhirlpoolIx.setRewardEmissionsIx(ctx.program, { whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority, - rewardVault: anchor.web3.PublicKey.default, + rewardVaultKey: anchor.web3.PublicKey.default, rewardIndex: rewardIndex, emissionsPerSecondX64, }) + ) .addSigner(configKeypairs.rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(), /0xbbf/ // AccountOwnedByWrongProgram @@ -153,29 +166,30 @@ describe("set_reward_emissions", () => { it("cannot set emission without the authority's signature", async () => { const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, TickSpacing.Standard ); const rewardIndex = 0; await initializeReward( - client, + ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, rewardIndex ); await assert.rejects( - client - .setRewardEmissionsTx({ + toTx( + ctx, + WhirlpoolIx.setRewardEmissionsIx(ctx.program, { rewardAuthority: configInitInfo.rewardEmissionsSuperAuthority, whirlpool: poolInitInfo.whirlpoolPda.publicKey, rewardIndex, - rewardVault: provider.wallet.publicKey, // TODO fix + rewardVaultKey: provider.wallet.publicKey, // TODO fix emissionsPerSecondX64, }) - .buildAndExecute(), + ).buildAndExecute(), /Signature verification failed/ ); }); diff --git a/sdk/tests/set_reward_emissions_super_authority.ts b/sdk/tests/integration/set_reward_emissions_super_authority.test.ts similarity index 54% rename from sdk/tests/set_reward_emissions_super_authority.ts rename to sdk/tests/integration/set_reward_emissions_super_authority.test.ts index d2c29ad..eaaed00 100644 --- a/sdk/tests/set_reward_emissions_super_authority.ts +++ b/sdk/tests/integration/set_reward_emissions_super_authority.test.ts @@ -1,35 +1,39 @@ import * as assert from "assert"; import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { generateDefaultConfigParams } from "./utils/test-builders"; +import { WhirlpoolContext, AccountFetcher, WhirlpoolsConfigData, WhirlpoolIx } from "../../src"; +import { generateDefaultConfigParams } from "../utils/test-builders"; +import { toTx } from "../../src/utils/instructions-util"; describe("set_reward_emissions_super_authority", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully set_reward_emissions_super_authority with super authority keypair", async () => { const { configInitInfo, configKeypairs: { rewardEmissionsSuperAuthorityKeypair }, - } = generateDefaultConfigParams(context); + } = generateDefaultConfigParams(ctx); - await client.initConfigTx(configInitInfo).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const newAuthorityKeypair = anchor.web3.Keypair.generate(); - await client - .setRewardEmissionsSuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + await toTx( + ctx, + WhirlpoolIx.setRewardEmissionsSuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, rewardEmissionsSuperAuthority: rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardEmissionsSuperAuthority: newAuthorityKeypair.publicKey, }) + ) .addSigner(rewardEmissionsSuperAuthorityKeypair) .buildAndExecute(); - const config = await client.getConfig(configInitInfo.whirlpoolConfigKeypair.publicKey); + const config = (await fetcher.getConfig( + configInitInfo.whirlpoolsConfigKeypair.publicKey + )) as WhirlpoolsConfigData; assert.ok(config.rewardEmissionsSuperAuthority.equals(newAuthorityKeypair.publicKey)); }); @@ -37,13 +41,13 @@ describe("set_reward_emissions_super_authority", () => { const { configInitInfo, configKeypairs: { rewardEmissionsSuperAuthorityKeypair }, - } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); await assert.rejects( - context.program.rpc.setRewardEmissionsSuperAuthority({ + ctx.program.rpc.setRewardEmissionsSuperAuthority({ accounts: { - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, rewardEmissionsSuperAuthority: rewardEmissionsSuperAuthorityKeypair.publicKey, newRewardEmissionsSuperAuthority: provider.wallet.publicKey, }, @@ -53,17 +57,18 @@ describe("set_reward_emissions_super_authority", () => { }); it("fails if incorrect reward_emissions_super_authority is passed in", async () => { - const { configInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); await assert.rejects( - client - .setRewardEmissionsSuperAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, + toTx( + ctx, + WhirlpoolIx.setRewardEmissionsSuperAuthorityIx(ctx.program, { + whirlpoolsConfig: configInitInfo.whirlpoolsConfigKeypair.publicKey, rewardEmissionsSuperAuthority: provider.wallet.publicKey, newRewardEmissionsSuperAuthority: provider.wallet.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // An address constraint was violated ); }); diff --git a/sdk/tests/swap.ts b/sdk/tests/integration/swap.test.ts similarity index 71% rename from sdk/tests/swap.ts rename to sdk/tests/integration/swap.test.ts index 1212f1b..49dd14d 100644 --- a/sdk/tests/swap.ts +++ b/sdk/tests/integration/swap.test.ts @@ -1,48 +1,53 @@ import * as anchor from "@project-serum/anchor"; +import * as assert from "assert"; import { web3 } from "@project-serum/anchor"; import Decimal from "decimal.js"; +import { u64 } from "@solana/spl-token"; import { - getTickArrayPda, + WhirlpoolContext, + AccountFetcher, SwapParams, - tickIndexToSqrtPriceX64, MAX_SQRT_PRICE, - toX64, MIN_SQRT_PRICE, - getOraclePda, -} from "../src"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; + TickArrayData, + swapQuoteByInputToken, + WhirlpoolData, + PoolUtil, + PriceMath, + WhirlpoolIx, + PDAUtil, +} from "../../src"; +import { TickSpacing, ZERO_BN, getTokenBalance, MAX_U64 } from "../utils"; import { - FundedPositionParams, - fundPositions, - initTestPoolWithLiquidity, initTestPoolWithTokens, initTickArrayRange, initTestPool, + FundedPositionParams, + fundPositions, + initTestPoolWithLiquidity, withdrawPositions, -} from "./utils/init-utils"; -import * as assert from "assert"; -import { getTokenBalance, MAX_U64, TickSpacing, ZERO_BN } from "./utils"; -import { u64 } from "@solana/spl-token"; +} from "../utils/init-utils"; +import { MathUtil, Percentage } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("swap", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("fail on token vault mint a does not match whirlpool token a", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const { poolInitInfo: anotherPoolInitInfo } = await initTestPoolWithTokens( - client, + ctx, TickSpacing.Stable ); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -50,18 +55,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: anotherPoolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -71,22 +77,22 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // ConstraintAddress ); }); it("fail on token vault mint b does not match whirlpool token b", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const { poolInitInfo: anotherPoolInitInfo } = await initTestPoolWithTokens( - client, + ctx, TickSpacing.Stable ); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -94,18 +100,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -115,24 +122,24 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7dc/ // ConstraintAddress ); }); it("fail on token owner account a does not match vault a mint", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountB } = await initTestPoolWithTokens( - client, + ctx, TickSpacing.Standard ); const { tokenAccountA: anotherTokenAccountA } = await initTestPoolWithTokens( - client, + ctx, TickSpacing.Stable ); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -140,18 +147,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: anotherTokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -161,24 +169,24 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fail on token owner account b does not match vault b mint", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA } = await initTestPoolWithTokens( - client, + ctx, TickSpacing.Standard ); const { tokenAccountB: anotherTokenAccountB } = await initTestPoolWithTokens( - client, + ctx, TickSpacing.Stable ); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -186,18 +194,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: anotherTokenAccountB, @@ -207,17 +216,17 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails to swap with incorrect token authority", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -227,14 +236,15 @@ describe("swap", () => { const otherTokenAuthority = web3.Keypair.generate(); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, @@ -248,6 +258,7 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) + ) .addSigner(otherTokenAuthority) .buildAndExecute(), /0x4/ // OwnerMismatch @@ -256,10 +267,14 @@ describe("swap", () => { it("fails on passing in the wrong tick-array", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard, toX64(new Decimal(0.0242).sqrt())); // Negative Tick + await initTestPoolWithTokens( + ctx, + TickSpacing.Standard, + MathUtil.toX64(new Decimal(0.0242).sqrt()) + ); // Negative Tick const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -267,18 +282,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: tickIndexToSqrtPriceX64(-50000), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(-50000), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -288,19 +304,19 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1787/ // InvalidTickArraySequence ); }); it("fails on passing in the wrong whirlpool", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); - const { poolInitInfo: anotherPoolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo: anotherPoolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -308,18 +324,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: anotherPoolInitInfo.whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -329,19 +346,19 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails on passing in the tick-arrays from another whirlpool", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); - const { poolInitInfo: anotherPoolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo: anotherPoolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, anotherPoolInitInfo.whirlpoolPda.publicKey, 22528, 3, @@ -349,18 +366,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: anotherPoolInitInfo.whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -370,17 +388,17 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d3/ // ConstraintRaw ); }); it("fails on passing in an account of another type for the oracle", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -389,15 +407,16 @@ describe("swap", () => { ); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: poolInitInfo.whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -407,19 +426,19 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: tickArrays[0].publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d6/ // ConstraintSeeds ); }); it("fails on passing in an incorrectly hashed oracle PDA", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); - const { poolInitInfo: anotherPoolInitInfo } = await initTestPool(client, TickSpacing.Standard); + const { poolInitInfo: anotherPoolInitInfo } = await initTestPool(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, 3, @@ -427,21 +446,22 @@ describe("swap", () => { false ); - const anotherOraclePda = getOraclePda( - client.context.program.programId, + const anotherOraclePda = PDAUtil.getOracle( + ctx.program.programId, anotherPoolInitInfo.whirlpoolPda.publicKey ); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: poolInitInfo.whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -451,17 +471,17 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: anotherOraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x7d6/ // ConstraintSeeds ); }); it("fail on passing in zero tradable amount", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 33792, 3, @@ -469,18 +489,19 @@ describe("swap", () => { false ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); await assert.rejects( - client - .swapTx({ + toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(0), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -490,73 +511,100 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x1793/ // ZeroTradableAmount ); }); it("swaps across one tick array", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); - + await initTestPoolWithTokens(ctx, TickSpacing.Standard); + const aToB = false; const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, // to 33792 3, TickSpacing.Standard, - false + aToB ); const fundParams: FundedPositionParams[] = [ { - liquidityAmount: new u64(100_000), + liquidityAmount: new u64(10_000_000), tickLowerIndex: 29440, tickUpperIndex: 33536, }, ]; - await fundPositions(client, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); + await fundPositions(ctx, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); - assert.equal( - await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey), - "1302" + const tokenVaultABefore = new anchor.BN( + await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey) ); - assert.equal( - await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey), - "64238" + const tokenVaultBBefore = new anchor.BN( + await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey) ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); - await client - .swapTx({ - amount: new u64(10), - otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), - amountSpecifiedIsInput: true, - aToB: true, + const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey; + const whirlpoolData = (await fetcher.getPool(whirlpoolKey, true)) as WhirlpoolData; + const tickArrayAddresses = PoolUtil.getTickArrayPublicKeysForSwap( + whirlpoolData.tickCurrentIndex, + whirlpoolData.tickSpacing, + aToB, + ctx.program.programId, + whirlpoolPda.publicKey + ); + const tickArraySequenceData = await fetcher.listTickArrays(tickArrayAddresses, true); + + const quote = swapQuoteByInputToken({ + whirlpoolAddress: whirlpoolKey, + swapTokenMint: whirlpoolData.tokenMintB, + whirlpoolData, + tokenAmount: new u64(100000), + amountSpecifiedIsInput: true, + slippageTolerance: Percentage.fromFraction(1, 100), + tickArrayAddresses: tickArrayAddresses, + tickArrays: tickArraySequenceData, + }); + + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { + ...quote, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, tokenVaultB: poolInitInfo.tokenVaultBKeypair.publicKey, - tickArray0: tickArrays[0].publicKey, - tickArray1: tickArrays[0].publicKey, - tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); + + assert.equal( + await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey), + tokenVaultABefore.sub(quote.estimatedAmountOut).toString() + ); + assert.equal( + await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey), + tokenVaultBBefore.add(quote.estimatedAmountIn).toString() + ); }); it("swaps across three tick arrays", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Stable, tickIndexToSqrtPriceX64(27500)); + await initTestPoolWithTokens( + ctx, + TickSpacing.Stable, + PriceMath.tickIndexToSqrtPriceX64(27500) + ); const aToB = false; const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 27456, // to 28160, 28864 5, @@ -582,7 +630,7 @@ describe("swap", () => { }, ]; - await fundPositions(client, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); + await fundPositions(ctx, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); assert.equal( await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey), @@ -593,18 +641,19 @@ describe("swap", () => { "869058" ); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); // Tick - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(7051000), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: tickIndexToSqrtPriceX64(28500), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(28500), amountSpecifiedIsInput: true, aToB: aToB, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -614,7 +663,7 @@ describe("swap", () => { tickArray2: tickArrays[2].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); assert.equal( await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey), @@ -630,24 +679,21 @@ describe("swap", () => { it("Error on passing in uninitialized tick-array", async () => { const { poolInitInfo, tokenAccountA, tokenAccountB, tickArrays } = - await initTestPoolWithLiquidity(client); + await initTestPoolWithLiquidity(ctx); const whirlpool = poolInitInfo.whirlpoolPda.publicKey; - const uninitializedTickArrayPda = getTickArrayPda(context.program.programId, whirlpool, 0); + const uninitializedTickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpool, 0); - const oraclePda = getOraclePda( - client.context.program.programId, - poolInitInfo.whirlpoolPda.publicKey - ); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, poolInitInfo.whirlpoolPda.publicKey); const params: SwapParams = { amount: new u64(10), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4294886578)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4294886578)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpool, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -659,7 +705,7 @@ describe("swap", () => { }; try { - await client.swapTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.swapIx(ctx.program, params)).buildAndExecute(); assert.fail("should fail if a tick-array is uninitialized"); } catch (e) { const error = e as Error; @@ -669,13 +715,10 @@ describe("swap", () => { it("Error if sqrt_price_limit exceeds max", async () => { const { poolInitInfo, tokenAccountA, tokenAccountB, tickArrays } = - await initTestPoolWithLiquidity(client); + await initTestPoolWithLiquidity(ctx); const whirlpool = poolInitInfo.whirlpoolPda.publicKey; - const oraclePda = getOraclePda( - client.context.program.programId, - poolInitInfo.whirlpoolPda.publicKey - ); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, poolInitInfo.whirlpoolPda.publicKey); const params: SwapParams = { amount: new u64(10), @@ -684,7 +727,7 @@ describe("swap", () => { amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpool, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -696,7 +739,7 @@ describe("swap", () => { }; try { - await client.swapTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.swapIx(ctx.program, params)).buildAndExecute(); assert.fail("should fail if sqrt_price exceeds maximum"); } catch (e) { const error = e as Error; @@ -706,13 +749,10 @@ describe("swap", () => { it("Error if sqrt_price_limit subceed min", async () => { const { poolInitInfo, tokenAccountA, tokenAccountB, tickArrays } = - await initTestPoolWithLiquidity(client); + await initTestPoolWithLiquidity(ctx); const whirlpool = poolInitInfo.whirlpoolPda.publicKey; - const oraclePda = getOraclePda( - client.context.program.programId, - poolInitInfo.whirlpoolPda.publicKey - ); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, poolInitInfo.whirlpoolPda.publicKey); const params: SwapParams = { amount: new u64(10), @@ -721,7 +761,7 @@ describe("swap", () => { amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpool, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -733,7 +773,7 @@ describe("swap", () => { }; try { - await client.swapTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.swapIx(ctx.program, params)).buildAndExecute(); assert.fail("should fail if sqrt_price subceeds minimum"); } catch (e) { const error = e as Error; @@ -743,10 +783,10 @@ describe("swap", () => { it("Error if a to b swap below minimum output", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, // to 33792 3, @@ -762,9 +802,9 @@ describe("swap", () => { }, ]; - await fundPositions(client, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); + await fundPositions(ctx, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); const params = { amount: new u64(10), @@ -773,7 +813,7 @@ describe("swap", () => { amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -785,7 +825,7 @@ describe("swap", () => { }; try { - await client.swapTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.swapIx(ctx.program, params)).buildAndExecute(); assert.fail("should fail if amount out is below threshold"); } catch (e) { const error = e as Error; @@ -795,10 +835,10 @@ describe("swap", () => { it("Error if b to a swap below minimum output", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, // to 33792 3, @@ -814,9 +854,9 @@ describe("swap", () => { }, ]; - await fundPositions(client, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); + await fundPositions(ctx, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); const params = { amount: new u64(10), @@ -825,7 +865,7 @@ describe("swap", () => { amountSpecifiedIsInput: true, aToB: false, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -837,7 +877,7 @@ describe("swap", () => { }; try { - await client.swapTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.swapIx(ctx.program, params)).buildAndExecute(); assert.fail("should fail if amount out is below threshold"); } catch (e) { const error = e as Error; @@ -847,10 +887,10 @@ describe("swap", () => { it("Error if a to b swap above maximum input", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, // to 33792 3, @@ -866,9 +906,9 @@ describe("swap", () => { }, ]; - await fundPositions(client, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); + await fundPositions(ctx, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); const params = { amount: new u64(10), @@ -877,7 +917,7 @@ describe("swap", () => { amountSpecifiedIsInput: false, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -889,7 +929,7 @@ describe("swap", () => { }; try { - await client.swapTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.swapIx(ctx.program, params)).buildAndExecute(); assert.fail("should fail if amount out is below threshold"); } catch (e) { const error = e as Error; @@ -899,10 +939,10 @@ describe("swap", () => { it("Error if b to a swap below maximum input", async () => { const { poolInitInfo, whirlpoolPda, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(client, TickSpacing.Standard); + await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, // to 33792 3, @@ -918,9 +958,9 @@ describe("swap", () => { }, ]; - await fundPositions(client, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); + await fundPositions(ctx, poolInitInfo, tokenAccountA, tokenAccountB, fundParams); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); const params = { amount: new u64(10), @@ -929,7 +969,7 @@ describe("swap", () => { amountSpecifiedIsInput: false, aToB: false, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -941,7 +981,7 @@ describe("swap", () => { }; try { - await client.swapTx(params).buildAndExecute(); + await toTx(ctx, WhirlpoolIx.swapIx(ctx.program, params)).buildAndExecute(); assert.fail("should fail if amount out is below threshold"); } catch (e) { const error = e as Error; @@ -957,11 +997,15 @@ describe("swap", () => { whirlpoolPda, tokenAccountA, tokenAccountB, - } = await initTestPoolWithTokens(client, TickSpacing.Stable, tickIndexToSqrtPriceX64(27500)); + } = await initTestPoolWithTokens( + ctx, + TickSpacing.Stable, + PriceMath.tickIndexToSqrtPriceX64(27500) + ); const aToB = false; const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 27456, // to 30528 3, @@ -1019,7 +1063,7 @@ describe("swap", () => { ]; const positionInfos = await fundPositions( - client, + ctx, poolInitInfo, tokenAccountA, tokenAccountB, @@ -1030,33 +1074,35 @@ describe("swap", () => { console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); // Tick - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(829996), otherAmountThreshold: MAX_U64, - sqrtPriceLimit: tickIndexToSqrtPriceX64(29240), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(29240), amountSpecifiedIsInput: false, aToB, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -1066,36 +1112,38 @@ describe("swap", () => { tickArray2: tickArrays[2].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey)); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(14538074), otherAmountThreshold: MAX_U64, - sqrtPriceLimit: tickIndexToSqrtPriceX64(27712), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(27712), amountSpecifiedIsInput: false, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -1105,36 +1153,38 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey)); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(829996), otherAmountThreshold: MAX_U64, - sqrtPriceLimit: tickIndexToSqrtPriceX64(29240), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(29240), amountSpecifiedIsInput: false, aToB, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -1144,36 +1194,38 @@ describe("swap", () => { tickArray2: tickArrays[2].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey)); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(14538074), otherAmountThreshold: MAX_U64, - sqrtPriceLimit: tickIndexToSqrtPriceX64(27712), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(27712), amountSpecifiedIsInput: false, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -1183,36 +1235,38 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey)); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(829996), otherAmountThreshold: MAX_U64, - sqrtPriceLimit: tickIndexToSqrtPriceX64(29240), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(29240), amountSpecifiedIsInput: false, aToB, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -1222,36 +1276,38 @@ describe("swap", () => { tickArray2: tickArrays[2].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey)); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(14538074), otherAmountThreshold: MAX_U64, - sqrtPriceLimit: tickIndexToSqrtPriceX64(27712), + sqrtPriceLimit: PriceMath.tickIndexToSqrtPriceX64(27712), amountSpecifiedIsInput: false, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -1261,58 +1317,62 @@ describe("swap", () => { tickArray2: tickArrays[0].publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey)); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - await withdrawPositions(client, positionInfos, tokenAccountA, tokenAccountB); + await withdrawPositions(ctx, positionInfos, tokenAccountA, tokenAccountB); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultAKeypair.publicKey)); console.log(await getTokenBalance(provider, poolInitInfo.tokenVaultBKeypair.publicKey)); ( - await Promise.all(tickArrays.map((tickArray) => client.getTickArray(tickArray.publicKey))) + await Promise.all(tickArrays.map((tickArray) => fetcher.getTickArray(tickArray.publicKey))) ).map((tickArray) => { - tickArray.ticks.forEach((tick, index) => { + const ta = tickArray as TickArrayData; + ta.ticks.forEach((tick, index) => { if (!tick.initialized) { return; } console.log( - tickArray.startTickIndex + index * TickSpacing.Stable, + ta.startTickIndex + index * TickSpacing.Stable, tick.feeGrowthOutsideA.toString(), tick.feeGrowthOutsideB.toString() ); }); }); - await client - .collectProtocolFeesTx({ - whirlpoolsConfig: poolInitInfo.whirlpoolConfigKey, + await toTx( + ctx, + WhirlpoolIx.collectProtocolFeesIx(ctx.program, { + whirlpoolsConfig: poolInitInfo.whirlpoolsConfig, whirlpool: poolInitInfo.whirlpoolPda.publicKey, collectProtocolFeesAuthority: configKeypairs.collectProtocolFeesAuthorityKeypair.publicKey, tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey, tokenVaultB: poolInitInfo.tokenVaultBKeypair.publicKey, - tokenDestinationA: tokenAccountA, - tokenDestinationB: tokenAccountB, + tokenOwnerAccountA: tokenAccountA, + tokenOwnerAccountB: tokenAccountB, }) + ) .addSigner(configKeypairs.collectProtocolFeesAuthorityKeypair) .buildAndExecute(); diff --git a/sdk/tests/update_fees_and_rewards.ts b/sdk/tests/integration/update_fees_and_rewards.test.ts similarity index 68% rename from sdk/tests/update_fees_and_rewards.ts rename to sdk/tests/integration/update_fees_and_rewards.test.ts index 5572746..68b6cf2 100644 --- a/sdk/tests/update_fees_and_rewards.ts +++ b/sdk/tests/integration/update_fees_and_rewards.test.ts @@ -2,19 +2,19 @@ import * as anchor from "@project-serum/anchor"; import * as assert from "assert"; import { u64 } from "@solana/spl-token"; import Decimal from "decimal.js"; -import { getOraclePda, getTickArrayPda, toX64 } from "../src"; -import { WhirlpoolClient } from "../src/client"; -import { WhirlpoolContext } from "../src/context"; -import { sleep, TickSpacing, ZERO_BN } from "./utils"; -import { WhirlpoolTestFixture } from "./utils/fixture"; -import { initTestPool } from "./utils/init-utils"; +import { WhirlpoolContext, AccountFetcher, PositionData, WhirlpoolIx, PDAUtil } from "../../src"; +import { TickSpacing, ZERO_BN, sleep } from "../utils"; +import { WhirlpoolTestFixture } from "../utils/fixture"; +import { initTestPool } from "../utils/init-utils"; +import { MathUtil } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; describe("update_fees_and_rewards", () => { const provider = anchor.Provider.local(); anchor.setProvider(anchor.Provider.env()); const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); it("successfully updates fees and rewards", async () => { // In same tick array - start index 22528 @@ -22,10 +22,12 @@ describe("update_fees_and_rewards", () => { const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(1_000_000) }], - rewards: [{ emissionsPerSecondX64: toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }], + rewards: [ + { emissionsPerSecondX64: MathUtil.toX64(new Decimal(2)), vaultAmount: new u64(1_000_000) }, + ], }); const { poolInitInfo: { whirlpoolPda, tokenVaultAKeypair, tokenVaultBKeypair }, @@ -34,25 +36,29 @@ describe("update_fees_and_rewards", () => { positions, } = fixture.getInfos(); - const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528); + const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528); - const positionBefore = await client.getPosition(positions[0].publicKey); + const positionBefore = (await fetcher.getPosition( + positions[0].publicKey, + true + )) as PositionData; assert.ok(positionBefore.feeGrowthCheckpointA.eq(ZERO_BN)); assert.ok(positionBefore.feeGrowthCheckpointB.eq(ZERO_BN)); assert.ok(positionBefore.rewardInfos[0].amountOwed.eq(ZERO_BN)); assert.ok(positionBefore.rewardInfos[0].growthInsideCheckpoint.eq(ZERO_BN)); - const oraclePda = getOraclePda(client.context.program.programId, whirlpoolPda.publicKey); + const oraclePda = PDAUtil.getOracle(ctx.program.programId, whirlpoolPda.publicKey); - await client - .swapTx({ + await toTx( + ctx, + WhirlpoolIx.swapIx(ctx.program, { amount: new u64(100_000), otherAmountThreshold: ZERO_BN, - sqrtPriceLimit: toX64(new Decimal(4.95)), + sqrtPriceLimit: MathUtil.toX64(new Decimal(4.95)), amountSpecifiedIsInput: true, aToB: true, whirlpool: whirlpoolPda.publicKey, - tokenAuthority: context.wallet.publicKey, + tokenAuthority: ctx.wallet.publicKey, tokenOwnerAccountA: tokenAccountA, tokenVaultA: tokenVaultAKeypair.publicKey, tokenOwnerAccountB: tokenAccountB, @@ -62,19 +68,20 @@ describe("update_fees_and_rewards", () => { tickArray2: tickArrayPda.publicKey, oracle: oraclePda.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); await sleep(1_000); - await client - .updateFeesAndRewards({ + await toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(); - const positionAfter = await client.getPosition(positions[0].publicKey); + ).buildAndExecute(); + const positionAfter = (await fetcher.getPosition(positions[0].publicKey, true)) as PositionData; assert.ok(positionAfter.feeOwedA.gt(positionBefore.feeOwedA)); assert.ok(positionAfter.feeOwedB.eq(ZERO_BN)); assert.ok(positionAfter.feeGrowthCheckpointA.gt(positionBefore.feeGrowthCheckpointA)); @@ -94,7 +101,7 @@ describe("update_fees_and_rewards", () => { const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }], }); @@ -103,17 +110,18 @@ describe("update_fees_and_rewards", () => { positions, } = fixture.getInfos(); - const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528); + const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528); await assert.rejects( - client - .updateFeesAndRewards({ + toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0x177c/ // LiquidityZero ); }); @@ -125,24 +133,25 @@ describe("update_fees_and_rewards", () => { const tickSpacing = TickSpacing.Standard; const { poolInitInfo: { whirlpoolPda }, - } = await initTestPool(client, tickSpacing); - const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 22528); + } = await initTestPool(ctx, tickSpacing); + const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 22528); - const other = await new WhirlpoolTestFixture(client).init({ + const other = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new u64(1_000_000) }], }); const { positions: otherPositions } = other.getInfos(); await assert.rejects( - client - .updateFeesAndRewards({ + toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: otherPositions[0].publicKey, tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0xbbf/ // AccountOwnedByWrongProgram ); }); @@ -153,7 +162,7 @@ describe("update_fees_and_rewards", () => { const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: new anchor.BN(1_000_000) }], }); @@ -162,17 +171,18 @@ describe("update_fees_and_rewards", () => { positions, } = fixture.getInfos(); - const tickArrayPda = getTickArrayPda(context.program.programId, whirlpoolPda.publicKey, 0); + const tickArrayPda = PDAUtil.getTickArray(ctx.program.programId, whirlpoolPda.publicKey, 0); await assert.rejects( - client - .updateFeesAndRewards({ + toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0xbbf/ // AccountOwnedByWrongProgram ); }); @@ -183,7 +193,7 @@ describe("update_fees_and_rewards", () => { const tickUpperIndex = 33536; const tickSpacing = TickSpacing.Standard; - const fixture = await new WhirlpoolTestFixture(client).init({ + const fixture = await new WhirlpoolTestFixture(ctx).init({ tickSpacing, positions: [{ tickLowerIndex, tickUpperIndex, liquidityAmount: ZERO_BN }], }); @@ -194,23 +204,24 @@ describe("update_fees_and_rewards", () => { const { poolInitInfo: { whirlpoolPda: otherWhirlpoolPda }, - } = await initTestPool(client, tickSpacing); + } = await initTestPool(ctx, tickSpacing); - const tickArrayPda = getTickArrayPda( - context.program.programId, + const tickArrayPda = PDAUtil.getTickArray( + ctx.program.programId, otherWhirlpoolPda.publicKey, 22528 ); await assert.rejects( - client - .updateFeesAndRewards({ + toTx( + ctx, + WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { whirlpool: whirlpoolPda.publicKey, position: positions[0].publicKey, tickArrayLower: tickArrayPda.publicKey, tickArrayUpper: tickArrayPda.publicKey, }) - .buildAndExecute(), + ).buildAndExecute(), /0xbbf/ // AccountOwnedByWrongProgram ); }); diff --git a/sdk/tests/sdk/whirlpools/position-impl.test.ts b/sdk/tests/sdk/whirlpools/position-impl.test.ts new file mode 100644 index 0000000..d1c78e2 --- /dev/null +++ b/sdk/tests/sdk/whirlpools/position-impl.test.ts @@ -0,0 +1,110 @@ +import * as anchor from "@project-serum/anchor"; +import * as assert from "assert"; +import { WhirlpoolContext } from "../../../src/context"; +import { initTestPool } from "../../utils/init-utils"; +import { TickSpacing } from "../../utils"; +import { + AccountFetcher, + buildWhirlpoolClient, + decreaseLiquidityQuoteByLiquidity, + increaseLiquidityQuoteByInputToken, + PriceMath, +} from "../../../src"; +import Decimal from "decimal.js"; +import { Percentage } from "@orca-so/common-sdk"; +import { initPosition, mintTokensToTestAccount } from "../../utils/test-builders"; + +describe("position-impl", () => { + const provider = anchor.Provider.local(); + anchor.setProvider(anchor.Provider.env()); + const program = anchor.workspace.Whirlpool; + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); + const client = buildWhirlpoolClient(ctx, fetcher); + + it("increase and decrease liquidity on position", async () => { + const { poolInitInfo } = await initTestPool( + ctx, + TickSpacing.Standard, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6) + ); + + // Create and mint tokens in this wallet + await mintTokensToTestAccount( + ctx.provider, + poolInitInfo.tokenMintA, + 10_500_000_000, + poolInitInfo.tokenMintB, + 10_500_000_000 + ); + + const pool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + const lowerTick = PriceMath.priceToTickIndex( + new Decimal(89), + pool.getTokenAInfo().decimals, + pool.getTokenBInfo().decimals + ); + const upperTick = PriceMath.priceToTickIndex( + new Decimal(120), + pool.getTokenAInfo().decimals, + pool.getTokenBInfo().decimals + ); + + // [Action] Initialize Tick Arrays + const initTickArrayTx = await pool.initTickArrayForTicks([lowerTick, upperTick]); + await initTickArrayTx.buildAndExecute(); + + // [Action] Create a position at price 89, 120 with 50 token A + const lowerPrice = new Decimal(89); + const upperPrice = new Decimal(120); + const { positionAddress } = await initPosition( + ctx, + pool, + lowerPrice, + upperPrice, + poolInitInfo.tokenMintA, + 50 + ); + + // [Action] Increase liquidity by 70 tokens of tokenB + const position = await client.getPosition(positionAddress.publicKey); + const preIncreaseData = position.getData(); + const increase_quote = increaseLiquidityQuoteByInputToken( + poolInitInfo.tokenMintB, + new Decimal(70), + lowerTick, + upperTick, + Percentage.fromFraction(1, 100), + pool + ); + + await ( + await position.increaseLiquidity(increase_quote, ctx.wallet.publicKey, ctx.wallet.publicKey) + ).buildAndExecute(); + + const postIncreaseData = await position.refreshData(); + const expectedPostIncreaseLiquidity = preIncreaseData.liquidity.add( + increase_quote.liquidityAmount + ); + assert.equal(postIncreaseData.liquidity.toString(), expectedPostIncreaseLiquidity.toString()); + + // [Action] Withdraw half of the liquidity away from the position and verify + const withdrawHalf = postIncreaseData.liquidity.div(new anchor.BN(2)); + const decrease_quote = await decreaseLiquidityQuoteByLiquidity( + withdrawHalf, + Percentage.fromFraction(1, 100), + position, + pool + ); + + await ( + await position.decreaseLiquidity(decrease_quote, ctx.wallet.publicKey, ctx.wallet.publicKey) + ).buildAndExecute(); + + const postWithdrawData = await position.refreshData(); + const expectedPostWithdrawLiquidity = postIncreaseData.liquidity.sub( + decrease_quote.liquidityAmount + ); + assert.equal(postWithdrawData.liquidity.toString(), expectedPostWithdrawLiquidity.toString()); + }); +}); diff --git a/sdk/tests/sdk/whirlpools/whirlpool-impl.test.ts b/sdk/tests/sdk/whirlpools/whirlpool-impl.test.ts new file mode 100644 index 0000000..1003096 --- /dev/null +++ b/sdk/tests/sdk/whirlpools/whirlpool-impl.test.ts @@ -0,0 +1,130 @@ +import * as assert from "assert"; +import * as anchor from "@project-serum/anchor"; +import { WhirlpoolContext } from "../../../src/context"; +import { initTestPool } from "../../utils/init-utils"; +import { getTokenBalance, ONE_SOL, systemTransferTx, TickSpacing } from "../../utils"; +import { + AccountFetcher, + buildWhirlpoolClient, + increaseLiquidityQuoteByInputToken, + PDAUtil, + PriceMath, + TickUtil, +} from "../../../src"; +import Decimal from "decimal.js"; +import { Percentage } from "@orca-so/common-sdk"; +import { mintTokensToTestAccount } from "../../utils/test-builders"; + +describe("whirlpool-impl", () => { + const provider = anchor.Provider.local(); + anchor.setProvider(anchor.Provider.env()); + const program = anchor.workspace.Whirlpool; + const ctx = WhirlpoolContext.fromWorkspace(provider, program); + const fetcher = new AccountFetcher(ctx.connection); + const client = buildWhirlpoolClient(ctx, fetcher); + + it("open and add liquidity to a position, then close", async () => { + const funderKeypair = anchor.web3.Keypair.generate(); + await systemTransferTx(provider, funderKeypair.publicKey, ONE_SOL).buildAndExecute(); + + const { poolInitInfo } = await initTestPool( + ctx, + TickSpacing.Standard, + PriceMath.priceToSqrtPriceX64(new Decimal(100), 6, 6) + ); + const pool = await client.getPool(poolInitInfo.whirlpoolPda.publicKey); + + // Verify token mint info is correct + const tokenAInfo = pool.getTokenAInfo(); + const tokenBInfo = pool.getTokenBInfo(); + assert.ok(tokenAInfo.mint.equals(poolInitInfo.tokenMintA)); + assert.ok(tokenBInfo.mint.equals(poolInitInfo.tokenMintB)); + + // Create and mint tokens in this wallet + const mintedTokenAmount = 150_000_000; + const [userTokenAAccount, userTokenBAccount] = await mintTokensToTestAccount( + ctx.provider, + tokenAInfo.mint, + mintedTokenAmount, + tokenBInfo.mint, + mintedTokenAmount + ); + + // Open a position with no tick arrays initialized. + const lowerPrice = new Decimal(96); + const upperPrice = new Decimal(101); + const poolData = pool.getData(); + const tokenADecimal = tokenAInfo.decimals; + const tokenBDecimal = tokenBInfo.decimals; + + const tickLower = TickUtil.getInitializableTickIndex( + PriceMath.priceToTickIndex(lowerPrice, tokenADecimal, tokenBDecimal), + poolData.tickSpacing + ); + const tickUpper = TickUtil.getInitializableTickIndex( + PriceMath.priceToTickIndex(upperPrice, tokenADecimal, tokenBDecimal), + poolData.tickSpacing + ); + + const inputTokenMint = poolData.tokenMintA; + const quote = increaseLiquidityQuoteByInputToken( + inputTokenMint, + new Decimal(50), + tickLower, + tickUpper, + Percentage.fromFraction(1, 100), + pool + ); + + // [Action] Initialize Tick Arrays + const initTickArrayTx = await pool.initTickArrayForTicks( + [tickLower, tickUpper], + funderKeypair.publicKey + ); + await initTickArrayTx.addSigner(funderKeypair).buildAndExecute(); + + // [Action] Open Position (and increase L) + const { positionMint, tx } = await pool.openPosition( + tickLower, + tickUpper, + quote, + ctx.wallet.publicKey, + ctx.wallet.publicKey, + funderKeypair.publicKey + ); + + await tx.addSigner(funderKeypair).buildAndExecute(); + + // Verify position exists and numbers fit input parameters + const positionAddress = PDAUtil.getPosition(ctx.program.programId, positionMint).publicKey; + const position = await client.getPosition(positionAddress); + const positionData = position.getData(); + + const tickLowerIndex = TickUtil.getInitializableTickIndex( + PriceMath.priceToTickIndex(lowerPrice, tokenAInfo.decimals, tokenBInfo.decimals), + poolData.tickSpacing + ); + const tickUpperIndex = TickUtil.getInitializableTickIndex( + PriceMath.priceToTickIndex(upperPrice, tokenAInfo.decimals, tokenBInfo.decimals), + poolData.tickSpacing + ); + assert.ok(positionData.liquidity.eq(quote.liquidityAmount)); + assert.ok(positionData.tickLowerIndex === tickLowerIndex); + assert.ok(positionData.tickUpperIndex === tickUpperIndex); + assert.ok(positionData.positionMint.equals(positionMint)); + assert.ok(positionData.whirlpool.equals(poolInitInfo.whirlpoolPda.publicKey)); + + // [Action] Close Position + await ( + await pool.closePosition(positionAddress, Percentage.fromFraction(1, 100)) + ).buildAndExecute(); + + // Verify position is closed and owner wallet has the tokens back + const postClosePosition = await fetcher.getPosition(positionAddress, true); + assert.ok(postClosePosition === null); + + // TODO: we are leaking 1 decimal place of token? + assert.equal(await getTokenBalance(ctx.provider, userTokenAAccount), mintedTokenAmount - 1); + assert.equal(await getTokenBalance(ctx.provider, userTokenBAccount), mintedTokenAmount - 1); + }); +}); diff --git a/sdk/tests/set_fee_authority.ts b/sdk/tests/set_fee_authority.ts deleted file mode 100644 index 33bcb99..0000000 --- a/sdk/tests/set_fee_authority.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as assert from "assert"; -import * as anchor from "@project-serum/anchor"; -import { WhirlpoolContext } from "../src/context"; -import { WhirlpoolClient } from "../src/client"; -import { generateDefaultConfigParams } from "./utils/test-builders"; - -describe("set_fee_authority", () => { - const provider = anchor.Provider.local(); - anchor.setProvider(anchor.Provider.env()); - const program = anchor.workspace.Whirlpool; - const context = WhirlpoolContext.fromWorkspace(provider, program); - const client = new WhirlpoolClient(context); - - it("successfully set_fee_authority", async () => { - const { - configInitInfo, - configKeypairs: { feeAuthorityKeypair }, - } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); - const newAuthorityKeypair = anchor.web3.Keypair.generate(); - await client - .setFeeAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, - feeAuthority: feeAuthorityKeypair.publicKey, - newFeeAuthority: newAuthorityKeypair.publicKey, - }) - .addSigner(feeAuthorityKeypair) - .buildAndExecute(); - const config = await client.getConfig(configInitInfo.whirlpoolConfigKeypair.publicKey); - assert.ok(config.feeAuthority.equals(newAuthorityKeypair.publicKey)); - }); - - it("fails if current fee_authority is not a signer", async () => { - const { - configInitInfo, - configKeypairs: { feeAuthorityKeypair }, - } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); - - await assert.rejects( - client - .setFeeAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, - feeAuthority: feeAuthorityKeypair.publicKey, - newFeeAuthority: provider.wallet.publicKey, - }) - .buildAndExecute(), - /Signature verification failed/ - ); - }); - - it("fails if invalid fee_authority provided", async () => { - const { configInitInfo } = generateDefaultConfigParams(context); - await client.initConfigTx(configInitInfo).buildAndExecute(); - - await assert.rejects( - client - .setFeeAuthorityTx({ - whirlpoolsConfig: configInitInfo.whirlpoolConfigKeypair.publicKey, - feeAuthority: provider.wallet.publicKey, - newFeeAuthority: provider.wallet.publicKey, - }) - .buildAndExecute(), - /0x7dc/ // An address constraint was violated - ); - }); -}); diff --git a/sdk/tests/utils/assert.ts b/sdk/tests/utils/assert.ts index f93db87..fe1fec4 100644 --- a/sdk/tests/utils/assert.ts +++ b/sdk/tests/utils/assert.ts @@ -3,7 +3,7 @@ import { Program, web3, Coder, BN } from "@project-serum/anchor"; import { AccountLayout } from "@solana/spl-token"; import { TEST_TOKEN_PROGRAM_ID } from "./test-consts"; import { Whirlpool } from "../../src/artifacts/whirlpool"; -import { TickData } from "../../src/types/anchor-types"; +import { TickData } from "../../src/types/public"; // Helper for token vault assertion checks. export async function asyncAssertTokenVault( diff --git a/sdk/tests/utils/fixture.ts b/sdk/tests/utils/fixture.ts index 8d3bf20..c123089 100644 --- a/sdk/tests/utils/fixture.ts +++ b/sdk/tests/utils/fixture.ts @@ -1,6 +1,6 @@ import { PublicKey, Keypair } from "@solana/web3.js"; import { u64 } from "@solana/spl-token"; -import { getStartTickIndex, InitPoolParams, WhirlpoolClient } from "../../src"; +import { InitConfigParams, InitPoolParams, TickUtil, WhirlpoolContext } from "../../src"; import { FundedPositionInfo, FundedPositionParams, @@ -30,9 +30,9 @@ interface InitializedRewardInfo { } export class WhirlpoolTestFixture { - private client: WhirlpoolClient; - private poolInitInfo = defaultPoolInitInfo; - private configInitInfo = defaultConfigInitInfo; + private ctx: WhirlpoolContext; + private poolInitInfo: InitPoolParams = defaultPoolInitInfo; + private configInitInfo: InitConfigParams = defaultConfigInitInfo; private configKeypairs = defaultConfigKeypairs; private positions: FundedPositionInfo[] = []; private rewards: InitializedRewardInfo[] = []; @@ -40,15 +40,15 @@ export class WhirlpoolTestFixture { private tokenAccountB = PublicKey.default; private initialized = false; - constructor(client: WhirlpoolClient) { - this.client = client; + constructor(ctx: WhirlpoolContext) { + this.ctx = ctx; } async init(params: InitFixtureParams): Promise { const { tickSpacing, initialSqrtPrice, positions, rewards } = params; const { poolInitInfo, configInitInfo, configKeypairs, tokenAccountA, tokenAccountB } = - await initTestPoolWithTokens(this.client, tickSpacing, initialSqrtPrice); + await initTestPoolWithTokens(this.ctx, tickSpacing, initialSqrtPrice); this.poolInitInfo = poolInitInfo; this.configInitInfo = configInitInfo; @@ -57,10 +57,10 @@ export class WhirlpoolTestFixture { this.tokenAccountB = tokenAccountB; if (positions) { - await initTickArrays(this.client, poolInitInfo, positions); + await initTickArrays(this.ctx, poolInitInfo, positions); this.positions = await fundPositions( - this.client, + this.ctx, poolInitInfo, tokenAccountA, tokenAccountB, @@ -74,7 +74,7 @@ export class WhirlpoolTestFixture { // Iterate because we enforce sequential initialization on the smart contract initRewards.push( await initRewardAndSetEmissions( - this.client, + this.ctx, configKeypairs.rewardEmissionsSuperAuthorityKeypair, poolInitInfo.whirlpoolPda.publicKey, i, @@ -106,37 +106,38 @@ export class WhirlpoolTestFixture { } async function initTickArrays( - client: WhirlpoolClient, + ctx: WhirlpoolContext, poolInitInfo: InitPoolParams, positions: FundedPositionParams[] ) { const startTickSet = new Set(); positions.forEach((p) => { - startTickSet.add(getStartTickIndex(p.tickLowerIndex, poolInitInfo.tickSpacing)); - startTickSet.add(getStartTickIndex(p.tickUpperIndex, poolInitInfo.tickSpacing)); + startTickSet.add(TickUtil.getStartTickIndex(p.tickLowerIndex, poolInitInfo.tickSpacing)); + startTickSet.add(TickUtil.getStartTickIndex(p.tickUpperIndex, poolInitInfo.tickSpacing)); }); return Promise.all( Array.from(startTickSet).map((startTick) => - initTickArray(client, poolInitInfo.whirlpoolPda.publicKey, startTick) + initTickArray(ctx, poolInitInfo.whirlpoolPda.publicKey, startTick) ) ); } -const defaultPoolInitInfo = { +const defaultPoolInitInfo: InitPoolParams = { initSqrtPrice: ZERO_BN, - whirlpoolConfigKey: PublicKey.default, + whirlpoolsConfig: PublicKey.default, tokenMintA: PublicKey.default, tokenMintB: PublicKey.default, whirlpoolPda: { publicKey: PublicKey.default, bump: 0 }, tokenVaultAKeypair: Keypair.generate(), tokenVaultBKeypair: Keypair.generate(), tickSpacing: TickSpacing.Standard, + feeTierKey: PublicKey.default, funder: PublicKey.default, }; const defaultConfigInitInfo = { - whirlpoolConfigKeypair: Keypair.generate(), + whirlpoolsConfigKeypair: Keypair.generate(), feeAuthority: PublicKey.default, collectProtocolFeesAuthority: PublicKey.default, rewardEmissionsSuperAuthority: PublicKey.default, diff --git a/sdk/tests/utils/init-utils.ts b/sdk/tests/utils/init-utils.ts index a82e517..1c811a3 100644 --- a/sdk/tests/utils/init-utils.ts +++ b/sdk/tests/utils/init-utils.ts @@ -5,14 +5,14 @@ import { InitPoolParams, InitializeRewardParams, TICK_ARRAY_SIZE, - tickIndexToSqrtPriceX64, - getTokenAmountsFromLiquidity, - toX64_BN, - getTickArrayPda, - getStartTickIndex, + WhirlpoolContext, + AccountFetcher, InitConfigParams, + TickUtil, + PriceMath, + WhirlpoolIx, + PDAUtil, } from "../../src"; -import { WhirlpoolClient } from "../../src/client"; import { generateDefaultConfigParams, generateDefaultInitFeeTierParams, @@ -29,37 +29,39 @@ import { ZERO_BN, } from "."; import { u64 } from "@solana/spl-token"; -import { PDA } from "../../src/types/public/helper-types"; +import { PoolUtil } from "../../src/utils/public/pool-utils"; +import { MathUtil, PDA } from "@orca-so/common-sdk"; +import { toTx } from "../../src/utils/instructions-util"; -const defaultInitSqrtPrice = toX64_BN(new u64(5)); +const defaultInitSqrtPrice = MathUtil.toX64_BN(new u64(5)); /** - * Initialize a brand new WhirlpoolConfig account and construct a set of InitPoolParams + * Initialize a brand new WhirlpoolsConfig account and construct a set of InitPoolParams * that can be used to initialize a pool with. * @param client - an instance of whirlpool client containing the program & provider * @param initSqrtPrice - the initial sqrt-price for this newly generated pool * @returns An object containing the params used to init the config account & the param that can be used to init the pool account. */ export async function buildTestPoolParams( - client: WhirlpoolClient, + ctx: WhirlpoolContext, tickSpacing: number, defaultFeeRate = 3000, initSqrtPrice = defaultInitSqrtPrice, funder?: PublicKey ) { - const { configInitInfo, configKeypairs } = generateDefaultConfigParams(client.context); - await client.initConfigTx(configInitInfo).buildAndExecute(); + const { configInitInfo, configKeypairs } = generateDefaultConfigParams(ctx); + await toTx(ctx, WhirlpoolIx.initializeConfigIx(ctx.program, configInitInfo)).buildAndExecute(); const { params: feeTierParams } = await initFeeTier( - client, + ctx, configInitInfo, configKeypairs.feeAuthorityKeypair, tickSpacing, defaultFeeRate ); const poolInitInfo = await generateDefaultInitPoolParams( - client.context, - configInitInfo.whirlpoolConfigKeypair.publicKey, + ctx, + configInitInfo.whirlpoolsConfigKeypair.publicKey, feeTierParams.feeTierPda.publicKey, tickSpacing, initSqrtPrice, @@ -74,26 +76,26 @@ export async function buildTestPoolParams( } /** - * Initialize a brand new set of WhirlpoolConfig & Whirlpool account + * Initialize a brand new set of WhirlpoolsConfig & Whirlpool account * @param client - an instance of whirlpool client containing the program & provider * @param initSqrtPrice - the initial sqrt-price for this newly generated pool * @returns An object containing the params used to initialize both accounts. */ export async function initTestPool( - client: WhirlpoolClient, + ctx: WhirlpoolContext, tickSpacing: number, initSqrtPrice = defaultInitSqrtPrice, funder?: Keypair ) { const { configInitInfo, poolInitInfo, configKeypairs, feeTierParams } = await buildTestPoolParams( - client, + ctx, tickSpacing, 3000, initSqrtPrice, funder?.publicKey ); - const tx = client.initPoolTx(poolInitInfo); + const tx = toTx(ctx, WhirlpoolIx.initializePoolIx(ctx.program, poolInitInfo)); if (funder) { tx.addSigner(funder); } @@ -108,7 +110,7 @@ export async function initTestPool( } export async function initFeeTier( - client: WhirlpoolClient, + ctx: WhirlpoolContext, configInitInfo: InitConfigParams, feeAuthorityKeypair: Keypair, tickSpacing: number, @@ -116,15 +118,17 @@ export async function initFeeTier( funder?: Keypair ) { const params = generateDefaultInitFeeTierParams( - client.context, - configInitInfo.whirlpoolConfigKeypair.publicKey, + ctx, + configInitInfo.whirlpoolsConfigKeypair.publicKey, configInitInfo.feeAuthority, tickSpacing, defaultFeeRate, funder?.publicKey ); - const tx = client.initFeeTierTx(params).addSigner(feeAuthorityKeypair); + const tx = toTx(ctx, WhirlpoolIx.initializeFeeTierIx(ctx.program, params)).addSigner( + feeAuthorityKeypair + ); if (funder) { tx.addSigner(funder); } @@ -136,26 +140,28 @@ export async function initFeeTier( } export async function initializeReward( - client: WhirlpoolClient, + ctx: WhirlpoolContext, rewardAuthorityKeypair: anchor.web3.Keypair, whirlpool: PublicKey, rewardIndex: number, funder?: Keypair ): Promise<{ txId: string; params: InitializeRewardParams }> { - const provider = client.context.provider; + const provider = ctx.provider; const rewardMint = await createMint(provider); const rewardVaultKeypair = anchor.web3.Keypair.generate(); const params = { rewardAuthority: rewardAuthorityKeypair.publicKey, - funder: funder?.publicKey || client.context.wallet.publicKey, + funder: funder?.publicKey || ctx.wallet.publicKey, whirlpool, rewardMint, rewardVaultKeypair, rewardIndex, }; - const tx = client.initializeRewardTx(params).addSigner(rewardAuthorityKeypair); + const tx = toTx(ctx, WhirlpoolIx.initializeRewardIx(ctx.program, params)).addSigner( + rewardAuthorityKeypair + ); if (funder) { tx.addSigner(funder); } @@ -167,7 +173,7 @@ export async function initializeReward( } export async function initRewardAndSetEmissions( - client: WhirlpoolClient, + ctx: WhirlpoolContext, rewardAuthorityKeypair: anchor.web3.Keypair, whirlpool: PublicKey, rewardIndex: number, @@ -177,36 +183,33 @@ export async function initRewardAndSetEmissions( ) { const { params: { rewardMint, rewardVaultKeypair }, - } = await initializeReward(client, rewardAuthorityKeypair, whirlpool, rewardIndex, funder); - await mintToByAuthority( - client.context.provider, - rewardMint, - rewardVaultKeypair.publicKey, - vaultAmount - ); - await client - .setRewardEmissionsTx({ + } = await initializeReward(ctx, rewardAuthorityKeypair, whirlpool, rewardIndex, funder); + await mintToByAuthority(ctx.provider, rewardMint, rewardVaultKeypair.publicKey, vaultAmount); + await toTx( + ctx, + WhirlpoolIx.setRewardEmissionsIx(ctx.program, { rewardAuthority: rewardAuthorityKeypair.publicKey, whirlpool, rewardIndex, - rewardVault: rewardVaultKeypair.publicKey, + rewardVaultKey: rewardVaultKeypair.publicKey, emissionsPerSecondX64, }) + ) .addSigner(rewardAuthorityKeypair) .buildAndExecute(); return { rewardMint, rewardVaultKeypair }; } export async function openPosition( - client: WhirlpoolClient, + ctx: WhirlpoolContext, whirlpool: PublicKey, tickLowerIndex: number, tickUpperIndex: number, - owner: PublicKey = client.context.provider.wallet.publicKey, + owner: PublicKey = ctx.provider.wallet.publicKey, funder?: Keypair ) { return openPositionWithOptMetadata( - client, + ctx, whirlpool, tickLowerIndex, tickUpperIndex, @@ -217,15 +220,15 @@ export async function openPosition( } export async function openPositionWithMetadata( - client: WhirlpoolClient, + ctx: WhirlpoolContext, whirlpool: PublicKey, tickLowerIndex: number, tickUpperIndex: number, - owner: PublicKey = client.context.provider.wallet.publicKey, + owner: PublicKey = ctx.provider.wallet.publicKey, funder?: Keypair ) { return openPositionWithOptMetadata( - client, + ctx, whirlpool, tickLowerIndex, tickUpperIndex, @@ -236,23 +239,25 @@ export async function openPositionWithMetadata( } async function openPositionWithOptMetadata( - client: WhirlpoolClient, + ctx: WhirlpoolContext, whirlpool: PublicKey, tickLowerIndex: number, tickUpperIndex: number, withMetadata: boolean = false, - owner: PublicKey = client.context.provider.wallet.publicKey, + owner: PublicKey = ctx.provider.wallet.publicKey, funder?: Keypair ) { const { params, mint } = await generateDefaultOpenPositionParams( - client.context, + ctx, whirlpool, tickLowerIndex, tickUpperIndex, owner, - funder?.publicKey || client.context.provider.wallet.publicKey + funder?.publicKey || ctx.provider.wallet.publicKey ); - let tx = withMetadata ? client.openPositionWithMetadataTx(params) : client.openPositionTx(params); + let tx = withMetadata + ? toTx(ctx, WhirlpoolIx.openPositionWithMetadataIx(ctx.program, params)) + : toTx(ctx, WhirlpoolIx.openPositionIx(ctx.program, params)); tx.addSigner(mint); if (funder) { tx.addSigner(funder); @@ -262,18 +267,18 @@ async function openPositionWithOptMetadata( } export async function initTickArray( - client: WhirlpoolClient, + ctx: WhirlpoolContext, whirlpool: PublicKey, startTickIndex: number, funder?: Keypair ): Promise<{ txId: string; params: InitTickArrayParams }> { const params = generateDefaultInitTickArrayParams( - client.context, + ctx, whirlpool, startTickIndex, funder?.publicKey ); - const tx = client.initTickArrayTx(params); + const tx = toTx(ctx, WhirlpoolIx.initTickArrayIx(ctx.program, params)); if (funder) { tx.addSigner(funder); } @@ -281,14 +286,14 @@ export async function initTickArray( } export async function initTestPoolWithTokens( - client: WhirlpoolClient, + ctx: WhirlpoolContext, tickSpacing: number, initSqrtPrice = defaultInitSqrtPrice ) { - const provider = client.context.provider; + const provider = ctx.provider; const { poolInitInfo, configInitInfo, configKeypairs } = await initTestPool( - client, + ctx, tickSpacing, initSqrtPrice ); @@ -307,7 +312,7 @@ export async function initTestPoolWithTokens( } export async function initTickArrayRange( - client: WhirlpoolClient, + ctx: WhirlpoolContext, whirlpool: PublicKey, startTickIndex: number, arrayCount: number, @@ -320,7 +325,7 @@ export async function initTickArrayRange( for (let i = 0; i < arrayCount; i++) { const { params } = await initTickArray( - client, + ctx, whirlpool, startTickIndex + direction * ticksInArray * i ); @@ -337,20 +342,29 @@ export type FundedPositionParams = { }; export async function withdrawPositions( - client: WhirlpoolClient, + ctx: WhirlpoolContext, positionInfos: FundedPositionInfo[], tokenOwnerAccountA: PublicKey, tokenOwnerAccountB: PublicKey ) { + const fetcher = new AccountFetcher(ctx.connection); await Promise.all( positionInfos.map(async (info) => { - const pool = await client.getPool(info.initParams.whirlpoolKey); - const position = await client.getPosition(info.initParams.positionPda.publicKey); + const pool = await fetcher.getPool(info.initParams.whirlpool); + const position = await fetcher.getPosition(info.initParams.positionPda.publicKey); - const priceLower = tickIndexToSqrtPriceX64(position.tickLowerIndex); - const priceUpper = tickIndexToSqrtPriceX64(position.tickUpperIndex); + if (!pool) { + throw new Error(`Failed to fetch pool - ${info.initParams.whirlpool}`); + } - const { tokenA, tokenB } = getTokenAmountsFromLiquidity( + if (!position) { + throw new Error(`Failed to fetch position - ${info.initParams.whirlpool}`); + } + + const priceLower = PriceMath.tickIndexToSqrtPriceX64(position.tickLowerIndex); + const priceUpper = PriceMath.tickIndexToSqrtPriceX64(position.tickUpperIndex); + + const { tokenA, tokenB } = PoolUtil.getTokenAmountsFromLiquidity( position.liquidity, pool.sqrtPrice, priceLower, @@ -361,28 +375,29 @@ export async function withdrawPositions( const numTicksInTickArray = pool.tickSpacing * TICK_ARRAY_SIZE; const lowerStartTick = position.tickLowerIndex - (position.tickLowerIndex % numTicksInTickArray); - const tickArrayLower = getTickArrayPda( - client.context.program.programId, - info.initParams.whirlpoolKey, + const tickArrayLower = PDAUtil.getTickArray( + ctx.program.programId, + info.initParams.whirlpool, lowerStartTick ); const upperStartTick = position.tickUpperIndex - (position.tickUpperIndex % numTicksInTickArray); - const tickArrayUpper = getTickArrayPda( - client.context.program.programId, - info.initParams.whirlpoolKey, + const tickArrayUpper = PDAUtil.getTickArray( + ctx.program.programId, + info.initParams.whirlpool, upperStartTick ); - await client - .decreaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.decreaseLiquidityIx(ctx.program, { liquidityAmount: position.liquidity, tokenMinA: tokenA, tokenMinB: tokenB, - whirlpool: info.initParams.whirlpoolKey, - positionAuthority: client.context.provider.wallet.publicKey, + whirlpool: info.initParams.whirlpool, + positionAuthority: ctx.provider.wallet.publicKey, position: info.initParams.positionPda.publicKey, - positionTokenAccount: info.initParams.positionTokenAccountAddress, + positionTokenAccount: info.initParams.positionTokenAccount, tokenOwnerAccountA, tokenOwnerAccountB, tokenVaultA: pool.tokenVaultA, @@ -390,20 +405,21 @@ export async function withdrawPositions( tickArrayLower: tickArrayLower.publicKey, tickArrayUpper: tickArrayUpper.publicKey, }) - .buildAndExecute(); + ).buildAndExecute(); - await client - .collectFeesTx({ - whirlpool: info.initParams.whirlpoolKey, - positionAuthority: client.context.provider.wallet.publicKey, + await toTx( + ctx, + WhirlpoolIx.collectFeesIx(ctx.program, { + whirlpool: info.initParams.whirlpool, + positionAuthority: ctx.provider.wallet.publicKey, position: info.initParams.positionPda.publicKey, - positionTokenAccount: info.initParams.positionTokenAccountAddress, + positionTokenAccount: info.initParams.positionTokenAccount, tokenOwnerAccountA, tokenOwnerAccountB, tokenVaultA: pool.tokenVaultA, tokenVaultB: pool.tokenVaultB, }) - .buildAndExecute(); + ).buildAndExecute(); }) ); } @@ -418,7 +434,7 @@ export interface FundedPositionInfo { } export async function fundPositions( - client: WhirlpoolClient, + ctx: WhirlpoolContext, poolInitInfo: InitPoolParams, tokenAccountA: PublicKey, tokenAccountB: PublicKey, @@ -435,41 +451,42 @@ export async function fundPositions( return await Promise.all( fundParams.map(async (param): Promise => { const { params: positionInfo, mint } = await openPosition( - client, + ctx, whirlpool, param.tickLowerIndex, param.tickUpperIndex ); - const tickArrayLower = getTickArrayPda( - client.context.program.programId, + const tickArrayLower = PDAUtil.getTickArray( + ctx.program.programId, whirlpool, - getStartTickIndex(param.tickLowerIndex, tickSpacing) + TickUtil.getStartTickIndex(param.tickLowerIndex, tickSpacing) ).publicKey; - const tickArrayUpper = getTickArrayPda( - client.context.program.programId, + const tickArrayUpper = PDAUtil.getTickArray( + ctx.program.programId, whirlpool, - getStartTickIndex(param.tickUpperIndex, tickSpacing) + TickUtil.getStartTickIndex(param.tickUpperIndex, tickSpacing) ).publicKey; if (param.liquidityAmount.gt(ZERO_BN)) { - const { tokenA, tokenB } = getTokenAmountsFromLiquidity( + const { tokenA, tokenB } = PoolUtil.getTokenAmountsFromLiquidity( param.liquidityAmount, initSqrtPrice, - tickIndexToSqrtPriceX64(param.tickLowerIndex), - tickIndexToSqrtPriceX64(param.tickUpperIndex), + PriceMath.tickIndexToSqrtPriceX64(param.tickLowerIndex), + PriceMath.tickIndexToSqrtPriceX64(param.tickUpperIndex), true ); - await client - .increaseLiquidityTx({ + await toTx( + ctx, + WhirlpoolIx.increaseLiquidityIx(ctx.program, { liquidityAmount: param.liquidityAmount, tokenMaxA: tokenA, tokenMaxB: tokenB, whirlpool: whirlpool, - positionAuthority: client.context.provider.wallet.publicKey, + positionAuthority: ctx.provider.wallet.publicKey, position: positionInfo.positionPda.publicKey, - positionTokenAccount: positionInfo.positionTokenAccountAddress, + positionTokenAccount: positionInfo.positionTokenAccount, tokenOwnerAccountA: tokenAccountA, tokenOwnerAccountB: tokenAccountB, tokenVaultA: tokenVaultAKeypair.publicKey, @@ -477,12 +494,12 @@ export async function fundPositions( tickArrayLower, tickArrayUpper, }) - .buildAndExecute(); + ).buildAndExecute(); } return { initParams: positionInfo, publicKey: positionInfo.positionPda.publicKey, - tokenAccount: positionInfo.positionTokenAccountAddress, + tokenAccount: positionInfo.positionTokenAccount, mintKeypair: mint, tickArrayLower, tickArrayUpper, @@ -491,7 +508,7 @@ export async function fundPositions( ); } -export async function initTestPoolWithLiquidity(client: WhirlpoolClient) { +export async function initTestPoolWithLiquidity(ctx: WhirlpoolContext) { const { poolInitInfo, configInitInfo, @@ -499,10 +516,10 @@ export async function initTestPoolWithLiquidity(client: WhirlpoolClient) { whirlpoolPda, tokenAccountA, tokenAccountB, - } = await initTestPoolWithTokens(client, TickSpacing.Standard); + } = await initTestPoolWithTokens(ctx, TickSpacing.Standard); const tickArrays = await initTickArrayRange( - client, + ctx, whirlpoolPda.publicKey, 22528, // to 33792 3, @@ -519,7 +536,7 @@ export async function initTestPoolWithLiquidity(client: WhirlpoolClient) { ]; const positionInfos = await fundPositions( - client, + ctx, poolInitInfo, tokenAccountA, tokenAccountB, diff --git a/sdk/tests/utils/test-builders.ts b/sdk/tests/utils/test-builders.ts index 2f3d883..781fa1e 100644 --- a/sdk/tests/utils/test-builders.ts +++ b/sdk/tests/utils/test-builders.ts @@ -1,19 +1,19 @@ +import { MathUtil, PDA, Percentage } from "@orca-so/common-sdk"; +import { Provider } from "@project-serum/anchor"; import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { Keypair, PublicKey } from "@solana/web3.js"; import Decimal from "decimal.js"; -import { createMint } from "."; +import { createAndMintToAssociatedTokenAccount, createMint } from "."; import { - getFeeTierPda, - getPositionMetadataPda, - getPositionPda, - getTickArrayPda, - getWhirlpoolPda, + increaseLiquidityQuoteByInputToken, InitConfigParams, InitFeeTierParams, InitPoolParams, InitTickArrayParams, OpenPositionParams, - toX64, + PDAUtil, + PriceMath, + Whirlpool, } from "../../src"; import { WhirlpoolContext } from "../../src/context"; @@ -36,7 +36,7 @@ export const generateDefaultConfigParams = ( rewardEmissionsSuperAuthorityKeypair: Keypair.generate(), }; const configInitInfo = { - whirlpoolConfigKeypair: Keypair.generate(), + whirlpoolsConfigKeypair: Keypair.generate(), feeAuthority: configKeypairs.feeAuthorityKeypair.publicKey, collectProtocolFeesAuthority: configKeypairs.collectProtocolFeesAuthorityKeypair.publicKey, rewardEmissionsSuperAuthority: configKeypairs.rewardEmissionsSuperAuthorityKeypair.publicKey, @@ -68,12 +68,12 @@ export const generateDefaultInitPoolParams = async ( configKey: PublicKey, feeTierKey: PublicKey, tickSpacing: number, - initSqrtPrice = toX64(new Decimal(5)), + initSqrtPrice = MathUtil.toX64(new Decimal(5)), funder?: PublicKey ): Promise => { const [tokenAMintPubKey, tokenBMintPubKey] = await createInOrderMints(context); - const whirlpoolPda = getWhirlpoolPda( + const whirlpoolPda = PDAUtil.getWhirlpool( context.program.programId, configKey, tokenAMintPubKey, @@ -85,7 +85,7 @@ export const generateDefaultInitPoolParams = async ( return { initSqrtPrice, - whirlpoolConfigKey: configKey, + whirlpoolsConfig: configKey, tokenMintA: tokenAMintPubKey, tokenMintB: tokenBMintPubKey, whirlpoolPda, @@ -99,16 +99,20 @@ export const generateDefaultInitPoolParams = async ( export const generateDefaultInitFeeTierParams = ( context: WhirlpoolContext, - whirlpoolConfigKey: PublicKey, + whirlpoolsConfigKey: PublicKey, whirlpoolFeeAuthority: PublicKey, tickSpacing: number, defaultFeeRate: number, funder?: PublicKey ): InitFeeTierParams => { - const feeTierPda = getFeeTierPda(context.program.programId, whirlpoolConfigKey, tickSpacing); + const feeTierPda = PDAUtil.getFeeTier( + context.program.programId, + whirlpoolsConfigKey, + tickSpacing + ); return { feeTierPda, - whirlpoolConfigKey, + whirlpoolsConfig: whirlpoolsConfigKey, tickSpacing, defaultFeeRate, feeAuthority: whirlpoolFeeAuthority, @@ -122,7 +126,7 @@ export const generateDefaultInitTickArrayParams = ( startTick: number, funder?: PublicKey ): InitTickArrayParams => { - const tickArrayPda = getTickArrayPda(context.program.programId, whirlpool, startTick); + const tickArrayPda = PDAUtil.getTickArray(context.program.programId, whirlpool, startTick); return { whirlpool, @@ -139,11 +143,11 @@ export async function generateDefaultOpenPositionParams( tickUpperIndex: number, owner: PublicKey, funder?: PublicKey -): Promise<{ params: Required; mint: Keypair }> { +): Promise<{ params: Required; mint: Keypair }> { const positionMintKeypair = Keypair.generate(); - const positionPda = getPositionPda(context.program.programId, positionMintKeypair.publicKey); + const positionPda = PDAUtil.getPosition(context.program.programId, positionMintKeypair.publicKey); - const metadataPda = getPositionMetadataPda(positionMintKeypair.publicKey); + const metadataPda = PDAUtil.getPositionMetadata(positionMintKeypair.publicKey); const positionTokenAccountAddress = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, @@ -152,14 +156,14 @@ export async function generateDefaultOpenPositionParams( owner ); - const params: Required = { + const params: Required = { funder: funder || context.wallet.publicKey, - ownerKey: owner, + owner: owner, positionPda, metadataPda, positionMintAddress: positionMintKeypair.publicKey, - positionTokenAccountAddress: positionTokenAccountAddress, - whirlpoolKey: whirlpool, + positionTokenAccount: positionTokenAccountAddress, + whirlpool: whirlpool, tickLowerIndex, tickUpperIndex, }; @@ -168,3 +172,73 @@ export async function generateDefaultOpenPositionParams( mint: positionMintKeypair, }; } + +export async function mintTokensToTestAccount( + provider: Provider, + tokenAMint: PublicKey, + tokenMintForA: number, + tokenBMint: PublicKey, + tokenMintForB: number +) { + const userTokenAAccount = await createAndMintToAssociatedTokenAccount( + provider, + tokenAMint, + tokenMintForA + ); + const userTokenBAccount = await createAndMintToAssociatedTokenAccount( + provider, + tokenBMint, + tokenMintForB + ); + + return [userTokenAAccount, userTokenBAccount]; +} + +export async function initPosition( + ctx: WhirlpoolContext, + pool: Whirlpool, + lowerPrice: Decimal, + upperPrice: Decimal, + inputTokenMint: PublicKey, + inputTokenAmount: number +) { + const tokenADecimal = pool.getTokenAInfo().decimals; + const tokenBDecimal = pool.getTokenBInfo().decimals; + const tickSpacing = pool.getData().tickSpacing; + const lowerTick = PriceMath.priceToInitializableTickIndex( + lowerPrice, + tokenADecimal, + tokenBDecimal, + tickSpacing + ); + const upperTick = PriceMath.priceToInitializableTickIndex( + upperPrice, + tokenADecimal, + tokenBDecimal, + tickSpacing + ); + const quote = await increaseLiquidityQuoteByInputToken( + inputTokenMint, + new Decimal(inputTokenAmount), + lowerTick, + upperTick, + Percentage.fromFraction(1, 100), + pool + ); + + // [Action] Open Position (and increase L) + const { positionMint, tx } = await pool.openPosition( + lowerTick, + upperTick, + quote, + ctx.wallet.publicKey, + ctx.wallet.publicKey + ); + + await tx.buildAndExecute(); + + return { + positionMint, + positionAddress: PDAUtil.getPosition(ctx.program.programId, positionMint), + }; +} diff --git a/sdk/tests/utils/token.ts b/sdk/tests/utils/token.ts index d96b17b..6e6fc2d 100644 --- a/sdk/tests/utils/token.ts +++ b/sdk/tests/utils/token.ts @@ -1,5 +1,12 @@ +import { deriveATA } from "@orca-so/common-sdk"; import { BN, Provider, web3 } from "@project-serum/anchor"; -import { AuthorityType, Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token"; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + AuthorityType, + Token, + TOKEN_PROGRAM_ID, + u64, +} from "@solana/spl-token"; import { TEST_TOKEN_PROGRAM_ID } from "./test-consts"; export async function createMint( @@ -50,6 +57,27 @@ export async function createTokenAccount( return tokenAccount.publicKey; } +export async function createAssociatedTokenAccount( + provider: Provider, + mint: web3.PublicKey, + owner: web3.PublicKey +) { + const ataAddress = await deriveATA(owner, mint); + + const instr = Token.createAssociatedTokenAccountInstruction( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + mint, + ataAddress, + owner, + owner + ); + const tx = new web3.Transaction(); + tx.add(instr); + await provider.send(tx, [], { commitment: "confirmed" }); + return ataAddress; +} + async function createTokenAccountInstrs( provider: Provider, newAccountPubkey: web3.PublicKey, @@ -116,6 +144,20 @@ export async function createAndMintToTokenAccount( return tokenAccount; } +export async function createAndMintToAssociatedTokenAccount( + provider: Provider, + mint: web3.PublicKey, + amount: number | BN +): Promise { + const tokenAccount = await createAssociatedTokenAccount( + provider, + mint, + provider.wallet.publicKey + ); + await mintToByAuthority(provider, mint, tokenAccount, amount); + return tokenAccount; +} + export async function getTokenBalance(provider: Provider, vault: web3.PublicKey) { return (await provider.connection.getTokenAccountBalance(vault, "confirmed")).value.amount; } diff --git a/sdk/tests/utils/utils.ts b/sdk/tests/utils/utils.ts index 43676d7..bbf8c57 100644 --- a/sdk/tests/utils/utils.ts +++ b/sdk/tests/utils/utils.ts @@ -1,12 +1,5 @@ -import { web3, utils, Provider } from "@project-serum/anchor"; -import { PDA } from "../../src/types/public/helper-types"; -import { TransactionBuilder } from "../../src/utils/transactions/transactions-builder"; - -// Wrapper around findProgramAddress that returns a PDA object -export function findProgramAddress(seeds: (Uint8Array | Buffer)[], programId: web3.PublicKey): PDA { - const [publicKey, bump] = utils.publicKey.findProgramAddressSync(seeds, programId); - return { publicKey, bump }; -} +import { TransactionBuilder } from "@orca-so/common-sdk"; +import { web3, Provider } from "@project-serum/anchor"; export function systemTransferTx( provider: Provider, diff --git a/sdk/tsconfig-base.json b/sdk/tsconfig-base.json index 18dcec1..8e774cc 100644 --- a/sdk/tsconfig-base.json +++ b/sdk/tsconfig-base.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es6", + "target": "es6", "module": "commonjs", "allowJs": false, "declaration": true, @@ -11,5 +11,15 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - } + "experimentalDecorators": true + }, + "exclude": ["./dist/**/*"], + "references": [ + { + "path": "./src/tsconfig.json" + }, + { + "path": "./tests/tsconfig.json" + } + ] } diff --git a/tsconfig.json b/tsconfig.json index 837bc21..56e10de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ ], "references": [ { - "path": "./sdk" + "path": "./sdk/src" }, { "path": "./scripts" diff --git a/yarn.lock b/yarn.lock index c4ba5d0..ad7118d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,34 +2,531 @@ # yarn lockfile v1 -"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a" - integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA== +"@ampproject/remapping@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" + integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" + integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" + integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.9" + "@babel/parser" "^7.17.9" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.9" + "@babel/types" "^7.17.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.17.9", "@babel/generator@^7.7.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" + integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-compilation-targets@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" + integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.17.5" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-transforms@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" + integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== + +"@babel/helper-simple-access@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" + integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== + dependencies: + "@babel/types" "^7.17.0" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + +"@babel/helpers@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" + integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.9" + "@babel/types" "^7.17.0" + +"@babel/highlight@^7.16.7": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" + integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" + integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" + integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== dependencies: regenerator-runtime "^0.13.4" -"@ethersproject/bytes@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c" - integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog== +"@babel/template@^7.16.7", "@babel/template@^7.3.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== dependencies: - "@ethersproject/logger" "^5.5.0" + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" -"@ethersproject/logger@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d" - integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg== +"@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9", "@babel/traverse@^7.7.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" + integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.9" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@ethersproject/bytes@^5.6.0": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7" + integrity sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g== + dependencies: + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/logger@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" + integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg== "@ethersproject/sha2@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.5.0.tgz#a40a054c61f98fd9eee99af2c3cc6ff57ec24db7" - integrity sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA== + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.6.0.tgz#364c4c11cc753bda36f31f001628706ebadb64d9" + integrity sha512-1tNWCPFLu1n3JM9t4/kytz35DkuF9MxqkGGEHNauEbaARdm2fafnOyw1s0tIQDPKF/7bkP1u3dbrmjpn5CelyA== dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/logger" "^5.6.0" hash.js "1.1.7" +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== + dependencies: + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== + dependencies: + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" + +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.1.0" + +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.9" + source-map "^0.6.0" + +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== + dependencies: + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== + dependencies: + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" + +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.6.tgz#4ac237f4dabc8dd93330386907b97591801f7352" + integrity sha512-R7xHtBSNm+9SyvpJkdQl+qrM3Hm2fea3Ef197M3mUug+v+yR+Rhfbs7PBtcBUVnIWJ4JcAdjvij+c8hXS9p5aw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.11" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" + integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + +"@jridgewell/trace-mapping@^0.3.0": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@metaplex-foundation/mpl-core@^0.0.2": version "0.0.2" resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-core/-/mpl-core-0.0.2.tgz#17ee2cc216e17629d6df1dbba75964625ebbd603" @@ -68,7 +565,26 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@project-serum/anchor@^0.20.1": +"@orca-so/common-sdk@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@orca-so/common-sdk/-/common-sdk-0.0.4.tgz#cbe135ed2026ce98c5771ff9e3de5e24ec90cf8d" + integrity sha512-3YrVgrgt2HlDyzwE6KwmD/u3knFjNCGtKi/1HJsBQMcR95R99vVnsvQylnUcZ6CJm1KyjTAOl5lvEIzgFnu+3g== + dependencies: + "@project-serum/anchor" "0.20.1" + "@solana/spl-token" "^0.1.8" + decimal.js "^10.3.1" + +"@orca-so/whirlpool-client-sdk@0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@orca-so/whirlpool-client-sdk/-/whirlpool-client-sdk-0.0.7.tgz#a589a7437807d9840e1813961a4b17c7fe58f5f0" + integrity sha512-fmGQjdRkW2lX1GqZVvrkklYfk0VpXbwuOw8AZA05RrqMZtSL+ePNZMBjBztlccxqKulweCPRCEYFXC17ZdNbAQ== + dependencies: + "@metaplex-foundation/mpl-token-metadata" "1.2.5" + "@project-serum/anchor" "^0.20.1" + "@solana/spl-token" "^0.1.8" + decimal.js "^10.3.1" + +"@project-serum/anchor@0.20.1", "@project-serum/anchor@^0.20.1": version "0.20.1" resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.20.1.tgz#0937807e807e8332aa708cfef4bcb6cbb88b4129" integrity sha512-2TuBmGUn9qeYz6sJINJlElrBuPsaUAtYyUsJ3XplEBf1pczrANAgs5ceJUFzdiqGEWLn+84ObSdBeChT/AXYFA== @@ -89,17 +605,31 @@ toml "^3.0.0" "@project-serum/borsh@^0.2.2": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.3.tgz#1d705c5887484cb6a127dd5feff58e90cbfcb558" - integrity sha512-lH9zEYADZE3cxrgiFym8+jbUE3NM/LH+WOKYcUjs65CT10Q64Hv45bcAAa/phwYk4Tpz0uQ1x+ergFaAoGt67Q== + version "0.2.5" + resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.5.tgz#6059287aa624ecebbfc0edd35e4c28ff987d8663" + integrity sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q== dependencies: bn.js "^5.1.2" buffer-layout "^1.2.0" -"@solana/buffer-layout@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-3.0.0.tgz#b9353caeb9a1589cb77a1b145bcb1a9a93114326" - integrity sha512-MVdgAKKL39tEs0l8je0hKaXLQFb7Rdfb0Xg2LjFZd8Lfdazkg6xiS98uAZrEKvaoF3i4M95ei9RydkGIDMeo3w== +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@solana/buffer-layout@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz#75b1b11adc487234821c81dfae3119b73a5fd734" + integrity sha512-lR0EMP2HC3+Mxwd4YcnZb0smnaDw7Bl2IQWZiTevRH5ZZBZn6VRWn3/92E3qdU4SSImJkA6IDHawOHAnx/qUvQ== dependencies: buffer "~6.0.3" @@ -115,16 +645,16 @@ buffer-layout "^1.2.0" dotenv "10.0.0" -"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0": - version "1.31.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.31.0.tgz#7a313d4c1a90b77f27ddbfe845a10d6883e06452" - integrity sha512-7nHHx1JNFnrt15e9y8m38I/EJCbaB+bFC3KZVM1+QhybCikFxGMtGA5r7PDC3GEL1R2RZA8yKoLkDKo3vzzqnw== +"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.31.0": + version "1.41.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.41.0.tgz#f83af98d4ead2b45aa064036d8bdb006a83d1bae" + integrity sha512-m8NDOorYficxn/SbPTCJH1Rwnv/bBIkSr1HABzdSSshRjf92ndrilMCNp61egNGMyd+hAjKfNJkHM5bKvPu5iA== dependencies: "@babel/runtime" "^7.12.5" "@ethersproject/sha2" "^5.5.0" - "@solana/buffer-layout" "^3.0.0" + "@solana/buffer-layout" "^4.0.0" bn.js "^5.0.0" - borsh "^0.4.0" + borsh "^0.7.0" bs58 "^4.0.1" buffer "6.0.1" cross-fetch "^3.1.4" @@ -135,32 +665,43 @@ superstruct "^0.14.2" tweetnacl "^1.0.0" -"@solana/web3.js@^1.31.0": - version "1.36.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.36.0.tgz#79d7d5217b49b80139f4de68953adc5b9a9a264f" - integrity sha512-RNT1451iRR7TyW7EJKMCrH/0OXawIe4zVm0DWQASwXlR/u1jmW6FrmH0lujIh7cGTlfOVbH+2ZU9AVUPLBFzwA== - dependencies: - "@babel/runtime" "^7.12.5" - "@ethersproject/sha2" "^5.5.0" - "@solana/buffer-layout" "^3.0.0" - bn.js "^5.0.0" - borsh "^0.4.0" - bs58 "^4.0.1" - buffer "6.0.1" - cross-fetch "^3.1.4" - jayson "^3.4.4" - js-sha3 "^0.8.0" - rpc-websockets "^7.4.2" - secp256k1 "^4.0.2" - superstruct "^0.14.2" - tweetnacl "^1.0.0" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@types/bn.js@^4.11.5": - version "4.11.6" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" - integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== dependencies: - "@types/node" "*" + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.17.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== + dependencies: + "@babel/types" "^7.3.0" "@types/connect@^3.4.33": version "3.4.35" @@ -177,28 +718,62 @@ decimal.js "*" "@types/express-serve-static-core@^4.17.9": - version "4.17.26" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz#5d9a8eeecb9d5f9d7fc1d85f541512a84638ae88" - integrity sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ== + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^26.0.24": + version "26.0.24" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" + integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== + dependencies: + jest-diff "^26.0.0" + pretty-format "^26.0.0" + "@types/json-schema@^7.0.7": - version "7.0.10" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.10.tgz#9b05b7896166cd00e9cbd59864853abf65d9ac23" - integrity sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/lodash@^4.14.159": - version "4.14.178" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8" - integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw== + version "4.14.182" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" + integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== "@types/mocha@^9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297" - integrity sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA== + version "9.1.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" + integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/mz@^2.7.3": version "2.7.4" @@ -208,14 +783,19 @@ "@types/node" "*" "@types/node@*": - version "17.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.0.tgz#62797cee3b8b497f6547503b2312254d4fe3c2bb" - integrity sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw== + version "17.0.29" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.29.tgz#7f2e1159231d4a077bb660edab0fde373e375a3d" + integrity sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA== "@types/node@^12.12.54": - version "12.20.37" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.37.tgz#abb38afa9d6e8a2f627a8cb52290b3c80fbe61ed" - integrity sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA== + version "12.20.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.50.tgz#14ba5198f1754ffd0472a2f84ab433b45ee0b65e" + integrity sha512-+9axpWx2b2JCVovr7Ilgt96uc6C1zBKOQMpGtRbWT9IoR/8ue32GGMfGA4woP8QyP2gBs6GQWEVM3tCybGCxDA== + +"@types/prettier@^2.1.5": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.0.tgz#efcbd41937f9ae7434c714ab698604822d890759" + integrity sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw== "@types/qs@*": version "6.9.7" @@ -227,6 +807,11 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + "@types/ws@^7.4.4": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -234,6 +819,25 @@ dependencies: "@types/node" "*" +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^15.0.0": + version "15.0.14" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" + integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^4.26.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" @@ -317,16 +921,65 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" +abab@^2.0.3, abab@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.2.4: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-regex@^5.0.1: +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -334,12 +987,17 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= -anymatch@~3.1.2: +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -347,6 +1005,13 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -362,6 +1027,72 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== + dependencies: + babel-plugin-jest-hoist "^27.5.1" + babel-preset-current-node-syntax "^1.0.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -389,18 +1120,17 @@ bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.2: +bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.2, bn.js@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== -borsh@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.4.0.tgz#9dd6defe741627f1315eac2a73df61421f6ddb9f" - integrity sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g== +borsh@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== dependencies: - "@types/bn.js" "^4.11.5" - bn.js "^5.0.0" + bn.js "^5.2.0" bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" @@ -412,7 +1142,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -424,11 +1154,34 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +browserslist@^4.17.5: + version "4.20.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" + integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== + dependencies: + caniuse-lite "^1.0.30001332" + electron-to-chromium "^1.4.118" + escalade "^3.1.1" + node-releases "^2.0.3" + picocolors "^1.0.0" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -436,6 +1189,18 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + buffer-layout@^1.2.0, buffer-layout@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" @@ -458,35 +1223,55 @@ buffer@6.0.3, buffer@~6.0.3: ieee754 "^1.2.1" bufferutil@^4.0.1: - version "4.0.5" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.5.tgz#da9ea8166911cc276bf677b8aed2d02d31f59028" - integrity sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A== + version "4.0.6" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433" + integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw== dependencies: node-gyp-build "^4.3.0" +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" - integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== +camelcase@^6.0.0, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001332: + version "1.0.30001332" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz#39476d3aa8d83ea76359c70302eafdd4a1d727dd" + integrity sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw== chai@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" - integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== + version "4.3.6" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" + integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== dependencies: assertion-error "^1.1.0" check-error "^1.0.2" deep-eql "^3.0.1" get-func-name "^2.0.0" + loupe "^2.3.1" pathval "^1.1.1" type-detect "^4.0.5" -chalk@^4.1.0: +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -494,15 +1279,20 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -chokidar@3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== +chokidar@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -514,10 +1304,15 @@ chokidar@3.5.2: optionalDependencies: fsevents "~2.3.2" -circular-json@^0.5.9: - version "0.5.9" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d" - integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ== +ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== cliui@^7.0.2: version "7.0.4" @@ -528,6 +1323,23 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -535,11 +1347,23 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -550,42 +1374,89 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -cross-fetch@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" - integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: - node-fetch "2.6.1" + safe-buffer "~5.1.1" + +cross-fetch@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" crypto-hash@^1.3.0: version "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.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== -debug@^4.3.1: +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decimal.js@*, decimal.js@^10.3.1: +decimal.js@*, decimal.js@^10.2.1, decimal.js@^10.3.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -593,11 +1464,41 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + delay@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -610,6 +1511,13 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -623,7 +1531,12 @@ dotenv@10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -elliptic@^6.5.2: +electron-to-chromium@^1.4.118: + version "1.4.122" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.122.tgz#56e518e8c4433876b01d4460eac0f653841ed510" + integrity sha512-VuLNxTIt8sBWIT2sd186xPd18Y8KcK8myLd9nMdSJOYZwFUxxbLVmX/T1VX+qqaytRlrYYQv39myxJdXtu7Ysw== + +elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -636,11 +1549,23 @@ elliptic@^6.5.2: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -663,6 +1588,28 @@ escape-string-regexp@4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-config-prettier@^8.3.0: version "8.5.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" @@ -688,6 +1635,11 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -705,11 +1657,46 @@ estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== + dependencies: + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + eyes@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" @@ -726,6 +1713,16 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -733,6 +1730,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -748,6 +1752,14 @@ find-up@5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + find@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find/-/find-0.3.0.tgz#4082e8fc8d8320f1a382b5e4f521b9bc50775cb8" @@ -760,21 +1772,40 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + 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" @@ -785,6 +1816,16 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -792,10 +1833,10 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@7.1.7: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== +glob@7.2.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -804,6 +1845,11 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + globby@^11.0.3: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -816,16 +1862,33 @@ globby@^11.0.3: merge2 "^1.4.1" slash "^3.0.0" +graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -848,6 +1911,47 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -858,6 +1962,19 @@ ignore@^5.1.8, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -871,6 +1988,11 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -878,6 +2000,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-core-module@^2.8.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -888,6 +2017,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -905,6 +2039,21 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" @@ -920,6 +2069,48 @@ 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== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + jayson@^3.4.4: version "3.6.6" resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.6.6.tgz#189984f624e398f831bd2be8e8c80eb3abf764a1" @@ -941,6 +2132,426 @@ jayson@^3.4.4: uuid "^8.3.2" ws "^7.4.5" +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== + dependencies: + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + prompts "^2.0.1" + yargs "^16.2.0" + +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== + dependencies: + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^26.0.0: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== + dependencies: + detect-newline "^3.0.0" + +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" + +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== + dependencies: + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" + +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== + dependencies: + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== + +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== + dependencies: + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" + +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + natural-compare "^1.4.0" + pretty-format "^27.5.1" + semver "^7.3.2" + +jest-util@^27.0.0, jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== + dependencies: + "@jest/types" "^27.5.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.5.1" + leven "^3.1.0" + pretty-format "^27.5.1" + +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== + dependencies: + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.5.1" + string-length "^4.0.1" + +jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^27.0.6: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== + dependencies: + "@jest/core" "^27.5.1" + import-local "^3.0.2" + jest-cli "^27.5.1" + js-sha256@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" @@ -951,6 +2562,11 @@ js-sha3@^0.8.0: resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + 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" @@ -958,16 +2574,102 @@ js-yaml@4.1.0: dependencies: argparse "^2.0.1" +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.6" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json5@2.x, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -975,7 +2677,12 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash@^4.17.20: +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash@^4.17.20, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -988,6 +2695,13 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +loupe@^2.3.1: + version "2.3.4" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" + integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== + dependencies: + get-func-name "^2.0.0" + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -1002,18 +2716,59 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" @@ -1025,39 +2780,46 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" + integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" mocha@^9.0.3: - version "9.1.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.3.tgz#8a623be6b323810493d8c8f6f7667440fa469fdb" - integrity sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw== + version "9.2.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" + integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" - chokidar "3.5.2" - debug "4.3.2" + chokidar "3.5.3" + debug "4.3.3" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" - glob "7.1.7" + glob "7.2.0" growl "1.10.5" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "3.0.4" + minimatch "4.2.1" ms "2.1.3" - nanoid "3.1.25" + nanoid "3.3.1" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" which "2.0.2" - workerpool "6.1.5" + workerpool "6.2.0" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" @@ -1081,10 +2843,15 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@3.1.25: - version "3.1.25" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" - integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== +nanoid@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= no-case@^3.0.4: version "3.0.4" @@ -1099,21 +2866,45 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== -node-fetch@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" - integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + version "4.4.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4" + integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-releases@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.3.tgz#225ee7488e4a5e636da8da52854844f9d716ca96" + integrity sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -1126,6 +2917,32 @@ once@^1.3.0: dependencies: wrappy "1" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -1133,6 +2950,13 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -1140,11 +2964,31 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + pako@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1155,6 +2999,16 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -1165,20 +3019,74 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.2.3: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + prettier@^2.3.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4" - integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A== + version "2.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== + +pretty-format@^26.0.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + +pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== queue-microtask@^1.2.2: version "1.2.3" @@ -1192,6 +3100,11 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -1214,21 +3127,53 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.20.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rpc-websockets@^7.4.2: - version "7.4.16" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.4.16.tgz#eb701cdef577d4357ba5f526d50e25f370396fac" - integrity sha512-0b7OVhutzwRIaYAtJo5tqtaQTWKfwAsKnaThOSOy+VkhVdleNUgb8eZnWSdWITRZZEigV5uPEIDr5KZe4DBrdQ== +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: - "@babel/runtime" "^7.11.2" - circular-json "^0.5.9" + glob "^7.1.3" + +rpc-websockets@^7.4.2: + version "7.4.18" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.4.18.tgz#274c825c0efadbf6fe75f10289229ae537fe9ffb" + integrity sha512-bVu+4qM5CkGVlTqJa6FaAxLbb5uRnyH4te7yjFvoCzbnif7PT4BcvXtNTprHlNvsH+/StB81zUQicxMrUrIomA== + dependencies: + "@babel/runtime" "^7.17.2" eventemitter3 "^4.0.7" - uuid "^8.3.0" - ws "^7.4.5" + uuid "^8.3.2" + ws "^8.5.0" optionalDependencies: bufferutil "^4.0.1" utf-8-validate "^5.0.2" @@ -1245,22 +3190,44 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -secp256k1@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1" - integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg== +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: - elliptic "^6.5.2" + xmlchars "^2.2.0" + +secp256k1@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== +semver@7.x, semver@^7.3.2, semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -1268,6 +3235,28 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -1281,6 +3270,49 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +source-map-support@^0.5.6: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -1297,7 +3329,17 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-json-comments@3.1.1: +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@3.1.1, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -1307,20 +3349,62 @@ superstruct@^0.14.2: resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== -supports-color@8.1.1: +supports-color@8.1.1, supports-color@^8.0.0: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -supports-color@^7.1.0: +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-encoding-utf-8@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" @@ -1340,11 +3424,31 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + "through@>=2.2.7 <3": version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +tiny-invariant@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" + integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -1357,20 +3461,55 @@ toml@^3.0.0: resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + traverse-chain@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1" integrity sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE= +ts-jest@^27.0.3: + version "27.1.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00" + integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" + tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.3: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== tsutils@^3.21.0: version "3.21.0" @@ -1384,39 +3523,142 @@ tweetnacl@^1.0.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== -type-detect@^4.0.0, type-detect@^4.0.5: +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typescript@^4.5.5: - version "4.5.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" - integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + version "4.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== + +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== utf-8-validate@^5.0.2: - version "5.0.7" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.7.tgz#c15a19a6af1f7ad9ec7ddc425747ca28c3644922" - integrity sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q== + version "5.0.9" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.9.tgz#ba16a822fbeedff1a58918f2a6a6b36387493ea3" + integrity sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q== dependencies: node-gyp-build "^4.3.0" -uuid@^8.3.0, uuid@^8.3.2: +uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -which@2.0.2: +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + +which@2.0.2, which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -workerpool@6.1.5: - version "6.1.5" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" - integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +workerpool@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" + integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== wrap-ansi@^7.0.0: version "7.0.0" @@ -1432,10 +3674,35 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^7.4.5: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.4.5, ws@^7.4.6: + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + +ws@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== y18n@^5.0.5: version "5.0.8" @@ -1452,7 +3719,7 @@ yargs-parser@20.2.4: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@^20.2.2: +yargs-parser@20.x, yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -1467,7 +3734,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0: +yargs@16.2.0, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==