test-raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
229 lines (202 loc) • 7.79 kB
text/typescript
import { MintLayout, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { Price, Token, TokenAmount, Fraction } from "@/module";
import { PublicKeyish, validateAndParsePublicKey, SOLMint } 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 { 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.Strict } = 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,
programId:
token.programId ??
(token.tags.includes("token-2022") ? TOKEN_2022_PROGRAM_ID.toBase58() : TOKEN_PROGRAM_ID.toBase58()),
});
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,
programId:
token.programId ??
(token.tags.includes("token-2022") ? TOKEN_2022_PROGRAM_ID.toBase58() : TOKEN_PROGRAM_ID.toBase58()),
});
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,
programId:
token.programId || token.tags.includes("token-2022")
? TOKEN_2022_PROGRAM_ID.toBase58()
: TOKEN_PROGRAM_ID.toBase58(),
});
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;
}
/** === 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);
}
}