@orca-so/whirlpool-sdk
Version:
Whirlpool SDK for the Orca protocol.
194 lines (193 loc) • 10.5 kB
JavaScript
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());
}
;