UNPKG

@orca-so/whirlpools-sdk

Version:

Typescript SDK to interact with Orca's Whirlpool program.

193 lines 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.collectAllForPositionAddressesTxns = collectAllForPositionAddressesTxns; exports.collectAllForPositionsTxns = collectAllForPositionsTxns; const common_sdk_1 = require("@orca-so/common-sdk"); const spl_token_1 = require("@solana/spl-token"); const web3_js_1 = require("@solana/web3.js"); const ix_1 = require("../../ix"); const fetcher_1 = require("../../network/public/fetcher"); const public_1 = require("../../utils/public"); const txn_utils_1 = require("../../utils/txn-utils"); const whirlpool_ata_utils_1 = require("../../utils/whirlpool-ata-utils"); const update_fees_and_rewards_ix_1 = require("../update-fees-and-rewards-ix"); const token_extension_util_1 = require("../../utils/public/token-extension-util"); async function collectAllForPositionAddressesTxns(ctx, params, opts = fetcher_1.PREFER_CACHE) { const { positions, ...rest } = params; const fetchedPositions = await ctx.fetcher.getPositions(positions, opts); const positionMap = {}; fetchedPositions.forEach((pos, addr) => { if (pos) { positionMap[addr] = pos; } }); return collectAllForPositionsTxns(ctx, { positions: positionMap, ...rest }); } async function collectAllForPositionsTxns(ctx, params) { const { positions, receiver, positionAuthority, positionOwner, payer } = params; const receiverKey = receiver ?? ctx.wallet.publicKey; const positionAuthorityKey = positionAuthority ?? ctx.wallet.publicKey; const positionOwnerKey = positionOwner ?? ctx.wallet.publicKey; const payerKey = payer ?? ctx.wallet.publicKey; const positionList = Object.entries(positions); if (positionList.length === 0) { return []; } const whirlpoolAddrs = positionList.map(([, pos]) => pos.whirlpool.toBase58()); const whirlpools = await ctx.fetcher.getPools(whirlpoolAddrs, fetcher_1.PREFER_CACHE); const allMints = (0, whirlpool_ata_utils_1.getTokenMintsFromWhirlpools)(Array.from(whirlpools.values())); const accountExemption = await ctx.fetcher.getAccountRentExempt(); const positionMintAddrs = positionList.map(([, pos]) => pos.positionMint); const positionMintInfos = await ctx.fetcher.getMintInfos(positionMintAddrs); await ctx.fetcher.getMintInfos(allMints.mintMap); const resolvedAtas = (0, txn_utils_1.convertListToMap)(await (0, common_sdk_1.resolveOrCreateATAs)(ctx.connection, receiverKey, allMints.mintMap.map((tokenMint) => ({ tokenMint })), async () => accountExemption, payerKey, true, ctx.accountResolverOpts.allowPDAOwnerAddress, ctx.accountResolverOpts.createWrappedSolAccountMethod), allMints.mintMap.map((mint) => mint.toBase58())); const latestBlockhash = await ctx.connection.getLatestBlockhash(); const txBuilders = []; const collectionTasks = []; positionList.forEach(([positionAddr, position]) => { const whirlpool = whirlpools.get(position.whirlpool.toBase58()); if (!whirlpool) { throw new Error(`Unable to process positionMint ${position.positionMint.toBase58()} - unable to derive whirlpool ${position.whirlpool.toBase58()}`); } const positionMintInfo = positionMintInfos.get(position.positionMint.toBase58()); if (!positionMintInfo) { throw new Error(`Unable to process positionMint ${position.positionMint.toBase58()} - missing mint info`); } collectionTasks.push({ collectionType: "fee", positionAddr, position, whirlpool, positionMintTokenProgramId: positionMintInfo.tokenProgram, }); whirlpool.rewardInfos.forEach((rewardInfo, index) => { if (public_1.PoolUtil.isRewardInitialized(rewardInfo)) { collectionTasks.push({ collectionType: "reward", rewardIndex: index, positionAddr, position, whirlpool, positionMintTokenProgramId: positionMintInfo.tokenProgram, }); } }); }); let cursor = 0; let pendingTxBuilder = null; let touchedMints = null; let lastUpdatedPosition = null; let reattempt = false; while (cursor < collectionTasks.length) { if (!pendingTxBuilder || !touchedMints) { pendingTxBuilder = new common_sdk_1.TransactionBuilder(ctx.connection, ctx.wallet, ctx.txBuilderOpts); touchedMints = new Set(); resolvedAtas[spl_token_1.NATIVE_MINT.toBase58()] = common_sdk_1.TokenUtil.createWrappedNativeAccountInstruction(receiverKey, common_sdk_1.ZERO, accountExemption, undefined, undefined, ctx.accountResolverOpts.createWrappedSolAccountMethod); } const task = collectionTasks[cursor]; const alreadyUpdated = lastUpdatedPosition === task.positionAddr; const collectIxForPosition = await constructCollectIxForPosition(ctx, task, alreadyUpdated, positionOwnerKey, positionAuthorityKey, resolvedAtas, touchedMints); const positionTxBuilder = new common_sdk_1.TransactionBuilder(ctx.connection, ctx.wallet, ctx.txBuilderOpts); positionTxBuilder.addInstructions(collectIxForPosition); const mergeable = await (0, txn_utils_1.checkMergedTransactionSizeIsValid)(ctx, [pendingTxBuilder, positionTxBuilder], latestBlockhash); if (mergeable) { pendingTxBuilder.addInstruction(positionTxBuilder.compressIx(false)); cursor += 1; lastUpdatedPosition = task.positionAddr; reattempt = false; } else { if (reattempt) { throw new Error(`Unable to fit collection ix for ${task.position.positionMint.toBase58()} in a Transaction.`); } txBuilders.push(pendingTxBuilder); pendingTxBuilder = null; touchedMints = null; lastUpdatedPosition = null; reattempt = true; } } if (pendingTxBuilder) { txBuilders.push(pendingTxBuilder); } return txBuilders; } const constructCollectIxForPosition = async (ctx, task, alreadyUpdated, positionOwner, positionAuthority, resolvedAtas, touchedMints) => { const ixForPosition = []; const { whirlpool: whirlpoolKey, liquidity, tickLowerIndex, tickUpperIndex, positionMint, } = task.position; const positionMintTokenProgramId = task.positionMintTokenProgramId; const whirlpool = task.whirlpool; const { tickSpacing } = whirlpool; const mintA = whirlpool.tokenMintA.toBase58(); const mintB = whirlpool.tokenMintB.toBase58(); const tokenExtensionCtx = await token_extension_util_1.TokenExtensionUtil.buildTokenExtensionContext(ctx.fetcher, whirlpool, fetcher_1.PREFER_CACHE); const positionTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(positionMint, positionOwner, ctx.accountResolverOpts.allowPDAOwnerAddress, positionMintTokenProgramId); if (!liquidity.eq(common_sdk_1.ZERO) && !alreadyUpdated) { ixForPosition.push((0, update_fees_and_rewards_ix_1.updateFeesAndRewardsIx)(ctx.program, { position: new web3_js_1.PublicKey(task.positionAddr), whirlpool: whirlpoolKey, tickArrayLower: public_1.PDAUtil.getTickArray(ctx.program.programId, whirlpoolKey, public_1.TickUtil.getStartTickIndex(tickLowerIndex, tickSpacing)).publicKey, tickArrayUpper: public_1.PDAUtil.getTickArray(ctx.program.programId, whirlpoolKey, public_1.TickUtil.getStartTickIndex(tickUpperIndex, tickSpacing)).publicKey, })); } if (task.collectionType === "fee") { if (!touchedMints.has(mintA)) { ixForPosition.push(resolvedAtas[mintA]); touchedMints.add(mintA); } if (!touchedMints.has(mintB)) { ixForPosition.push(resolvedAtas[mintB]); touchedMints.add(mintB); } const collectFeesBaseParams = { whirlpool: whirlpoolKey, position: new web3_js_1.PublicKey(task.positionAddr), positionAuthority, positionTokenAccount, tokenOwnerAccountA: resolvedAtas[mintA].address, tokenOwnerAccountB: resolvedAtas[mintB].address, tokenVaultA: whirlpool.tokenVaultA, tokenVaultB: whirlpool.tokenVaultB, }; ixForPosition.push(!token_extension_util_1.TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx) ? ix_1.WhirlpoolIx.collectFeesIx(ctx.program, collectFeesBaseParams) : ix_1.WhirlpoolIx.collectFeesV2Ix(ctx.program, { ...collectFeesBaseParams, tokenMintA: tokenExtensionCtx.tokenMintWithProgramA.address, tokenMintB: tokenExtensionCtx.tokenMintWithProgramB.address, tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram, tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram, ...(await token_extension_util_1.TokenExtensionUtil.getExtraAccountMetasForTransferHookForPool(ctx.connection, tokenExtensionCtx, collectFeesBaseParams.tokenVaultA, collectFeesBaseParams.tokenOwnerAccountA, collectFeesBaseParams.whirlpool, collectFeesBaseParams.tokenVaultB, collectFeesBaseParams.tokenOwnerAccountB, collectFeesBaseParams.whirlpool)), })); } else { const index = task.rewardIndex; const rewardInfo = whirlpool.rewardInfos[index]; const mintReward = rewardInfo.mint.toBase58(); if (!touchedMints.has(mintReward)) { ixForPosition.push(resolvedAtas[mintReward]); touchedMints.add(mintReward); } const collectRewardBaseParams = { whirlpool: whirlpoolKey, position: new web3_js_1.PublicKey(task.positionAddr), positionAuthority, positionTokenAccount, rewardIndex: index, rewardOwnerAccount: resolvedAtas[mintReward].address, rewardVault: rewardInfo.vault, }; ixForPosition.push(!token_extension_util_1.TokenExtensionUtil.isV2IxRequiredReward(tokenExtensionCtx, index) ? ix_1.WhirlpoolIx.collectRewardIx(ctx.program, collectRewardBaseParams) : ix_1.WhirlpoolIx.collectRewardV2Ix(ctx.program, { ...collectRewardBaseParams, rewardMint: tokenExtensionCtx.rewardTokenMintsWithProgram[index].address, rewardTokenProgram: tokenExtensionCtx.rewardTokenMintsWithProgram[index] .tokenProgram, rewardTransferHookAccounts: await token_extension_util_1.TokenExtensionUtil.getExtraAccountMetasForTransferHook(ctx.connection, tokenExtensionCtx.rewardTokenMintsWithProgram[index], collectRewardBaseParams.rewardVault, collectRewardBaseParams.rewardOwnerAccount, collectRewardBaseParams.whirlpool), })); } return ixForPosition; }; //# sourceMappingURL=collect-all-txn.js.map