UNPKG

@orca-so/whirlpool-sdk

Version:

Whirlpool SDK for the Orca protocol.

194 lines (193 loc) 10.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildCollectFeesAndRewardsTx = exports.buildMultipleCollectFeesAndRewardsTx = void 0; const common_sdk_1 = require("@orca-so/common-sdk"); const multi_transaction_builder_1 = require("../../utils/public/multi-transaction-builder"); const whirlpool_client_sdk_1 = require("@orca-so/whirlpool-client-sdk"); const tick_util_1 = require("../../utils/whirlpool/tick-util"); const address_1 = require("../../utils/address"); const tiny_invariant_1 = __importDefault(require("tiny-invariant")); const pool_util_1 = require("../../utils/whirlpool/pool-util"); const spl_token_1 = require("@solana/spl-token"); function buildMultipleCollectFeesAndRewardsTx(dal, param) { return __awaiter(this, void 0, void 0, function* () { const { provider, positionAddresses, resolvedAssociatedTokenAddresses } = param; const ctx = whirlpool_client_sdk_1.WhirlpoolContext.withProvider(provider, dal.programId); const client = new whirlpool_client_sdk_1.WhirlpoolClient(ctx); const collectPositionTransactions = []; // If we don't create an empty map here when resolvedAssociatedTokenAddresses is undefined, // then the ataMap ends up getting set in the buildSingleCollectFeeAndRewardsTx // and not shared across the multiple fee collection txs const ataMap = resolvedAssociatedTokenAddresses !== null && resolvedAssociatedTokenAddresses !== void 0 ? resolvedAssociatedTokenAddresses : {}; for (const positionAddress of positionAddresses) { const txn = yield buildSingleCollectFeeAndRewardsTx(positionAddress, dal, client, provider, ataMap); if (!txn.isEmpty()) { collectPositionTransactions.push(txn); } } /** * TODO: Find the maximum number of collect position calls we can fit in a transaction. * Note that the calls may not be the same size. The maximum size is a collect ix where * 1. TokenMintA requires a create ATA ix * 2. TokenMintB is a SOL account. Requires the create & clean up WSOL ATA ix * 3. Position liquidity is not null. updateFee Ix is required * 4. Need to collect fees * 5. Need to collect all 3 rewards * */ const collectAllTransactionBuilder = new multi_transaction_builder_1.MultiTransactionBuilder(provider, []); collectPositionTransactions.forEach((collectTxn) => collectAllTransactionBuilder.addTxBuilder(collectTxn)); return collectAllTransactionBuilder; }); } exports.buildMultipleCollectFeesAndRewardsTx = buildMultipleCollectFeesAndRewardsTx; function buildCollectFeesAndRewardsTx(dal, param) { return __awaiter(this, void 0, void 0, function* () { const { provider, positionAddress, resolvedAssociatedTokenAddresses } = param; const ctx = whirlpool_client_sdk_1.WhirlpoolContext.withProvider(provider, dal.programId); const client = new whirlpool_client_sdk_1.WhirlpoolClient(ctx); return yield buildSingleCollectFeeAndRewardsTx(positionAddress, dal, client, provider, resolvedAssociatedTokenAddresses); }); } exports.buildCollectFeesAndRewardsTx = buildCollectFeesAndRewardsTx; function buildSingleCollectFeeAndRewardsTx(positionAddress, dal, client, provider, ataMap) { return __awaiter(this, void 0, void 0, function* () { const txn = new whirlpool_client_sdk_1.TransactionBuilder(provider); const positionInfo = yield derivePositionInfo(positionAddress, dal, provider.wallet.publicKey); if (positionInfo == null) { return txn; } const { position, whirlpool, tickArrayLower, tickArrayUpper, positionTokenAccount, nothingToCollect, } = positionInfo; if (nothingToCollect) { return txn; } if (!ataMap) { ataMap = {}; } // Derive and add the createATA instructions for each token mint. Note that // if the user already has the token ATAs, the instructions will be empty. const { tokenOwnerAccount: tokenOwnerAccountA, createTokenOwnerAccountIx: createTokenOwnerAccountAIx, } = yield getTokenAtaAndPopulateATAMap(dal, provider, whirlpool.tokenMintA, ataMap); const { tokenOwnerAccount: tokenOwnerAccountB, createTokenOwnerAccountIx: createTokenOwnerAccountBIx, } = yield getTokenAtaAndPopulateATAMap(dal, provider, whirlpool.tokenMintB, ataMap); txn.addInstruction(createTokenOwnerAccountAIx).addInstruction(createTokenOwnerAccountBIx); // If the position has zero liquidity, then the fees are already the most up to date. // No need to make an update call here. if (!position.liquidity.isZero()) { txn.addInstruction(client .updateFeesAndRewards({ whirlpool: position.whirlpool, position: (0, address_1.toPubKey)(positionAddress), tickArrayLower, tickArrayUpper, }) .compressIx(false)); } // Add a collectFee ix for this position txn.addInstruction(client .collectFeesTx({ whirlpool: position.whirlpool, positionAuthority: provider.wallet.publicKey, position: (0, address_1.toPubKey)(positionAddress), positionTokenAccount, tokenOwnerAccountA, tokenOwnerAccountB, tokenVaultA: whirlpool.tokenVaultA, tokenVaultB: whirlpool.tokenVaultB, }) .compressIx(false)); // Add a collectReward ix for a reward mint if the particular reward is initialized. for (const i of [...Array(whirlpool_client_sdk_1.NUM_REWARDS).keys()]) { const rewardInfo = whirlpool.rewardInfos[i]; (0, tiny_invariant_1.default)(!!rewardInfo, "rewardInfo cannot be undefined"); if (!pool_util_1.PoolUtil.isRewardInitialized(rewardInfo)) { continue; } const { tokenOwnerAccount: rewardOwnerAccount, createTokenOwnerAccountIx: createRewardTokenOwnerAccountIx, } = yield getTokenAtaAndPopulateATAMap(dal, provider, rewardInfo.mint, ataMap); if (createRewardTokenOwnerAccountIx) { txn.addInstruction(createRewardTokenOwnerAccountIx); } txn.addInstruction(client .collectRewardTx({ whirlpool: position.whirlpool, positionAuthority: provider.wallet.publicKey, position: (0, address_1.toPubKey)(positionAddress), positionTokenAccount, rewardOwnerAccount, rewardVault: rewardInfo.vault, rewardIndex: i, }) .compressIx(false)); } return txn; }); } function getTokenAtaAndPopulateATAMap(dal, provider, tokenMint, ataMap) { return __awaiter(this, void 0, void 0, function* () { let _tokenMintA = tokenMint.toBase58(); let tokenOwnerAccount; let createTokenOwnerAccountIx = whirlpool_client_sdk_1.EMPTY_INSTRUCTION; const mappedTokenAAddress = ataMap[_tokenMintA]; if (!mappedTokenAAddress) { const _a = yield (0, common_sdk_1.resolveOrCreateATA)(provider.connection, provider.wallet.publicKey, tokenMint, () => dal.getAccountRentExempt()), { address: _tokenOwnerAccount } = _a, _tokenOwnerAccountAIx = __rest(_a, ["address"]); tokenOwnerAccount = _tokenOwnerAccount; createTokenOwnerAccountIx = _tokenOwnerAccountAIx; if (!tokenMint.equals(spl_token_1.NATIVE_MINT)) { ataMap[_tokenMintA] = _tokenOwnerAccount; } } else { tokenOwnerAccount = mappedTokenAAddress; } return { tokenOwnerAccount, createTokenOwnerAccountIx }; }); } function derivePositionInfo(positionAddress, dal, walletKey) { return __awaiter(this, void 0, void 0, function* () { const position = yield dal.getPosition(positionAddress, false); if (!position) { return null; } const whirlpool = yield dal.getPool(position.whirlpool, false); if (!whirlpool) { return null; } const [tickArrayLower, tickArrayUpper] = tick_util_1.TickUtil.getLowerAndUpperTickArrayAddresses(position.tickLowerIndex, position.tickUpperIndex, whirlpool.tickSpacing, position.whirlpool, dal.programId); const positionTokenAccount = yield (0, common_sdk_1.deriveATA)(walletKey, position.positionMint); const nothingToCollect = position.liquidity.isZero() && !hasOwedFees(position) && !hasOwedRewards(position); return { position, whirlpool, tickArrayLower, tickArrayUpper, positionTokenAccount, nothingToCollect, }; }); } function hasOwedFees(position) { return !(position.feeOwedA.isZero() && position.feeOwedB.isZero()); } function hasOwedRewards(position) { return position.rewardInfos.some((rewardInfo) => !rewardInfo.amountOwed.isZero()); }