test-raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
239 lines (210 loc) • 8.58 kB
text/typescript
import { PublicKey } from "@solana/web3.js";
import { MintLayout, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import { Price, Token, TokenAmount, Fraction } from "@/module";
import { PublicKeyish, validateAndParsePublicKey, SOLMint, WSOLMint } from "@/common/pubKey";
import { BigNumberish, parseNumberInfo, toBN } from "@/common/bignumber";
import { JupTokenType } from "@/api/type";
import ModuleBase, { ModuleBaseProps } from "../moduleBase";
import { LoadParams } from "../type";
import { TokenInfo } from "./type";
import { parseTokenPrice } from "./utils";
import { SOL_INFO } from "./constant";
import BN from "bn.js";
export interface MintToTokenAmount {
token?: Token;
mint: PublicKeyish;
amount: BigNumberish;
decimalDone?: boolean;
}
export default class TokenModule extends ModuleBase {
private _tokenList: TokenInfo[] = [];
private _tokenMap: Map<string, TokenInfo> = new Map();
private _blackTokenMap: Map<string, TokenInfo> = new Map();
private _tokenPrice: Map<string, Price> = new Map();
private _tokenPriceOrg: Map<string, number> = new Map();
private _tokenPriceFetched = { prevCount: 0, fetched: 0 };
private _mintGroup: { official: Set<string>; jup: Set<string>; extra: Set<string> } = {
official: new Set(),
jup: new Set(),
extra: new Set(),
};
private _extraTokenList: TokenInfo[] = [];
constructor(params: ModuleBaseProps) {
super(params);
}
public async load(params?: LoadParams & { fetchTokenPrice?: boolean; type?: JupTokenType }): Promise<void> {
this.checkDisabled();
const { forceUpdate = false, type = JupTokenType.ALL, fetchTokenPrice = false } = params || {};
const { mintList, blacklist } = await this.scope.fetchV3TokenList(forceUpdate);
const jup = await this.scope.fetchJupTokenList(type, forceUpdate);
// reset all data
this._tokenList = [];
this._tokenMap = new Map();
this._blackTokenMap = new Map();
this._mintGroup = { official: new Set(), jup: new Set(), extra: new Set() };
this._tokenMap.set(SOL_INFO.address, SOL_INFO);
this._mintGroup.official.add(SOL_INFO.address);
blacklist.forEach((token) => {
this._blackTokenMap.set(token.address, { ...token, priority: -1 });
});
mintList.forEach((token) => {
if (this._blackTokenMap.has(token.address)) return;
this._tokenMap.set(token.address, { ...token, type: "raydium", priority: 2 });
this._mintGroup.official.add(token.address);
});
jup.forEach((token) => {
if (this._blackTokenMap.has(token.address) || this._tokenMap.has(token.address)) return;
this._tokenMap.set(token.address, { ...token, type: "jupiter", priority: 1 });
this._mintGroup.jup.add(token.address);
});
this._extraTokenList.forEach((token) => {
if (this._blackTokenMap.has(token.address) || this._tokenMap.has(token.address)) return;
this._tokenMap.set(token.address, { ...token, type: "extra", priority: 1 });
this._mintGroup.extra.add(token.address);
});
this._tokenList = Array.from(this._tokenMap).map((data) => data[1]);
// if (fetchTokenPrice) await this.fetchTokenPrices(forceUpdate);
}
get tokenList(): TokenInfo[] {
return this._tokenList;
}
get tokenMap(): Map<string, TokenInfo> {
return this._tokenMap;
}
get blackTokenMap(): Map<string, TokenInfo> {
return this._blackTokenMap;
}
get mintGroup(): { official: Set<string>; jup: Set<string> } {
return this._mintGroup;
}
// public async fetchTokenPrices(forceUpdate?: boolean): Promise<Map<string, Price>> {
// const totalCount = this._mintGroup.official.size + this._mintGroup.jup.size;
// if (
// !forceUpdate &&
// totalCount <= this._tokenPriceFetched.prevCount &&
// Date.now() - this._tokenPriceFetched.fetched < 60 * 1000 * 5
// )
// return this._tokenPrice;
// this._tokenPrice = new Map();
// const raydiumPriceRes = await this.scope.api.getRaydiumTokenPrice();
// const raydiumPrices: { [key: string]: Price } = Object.keys(raydiumPriceRes).reduce((acc, key) => {
// this._tokenPriceOrg.set(key, raydiumPriceRes[key]);
// return this._tokenMap.get(key)
// ? {
// ...acc,
// [key]: parseTokenPrice({
// token: this._tokenMap.get(key)!,
// numberPrice: raydiumPriceRes[key],
// decimalDone: true,
// }),
// }
// : acc;
// }, {});
// this._tokenPrice = new Map(Object.entries(raydiumPrices));
// this._tokenPrice.set(PublicKey.default.toString(), this._tokenPrice.get(WSOLMint.toString())!);
// this._tokenPriceFetched = { prevCount: totalCount, fetched: Date.now() };
// return this._tokenPrice;
// }
/** === util functions === */
public async getChainTokenInfo(mint: PublicKeyish): Promise<{ token: Token; tokenInfo: TokenInfo }> {
const _mint = validateAndParsePublicKey({ publicKey: mint });
const mintStr = _mint.toBase58();
const mintSymbol = _mint.toString().substring(0, 6);
const isSol = _mint.equals(SOLMint);
if (isSol) {
return {
token: new Token({
decimals: SOL_INFO.decimals,
name: SOL_INFO.name,
symbol: SOL_INFO.symbol,
skipMint: true,
mint: "",
}),
tokenInfo: SOL_INFO,
};
}
const tokenInfo = await this.scope.api.getTokenInfo(_mint);
if (tokenInfo) {
this._mintGroup.extra.add(mintStr);
const fullInfo = { ...tokenInfo, priority: 2 };
this._tokenMap.set(mintStr, fullInfo);
return {
token: new Token({
mint: _mint,
decimals: tokenInfo.decimals,
symbol: tokenInfo.symbol || mintSymbol,
name: tokenInfo.name || mintSymbol,
isToken2022: tokenInfo.programId === TOKEN_2022_PROGRAM_ID.toBase58(),
}),
tokenInfo: fullInfo,
};
}
const info = await this.scope.connection.getAccountInfo(_mint);
if (!info) this.logAndCreateError("On chain token not found, mint:", _mint.toBase58());
const data = MintLayout.decode(info!.data);
const fullInfo = {
chainId: 101,
address: mintStr,
programId: info!.owner.toBase58(),
logoURI: "",
symbol: mintSymbol,
name: mintSymbol,
decimals: data.decimals,
tags: [],
extensions: {},
priority: 0,
};
if (!this._tokenMap.has(mintStr)) {
this._mintGroup.extra.add(mintStr);
this._tokenMap.set(mintStr, fullInfo);
}
return {
token: new Token({
mint: _mint,
decimals: data.decimals,
symbol: mintSymbol,
name: mintSymbol,
isToken2022: info!.owner.equals(TOKEN_2022_PROGRAM_ID),
}),
tokenInfo: fullInfo,
};
}
public mintToToken(mint: PublicKeyish): Token {
const _mint = validateAndParsePublicKey({ publicKey: mint });
const tokenInfo = this._tokenMap.get(_mint.toBase58());
if (!tokenInfo)
this.logAndCreateError("token not found, mint:", _mint.toBase58(), ", use getChainTokenInfo to get info instead");
const { decimals, name, symbol } = tokenInfo!;
const isSol = _mint.equals(SOLMint);
return new Token({
decimals,
name,
symbol,
skipMint: isSol,
mint: isSol ? "" : mint,
isToken2022: tokenInfo!.programId === TOKEN_2022_PROGRAM_ID.toBase58(),
});
}
public mintToTokenAmount({ mint, amount, decimalDone, token }: MintToTokenAmount): TokenAmount {
const _token = token || this.mintToToken(mint);
if (decimalDone) {
const numberDetails = parseNumberInfo(amount);
const amountBigNumber = toBN(new Fraction(numberDetails.numerator, numberDetails.denominator));
return new TokenAmount(_token, amountBigNumber);
}
return new TokenAmount(_token, this.decimalAmount({ mint, amount, decimalDone }));
}
public decimalAmount({ mint, amount, token }: MintToTokenAmount): BN {
const numberDetails = parseNumberInfo(amount);
const _token = token || this.mintToToken(mint);
return toBN(new Fraction(numberDetails.numerator, numberDetails.denominator).mul(new BN(10 ** _token.decimals)));
}
public uiAmount({ mint, amount, token }: MintToTokenAmount): string {
const numberDetails = parseNumberInfo(amount);
const _token = token || this.mintToToken(mint);
if (!_token) return "";
return new Fraction(numberDetails.numerator, numberDetails.denominator)
.div(new BN(10 ** _token.decimals))
.toSignificant(_token.decimals);
}
}