UNPKG

goosefx-amm-sdk

Version:

SDK for the GooseFx AMM

235 lines (209 loc) 6.97 kB
import { Connection, Keypair, PublicKey, EpochInfo, Commitment } from "@solana/web3.js"; import { merge } from "lodash"; import { Api, API_URL_CONFIG, JupiterListToken } from "../api"; import { EMPTY_CONNECTION, EMPTY_OWNER } from "../common/error"; import { createLogger, Logger } from "../common/logger"; import { Owner } from "../common/owner"; import { Cluster } from "../solana"; import Account, { TokenAccountDataProp } from "./account/account"; import Cpmm from "./cpmm/cpmm"; import TokenModule from "./token/token"; import { SignAllTransactions } from "./type"; import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor"; import { Gamma } from "./idl/gamma.type"; import IDL from "./idl/gamma.json"; export interface ClientLoadParams extends TokenAccountDataProp, Omit<ClientApiBatchRequestParams, "api"> { /* ================= solana ================= */ // solana web3 connection connection: Connection; // solana cluster/network/env cluster?: Cluster; // user public key owner?: PublicKey | Keypair; /* ================= api ================= */ // api request interval in ms, -1 means never request again, 0 means always use fresh data, default is 5 mins (5 * 60 * 1000) apiRequestInterval?: number; // api request timeout in ms, default is 10 secs (10 * 1000) apiRequestTimeout?: number; apiCacheTime?: number; signAllTransactions?: SignAllTransactions; urlConfigs?: API_URL_CONFIG; logRequests?: boolean; logCount?: number; disableFeatureCheck?: boolean; disableLoadToken?: boolean; blockhashCommitment?: Commitment; kamino?: { market?: PublicKey; programId?: PublicKey; }; } export interface ClientApiBatchRequestParams { api: Api; defaultChainTimeOffset?: number; defaultChainTime?: number; } export type ClientConstructorParams = Required<ClientLoadParams> & ClientApiBatchRequestParams; interface DataBase<T> { fetched: number; data: T; extInfo?: Record<string, any>; } interface ApiData { jupTokenList?: DataBase<JupiterListToken[]>; } export class GfxCpmmClient { public cluster: Cluster; public account: Account; public cpmm: Cpmm; public token: TokenModule; public rawBalances: Map<string, string> = new Map(); public apiData: ApiData; public blockhashCommitment: Commitment; public program: Program<Gamma>; public kamino?: { market?: PublicKey; programId?: PublicKey; }; private _connection: Connection; private _owner: Owner | undefined; public api: Api; private _apiCacheTime: number; private _signAllTransactions?: SignAllTransactions; private logger: Logger; private _chainTime?: { fetched: number; value: { chainTime: number; offset: number; }; }; private _epochInfo?: { fetched: number; value: EpochInfo; }; constructor(config: ClientConstructorParams) { const { connection, cluster, owner, api, defaultChainTime, defaultChainTimeOffset, apiCacheTime, blockhashCommitment = "confirmed", } = config; this._connection = connection; this.cluster = cluster || "mainnet"; this._owner = owner ? new Owner(owner) : undefined; this._signAllTransactions = config.signAllTransactions; this.blockhashCommitment = blockhashCommitment; this.program = new Program( IDL, new AnchorProvider(connection, new Wallet(owner instanceof Keypair ? owner : Keypair.generate())), ); this.api = api; this._apiCacheTime = apiCacheTime || 5 * 60 * 1000; this.logger = createLogger("Gfx"); this.account = new Account({ scope: this, moduleName: "Gfx_Account", tokenAccounts: config.tokenAccounts, tokenAccountRawInfos: config.tokenAccountRawInfos, }); this.token = new TokenModule({ scope: this, moduleName: "Gfx_tokenV2" }); this.cpmm = new Cpmm({ scope: this, moduleName: "Gfx_cpmm" }); const now = new Date().getTime(); this.apiData = {}; if (defaultChainTimeOffset) this._chainTime = { fetched: now, value: { chainTime: defaultChainTime || Date.now() - defaultChainTimeOffset, offset: defaultChainTimeOffset, }, }; } static async load(config: ClientLoadParams): Promise<GfxCpmmClient> { const custom: Required<ClientLoadParams> = merge( // default { cluster: "mainnet", owner: null, apiRequestInterval: 5 * 60 * 1000, apiRequestTimeout: 10 * 1000, }, config, ); const { cluster, apiRequestTimeout, logCount, logRequests, urlConfigs } = custom; const api = new Api({ cluster, timeout: apiRequestTimeout, urlConfigs, logCount, logRequests }); const client = new GfxCpmmClient({ ...custom, api, }); if (!config.disableLoadToken) await client.token.load({}); return client; } get owner(): Owner | undefined { return this._owner; } get ownerPubKey(): PublicKey { if (!this._owner) throw new Error(EMPTY_OWNER); return this._owner.publicKey; } public setOwner(owner?: PublicKey | Keypair): GfxCpmmClient { this._owner = owner ? new Owner(owner) : undefined; this.account.resetTokenAccounts(); return this; } get connection(): Connection { if (!this._connection) throw new Error(EMPTY_CONNECTION); return this._connection; } public setConnection(connection: Connection): GfxCpmmClient { this._connection = connection; return this; } get signAllTransactions(): SignAllTransactions | undefined { return this._signAllTransactions; } public setSignAllTransactions(signAllTransactions?: SignAllTransactions): GfxCpmmClient { this._signAllTransactions = signAllTransactions; return this; } public checkOwner(): void { if (!this.owner) { this.logger.error(EMPTY_OWNER); throw new Error(EMPTY_OWNER); } } private isCacheInvalidate(time: number): boolean { return new Date().getTime() - time > this._apiCacheTime; } public async fetchJupTokenList(forceUpdate?: boolean): Promise<JupiterListToken[]> { const prevFetched = this.apiData.jupTokenList; if (prevFetched && !this.isCacheInvalidate(prevFetched.fetched) && !forceUpdate) return prevFetched.data; try { const jupList = await this.api.getJupTokenList(); this.apiData.jupTokenList = { fetched: Date.now(), data: jupList, }; return this.apiData.jupTokenList.data; } catch (e) { console.error(e); return []; } } get chainTimeData(): { offset: number; chainTime: number } | undefined { return this._chainTime?.value; } public async fetchEpochInfo(): Promise<EpochInfo> { if (this._epochInfo && Date.now() - this._epochInfo.fetched <= 1000 * 30) return this._epochInfo.value; this._epochInfo = { fetched: Date.now(), value: await this.connection.getEpochInfo(), }; return this._epochInfo.value; } }