@renec-foundation/redex-sdk
Version:
Typescript SDK to interact with Orca's Whirlpool program.
334 lines (333 loc) • 20.2 kB
JavaScript
"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;