UNPKG

@renec-foundation/redex-sdk

Version:

Typescript SDK to interact with Orca's Whirlpool program.

224 lines (223 loc) 13.1 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 __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;