UNPKG

@jup-ag/whirlpools-sdk

Version:

Typescript SDK to interact with Orca's Whirlpool program.

1 lines 400 kB
{"version":3,"sources":["../src/index.ts","../src/context.ts","../src/network/public/fetcher.ts","../src/network/public/parsing.ts","../src/types/public/constants.ts","../src/types/public/anchor-types.ts","../src/instructions/close-position-ix.ts","../src/instructions/collect-fees-ix.ts","../src/instructions/collect-protocol-fees-ix.ts","../src/instructions/collect-reward-ix.ts","../src/instructions/composites/collect-all-txn.ts","../src/ix.ts","../src/utils/public/ix-utils.ts","../src/utils/public/pda-utils.ts","../src/utils/public/price-math.ts","../src/utils/public/tick-utils.ts","../src/utils/public/pool-utils.ts","../src/utils/public/types.ts","../src/utils/public/swap-utils.ts","../src/utils/spl-token-utils.ts","../src/utils/txn-utils.ts","../src/utils/whirlpool-ata-utils.ts","../src/instructions/update-fees-and-rewards-ix.ts","../src/instructions/decrease-liquidity-ix.ts","../src/instructions/increase-liquidity-ix.ts","../src/instructions/initialize-config-ix.ts","../src/instructions/initialize-fee-tier-ix.ts","../src/instructions/initialize-pool-ix.ts","../src/instructions/initialize-reward-ix.ts","../src/instructions/initialize-tick-array-ix.ts","../src/instructions/open-position-ix.ts","../src/utils/instructions-util.ts","../src/instructions/set-collect-protocol-fees-authority-ix.ts","../src/instructions/set-default-fee-rate-ix.ts","../src/instructions/set-default-protocol-fee-rate-ix.ts","../src/instructions/set-fee-authority-ix.ts","../src/instructions/set-fee-rate-ix.ts","../src/instructions/set-protocol-fee-rate-ix.ts","../src/instructions/set-reward-authority-by-super-authority-ix.ts","../src/instructions/set-reward-authority-ix.ts","../src/instructions/set-reward-emissions-ix.ts","../src/instructions/set-reward-emissions-super-authority-ix.ts","../src/instructions/swap-ix.ts","../src/impl/position-impl.ts","../src/utils/builder/position-builder-util.ts","../src/quotes/public/increase-liquidity-quote.ts","../src/utils/position-util.ts","../src/utils/swap-utils.ts","../src/quotes/public/decrease-liquidity-quote.ts","../src/quotes/public/collect-fees-quote.ts","../src/quotes/public/collect-rewards-quote.ts","../src/quotes/public/swap-quote.ts","../src/quotes/swap/swap-quote-impl.ts","../src/errors/errors.ts","../src/quotes/swap/tick-array-index.ts","../src/quotes/swap/tick-array-sequence.ts","../src/quotes/swap/swap-manager.ts","../src/utils/math/swap-math.ts","../src/utils/math/token-math.ts","../src/utils/math/bit-math.ts","../src/quotes/public/dev-fee-swap-quote.ts","../src/impl/whirlpool-client-impl.ts","../src/impl/util.ts","../src/impl/whirlpool-impl.ts","../src/whirlpool-client.ts"],"sourcesContent":["import Decimal from \"decimal.js\";\n\nexport * from \"./context\";\nexport * from \"./impl/position-impl\";\nexport * from \"./ix\";\nexport * from \"./network/public\";\nexport * from \"./quotes/public\";\nexport * from \"./types/public\";\nexport * from \"./types/public/anchor-types\";\nexport * from \"./utils/public\";\nexport * from \"./whirlpool-client\";\nexport { Percentage } from \"@orca-so/common-sdk\";\n\n// Global rules for Decimals\n// - 40 digits of precision for the largest number\n// - 20 digits of precision for the smallest number\n// - Always round towards 0 to mirror smart contract rules\nDecimal.set({ precision: 40, toExpPos: 40, toExpNeg: -20, rounding: 1 });\n","import { AnchorProvider, Idl, Program } from \"@project-serum/anchor\";\nimport { Wallet } from \"@project-serum/anchor/dist/cjs/provider\";\nimport { ConfirmOptions, Connection, PublicKey } from \"@solana/web3.js\";\nimport { Whirlpool } from \"./artifacts/whirlpool\";\nimport WhirlpoolIDL from \"./artifacts/whirlpool.json\";\nimport { AccountFetcher } from \"./network/public\";\n/**\n * @category Core\n */\nexport class WhirlpoolContext {\n readonly connection: Connection;\n readonly wallet: Wallet;\n readonly opts: ConfirmOptions;\n readonly program: Program<Whirlpool>;\n readonly provider: AnchorProvider;\n readonly fetcher: AccountFetcher;\n\n public static from(\n connection: Connection,\n wallet: Wallet,\n programId: PublicKey,\n fetcher = new AccountFetcher(connection),\n opts: ConfirmOptions = AnchorProvider.defaultOptions()\n ): WhirlpoolContext {\n const anchorProvider = new AnchorProvider(connection, wallet, opts);\n const program = new Program(WhirlpoolIDL as Idl, programId, anchorProvider);\n return new WhirlpoolContext(anchorProvider, anchorProvider.wallet, program, fetcher, opts);\n }\n\n public static fromWorkspace(\n provider: AnchorProvider,\n program: Program,\n fetcher = new AccountFetcher(provider.connection),\n opts: ConfirmOptions = AnchorProvider.defaultOptions()\n ) {\n return new WhirlpoolContext(provider, provider.wallet, program, fetcher, opts);\n }\n\n public static withProvider(\n provider: AnchorProvider,\n programId: PublicKey,\n fetcher = new AccountFetcher(provider.connection),\n opts: ConfirmOptions = AnchorProvider.defaultOptions()\n ): WhirlpoolContext {\n const program = new Program(WhirlpoolIDL as Idl, programId, provider);\n return new WhirlpoolContext(provider, provider.wallet, program, fetcher, opts);\n }\n\n public constructor(\n provider: AnchorProvider,\n wallet: Wallet,\n program: Program,\n fetcher: AccountFetcher,\n opts: ConfirmOptions\n ) {\n this.connection = provider.connection;\n this.wallet = wallet;\n this.opts = opts;\n // It's a hack but it works on Anchor workspace *shrug*\n this.program = program as unknown as Program<Whirlpool>;\n this.provider = provider;\n this.fetcher = fetcher;\n }\n\n // TODO: Add another factory method to build from on-chain IDL\n}\n","import { Connection, PublicKey } from \"@solana/web3.js\";\nimport invariant from \"tiny-invariant\";\nimport { AccountInfo, AccountLayout, MintInfo } from \"@solana/spl-token\";\nimport {\n ParsableEntity,\n ParsableFeeTier,\n ParsableMintInfo,\n ParsablePosition,\n ParsableTickArray,\n ParsableTokenInfo,\n ParsableWhirlpool,\n ParsableWhirlpoolsConfig,\n} from \"./parsing\";\nimport { Address } from \"@project-serum/anchor\";\nimport {\n PositionData,\n TickArrayData,\n WhirlpoolsConfigData,\n WhirlpoolData,\n WHIRLPOOL_ACCOUNT_SIZE,\n WHIRLPOOL_CODER,\n AccountName,\n} from \"../..\";\nimport { FeeTierData } from \"../../types/public\";\nimport { AddressUtil } from \"@orca-so/common-sdk\";\n\n/**\n * Supported accounts\n */\ntype CachedValue =\n | WhirlpoolsConfigData\n | WhirlpoolData\n | PositionData\n | TickArrayData\n | FeeTierData\n | AccountInfo\n | MintInfo;\n\n/**\n * Include both the entity (i.e. type) of the stored value, and the value itself\n */\ninterface CachedContent<T extends CachedValue> {\n entity: ParsableEntity<T>;\n value: CachedValue | null;\n}\n\n/**\n * Type for rpc batch request response\n */\ntype GetMultipleAccountsResponse = {\n error?: string;\n result?: {\n value?: ({ data: [string, string] } | null)[];\n };\n};\n\n/**\n * Filter params for Whirlpools when invoking getProgramAccounts.\n */\ntype ListWhirlpoolParams = {\n programId: Address;\n configId: Address;\n};\n\n/**\n * Tuple containing Whirlpool address and parsed account data.\n */\ntype WhirlpoolAccount = [Address, WhirlpoolData];\n\n/**\n * Data access layer to access Whirlpool related accounts\n * Includes internal cache that can be refreshed by the client.\n *\n * @category Core\n */\nexport class AccountFetcher {\n private readonly connection: Connection;\n private readonly _cache: Record<string, CachedContent<CachedValue>> = {};\n private _accountRentExempt: number | undefined;\n\n constructor(connection: Connection, cache?: Record<string, CachedContent<CachedValue>>) {\n this.connection = connection;\n this._cache = cache ?? {};\n }\n\n /*** Public Methods ***/\n\n /**\n * Retrieve minimum balance for rent exemption of a Token Account;\n *\n * @param refresh force refresh of account rent exemption\n * @returns minimum balance for rent exemption\n */\n public async getAccountRentExempt(refresh: boolean = false) {\n // This value should be relatively static or at least not break according to spec\n // https://docs.solana.com/developing/programming-model/accounts#rent-exemption\n if (!this._accountRentExempt || refresh) {\n this._accountRentExempt = await this.connection.getMinimumBalanceForRentExemption(\n AccountLayout.span\n );\n }\n return this._accountRentExempt;\n }\n\n /**\n * Retrieve a cached whirlpool account. Fetch from rpc on cache miss.\n *\n * @param address whirlpool address\n * @param refresh force cache refresh\n * @returns whirlpool account\n */\n public async getPool(address: Address, refresh = false): Promise<WhirlpoolData | null> {\n return this.get(AddressUtil.toPubKey(address), ParsableWhirlpool, refresh);\n }\n\n /**\n * Retrieve a cached position account. Fetch from rpc on cache miss.\n *\n * @param address position address\n * @param refresh force cache refresh\n * @returns position account\n */\n public async getPosition(address: Address, refresh = false): Promise<PositionData | null> {\n return this.get(AddressUtil.toPubKey(address), ParsablePosition, refresh);\n }\n\n /**\n * Retrieve a cached tick array account. Fetch from rpc on cache miss.\n *\n * @param address tick array address\n * @param refresh force cache refresh\n * @returns tick array account\n */\n public async getTickArray(address: Address, refresh = false): Promise<TickArrayData | null> {\n return this.get(AddressUtil.toPubKey(address), ParsableTickArray, refresh);\n }\n\n /**\n * Retrieve a cached fee tier account. Fetch from rpc on cache miss.\n *\n * @param address fee tier address\n * @param refresh force cache refresh\n * @returns fee tier account\n */\n public async getFeeTier(address: Address, refresh = false): Promise<FeeTierData | null> {\n return this.get(AddressUtil.toPubKey(address), ParsableFeeTier, refresh);\n }\n\n /**\n * Retrieve a cached token info account. Fetch from rpc on cache miss.\n *\n * @param address token info address\n * @param refresh force cache refresh\n * @returns token info account\n */\n public async getTokenInfo(address: Address, refresh = false): Promise<AccountInfo | null> {\n return this.get(AddressUtil.toPubKey(address), ParsableTokenInfo, refresh);\n }\n\n /**\n * Retrieve a cached mint info account. Fetch from rpc on cache miss.\n *\n * @param address mint info address\n * @param refresh force cache refresh\n * @returns mint info account\n */\n public async getMintInfo(address: Address, refresh = false): Promise<MintInfo | null> {\n return this.get(AddressUtil.toPubKey(address), ParsableMintInfo, refresh);\n }\n\n /**\n * Retrieve a cached whirlpool config account. Fetch from rpc on cache miss.\n *\n * @param address whirlpool config address\n * @param refresh force cache refresh\n * @returns whirlpool config account\n */\n public async getConfig(address: Address, refresh = false): Promise<WhirlpoolsConfigData | null> {\n return this.get(AddressUtil.toPubKey(address), ParsableWhirlpoolsConfig, refresh);\n }\n\n /**\n * Retrieve a list of cached whirlpool accounts. Fetch from rpc for cache misses.\n *\n * @param addresses whirlpool addresses\n * @param refresh force cache refresh\n * @returns whirlpool accounts\n */\n public async listPools(\n addresses: Address[],\n refresh: boolean\n ): Promise<(WhirlpoolData | null)[]> {\n return this.list(AddressUtil.toPubKeys(addresses), ParsableWhirlpool, refresh);\n }\n\n /**\n * Retrieve a list of cached whirlpool addresses and accounts filtered by the given params using\n * getProgramAccounts.\n *\n * @param params whirlpool filter params\n * @returns tuple of whirlpool addresses and accounts\n */\n public async listPoolsWithParams({\n programId,\n configId,\n }: ListWhirlpoolParams): Promise<WhirlpoolAccount[]> {\n const filters = [\n { dataSize: WHIRLPOOL_ACCOUNT_SIZE },\n {\n memcmp: WHIRLPOOL_CODER.memcmp(\n AccountName.Whirlpool,\n AddressUtil.toPubKey(configId).toBuffer()\n ),\n },\n ];\n\n const accounts = await this.connection.getProgramAccounts(AddressUtil.toPubKey(programId), {\n filters,\n });\n\n const parsedAccounts: WhirlpoolAccount[] = [];\n accounts.forEach(({ pubkey, account }) => {\n const parsedAccount = ParsableWhirlpool.parse(account.data);\n invariant(!!parsedAccount, `could not parse whirlpool: ${pubkey.toBase58()}`);\n parsedAccounts.push([pubkey, parsedAccount]);\n this._cache[pubkey.toBase58()] = { entity: ParsableWhirlpool, value: parsedAccount };\n });\n\n return parsedAccounts;\n }\n\n /**\n * Retrieve a list of cached position accounts. Fetch from rpc for cache misses.\n *\n * @param addresses position addresses\n * @param refresh force cache refresh\n * @returns position accounts\n */\n public async listPositions(\n addresses: Address[],\n refresh: boolean\n ): Promise<(PositionData | null)[]> {\n return this.list(AddressUtil.toPubKeys(addresses), ParsablePosition, refresh);\n }\n\n /**\n * Retrieve a list of cached tick array accounts. Fetch from rpc for cache misses.\n *\n * @param addresses tick array addresses\n * @param refresh force cache refresh\n * @returns tick array accounts\n */\n public async listTickArrays(\n addresses: Address[],\n refresh: boolean\n ): Promise<(TickArrayData | null)[]> {\n return this.list(AddressUtil.toPubKeys(addresses), ParsableTickArray, refresh);\n }\n\n /**\n * Retrieve a list of cached token info accounts. Fetch from rpc for cache misses.\n *\n * @param addresses token info addresses\n * @param refresh force cache refresh\n * @returns token info accounts\n */\n public async listTokenInfos(\n addresses: Address[],\n refresh: boolean\n ): Promise<(AccountInfo | null)[]> {\n return this.list(AddressUtil.toPubKeys(addresses), ParsableTokenInfo, refresh);\n }\n\n /**\n * Retrieve a list of cached mint info accounts. Fetch from rpc for cache misses.\n *\n * @param addresses mint info addresses\n * @param refresh force cache refresh\n * @returns mint info accounts\n */\n public async listMintInfos(addresses: Address[], refresh: boolean): Promise<(MintInfo | null)[]> {\n return this.list(AddressUtil.toPubKeys(addresses), ParsableMintInfo, refresh);\n }\n\n /**\n * Update the cached value of all entities currently in the cache.\n * Uses batched rpc request for network efficient fetch.\n */\n public async refreshAll(): Promise<void> {\n const addresses: string[] = Object.keys(this._cache);\n const data = await this.bulkRequest(addresses);\n\n for (const [idx, [key, cachedContent]] of Object.entries(this._cache).entries()) {\n const entity = cachedContent.entity;\n const value = entity.parse(data[idx]);\n\n this._cache[key] = { entity, value };\n }\n }\n\n /*** Private Methods ***/\n\n /**\n * Retrieve from cache or fetch from rpc, an account\n */\n private async get<T extends CachedValue>(\n address: PublicKey,\n entity: ParsableEntity<T>,\n refresh: boolean\n ): Promise<T | null> {\n const key = address.toBase58();\n const cachedValue: CachedValue | null | undefined = this._cache[key]?.value;\n\n if (cachedValue !== undefined && !refresh) {\n return cachedValue as T | null;\n }\n\n const accountInfo = await this.connection.getAccountInfo(address);\n const accountData = accountInfo?.data;\n const value = entity.parse(accountData);\n this._cache[key] = { entity, value };\n\n return value;\n }\n\n /**\n * Retrieve from cache or fetch from rpc, a list of accounts\n */\n private async list<T extends CachedValue>(\n addresses: PublicKey[],\n entity: ParsableEntity<T>,\n refresh: boolean\n ): Promise<(T | null)[]> {\n const keys = addresses.map((address) => address.toBase58());\n const cachedValues: [string, CachedValue | null | undefined][] = keys.map((key) => [\n key,\n refresh ? undefined : this._cache[key]?.value,\n ]);\n\n /* Look for accounts not found in cache */\n const undefinedAccounts: { cacheIndex: number; key: string }[] = [];\n cachedValues.forEach(([key, value], cacheIndex) => {\n if (value === undefined) {\n undefinedAccounts.push({ cacheIndex, key });\n }\n });\n\n /* Fetch accounts not found in cache */\n if (undefinedAccounts.length > 0) {\n const data = await this.bulkRequest(undefinedAccounts.map((account) => account.key));\n undefinedAccounts.forEach(({ cacheIndex, key }, dataIndex) => {\n const value = entity.parse(data[dataIndex]);\n invariant(cachedValues[cacheIndex]?.[1] === undefined, \"unexpected non-undefined value\");\n cachedValues[cacheIndex] = [key, value];\n this._cache[key] = { entity, value };\n });\n }\n\n const result = cachedValues\n .map(([_, value]) => value)\n .filter((value): value is T | null => value !== undefined);\n invariant(result.length === addresses.length, \"not enough results fetched\");\n return result;\n }\n\n /**\n * Make batch rpc request\n */\n private async bulkRequest(addresses: string[]): Promise<(Buffer | null)[]> {\n const responses: Promise<GetMultipleAccountsResponse>[] = [];\n const chunk = 100; // getMultipleAccounts has limitation of 100 accounts per request\n\n for (let i = 0; i < addresses.length; i += chunk) {\n const addressesSubset = addresses.slice(i, i + chunk);\n const res = (this.connection as any)._rpcRequest(\"getMultipleAccounts\", [\n addressesSubset,\n { commitment: this.connection.commitment },\n ]);\n responses.push(res);\n }\n\n const combinedResult: (Buffer | null)[] = [];\n\n (await Promise.all(responses)).forEach((res) => {\n invariant(!res.error, `bulkRequest result error: ${res.error}`);\n invariant(!!res.result?.value, \"bulkRequest no value\");\n\n res.result.value.forEach((account) => {\n if (!account || account.data[1] !== \"base64\") {\n combinedResult.push(null);\n } else {\n combinedResult.push(Buffer.from(account.data[0], account.data[1]));\n }\n });\n });\n\n invariant(combinedResult.length === addresses.length, \"bulkRequest not enough results\");\n return combinedResult;\n }\n}\n","import { AccountInfo, MintInfo, MintLayout, u64 } from \"@solana/spl-token\";\nimport { PublicKey } from \"@solana/web3.js\";\nimport {\n WhirlpoolsConfigData,\n WhirlpoolData,\n PositionData,\n TickArrayData,\n AccountName,\n FeeTierData,\n} from \"../../types/public\";\nimport { BorshAccountsCoder, Idl } from \"@project-serum/anchor\";\nimport * as WhirlpoolIDL from \"../../artifacts/whirlpool.json\";\nimport { TokenUtil } from \"@orca-so/common-sdk\";\n\n/**\n * Static abstract class definition to parse entities.\n * @category Parsables\n */\nexport interface ParsableEntity<T> {\n /**\n * Parse account data\n *\n * @param accountData Buffer data for the entity\n * @returns Parsed entity\n */\n parse: (accountData: Buffer | undefined | null) => T | null;\n}\n\n/**\n * @category Parsables\n */\n@staticImplements<ParsableEntity<WhirlpoolsConfigData>>()\nexport class ParsableWhirlpoolsConfig {\n private constructor() {}\n\n public static parse(data: Buffer | undefined | null): WhirlpoolsConfigData | null {\n if (!data) {\n return null;\n }\n\n try {\n return parseAnchorAccount(AccountName.WhirlpoolsConfig, data);\n } catch (e) {\n console.error(`error while parsing WhirlpoolsConfig: ${e}`);\n return null;\n }\n }\n}\n\n/**\n * @category Parsables\n */\n@staticImplements<ParsableEntity<WhirlpoolData>>()\nexport class ParsableWhirlpool {\n private constructor() {}\n\n public static parse(data: Buffer | undefined | null): WhirlpoolData | null {\n if (!data) {\n return null;\n }\n\n try {\n return parseAnchorAccount(AccountName.Whirlpool, data);\n } catch (e) {\n console.error(`error while parsing Whirlpool: ${e}`);\n return null;\n }\n }\n}\n\n/**\n * @category Parsables\n */\n@staticImplements<ParsableEntity<PositionData>>()\nexport class ParsablePosition {\n private constructor() {}\n\n public static parse(data: Buffer | undefined | null): PositionData | null {\n if (!data) {\n return null;\n }\n\n try {\n return parseAnchorAccount(AccountName.Position, data);\n } catch (e) {\n console.error(`error while parsing Position: ${e}`);\n return null;\n }\n }\n}\n\n/**\n * @category Parsables\n */\n@staticImplements<ParsableEntity<TickArrayData>>()\nexport class ParsableTickArray {\n private constructor() {}\n\n public static parse(data: Buffer | undefined | null): TickArrayData | null {\n if (!data) {\n return null;\n }\n\n try {\n return parseAnchorAccount(AccountName.TickArray, data);\n } catch (e) {\n console.error(`error while parsing TickArray: ${e}`);\n return null;\n }\n }\n}\n\n/**\n * @category Parsables\n */\n@staticImplements<ParsableEntity<FeeTierData>>()\nexport class ParsableFeeTier {\n private constructor() {}\n\n public static parse(data: Buffer | undefined | null): FeeTierData | null {\n if (!data) {\n return null;\n }\n\n try {\n return parseAnchorAccount(AccountName.FeeTier, data);\n } catch (e) {\n console.error(`error while parsing FeeTier: ${e}`);\n return null;\n }\n }\n}\n\n/**\n * @category Parsables\n */\n@staticImplements<ParsableEntity<AccountInfo>>()\nexport class ParsableTokenInfo {\n private constructor() {}\n\n public static parse(data: Buffer | undefined | null): AccountInfo | null {\n if (!data) {\n return null;\n }\n\n try {\n return TokenUtil.deserializeTokenAccount(data);\n } catch (e) {\n console.error(`error while parsing TokenAccount: ${e}`);\n return null;\n }\n }\n}\n\n/**\n * @category Parsables\n */\n@staticImplements<ParsableEntity<MintInfo>>()\nexport class ParsableMintInfo {\n private constructor() {}\n\n public static parse(data: Buffer | undefined | null): MintInfo | null {\n if (!data) {\n return null;\n }\n\n try {\n const buffer = MintLayout.decode(data);\n const mintInfo: MintInfo = {\n mintAuthority:\n buffer.mintAuthorityOption === 0 ? null : new PublicKey(buffer.mintAuthority),\n supply: u64.fromBuffer(buffer.supply),\n decimals: buffer.decimals,\n isInitialized: buffer.isInitialized !== 0,\n freezeAuthority:\n buffer.freezeAuthority === 0 ? null : new PublicKey(buffer.freezeAuthority),\n };\n\n return mintInfo;\n } catch (e) {\n console.error(`error while parsing MintInfo: ${e}`);\n return null;\n }\n }\n}\n\n/**\n * Class decorator to define an interface with static methods\n * Reference: https://github.com/Microsoft/TypeScript/issues/13462#issuecomment-295685298\n */\nfunction staticImplements<T>() {\n return <U extends T>(constructor: U) => {\n constructor;\n };\n}\n\nconst WhirlpoolCoder = new BorshAccountsCoder(WhirlpoolIDL as Idl);\n\nfunction parseAnchorAccount(accountName: AccountName, data: Buffer) {\n const discriminator = BorshAccountsCoder.accountDiscriminator(accountName);\n if (discriminator.compare(data.slice(0, 8))) {\n console.error(\"incorrect account name during parsing\");\n return null;\n }\n\n try {\n return WhirlpoolCoder.decode(accountName, data);\n } catch (_e) {\n console.error(\"unknown account name during parsing\");\n return null;\n }\n}\n","import { BN } from \"@project-serum/anchor\";\nimport { PublicKey } from \"@solana/web3.js\";\n\n/**\n * Program ID hosting Orca's Whirlpool program.\n * @category Constants\n */\nexport const ORCA_WHIRLPOOL_PROGRAM_ID = new PublicKey(\n \"whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc\"\n);\n\n/**\n * Orca's WhirlpoolsConfig PublicKey.\n * @category Constants\n */\nexport const ORCA_WHIRLPOOLS_CONFIG = new PublicKey(\"2LecshUwdy9xi7meFgHtFJQNSKk4KdTrcpvaB56dP2NQ\");\n\n/**\n * The number of rewards supported by this whirlpool.\n * @category Constants\n */\nexport const NUM_REWARDS = 3;\n\n/**\n * The maximum tick index supported by the Whirlpool program.\n * @category Constants\n */\nexport const MAX_TICK_INDEX = 443636;\n\n/**\n * The minimum tick index supported by the Whirlpool program.\n * @category Constants\n */\nexport const MIN_TICK_INDEX = -443636;\n\n/**\n * The maximum sqrt-price supported by the Whirlpool program.\n * @category Constants\n */\nexport const MAX_SQRT_PRICE = \"79226673515401279992447579055\";\n\n/**\n * The minimum sqrt-price supported by the Whirlpool program.\n * @category Constants\n */\nexport const MIN_SQRT_PRICE = \"4295048016\";\n\n/**\n * The number of initialized ticks that a tick-array account can hold.\n * @category Constants\n */\nexport const TICK_ARRAY_SIZE = 88;\n\n/**\n * @category Constants\n */\nexport const METADATA_PROGRAM_ADDRESS = new PublicKey(\n \"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s\"\n);\n\n/**\n * The maximum number of tick-arrays that can traversed across in a swap.\n * @category Constants\n */\nexport const MAX_SWAP_TICK_ARRAYS = 3;\n\n/**\n * The denominator which the protocol fee rate is divided on.\n * @category Constants\n */\nexport const PROTOCOL_FEE_RATE_MUL_VALUE = new BN(10_000);\n\n/**\n * The denominator which the fee rate is divided on.\n * @category Constants\n */\nexport const FEE_RATE_MUL_VALUE = new BN(1_000_000);\n","import { BN, BorshAccountsCoder, Idl } from \"@project-serum/anchor\";\nimport WhirlpoolIDL from \"../../artifacts/whirlpool.json\";\nimport { PublicKey } from \"@solana/web3.js\";\n\n/**\n * This file contains the types that has the same structure as the types anchor functions returns.\n * These types are hard-casted by the client function.\n *\n * This file must be manually updated every time the idl updates as accounts will\n * be hard-casted to fit the type.\n */\n\n/**\n * Supported parasable account names from the Whirlpool contract.\n * @category Parsables\n */\nexport enum AccountName {\n WhirlpoolsConfig = \"WhirlpoolsConfig\",\n Position = \"Position\",\n TickArray = \"TickArray\",\n Whirlpool = \"Whirlpool\",\n FeeTier = \"FeeTier\",\n}\n\nconst IDL = WhirlpoolIDL as Idl;\nexport const WHIRLPOOL_CODER = new BorshAccountsCoder(IDL);\n\n/**\n * Size of the Whirlpool account in bytes.\n */\nexport const WHIRLPOOL_ACCOUNT_SIZE = WHIRLPOOL_CODER.size(IDL.accounts![4]);\n\n/**\n * @category Solana Accounts\n */\nexport type WhirlpoolsConfigData = {\n feeAuthority: PublicKey;\n collectProtocolFeesAuthority: PublicKey;\n rewardEmissionsSuperAuthority: PublicKey;\n defaultFeeRate: number;\n defaultProtocolFeeRate: number;\n};\n\n/**\n * @category Solana Accounts\n */\nexport type WhirlpoolRewardInfoData = {\n mint: PublicKey;\n vault: PublicKey;\n authority: PublicKey;\n emissionsPerSecondX64: BN;\n growthGlobalX64: BN;\n};\n\n/**\n * @category Solana Accounts\n */\nexport type WhirlpoolBumpsData = {\n whirlpoolBump: number;\n};\n\n/**\n * @category Solana Accounts\n */\nexport type WhirlpoolData = {\n whirlpoolsConfig: PublicKey;\n whirlpoolBump: number[];\n feeRate: number;\n protocolFeeRate: number;\n liquidity: BN;\n sqrtPrice: BN;\n tickCurrentIndex: number;\n protocolFeeOwedA: BN;\n protocolFeeOwedB: BN;\n tokenMintA: PublicKey;\n tokenVaultA: PublicKey;\n feeGrowthGlobalA: BN;\n tokenMintB: PublicKey;\n tokenVaultB: PublicKey;\n feeGrowthGlobalB: BN;\n rewardLastUpdatedTimestamp: BN;\n rewardInfos: WhirlpoolRewardInfoData[];\n tickSpacing: number;\n};\n\n/**\n * @category Solana Accounts\n */\nexport type TickArrayData = {\n whirlpool: PublicKey;\n startTickIndex: number;\n ticks: TickData[];\n};\n\n/**\n * @category Solana Accounts\n */\nexport type TickData = {\n initialized: boolean;\n liquidityNet: BN;\n liquidityGross: BN;\n feeGrowthOutsideA: BN;\n feeGrowthOutsideB: BN;\n rewardGrowthsOutside: BN[];\n};\n\n/**\n * @category Solana Accounts\n */\nexport type PositionRewardInfoData = {\n growthInsideCheckpoint: BN;\n amountOwed: BN;\n};\n\n/**\n * @category Solana Accounts\n */\nexport type OpenPositionBumpsData = {\n positionBump: number;\n};\n\n/**\n * @category Solana Accounts\n */\nexport type OpenPositionWithMetadataBumpsData = {\n positionBump: number;\n metadataBump: number;\n};\n\n/**\n * @category Solana Accounts\n */\nexport type PositionData = {\n whirlpool: PublicKey;\n positionMint: PublicKey;\n liquidity: BN;\n tickLowerIndex: number;\n tickUpperIndex: number;\n feeGrowthCheckpointA: BN;\n feeOwedA: BN;\n feeGrowthCheckpointB: BN;\n feeOwedB: BN;\n rewardInfos: PositionRewardInfoData[];\n};\n\n/**\n * @category Solana Accounts\n */\nexport type FeeTierData = {\n whirlpoolsConfig: PublicKey;\n tickSpacing: number;\n defaultFeeRate: number;\n};\n","import { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\nimport { Instruction } from \"@orca-so/common-sdk\";\nimport { PublicKey } from \"@solana/web3.js\";\nimport { Program } from \"@project-serum/anchor\";\nimport { Whirlpool } from \"../artifacts/whirlpool\";\n\n/**\n * Parameters to close a position in a Whirlpool.\n *\n * @category Instruction Types\n * @param receiver - PublicKey for the wallet that will receive the rented lamports.\n * @param position - PublicKey for the position.\n * @param positionMint - PublicKey for the mint token for the Position token.\n * @param positionTokenAccount - The associated token address for the position token in the owners wallet.\n * @param positionAuthority - Authority that owns the position token.\n */\nexport type ClosePositionParams = {\n receiver: PublicKey;\n position: PublicKey;\n positionMint: PublicKey;\n positionTokenAccount: PublicKey;\n positionAuthority: PublicKey;\n};\n\n/**\n * Close a position in a Whirlpool. Burns the position token in the owner's wallet.\n *\n * @category Instructions\n * @param context - Context object containing services required to generate the instruction\n * @param params - ClosePositionParams object\n * @returns - Instruction to perform the action.\n */\nexport function closePositionIx(\n program: Program<Whirlpool>,\n params: ClosePositionParams\n): Instruction {\n const {\n positionAuthority,\n receiver: receiver,\n position: position,\n positionMint: positionMint,\n positionTokenAccount,\n } = params;\n\n const ix = program.instruction.closePosition({\n accounts: {\n positionAuthority,\n receiver,\n position,\n positionMint,\n positionTokenAccount,\n tokenProgram: TOKEN_PROGRAM_ID,\n },\n });\n\n return {\n instructions: [ix],\n cleanupInstructions: [],\n signers: [],\n };\n}\n","import { Program } from \"@project-serum/anchor\";\nimport { Whirlpool } from \"../artifacts/whirlpool\";\nimport { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\nimport { PublicKey } from \"@solana/web3.js\";\n\nimport { Instruction } from \"@orca-so/common-sdk\";\n\n/**\n * Parameters to collect fees from a position.\n *\n * @category Instruction Types\n * @param whirlpool - PublicKey for the whirlpool that the position will be opened for.\n * @param position - PublicKey for the position will be opened for.\n * @param positionTokenAccount - PublicKey for the position token's associated token address.\n * @param tokenOwnerAccountA - PublicKey for the token A account that will be withdrawed from.\n * @param tokenOwnerAccountB - PublicKey for the token B account that will be withdrawed from.\n * @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool.\n * @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool.\n * @param positionAuthority - authority that owns the token corresponding to this desired position.\n */\nexport type CollectFeesParams = {\n whirlpool: PublicKey;\n position: PublicKey;\n positionTokenAccount: PublicKey;\n tokenOwnerAccountA: PublicKey;\n tokenOwnerAccountB: PublicKey;\n tokenVaultA: PublicKey;\n tokenVaultB: PublicKey;\n positionAuthority: PublicKey;\n};\n\n/**\n * Collect fees accrued for this position.\n * Call updateFeesAndRewards before this to update the position to the newest accrued values.\n *\n * @category Instructions\n * @param context - Context object containing services required to generate the instruction\n * @param params - CollectFeesParams object\n * @returns - Instruction to perform the action.\n */\nexport function collectFeesIx(program: Program<Whirlpool>, params: CollectFeesParams): Instruction {\n const {\n whirlpool,\n positionAuthority,\n position,\n positionTokenAccount,\n tokenOwnerAccountA,\n tokenOwnerAccountB,\n tokenVaultA,\n tokenVaultB,\n } = params;\n\n const ix = program.instruction.collectFees({\n accounts: {\n whirlpool,\n positionAuthority,\n position,\n positionTokenAccount,\n tokenOwnerAccountA,\n tokenOwnerAccountB,\n tokenVaultA,\n tokenVaultB,\n tokenProgram: TOKEN_PROGRAM_ID,\n },\n });\n\n return {\n instructions: [ix],\n cleanupInstructions: [],\n signers: [],\n };\n}\n","import { Program } from \"@project-serum/anchor\";\nimport { Whirlpool } from \"../artifacts/whirlpool\";\nimport { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\nimport { Instruction } from \"@orca-so/common-sdk\";\nimport { PublicKey } from \"@solana/web3.js\";\n\n/**\n * Parameters to collect protocol fees for a Whirlpool\n *\n * @category Instruction Types\n * @param whirlpoolsConfig - The public key for the WhirlpoolsConfig this pool is initialized in\n * @param whirlpool - PublicKey for the whirlpool that the position will be opened for.\n * @param tokenVaultA - PublicKey for the tokenA vault for this whirlpool.\n * @param tokenVaultB - PublicKey for the tokenB vault for this whirlpool.\n * @param tokenOwnerAccountA - PublicKey for the associated token account for tokenA in the collection wallet\n * @param tokenOwnerAccountB - PublicKey for the associated token account for tokenA in the collection wallet\n * @param collectProtocolFeesAuthority - assigned authority in the WhirlpoolsConfig that can collect protocol fees\n */\nexport type CollectProtocolFeesParams = {\n whirlpoolsConfig: PublicKey;\n whirlpool: PublicKey;\n tokenVaultA: PublicKey;\n tokenVaultB: PublicKey;\n tokenOwnerAccountA: PublicKey;\n tokenOwnerAccountB: PublicKey;\n collectProtocolFeesAuthority: PublicKey;\n};\n\n/**\n * Collect protocol fees accrued in this Whirlpool.\n *\n * @category Instructions\n * @param context - Context object containing services required to generate the instruction\n * @param params - CollectProtocolFeesParams object\n * @returns - Instruction to perform the action.\n */\nexport function collectProtocolFeesIx(\n program: Program<Whirlpool>,\n params: CollectProtocolFeesParams\n): Instruction {\n const {\n whirlpoolsConfig,\n whirlpool,\n collectProtocolFeesAuthority,\n tokenVaultA,\n tokenVaultB,\n tokenOwnerAccountA: tokenDestinationA,\n tokenOwnerAccountB: tokenDestinationB,\n } = params;\n\n const ix = program.instruction.collectProtocolFees({\n accounts: {\n whirlpoolsConfig,\n whirlpool,\n collectProtocolFeesAuthority,\n tokenVaultA,\n tokenVaultB,\n tokenDestinationA,\n tokenDestinationB,\n tokenProgram: TOKEN_PROGRAM_ID,\n },\n });\n\n return {\n instructions: [ix],\n cleanupInstructions: [],\n signers: [],\n };\n}\n","import { Program } from \"@project-serum/anchor\";\nimport { Whirlpool } from \"../artifacts/whirlpool\";\nimport { TOKEN_PROGRAM_ID } from \"@solana/spl-token\";\nimport { Instruction } from \"@orca-so/common-sdk\";\nimport { PublicKey } from \"@solana/web3.js\";\n\n/**\n * Parameters to collect rewards from a reward index in a position.\n *\n * @category Instruction Types\n * @param whirlpool - PublicKey for the whirlpool that the position will be opened for.\n * @param position - PublicKey for the position will be opened for.\n * @param positionTokenAccount - PublicKey for the position token's associated token address.\n * @param rewardIndex - The reward index that we'd like to initialize. (0 <= index <= NUM_REWARDS).\n * @param rewardOwnerAccount - PublicKey for the reward token account that the reward will deposit into.\n * @param rewardVault - PublicKey of the vault account that reward will be withdrawn from.\n * @param positionAuthority - authority that owns the token corresponding to this desired position.\n */\nexport type CollectRewardParams = {\n whirlpool: PublicKey;\n position: PublicKey;\n positionTokenAccount: PublicKey;\n rewardIndex: number;\n rewardOwnerAccount: PublicKey;\n rewardVault: PublicKey;\n positionAuthority: PublicKey;\n};\n\n/**\n * Collect rewards accrued for this reward index in a position.\n * Call updateFeesAndRewards before this to update the position to the newest accrued values.\n *\n * @category Instructions\n * @param context - Context object containing services required to generate the instruction\n * @param params - CollectRewardParams object\n * @returns - Instruction to perform the action.\n */\nexport function collectRewardIx(\n program: Program<Whirlpool>,\n params: CollectRewardParams\n): Instruction {\n const {\n whirlpool,\n positionAuthority,\n position,\n positionTokenAccount,\n rewardOwnerAccount,\n rewardVault,\n rewardIndex,\n } = params;\n\n const ix = program.instruction.collectReward(rewardIndex, {\n accounts: {\n whirlpool,\n positionAuthority,\n position,\n positionTokenAccount,\n rewardOwnerAccount,\n rewardVault,\n tokenProgram: TOKEN_PROGRAM_ID,\n },\n });\n\n return {\n instructions: [ix],\n cleanupInstructions: [],\n signers: [],\n };\n}\n","import { Instruction, TokenUtil, TransactionBuilder, ZERO } from \"@orca-so/common-sdk\";\nimport { createWSOLAccountInstructions } from \"@orca-so/common-sdk/dist/helpers/token-instructions\";\nimport { Address } from \"@project-serum/anchor\";\nimport { NATIVE_MINT } from \"@solana/spl-token\";\nimport { PACKET_DATA_SIZE, PublicKey } from \"@solana/web3.js\";\nimport { PositionData, WhirlpoolContext } from \"../..\";\nimport { WhirlpoolIx } from \"../../ix\";\nimport { WhirlpoolData } from \"../../types/public\";\nimport { PDAUtil, PoolUtil, TickUtil } from \"../../utils/public\";\nimport { getAssociatedTokenAddressSync } from \"../../utils/spl-token-utils\";\nimport { convertListToMap } from \"../../utils/txn-utils\";\nimport { getTokenMintsFromWhirlpools, resolveAtaForMints } from \"../../utils/whirlpool-ata-utils\";\nimport { updateFeesAndRewardsIx } from \"../update-fees-and-rewards-ix\";\n\n/**\n * Parameters to collect all fees and rewards from a list of positions.\n *\n * @category Instruction Types\n * @param positionAddrs - An array of Whirlpool position addresses.\n * @param receiver - The destination wallet that collected fees & reward will be sent to. Defaults to ctx.wallet key.\n * @param positionOwner - The wallet key that contains the position token. Defaults to ctx.wallet key.\n * @param positionAuthority - The authority key that can authorize operation on the position. Defaults to ctx.wallet key.\n * @param payer - The key that will pay for the initialization of ATA token accounts. Defaults to ctx.wallet key.\n */\nexport type CollectAllPositionAddressParams = {\n positions: Address[];\n} & CollectAllParams;\n\n/**\n * Parameters to collect all fees and rewards from a list of positions.\n *\n * @category Instruction Types\n * @param positions - An array of Whirlpool positions.\n * @param receiver - The destination wallet that collected fees & reward will be sent to. Defaults to ctx.wallet key.\n * @param positionOwner - The wallet key that contains the position token. Defaults to ctx.wallet key.\n * @param positionAuthority - The authority key that can authorize operation on the position. Defaults to ctx.wallet key.\n * @param payer - The key that will pay for the initialization of ATA token accounts. Defaults to ctx.wallet key.\n */\nexport type CollectAllPositionParams = {\n positions: Record<string, PositionData>;\n} & CollectAllParams;\n\ntype CollectAllParams = {\n receiver?: PublicKey;\n positionOwner?: PublicKey;\n positionAuthority?: PublicKey;\n payer?: PublicKey;\n};\n\n/**\n * Build a set of transactions to collect fees and rewards for a set of Whirlpool Positions.\n *\n * @category Instructions\n * @experimental\n * @param ctx - WhirlpoolContext object for the current environment.\n * @param params - CollectAllPositionAddressParams object\n * @param refresh - if true, will always fetch for the latest on-chain data.\n * @returns A set of transaction-builders to resolve ATA for affliated tokens, collect fee & rewards for all positions.\n * The first transaction should always be processed as it contains all the resolve ATA instructions to receive tokens.\n */\nexport async function collectAllForPositionAddressesTxns(\n ctx: WhirlpoolContext,\n params: CollectAllPositionAddressParams,\n refresh = false\n): Promise<TransactionBuilder[]> {\n const { positions, ...rest } = params;\n const posData = convertListToMap(\n await ctx.fetcher.listPositions(positions, refresh),\n positions.map((pos) => pos.toString())\n );\n const positionMap: Record<string, PositionData> = {};\n Object.entries(posData).forEach(([addr, pos]) => {\n if (pos) {\n positionMap[addr] = pos;\n }\n });\n\n return collectAllForPositionsTxns(ctx, { positions: positionMap, ...rest });\n}\n\n/**\n * Build a set of transactions to collect fees and rewards for a set of Whirlpool Positions.\n *\n * @experimental\n * @param ctx - WhirlpoolContext object for the current environment.\n * @param params - CollectAllPositionParams object\n * @returns A set of transaction-builders to resolve ATA for affliated tokens, collect fee & rewards for all positions.\n */\nexport async function collectAllForPositionsTxns(\n ctx: WhirlpoolContext,\n params: CollectAllPositionParams\n): Promise<TransactionBuilder[]> {\n const { positions, receiver, positionAuthority, positionOwner, payer } = params;\n const receiverKey = receiver ?? ctx.wallet.publicKey;\n const positionAuthorityKey = positionAuthority ?? ctx.wallet.publicKey;\n const positionOwnerKey = positionOwner ?? ctx.wallet.publicKey;\n const payerKey = payer ?? ctx.wallet.publicKey;\n const positionList = Object.entries(positions);\n\n if (positionList.length === 0) {\n return [];\n }\n\n const whirlpoolAddrs = positionList.map(([, pos]) => pos.whirlpool.toBase58());\n const whirlpoolDatas = await ctx.fetcher.listPools(whirlpoolAddrs, false);\n const whirlpools = convertListToMap(whirlpoolDatas, whirlpoolAddrs);\n\n const accountExemption = await ctx.fetcher.getAccountRentExempt();\n const { ataTokenAddresses: affliatedTokenAtaMap, resolveAtaIxs } = await resolveAtaForMints(ctx, {\n mints: getTokenMintsFromWhirlpools(whirlpoolDatas).mintMap,\n accountExemption,\n receiver: receiverKey,\n payer: payerKey,\n });\n\n const latestBlockhash = await ctx.connection.getLatestBlockhash(\"singleGossip\");\n const txBuilders: TransactionBuilder[] = [];\n\n let pendingTxBuilder = new TransactionBuilder(ctx.connection, ctx.wallet).addInstructions(\n resolveAtaIxs\n );\n let pendingTxBuilderTxSize = await pendingTxBuilder.txnSize({ latestBlockhash });\n let posIndex = 0;\n let reattempt = false;\n\n while (posIndex < positionList.length) {\n const [positionAddr, position] = positionList[posIndex];\n let positionTxBuilder = new TransactionBuilder(ctx.connection, ctx.wallet);\n const { whirlpool: whirlpoolKey, positionMint } = position;\n const whirlpool = whirlpools[whirlpoolKey.toBase58()];\n\n if (!whirlpool) {\n throw new Error(\n `Unable to process positionMint ${positionMint} - unable to derive whirlpool ${whirlpoolKey.toBase58()}`\n );\n }\n const posHandlesNativeMint =\n TokenUtil.isNativeMint(whirlpool.tokenMintA) || TokenUtil.isNativeMint(whirlpool.tokenMintB);\n const txBuilderHasNativeMint = !!affliatedTokenAtaMap[NATIVE_MINT.toBase58()];\n\n // Add NATIVE_MINT token account creation to this transaction if position requires NATIVE_MINT handling.\n if (posHandlesNativeMint && !txBuilderHasNativeMint) {\n addNativeMintHandlingIx(\n positionTxBuilder,\n affliatedTokenAtaMap,\n receiverKey,\n accountExemption\n );\n }\n\n // Build position instructions\n const collectIxForPosition = constructCollectPositionIx(\n ctx,\n new PublicKey(positionAddr),\n position,\n whirlpools,\n positionOwnerKey,\n positionAuthorityKey,\n affliatedTokenAtaMap\n );\n positionTxBuilder.addInstructions(collectIxForPosition);\n\n // Attempt to push the new instructions into the pending builder\n // Iterate to the next position if possible\n // Create a builder and reattempt if the current one is full.\n const incrementTxSize = await positionTxBuilder.txnSize({ latestBlockhash });\n if (pendingTxBuilderTxSize + incrementTxSize < PACKET_DATA_SIZE) {\n pendingTxBuilder.addInstruction(positionTxBuilder.compressIx(false));\n pendingTxBuilderTxSize = pendingTxBuilderTxSize + incrementTxSize;\n posIndex += 1;\n reattempt = false;\n } else {\n if (reattempt) {\n throw new Error(\n `Unable to fit collection ix for ${position.positionMint.toBase58()} in a Transaction.`\n );\n }\n\n txBuilders.push(pendingTxBuilder);\n delete affliatedTokenAtaMap[NATIVE_MINT.toBase58()];\n pendingTxBuilder = new TransactionBuilder(ctx.connection, ctx.provider.wallet);\n pendingTxBuilderTxSize = 0;\n reattempt = true;\n }\n }\n\n txBuilders.push(pendingTxBuilder);\n return txBuilders;\n}\n\n/**\n * Helper methods.\n */\nfunction addNativeMintHandlingIx(\n txBuilder: TransactionBuilder,\n affliatedTokenAtaMap: Record<string, PublicKey>,\n destinationWallet: PublicKey,\n accountExemption: number\n) {\n let { address: wSOLAta, ...resolveWSolIx } = createWSOLAccountInstructions(\n destinationWallet,\n ZERO,\n accountExemption\n );\n affliatedTokenAtaMap[NATIVE_MINT.toBase58()] = wSOLAta;\n txBuilder.prependInstruction(resolveWSolIx);\n}\n\n// TODO: Once individual collect ix for positions is implemented, maybe migrate over if it can take custom ATA?\nconst constructCollectPositionIx = (\n ctx: WhirlpoolContext,\n positionKey: PublicKey,\n position: PositionData,\n whirlpools: Record<string, WhirlpoolData | null>,\n positionOwner: PublicKey,\n positionAuthority: PublicKey,\n affliatedTokenAtaMap: Record<string, PublicKey>\n) => {\n const ixForPosition: Instruction[] = [];\n const {\n whirlpool: whirlpoolKey,\n liquidity,\n tickLowerIndex,\n tickUpperIndex,\n positionMint,\n rewardInfos: positionRewardInfos,\n } = position;\n const whirlpool = whirlpools[whirlpoolKey.toBase58()];\n\n if (!whirlpool) {\n throw new Error(\n `Unable to process positionMint ${positionMint} - unable to derive whirlpool ${whirlpoolKey.toBase58()}`\n );\n }\n const { tickSpacing } = whirlpool;\n\n // Update fee and reward values if necessary\n if (!liquidity.eq(ZERO)) {\n ixForPosition.push(\n updateFeesAndRewardsIx(ctx.program, {\n position: positionKey,\n whirlpool: whirlpoolKey,\n tickArrayLower: PDAUtil.getTickArray(\n ctx.program.programId,\n whirlpoolKey,\n TickUtil.getStartTickIndex(tickLowerIndex, tickSpacing)\n ).publicKey,\n tickArrayUpper: PDAUtil.getTickArray(\n ctx.program.programId,\n whirlpoolKey,\n TickUtil.getStartTickIndex(tickUpperIndex, tickSpacing)\n ).publicKey,\n })\n );\n }\n\n // Collect Fee\n const positionTokenAccount = getAssociatedTokenAddressSync(\n positionMint.toBase58(),\n positionOwner.toBase58()\n );\n ixForPosition.push(\n WhirlpoolIx.collectFeesIx(ctx.program, {\n whirlpool: whirlpoolKey,\n position: positionKey,\n positionAuthority,\n positionTokenAccount,\n tokenOwnerAccountA: affliatedTokenAtaMap[whirlpool.tokenMintA.toBase58()],\n tokenOwnerAccountB: affliatedTokenAtaMap[whirlpool.tokenMintB.toBase58()],\n tokenVaultA: whirlpool.tokenVaultA,\n tokenVaultB: whirlpool.tokenVaultB,\n })\n );\n\n // Collect Rewards\n // TODO: handle empty vault values?\n positionRewardInfos.forEach((_, index) => {\n const rewardInfo = whirlpool.rewardInfos[index];\n if (PoolUtil.isRewardInitialized(rewardInfo)) {\n ixForPosition.push(\n WhirlpoolIx.collectRewardIx(ctx.program, {\n whirlpool: whirlpoolKey,\n position: positionKey,\n positionAuthority,\n positionTokenAccount,\n rewardIndex: index,\n rewardOwnerAccount: affliatedTokenAtaMap[rewardInfo.mint.toBase58()],\n rewardVault: rewardInfo.vault,\n })\n );\n }\n });\n\n return ixForPosition;\n};\n","import { PDA } from \"@orca-so/common-sdk\";\nimport { Program } from \"@project-serum/anchor\";\nimport { WhirlpoolContext } from \".\";\nimport { Whirlpool } from \"./artifacts/whirlpool\";\nimport * as ix from \"./instructions\";\n\n/**\n * Instruction set for the Whirlpools program.\n *\n * @category Core\n */\nexport class WhirlpoolIx {\n /**\n * Initializes a Whi