UNPKG

goosefx-amm-sdk

Version:

SDK for the GooseFx AMM

225 lines (186 loc) 6.26 kB
import axios, { AxiosInstance } from "axios"; import { createLogger, sleep } from "../common"; import { Cluster } from "../solana"; import { ConfigInfo, PaginatedPoolInfos, PoolKeys, GammaToken, FetchPoolParams, JupiterListToken, ApiPoolInfo, } from "./type"; import { API_URLS, API_URL_CONFIG } from "./url"; import { updateReqHistory } from "./utils"; import { PublicKey } from "@solana/web3.js"; import { solToWSol } from "../common"; const logger = createLogger("Gfx_Api"); const poolKeysCache: Map<string, PoolKeys> = new Map(); export async function endlessRetry<T>(name: string, call: () => Promise<T>, interval = 1000): Promise<T> { let result: T | undefined; while (result == undefined) { try { logger.debug(`Request ${name} through endlessRetry`); result = await call(); } catch (err) { logger.error(`Request ${name} failed, retry after ${interval} ms`, err); await sleep(interval); } } return result; } export interface ApiProps { cluster: Cluster; timeout: number; logRequests?: boolean; logCount?: number; urlConfigs?: API_URL_CONFIG; } export class Api { public cluster: Cluster; public api: AxiosInstance; public logCount: number; public urlConfigs: API_URL_CONFIG; constructor({ cluster, timeout, logRequests, logCount, urlConfigs }: ApiProps) { this.cluster = cluster; this.urlConfigs = urlConfigs || {}; this.logCount = logCount || 1000; this.api = axios.create({ baseURL: this.urlConfigs.BASE_HOST || API_URLS.BASE_HOST, timeout }); this.api.interceptors.request.use( (config) => { // before request const { method, baseURL, url } = config; logger.debug(`${method?.toUpperCase()} ${baseURL}${url}`); return config; }, (error) => { // request error logger.error(`Request failed`); return Promise.reject(error); }, ); this.api.interceptors.response.use( (response) => { // 2xx const { config, data, status } = response; const { method, baseURL, url } = config; if (logRequests) { updateReqHistory({ status, url: `${baseURL}${url}`, params: config.params, data, logCount: this.logCount, }); } logger.debug(`${method?.toUpperCase()} ${baseURL}${url} ${status}`); return data; }, (error) => { // https://axios-http.com/docs/handling_errors // not 2xx const { config, response = {} } = error; const { status } = response; const { method, baseURL, url } = config; if (logRequests) { updateReqHistory({ status, url: `${baseURL}${url}`, params: config.params, data: error.message, logCount: this.logCount, }); } logger.error(`${method.toUpperCase()} ${baseURL}${url} ${status || error.message}`); return Promise.reject(error); }, ); } async getConfig(id: string): Promise<ConfigInfo> { const res = await this.api.get((this.urlConfigs.CONFIG || API_URLS.CONFIG) + `?id=${id}`); return res.data; } async getJupTokenList(): Promise<JupiterListToken[]> { return this.api.get("", { baseURL: this.urlConfigs.JUP_TOKEN_LIST || API_URLS.JUP_TOKEN_LIST, }); } async getTokenInfo(mint: (string | PublicKey)[]): Promise<GammaToken[]> { const res = await this.api.get( (this.urlConfigs.TOKEN_LIST || API_URLS.TOKEN_LIST) + `?ids=${mint.map((m) => m.toString()).join(",")}`, ); return res.data; } async getPoolList(props: FetchPoolParams = {}): Promise<PaginatedPoolInfos> { const { poolType = "all", sortBy = "liquidity", sortOrder = "desc", page = 1, pageSize = 100, search } = props; const res = await this.api.get( (this.urlConfigs.POOL_LIST || API_URLS.POOL_LIST) + `?poolType=${poolType}&sortOrder=${sortOrder}&sortBy=${sortBy}&page=${page}&pageSize=${pageSize}${ search ? `&search=${search}` : "" }`, ); return res.data; } async fetchPoolById(props: { idList: string[] }): Promise<ApiPoolInfo[]> { const { idList } = props; const res = await this.api.get((this.urlConfigs.POOL_BY_IDS || API_URLS.POOL_BY_IDS) + `?ids=${idList.join(",")}`); return res.data; } async fetchPoolKeysById(props: { idList: string[] }): Promise<PoolKeys[]> { const { idList } = props; const cacheList: PoolKeys[] = []; const readyList = idList.filter((poolId) => { if (poolKeysCache.has(poolId)) { cacheList.push(poolKeysCache.get(poolId)!); return false; } return true; }); let data: PoolKeys[] = []; if (readyList.length) { const res = await this.api.get<PoolKeys[]>( (this.urlConfigs.POOL_KEYS_BY_IDS || API_URLS.POOL_KEYS_BY_IDS) + `?ids=${readyList.join(",")}`, ); data = res.data.filter(Boolean); data.forEach((poolKey) => { poolKeysCache.set(poolKey.id, poolKey); }); } return cacheList.concat(data); } async fetchPoolByMints( props: { mint1: string | PublicKey; mint2?: string | PublicKey; } & Omit<FetchPoolParams, "pageSize">, ): Promise<PaginatedPoolInfos> { const { mint1: propMint1, mint2: propMint2, poolType = "all", sortBy = "liquidity", sortOrder = "desc", page = 1, } = props; const [mint1, mint2] = [ propMint1 ? solToWSol(propMint1).toBase58() : propMint1, propMint2 && propMint2 !== "undefined" ? solToWSol(propMint2).toBase58() : undefined, ]; const [baseMint, quoteMint] = mint2 && mint1 > mint2 ? [mint2, mint1] : [mint1, mint2]; // Build the query string const queryParams = new URLSearchParams({ mint1: baseMint || "", poolType, sortBy, sortOrder, pageSize: "100", page: String(page), }); if (quoteMint) { queryParams.append("mint2", quoteMint); } const url = (this.urlConfigs.POOL_BY_MINTS || API_URLS.POOL_BY_MINTS) + `?${queryParams.toString()}`; const res = await this.api.get(url); return res.data; } }