UNPKG

@renec-foundation/redex-sdk

Version:

Typescript SDK to interact with Orca's Whirlpool program.

334 lines (333 loc) 20.2 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.WhirlpoolImpl = void 0; const common_sdk_1 = require("@orca-so/common-sdk"); const anchor_1 = require("@project-serum/anchor"); const spl_token_1 = require("@solana/spl-token"); const web3_js_1 = require("@solana/web3.js"); const tiny_invariant_1 = __importDefault(require("tiny-invariant")); const instructions_1 = require("../instructions"); const public_1 = require("../quotes/public"); const position_builder_util_1 = require("../utils/builder/position-builder-util"); const public_2 = require("../utils/public"); const spl_token_utils_1 = require("../utils/spl-token-utils"); const whirlpool_ata_utils_1 = require("../utils/whirlpool-ata-utils"); const position_impl_1 = require("./position-impl"); const util_1 = require("./util"); class WhirlpoolImpl { constructor(ctx, address, tokenAInfo, tokenBInfo, tokenVaultAInfo, tokenVaultBInfo, rewardInfos, data) { this.ctx = ctx; this.address = address; this.tokenAInfo = tokenAInfo; this.tokenBInfo = tokenBInfo; this.tokenVaultAInfo = tokenVaultAInfo; this.tokenVaultBInfo = tokenVaultBInfo; this.rewardInfos = rewardInfos; this.data = data; } getAddress() { return this.address; } getData() { return this.data; } getTokenAInfo() { return this.tokenAInfo; } getTokenBInfo() { return this.tokenBInfo; } getTokenVaultAInfo() { return this.tokenVaultAInfo; } getTokenVaultBInfo() { return this.tokenVaultBInfo; } getRewardInfos() { return this.rewardInfos; } refreshData() { return __awaiter(this, void 0, void 0, function* () { yield this.refresh(); return this.data; }); } openPosition(tickLower, tickUpper, liquidityInput, wallet, funder) { return __awaiter(this, void 0, void 0, function* () { yield this.refresh(); return this.getOpenPositionWithOptMetadataTx(tickLower, tickUpper, liquidityInput, !!wallet ? common_sdk_1.AddressUtil.toPubKey(wallet) : this.ctx.wallet.publicKey, !!funder ? common_sdk_1.AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey); }); } openPositionWithMetadata(tickLower, tickUpper, liquidityInput, sourceWallet, positionWallet, funder) { return __awaiter(this, void 0, void 0, function* () { yield this.refresh(); return this.getOpenPositionWithOptMetadataTx(tickLower, tickUpper, liquidityInput, !!sourceWallet ? common_sdk_1.AddressUtil.toPubKey(sourceWallet) : this.ctx.wallet.publicKey, !!funder ? common_sdk_1.AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey, true); }); } initTickArrayForTicks(ticks, funder, refresh = true) { return __awaiter(this, void 0, void 0, function* () { const initTickArrayStartPdas = yield public_2.TickArrayUtil.getUninitializedArraysPDAs(ticks, this.ctx.program.programId, this.address, this.data.tickSpacing, this.ctx.fetcher, refresh); if (!initTickArrayStartPdas.length) { return null; } const txBuilder = new common_sdk_1.TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet); initTickArrayStartPdas.forEach((initTickArrayInfo) => { txBuilder.addInstruction((0, instructions_1.initTickArrayIx)(this.ctx.program, { startTick: initTickArrayInfo.startIndex, tickArrayPda: initTickArrayInfo.pda, whirlpool: this.address, funder: !!funder ? common_sdk_1.AddressUtil.toPubKey(funder) : this.ctx.provider.wallet.publicKey, })); }); return txBuilder; }); } closePosition(positionAddress, slippageTolerance, destinationWallet, positionWallet, payer) { return __awaiter(this, void 0, void 0, function* () { yield this.refresh(); const positionWalletKey = positionWallet ? common_sdk_1.AddressUtil.toPubKey(positionWallet) : this.ctx.wallet.publicKey; const destinationWalletKey = destinationWallet ? common_sdk_1.AddressUtil.toPubKey(destinationWallet) : this.ctx.wallet.publicKey; const payerKey = payer ? common_sdk_1.AddressUtil.toPubKey(payer) : this.ctx.wallet.publicKey; return this.getClosePositionIx(common_sdk_1.AddressUtil.toPubKey(positionAddress), slippageTolerance, destinationWalletKey, positionWalletKey, payerKey); }); } swap(quote, sourceWallet) { return __awaiter(this, void 0, void 0, function* () { const sourceWalletKey = sourceWallet ? common_sdk_1.AddressUtil.toPubKey(sourceWallet) : this.ctx.wallet.publicKey; return (0, instructions_1.swapAsync)(this.ctx, { swapInput: quote, whirlpool: this, wallet: sourceWalletKey, }, true); }); } swapWithDevFees(quote, devFeeWallet, wallet, payer) { return __awaiter(this, void 0, void 0, function* () { const sourceWalletKey = wallet ? common_sdk_1.AddressUtil.toPubKey(wallet) : this.ctx.wallet.publicKey; const payerKey = payer ? common_sdk_1.AddressUtil.toPubKey(payer) : this.ctx.wallet.publicKey; const txBuilder = new common_sdk_1.TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet); if (!quote.devFeeAmount.eq(common_sdk_1.ZERO)) { const inputToken = quote.aToB === quote.amountSpecifiedIsInput ? this.getTokenAInfo() : this.getTokenBInfo(); txBuilder.addInstruction(yield common_sdk_1.TokenUtil.createSendTokensToWalletInstruction(this.ctx.connection, sourceWalletKey, devFeeWallet, inputToken.mint, inputToken.decimals, quote.devFeeAmount, () => this.ctx.fetcher.getAccountRentExempt(), payerKey)); } const swapTxBuilder = yield (0, instructions_1.swapAsync)(this.ctx, { swapInput: quote, whirlpool: this, wallet: sourceWalletKey, }, true); txBuilder.addInstruction(swapTxBuilder.compressIx(true)); return txBuilder; }); } /** * Construct a transaction for opening an new position with optional metadata */ getOpenPositionWithOptMetadataTx(tickLower, tickUpper, liquidityInput, wallet, funder, withMetadata = false) { return __awaiter(this, void 0, void 0, function* () { (0, tiny_invariant_1.default)(public_2.TickUtil.checkTickInBounds(tickLower), "tickLower is out of bounds."); (0, tiny_invariant_1.default)(public_2.TickUtil.checkTickInBounds(tickUpper), "tickUpper is out of bounds."); const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput; (0, tiny_invariant_1.default)(liquidity.gt(new anchor_1.BN(0)), "liquidity must be greater than zero"); const whirlpool = yield this.ctx.fetcher.getPool(this.address, false); if (!whirlpool) { throw new Error(`Whirlpool not found: ${(0, anchor_1.translateAddress)(this.address).toBase58()}`); } (0, tiny_invariant_1.default)(public_2.TickUtil.isTickInitializable(tickLower, whirlpool.tickSpacing), `lower tick ${tickLower} is not an initializable tick for tick-spacing ${whirlpool.tickSpacing}`); (0, tiny_invariant_1.default)(public_2.TickUtil.isTickInitializable(tickUpper, whirlpool.tickSpacing), `upper tick ${tickUpper} is not an initializable tick for tick-spacing ${whirlpool.tickSpacing}`); const positionMintKeypair = web3_js_1.Keypair.generate(); const positionPda = public_2.PDAUtil.getPosition(this.ctx.program.programId, positionMintKeypair.publicKey); const metadataPda = public_2.PDAUtil.getPositionMetadata(positionMintKeypair.publicKey); const positionTokenAccountAddress = yield (0, common_sdk_1.deriveATA)(wallet, positionMintKeypair.publicKey); const txBuilder = new common_sdk_1.TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet); const positionIx = (withMetadata ? instructions_1.openPositionWithMetadataIx : instructions_1.openPositionIx)(this.ctx.program, { funder, owner: wallet, positionPda, metadataPda, positionMintAddress: positionMintKeypair.publicKey, positionTokenAccount: positionTokenAccountAddress, whirlpool: this.address, tickLowerIndex: tickLower, tickUpperIndex: tickUpper, }); txBuilder.addInstruction(positionIx).addSigner(positionMintKeypair); const [ataA, ataB] = yield (0, common_sdk_1.resolveOrCreateATAs)(this.ctx.connection, wallet, [ { tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: tokenMaxA }, { tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: tokenMaxB }, ], () => this.ctx.fetcher.getAccountRentExempt(), funder); const { address: tokenOwnerAccountA } = ataA, tokenOwnerAccountAIx = __rest(ataA, ["address"]); const { address: tokenOwnerAccountB } = ataB, tokenOwnerAccountBIx = __rest(ataB, ["address"]); txBuilder.addInstruction(tokenOwnerAccountAIx); txBuilder.addInstruction(tokenOwnerAccountBIx); const tickArrayLowerPda = public_2.PDAUtil.getTickArrayFromTickIndex(tickLower, this.data.tickSpacing, this.address, this.ctx.program.programId); const tickArrayUpperPda = public_2.PDAUtil.getTickArrayFromTickIndex(tickUpper, this.data.tickSpacing, this.address, this.ctx.program.programId); const liquidityIx = (0, instructions_1.increaseLiquidityIx)(this.ctx.program, { liquidityAmount: liquidity, tokenMaxA, tokenMaxB, whirlpool: this.address, positionAuthority: wallet, 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, }; }); } getClosePositionIx(positionAddress, slippageTolerance, destinationWallet, positionWallet, payerKey) { return __awaiter(this, void 0, void 0, function* () { const positionData = yield this.ctx.fetcher.getPosition(positionAddress, true); if (!positionData) { throw new Error(`Position not found: ${positionAddress.toBase58()}`); } const whirlpool = this.data; (0, tiny_invariant_1.default)(positionData.whirlpool.equals(this.address), `Position ${positionAddress.toBase58()} is not a position for Whirlpool ${this.address.toBase58()}`); const positionTokenAccount = yield (0, common_sdk_1.deriveATA)(positionWallet, positionData.positionMint); const tokenAccountsTxBuilder = new common_sdk_1.TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet); const accountExemption = yield this.ctx.fetcher.getAccountRentExempt(); const txBuilder = new common_sdk_1.TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet); const tickArrayLower = public_2.PDAUtil.getTickArrayFromTickIndex(positionData.tickLowerIndex, whirlpool.tickSpacing, positionData.whirlpool, this.ctx.program.programId).publicKey; const tickArrayUpper = public_2.PDAUtil.getTickArrayFromTickIndex(positionData.tickUpperIndex, whirlpool.tickSpacing, positionData.whirlpool, this.ctx.program.programId).publicKey; const [tickArrayLowerData, tickArrayUpperData] = yield (0, position_builder_util_1.getTickArrayDataForPosition)(this.ctx, positionData, whirlpool, true); (0, tiny_invariant_1.default)(!!tickArrayLowerData, `Tick array ${tickArrayLower} expected to be initialized for whirlpool ${this.address}`); (0, tiny_invariant_1.default)(!!tickArrayUpperData, `Tick array ${tickArrayUpper} expected to be initialized for whirlpool ${this.address}`); const position = new position_impl_1.PositionImpl(this.ctx, positionAddress, positionData, whirlpool, tickArrayLowerData, tickArrayUpperData); const tickLower = position.getLowerTickData(); const tickUpper = position.getUpperTickData(); const feesQuote = (0, public_1.collectFeesQuote)({ position: positionData, whirlpool, tickLower, tickUpper, }); const rewardsQuote = (0, public_1.collectRewardsQuote)({ position: positionData, whirlpool, tickLower, tickUpper, }); const shouldCollectFees = feesQuote.feeOwedA.gtn(0) || feesQuote.feeOwedB.gtn(0); (0, tiny_invariant_1.default)(this.data.rewardInfos.length === rewardsQuote.length, "Rewards quote does not match reward infos length"); const shouldDecreaseLiquidity = positionData.liquidity.gtn(0); const rewardsToCollect = this.data.rewardInfos .filter((_, i) => { var _a; return ((_a = rewardsQuote[i]) !== null && _a !== void 0 ? _a : common_sdk_1.ZERO).gtn(0); }) .map((info) => info.mint); const shouldCollectRewards = rewardsToCollect.length > 0; let mintType = whirlpool_ata_utils_1.TokenMintTypes.ALL; if ((shouldDecreaseLiquidity || shouldCollectFees) && !shouldCollectRewards) { mintType = whirlpool_ata_utils_1.TokenMintTypes.POOL_ONLY; } else if (!(shouldDecreaseLiquidity || shouldCollectFees) && shouldCollectRewards) { mintType = whirlpool_ata_utils_1.TokenMintTypes.REWARD_ONLY; } const affiliatedMints = (0, whirlpool_ata_utils_1.getTokenMintsFromWhirlpools)([whirlpool], mintType); const { ataTokenAddresses: walletTokenAccountsByMint, resolveAtaIxs } = yield (0, whirlpool_ata_utils_1.resolveAtaForMints)(this.ctx, { mints: affiliatedMints.mintMap, accountExemption, receiver: destinationWallet, payer: payerKey, }); tokenAccountsTxBuilder.addInstructions(resolveAtaIxs); // Handle native mint if (affiliatedMints.hasNativeMint) { let _a = (0, spl_token_utils_1.createWSOLAccountInstructions)(destinationWallet, common_sdk_1.ZERO, accountExemption, payerKey, destinationWallet), { address: wSOLAta } = _a, resolveWSolIx = __rest(_a, ["address"]); walletTokenAccountsByMint[spl_token_1.NATIVE_MINT.toBase58()] = wSOLAta; txBuilder.addInstruction(resolveWSolIx); } if (shouldDecreaseLiquidity) { /* Remove all liquidity remaining in the position */ const tokenOwnerAccountA = walletTokenAccountsByMint[whirlpool.tokenMintA.toBase58()]; const tokenOwnerAccountB = walletTokenAccountsByMint[whirlpool.tokenMintB.toBase58()]; const decreaseLiqQuote = (0, public_1.decreaseLiquidityQuoteByLiquidityWithParams)({ liquidity: positionData.liquidity, slippageTolerance, sqrtPrice: whirlpool.sqrtPrice, tickCurrentIndex: whirlpool.tickCurrentIndex, tickLowerIndex: positionData.tickLowerIndex, tickUpperIndex: positionData.tickUpperIndex, }); const liquidityIx = (0, instructions_1.decreaseLiquidityIx)(this.ctx.program, Object.assign(Object.assign({}, decreaseLiqQuote), { whirlpool: positionData.whirlpool, positionAuthority: positionWallet, position: positionAddress, positionTokenAccount, tokenOwnerAccountA, tokenOwnerAccountB, tokenVaultA: whirlpool.tokenVaultA, tokenVaultB: whirlpool.tokenVaultB, tickArrayLower, tickArrayUpper })); txBuilder.addInstruction(liquidityIx); } if (shouldCollectFees) { const collectFeexTx = yield position.collectFees(false, walletTokenAccountsByMint, destinationWallet, positionWallet, payerKey, true); txBuilder.addInstruction(collectFeexTx.compressIx(false)); } if (shouldCollectRewards) { const collectRewardsTx = yield position.collectRewards(rewardsToCollect, false, walletTokenAccountsByMint, destinationWallet, positionWallet, payerKey); txBuilder.addInstruction(collectRewardsTx.compressIx(false)); } /* Close position */ const positionIx = (0, instructions_1.closePositionIx)(this.ctx.program, { whirlpool: positionData.whirlpool, positionAuthority: positionWallet, receiver: destinationWallet, positionTokenAccount, position: positionAddress, positionMint: positionData.positionMint, }); txBuilder.addInstruction(positionIx); const txBuilders = []; if (!tokenAccountsTxBuilder.isEmpty()) { txBuilders.push(tokenAccountsTxBuilder); } txBuilders.push(txBuilder); return txBuilders; }); } refresh() { return __awaiter(this, void 0, void 0, function* () { const account = yield this.ctx.fetcher.getPool(this.address, true); if (!!account) { const rewardInfos = yield (0, util_1.getRewardInfos)(this.ctx.fetcher, account, true); const [tokenVaultAInfo, tokenVaultBInfo] = yield (0, util_1.getTokenVaultAccountInfos)(this.ctx.fetcher, account, true); this.data = account; this.tokenVaultAInfo = tokenVaultAInfo; this.tokenVaultBInfo = tokenVaultBInfo; this.rewardInfos = rewardInfos; } }); } } exports.WhirlpoolImpl = WhirlpoolImpl;