Implement batch getPools method (#42)

* Adding getPools method in SDK client

* Adding fetching of token vault infos to whirlpool retrieval

* Adding retrieval of reward infos

* Adding comment about empty pubkey constant

* Updating comments

* Using PoolUtil to check for initialized rewards

* Updating getRewardInfos comment
This commit is contained in:
tmoc 2022-08-10 14:50:02 -04:00 committed by GitHub
parent 8fc92ea1af
commit 16bca0ea1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 194 additions and 28 deletions

76
sdk/src/impl/util.ts Normal file
View File

@ -0,0 +1,76 @@
import BN from "bn.js";
import { AccountFetcher, PoolUtil, TokenInfo } from "..";
import {
WhirlpoolData,
WhirlpoolRewardInfo,
WhirlpoolRewardInfoData,
TokenAccountInfo,
} from "../types/public";
export async function getTokenMintInfos(
fetcher: AccountFetcher,
data: WhirlpoolData,
refresh: boolean
): Promise<TokenInfo[]> {
const mintA = data.tokenMintA;
const infoA = await fetcher.getMintInfo(mintA, refresh);
if (!infoA) {
throw new Error(`Unable to fetch MintInfo for mint - ${mintA}`);
}
const mintB = data.tokenMintB;
const infoB = await fetcher.getMintInfo(mintB, refresh);
if (!infoB) {
throw new Error(`Unable to fetch MintInfo for mint - ${mintB}`);
}
return [
{ mint: mintA, ...infoA },
{ mint: mintB, ...infoB },
];
}
export async function getRewardInfos(
fetcher: AccountFetcher,
data: WhirlpoolData,
refresh: boolean
): Promise<WhirlpoolRewardInfo[]> {
const rewardInfos: WhirlpoolRewardInfo[] = [];
for (const rewardInfo of data.rewardInfos) {
rewardInfos.push(await getRewardInfo(fetcher, rewardInfo, refresh));
}
return rewardInfos;
}
async function getRewardInfo(
fetcher: AccountFetcher,
data: WhirlpoolRewardInfoData,
refresh: boolean
): Promise<WhirlpoolRewardInfo> {
const rewardInfo = { ...data, initialized: false, vaultAmount: new BN(0) };
if (PoolUtil.isRewardInitialized(data)) {
const vaultInfo = await fetcher.getTokenInfo(data.vault, refresh);
if (!vaultInfo) {
throw new Error(`Unable to fetch TokenAccountInfo for vault - ${data.vault}`);
}
rewardInfo.initialized = true;
rewardInfo.vaultAmount = vaultInfo.amount;
}
return rewardInfo;
}
export async function getTokenVaultAccountInfos(
fetcher: AccountFetcher,
data: WhirlpoolData,
refresh: boolean
): Promise<TokenAccountInfo[]> {
const vaultA = data.tokenVaultA;
const vaultInfoA = await fetcher.getTokenInfo(vaultA, refresh);
if (!vaultInfoA) {
throw new Error(`Unable to fetch TokenAccountInfo for vault - ${vaultA}`);
}
const vaultB = data.tokenVaultB;
const vaultInfoB = await fetcher.getTokenInfo(vaultB, refresh);
if (!vaultInfoB) {
throw new Error(`Unable to fetch TokenAccountInfo for vault - ${vaultB}`);
}
return [vaultInfoA, vaultInfoB];
}

View File

@ -2,9 +2,11 @@ import { AddressUtil } from "@orca-so/common-sdk";
import { Address } from "@project-serum/anchor";
import { WhirlpoolContext } from "../context";
import { AccountFetcher } from "../network/public";
import { WhirlpoolData, TokenInfo } from "../types/public";
import { WhirlpoolData } from "../types/public";
import { PoolUtil } from "../utils/public";
import { WhirlpoolClient, Whirlpool, Position } from "../whirlpool-client";
import { PositionImpl } from "./position-impl";
import { getRewardInfos, getTokenMintInfos, getTokenVaultAccountInfos } from "./util";
import { WhirlpoolImpl } from "./whirlpool-impl";
export class WhirlpoolClientImpl implements WhirlpoolClient {
@ -23,17 +25,69 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
if (!account) {
throw new Error(`Unable to fetch Whirlpool at address at ${poolAddress}`);
}
const tokenInfos = await getTokenInfos(this.ctx.fetcher, account, false);
const tokenInfos = await getTokenMintInfos(this.ctx.fetcher, account, refresh);
const vaultInfos = await getTokenVaultAccountInfos(this.ctx.fetcher, account, refresh);
const rewardInfos = await getRewardInfos(this.ctx.fetcher, account, refresh);
return new WhirlpoolImpl(
this.ctx,
this.ctx.fetcher,
AddressUtil.toPubKey(poolAddress),
tokenInfos[0],
tokenInfos[1],
vaultInfos[0],
vaultInfos[1],
rewardInfos,
account
);
}
public async getPools(poolAddresses: Address[], refresh = false): Promise<Whirlpool[]> {
const accounts = (await this.ctx.fetcher.listPools(poolAddresses, refresh)).filter(
(account): account is WhirlpoolData => !!account
);
if (accounts.length !== poolAddresses.length) {
throw new Error(`Unable to fetch all Whirlpools at addresses ${poolAddresses}`);
}
const tokenMints = new Set<string>();
const tokenAccounts = new Set<string>();
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 (PoolUtil.isRewardInitialized(rewardInfo)) {
tokenAccounts.add(rewardInfo.vault.toBase58());
}
});
});
await this.ctx.fetcher.listMintInfos(Array.from(tokenMints), refresh);
await this.ctx.fetcher.listTokenInfos(Array.from(tokenAccounts), refresh);
const whirlpools: Whirlpool[] = [];
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
const poolAddress = poolAddresses[i];
const tokenInfos = await getTokenMintInfos(this.ctx.fetcher, account, false);
const vaultInfos = await getTokenVaultAccountInfos(this.ctx.fetcher, account, false);
const rewardInfos = await getRewardInfos(this.ctx.fetcher, account, false);
whirlpools.push(
new WhirlpoolImpl(
this.ctx,
this.ctx.fetcher,
AddressUtil.toPubKey(poolAddress),
tokenInfos[0],
tokenInfos[1],
vaultInfos[0],
vaultInfos[1],
rewardInfos,
account
)
);
}
return whirlpools;
}
public async getPosition(positionAddress: Address, refresh = false): Promise<Position> {
const account = await this.ctx.fetcher.getPosition(positionAddress, refresh);
if (!account) {
@ -47,24 +101,3 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
);
}
}
async function getTokenInfos(
fetcher: AccountFetcher,
data: WhirlpoolData,
refresh: boolean
): Promise<TokenInfo[]> {
const mintA = data.tokenMintA;
const infoA = await fetcher.getMintInfo(mintA, refresh);
if (!infoA) {
throw new Error(`Unable to fetch MintInfo for mint - ${mintA}`);
}
const mintB = data.tokenMintB;
const infoB = await fetcher.getMintInfo(mintB, refresh);
if (!infoB) {
throw new Error(`Unable to fetch MintInfo for mint - ${mintB}`);
}
return [
{ mint: mintA, ...infoA },
{ mint: mintB, ...infoB },
];
}

View File

@ -19,13 +19,14 @@ import {
swapIx,
SwapInput,
} from "../instructions";
import { TokenInfo, WhirlpoolData } from "../types/public";
import { TokenAccountInfo, TokenInfo, WhirlpoolData, WhirlpoolRewardInfo } from "../types/public";
import { Whirlpool } from "../whirlpool-client";
import { PublicKey, Keypair } from "@solana/web3.js";
import { AccountFetcher } from "../network/public";
import invariant from "tiny-invariant";
import { PDAUtil, PriceMath, TickArrayUtil, TickUtil } from "../utils/public";
import { PDAUtil, TickArrayUtil, TickUtil } from "../utils/public";
import { decreaseLiquidityQuoteByLiquidityWithParams, SwapQuote } from "../quotes/public";
import { getRewardInfos, getTokenVaultAccountInfos } from "./util";
export class WhirlpoolImpl implements Whirlpool {
private data: WhirlpoolData;
@ -35,6 +36,9 @@ export class WhirlpoolImpl implements Whirlpool {
readonly address: PublicKey,
readonly tokenAInfo: TokenInfo,
readonly tokenBInfo: TokenInfo,
private tokenVaultAInfo: TokenAccountInfo,
private tokenVaultBInfo: TokenAccountInfo,
private rewardInfos: WhirlpoolRewardInfo[],
data: WhirlpoolData
) {
this.data = data;
@ -56,6 +60,18 @@ export class WhirlpoolImpl implements Whirlpool {
return this.tokenBInfo;
}
getTokenVaultAInfo(): TokenAccountInfo {
return this.tokenVaultAInfo;
}
getTokenVaultBInfo(): TokenAccountInfo {
return this.tokenVaultBInfo;
}
getRewardInfos(): WhirlpoolRewardInfo[] {
return this.rewardInfos;
}
async refreshData() {
await this.refresh();
return this.data;
@ -422,7 +438,16 @@ export class WhirlpoolImpl implements Whirlpool {
private async refresh() {
const account = await this.fetcher.getPool(this.address, true);
if (!!account) {
const rewardInfos = await getRewardInfos(this.fetcher, account, true);
const [tokenVaultAInfo, tokenVaultBInfo] = await getTokenVaultAccountInfos(
this.fetcher,
account,
true
);
this.data = account;
this.tokenVaultAInfo = tokenVaultAInfo;
this.tokenVaultBInfo = tokenVaultBInfo;
this.rewardInfos = rewardInfos;
}
}
}

View File

@ -1,6 +1,6 @@
import { PublicKey } from "@solana/web3.js";
import { MintInfo } from "@solana/spl-token";
import { TickArrayData } from "./anchor-types";
import { AccountInfo, MintInfo, u64 } from "@solana/spl-token";
import { TickArrayData, WhirlpoolRewardInfoData } from "./anchor-types";
/**
* Extended MintInfo class to host token info.
@ -8,6 +8,13 @@ import { TickArrayData } from "./anchor-types";
*/
export type TokenInfo = MintInfo & { mint: PublicKey };
export type TokenAccountInfo = AccountInfo;
export type WhirlpoolRewardInfo = WhirlpoolRewardInfoData & {
initialized: boolean;
vaultAmount: u64;
};
/**
* A wrapper class of a TickArray on a Whirlpool
* @category WhirlpoolClient

View File

@ -11,7 +11,7 @@ import {
PositionData,
WhirlpoolData,
} from "./types/public";
import { TokenInfo } from "./types/public/client-types";
import { TokenAccountInfo, TokenInfo, WhirlpoolRewardInfo } from "./types/public/client-types";
/**
* Helper class to help interact with Whirlpool Accounts with a simpler interface.
@ -38,6 +38,13 @@ export interface WhirlpoolClient {
*/
getPool: (poolAddress: Address, refresh?: boolean) => Promise<Whirlpool>;
/**
* Get a list of Whirlpool objects matching the provided list of addresses.
* @param poolAddresses the addresses of the Whirlpool accounts
* @return a list of Whirlpool objects to interact with
*/
getPools: (poolAddresses: Address[], refresh?: boolean) => Promise<Whirlpool[]>;
/**
* Get a Position object to interact with the Position account at the given address.
* @param positionAddress the address of the Position account
@ -92,6 +99,24 @@ export interface Whirlpool {
*/
getTokenBInfo: () => TokenInfo;
/**
* Get the TokenAccountInfo for token vault A of this pool.
* @return TokenAccountInfo for token vault A
*/
getTokenVaultAInfo: () => TokenAccountInfo;
/**
* Get the TokenAccountInfo for token vault B of this pool.
* @return TokenAccountInfo for token vault B
*/
getTokenVaultBInfo: () => TokenAccountInfo;
/**
* Get the WhirlpoolRewardInfos for this pool.
* @return Array of 3 WhirlpoolRewardInfos. However, not all of them may be initialized. Use the initialized field on WhirlpoolRewardInfo to check if the reward is active.
*/
getRewardInfos: () => WhirlpoolRewardInfo[];
/**
* Initialize a set of tick-arrays that encompasses the provided ticks.
*