@raydium-io/raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
332 lines (284 loc) • 10.1 kB
text/typescript
import axios, { AxiosInstance } from "axios";
import { createLogger, sleep } from "../common";
import { Cluster } from "../solana";
import {
ApiClmmConfigInfo,
ApiCpmmConfigInfo,
ApiV3Token,
FetchPoolParams,
PoolsApiReturn,
ApiV3PoolInfoItem,
PoolKeys,
FormatFarmInfoOut,
FormatFarmKeyOut,
AvailabilityCheckAPI3,
PoolFetchType,
JupToken,
ApiLaunchConfig,
} from "./type";
import { API_URLS, API_URL_CONFIG, DEV_API_URLS } from "./url";
import { updateReqHistory } from "./utils";
import { PublicKey } from "@solana/web3.js";
import { solToWSol } from "../common";
import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
const logger = createLogger("Raydium_Api");
const poolKeysCache: Map<string, PoolKeys> = new Map();
const cacheLaunchConfigs: Map<Cluster, ApiLaunchConfig[]> = 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 || (this.cluster === "devnet" ? DEV_API_URLS.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 getClmmConfigs(): Promise<ApiClmmConfigInfo[]> {
const res = await this.api.get(this.urlConfigs.CLMM_CONFIG || API_URLS.CLMM_CONFIG);
return res.data;
}
async getCpmmConfigs(): Promise<ApiCpmmConfigInfo[]> {
const res = await this.api.get(this.urlConfigs.CPMM_CONFIG || API_URLS.CPMM_CONFIG);
return res.data;
}
async getClmmPoolLines(poolId: string): Promise<{ price: string; liquidity: string }[]> {
const res = await this.api.get(`${this.urlConfigs.POOL_POSITION_LINE || API_URLS.POOL_POSITION_LINE}?id=${poolId}`);
return res.data;
}
async getBlockSlotCountForSecond(endpointUrl?: string): Promise<number> {
if (!endpointUrl) return 2;
const res: {
id: string;
jsonrpc: string;
result: { numSlots: number; numTransactions: number; samplePeriodSecs: number; slot: number }[];
} = await axios.post(endpointUrl, {
id: "getRecentPerformanceSamples",
jsonrpc: "2.0",
method: "getRecentPerformanceSamples",
params: [4],
});
const slotList = res.result.map((data) => data.numSlots);
return slotList.reduce((a, b) => a + b, 0) / slotList.length / 60;
}
async getChainTimeOffset(): Promise<{ offset: number }> {
const res = await this.api.get(this.urlConfigs.CHAIN_TIME || API_URLS.CHAIN_TIME);
return res.data;
}
async getRpcs(): Promise<{
rpcs: { batch: boolean; name: string; url: string; weight: number }[];
strategy: string;
}> {
return this.api.get(this.urlConfigs.RPCS || API_URLS.RPCS);
}
async getTokenList(): Promise<{ mintList: ApiV3Token[]; blacklist: string[]; whiteList: string[] }> {
const res = await this.api.get(this.urlConfigs.TOKEN_LIST || API_URLS.TOKEN_LIST);
return res.data;
}
async getJupTokenList(): Promise<
(ApiV3Token & {
freeze_authority: string | null;
mint_authority: string | null;
permanent_delegate: string | null;
minted_at: string;
})[]
> {
const r: JupToken[] = await this.api.get("", {
baseURL: this.urlConfigs.JUP_TOKEN_LIST || API_URLS.JUP_TOKEN_LIST,
});
return r.map((t) => ({
...t,
chainId: 101,
programId: t.tokenProgram,
address: t.id,
logoURI: t.icon,
extensions: {},
freeze_authority: null,
permanent_delegate: null,
mint_authority: t.mintAuthority || null,
minted_at: Date.now().toString(),
}));
}
async getTokenInfo(mint: (string | PublicKey)[]): Promise<ApiV3Token[]> {
const res = await this.api.get(
(this.urlConfigs.MINT_INFO_ID || API_URLS.MINT_INFO_ID) + `?mints=${mint.map((m) => m.toString()).join(",")}`,
);
return res.data;
}
async getPoolList(props: FetchPoolParams = {}): Promise<PoolsApiReturn> {
const { type = "all", sort = "liquidity", order = "desc", showFarms = false, nextPageId, pageSize = 100 } = props;
const res = await this.api.get<PoolsApiReturn>(
(this.urlConfigs.POOL_LIST || API_URLS.POOL_LIST) +
`?size=${pageSize}&hasReward=${showFarms}${type && type !== "all" ? `&poolType=${type}` : ""}${
sort ? `&sortField=${sort}` : ""
}${order ? `&sortType=${order}` : ""}${nextPageId ? `&nextPageId=${nextPageId}` : ""}`,
);
return res.data;
}
async fetchPoolById(props: { ids: string }): Promise<ApiV3PoolInfoItem[]> {
const { ids } = props;
const res = await this.api.get((this.urlConfigs.POOL_SEARCH_BY_ID || API_URLS.POOL_SEARCH_BY_ID) + `?ids=${ids}`);
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_KEY_BY_ID || API_URLS.POOL_KEY_BY_ID) + `?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;
} & FetchPoolParams,
): Promise<PoolsApiReturn> {
const {
mint1: propMint1,
mint2: propMint2,
type = PoolFetchType.All,
sort = "default",
order = "desc",
nextPageId,
} = props;
const [mint1, mint2] = [
propMint1 ? solToWSol(propMint1).toBase58() : propMint1,
propMint2 && propMint2 !== "undefined" ? solToWSol(propMint2).toBase58() : "",
];
const [baseMint, quoteMint] = mint2 && mint1 > mint2 ? [mint2, mint1] : [mint1, mint2];
if (!mint1 && !mint2) throw new Error("not search mints provided");
const res = await this.api.get(
(this.urlConfigs.POOL_SEARCH_MINT || API_URLS.POOL_SEARCH_MINT) +
`?size=100&mint1=${baseMint}${quoteMint ? `&mint2=${quoteMint}` : ""}${
type && type !== "all" ? `&poolType=${type}` : ""
}${sort ? `&sortField=${sort}` : ""}${order ? `&sortType=${order}` : ""}${
nextPageId ? `&nextPageId=${nextPageId}` : ""
}`,
);
return res.data;
}
async fetchFarmInfoById(props: { ids: string }): Promise<FormatFarmInfoOut[]> {
const { ids } = props;
const res = await this.api.get<FormatFarmInfoOut[]>(
(this.urlConfigs.FARM_INFO || API_URLS.FARM_INFO) + `?ids=${ids}`,
);
return res.data;
}
async fetchFarmKeysById(props: { ids: string }): Promise<FormatFarmKeyOut[]> {
const { ids } = props;
const res = await this.api.get<FormatFarmKeyOut[]>(
(this.urlConfigs.FARM_KEYS || API_URLS.FARM_KEYS) + `?ids=${ids}`,
);
return res.data;
}
async fetchAvailabilityStatus(): Promise<AvailabilityCheckAPI3> {
const res = await this.api.get<AvailabilityCheckAPI3>(
this.urlConfigs.CHECK_AVAILABILITY || API_URLS.CHECK_AVAILABILITY,
);
return res.data;
}
async fetchLaunchConfigs(): Promise<ApiLaunchConfig[]> {
if (cacheLaunchConfigs.get(this.cluster)?.length) {
return cacheLaunchConfigs.get(this.cluster)!;
}
const res = await this.api.get<{
id: string;
success: boolean;
data: ApiLaunchConfig[];
}>(
this.cluster === "devnet"
? "https://launch-mint-v1-devnet.raydium.io/main/configs"
: "https://launch-mint-v1.raydium.io/main/configs",
);
cacheLaunchConfigs.set(this.cluster, res.data.data);
return res.data.data;
}
}