@renec-foundation/redex-sdk
Version:
Typescript SDK to interact with Orca's Whirlpool program.
224 lines (223 loc) • 13.1 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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WhirlpoolClientImpl = void 0;
const common_sdk_1 = require("@orca-so/common-sdk");
const anchor_1 = require("@project-serum/anchor");
const web3_js_1 = require("@solana/web3.js");
const spl_token_1 = require("@solana/spl-token");
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
const instructions_1 = require("../instructions");
const composites_1 = require("../instructions/composites");
const ix_1 = require("../ix");
const position_builder_util_1 = require("../utils/builder/position-builder-util");
const public_1 = require("../utils/public");
const position_impl_1 = require("./position-impl");
const util_1 = require("./util");
const whirlpool_impl_1 = require("./whirlpool-impl");
class WhirlpoolClientImpl {
constructor(ctx) {
this.ctx = ctx;
}
getContext() {
return this.ctx;
}
getFetcher() {
return this.ctx.fetcher;
}
getPool(poolAddress, refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
const account = yield this.ctx.fetcher.getPool(poolAddress, refresh);
if (!account) {
throw new Error(`Unable to fetch Whirlpool at address at ${poolAddress}`);
}
const tokenInfos = yield (0, util_1.getTokenMintInfos)(this.ctx.fetcher, account, refresh);
const vaultInfos = yield (0, util_1.getTokenVaultAccountInfos)(this.ctx.fetcher, account, refresh);
const rewardInfos = yield (0, util_1.getRewardInfos)(this.ctx.fetcher, account, refresh);
return new whirlpool_impl_1.WhirlpoolImpl(this.ctx, common_sdk_1.AddressUtil.toPubKey(poolAddress), tokenInfos[0], tokenInfos[1], vaultInfos[0], vaultInfos[1], rewardInfos, account);
});
}
getPools(poolAddresses, refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
const accounts = (yield this.ctx.fetcher.listPools(poolAddresses, refresh)).filter((account) => !!account);
if (accounts.length !== poolAddresses.length) {
throw new Error(`Unable to fetch all Whirlpools at addresses ${poolAddresses}`);
}
const tokenMints = new Set();
const tokenAccounts = new Set();
accounts.forEach((account) => {
tokenMints.add(account.tokenMintA.toBase58());
tokenMints.add(account.tokenMintB.toBase58());
tokenAccounts.add(account.tokenVaultA.toBase58());
tokenAccounts.add(account.tokenVaultB.toBase58());
account.rewardInfos.forEach((rewardInfo) => {
if (public_1.PoolUtil.isRewardInitialized(rewardInfo)) {
tokenAccounts.add(rewardInfo.vault.toBase58());
}
});
});
yield this.ctx.fetcher.listMintInfos(Array.from(tokenMints), refresh);
yield this.ctx.fetcher.listTokenInfos(Array.from(tokenAccounts), refresh);
const whirlpools = [];
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
const poolAddress = poolAddresses[i];
const tokenInfos = yield (0, util_1.getTokenMintInfos)(this.ctx.fetcher, account, false);
const vaultInfos = yield (0, util_1.getTokenVaultAccountInfos)(this.ctx.fetcher, account, false);
const rewardInfos = yield (0, util_1.getRewardInfos)(this.ctx.fetcher, account, false);
whirlpools.push(new whirlpool_impl_1.WhirlpoolImpl(this.ctx, common_sdk_1.AddressUtil.toPubKey(poolAddress), tokenInfos[0], tokenInfos[1], vaultInfos[0], vaultInfos[1], rewardInfos, account));
}
return whirlpools;
});
}
getAllPositionsOf(owner, refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
const { ctx } = this;
// Get all token accounts
const tokenAccounts = (yield ctx.connection.getTokenAccountsByOwner(owner, {
programId: spl_token_1.TOKEN_PROGRAM_ID,
})).value;
// Get candidate addresses for the position
let positionCandidatePubkeys = [];
tokenAccounts.forEach((ta) => {
const parsed = common_sdk_1.TokenUtil.deserializeTokenAccount(ta.account.data);
if (parsed) {
const pda = public_1.PDAUtil.getPosition(ctx.program.programId, parsed.mint);
// Returns the address of the Whirlpool position only if the number of tokens is 1 (ignores empty token accounts and non-NFTs)
if (new anchor_1.BN(parsed.amount.toString()).eq(new anchor_1.BN(1))) {
positionCandidatePubkeys.push(pda.publicKey);
}
}
});
const positions = [];
Object.values(yield this.getPositions(positionCandidatePubkeys, refresh)).forEach((p) => {
if (p) {
positions.push(p);
}
});
return positions;
});
}
getPosition(positionAddress, refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
const account = yield this.ctx.fetcher.getPosition(positionAddress, refresh);
if (!account) {
throw new Error(`Unable to fetch Position at address at ${positionAddress}`);
}
const whirlAccount = yield this.ctx.fetcher.getPool(account.whirlpool, refresh);
if (!whirlAccount) {
throw new Error(`Unable to fetch Whirlpool for Position at address at ${positionAddress}`);
}
const [lowerTickArray, upperTickArray] = yield (0, position_builder_util_1.getTickArrayDataForPosition)(this.ctx, account, whirlAccount, refresh);
if (!lowerTickArray || !upperTickArray) {
throw new Error(`Unable to fetch TickArrays for Position at address at ${positionAddress}`);
}
return new position_impl_1.PositionImpl(this.ctx, common_sdk_1.AddressUtil.toPubKey(positionAddress), account, whirlAccount, lowerTickArray, upperTickArray);
});
}
getPositions(positionAddresses, refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Prefetch and use fetcher as a cache - Think of a cleaner way to prefetch
const positions = yield this.ctx.fetcher.listPositions(positionAddresses, refresh);
const whirlpoolAddrs = positions
.map((position) => position === null || position === void 0 ? void 0 : position.whirlpool.toBase58())
.flatMap((x) => (!!x ? x : []));
yield this.ctx.fetcher.listPools(whirlpoolAddrs, refresh);
const tickArrayAddresses = new Set();
yield Promise.all(positions.map((pos) => __awaiter(this, void 0, void 0, function* () {
if (pos) {
const pool = yield this.ctx.fetcher.getPool(pos.whirlpool, false);
if (pool) {
const lowerTickArrayPda = public_1.PDAUtil.getTickArrayFromTickIndex(pos.tickLowerIndex, pool.tickSpacing, pos.whirlpool, this.ctx.program.programId).publicKey;
const upperTickArrayPda = public_1.PDAUtil.getTickArrayFromTickIndex(pos.tickUpperIndex, pool.tickSpacing, pos.whirlpool, this.ctx.program.programId).publicKey;
tickArrayAddresses.add(lowerTickArrayPda.toBase58());
tickArrayAddresses.add(upperTickArrayPda.toBase58());
}
}
})));
yield this.ctx.fetcher.listTickArrays(Array.from(tickArrayAddresses), true);
// Use getPosition and the prefetched values to generate the Positions
const results = yield Promise.all(positionAddresses.map((pos) => __awaiter(this, void 0, void 0, function* () {
try {
const position = yield this.getPosition(pos, false);
return [pos, position];
}
catch (_a) {
return [pos, null];
}
})));
return Object.fromEntries(results);
});
}
createPool(whirlpoolsConfig, tokenMintA, tokenMintB, tickSpacing, initialTick, funder, refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
(0, tiny_invariant_1.default)(public_1.TickUtil.checkTickInBounds(initialTick), "initialTick is out of bounds.");
(0, tiny_invariant_1.default)(public_1.TickUtil.isTickInitializable(initialTick, tickSpacing), `initial tick ${initialTick} is not an initializable tick for tick-spacing ${tickSpacing}`);
const correctTokenOrder = public_1.PoolUtil.orderMints(tokenMintA, tokenMintB).map((addr) => addr.toString());
(0, tiny_invariant_1.default)(correctTokenOrder[0] === tokenMintA.toString(), "Token order needs to be flipped to match the canonical ordering (i.e. sorted on the byte repr. of the mint pubkeys)");
whirlpoolsConfig = common_sdk_1.AddressUtil.toPubKey(whirlpoolsConfig);
const feeTierKey = public_1.PDAUtil.getFeeTier(this.ctx.program.programId, whirlpoolsConfig, tickSpacing).publicKey;
const initSqrtPrice = public_1.PriceMath.tickIndexToSqrtPriceX64(initialTick);
const tokenVaultAKeypair = web3_js_1.Keypair.generate();
const tokenVaultBKeypair = web3_js_1.Keypair.generate();
const whirlpoolPda = public_1.PDAUtil.getWhirlpool(this.ctx.program.programId, whirlpoolsConfig, new web3_js_1.PublicKey(tokenMintA), new web3_js_1.PublicKey(tokenMintB), tickSpacing);
const feeTier = yield this.ctx.fetcher.getFeeTier(feeTierKey, refresh);
(0, tiny_invariant_1.default)(!!feeTier, `Fee tier for ${tickSpacing} doesn't exist`);
const txBuilder = new common_sdk_1.TransactionBuilder(this.ctx.provider.connection, this.ctx.provider.wallet);
const initPoolIx = ix_1.WhirlpoolIx.initializePoolIx(this.ctx.program, {
initSqrtPrice,
whirlpoolsConfig,
whirlpoolPda,
tokenMintA: new web3_js_1.PublicKey(tokenMintA),
tokenMintB: new web3_js_1.PublicKey(tokenMintB),
tokenVaultAKeypair,
tokenVaultBKeypair,
feeTierKey,
tickSpacing,
funder: new web3_js_1.PublicKey(funder),
});
const initialTickArrayStartTick = public_1.TickUtil.getStartTickIndex(initialTick, tickSpacing);
const initialTickArrayPda = public_1.PDAUtil.getTickArray(this.ctx.program.programId, whirlpoolPda.publicKey, initialTickArrayStartTick);
txBuilder.addInstruction(initPoolIx);
txBuilder.addInstruction((0, instructions_1.initTickArrayIx)(this.ctx.program, {
startTick: initialTickArrayStartTick,
tickArrayPda: initialTickArrayPda,
whirlpool: whirlpoolPda.publicKey,
funder: common_sdk_1.AddressUtil.toPubKey(funder),
}));
return {
poolKey: whirlpoolPda.publicKey,
tx: txBuilder,
};
});
}
collectFeesAndRewardsForPositions(positionAddresses, refresh) {
return __awaiter(this, void 0, void 0, function* () {
const walletKey = this.ctx.wallet.publicKey;
return (0, composites_1.collectAllForPositionAddressesTxns)(this.ctx, {
positions: positionAddresses,
receiver: walletKey,
positionAuthority: walletKey,
positionOwner: walletKey,
payer: walletKey,
}, refresh);
});
}
collectProtocolFeesForPools(poolAddresses) {
return __awaiter(this, void 0, void 0, function* () {
return (0, composites_1.collectProtocolFees)(this.ctx, poolAddresses);
});
}
}
exports.WhirlpoolClientImpl = WhirlpoolClientImpl;