forked from solana-foundation/explorer
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtoken-info.ts
More file actions
184 lines (161 loc) · 5.63 KB
/
token-info.ts
File metadata and controls
184 lines (161 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import { Connection, PublicKey } from '@solana/web3.js';
import { ChainId, Client, Token, UtlConfig } from '@solflare-wallet/utl-sdk';
import { Cluster } from './cluster';
type TokenExtensions = {
readonly website?: string;
readonly bridgeContract?: string;
readonly assetContract?: string;
readonly address?: string;
readonly explorer?: string;
readonly twitter?: string;
readonly github?: string;
readonly medium?: string;
readonly tgann?: string;
readonly tggroup?: string;
readonly discord?: string;
readonly serumV3Usdt?: string;
readonly serumV3Usdc?: string;
readonly coingeckoId?: string;
readonly imageUrl?: string;
readonly description?: string;
};
export type FullLegacyTokenInfo = {
readonly chainId: number;
readonly address: string;
readonly name: string;
readonly decimals: number;
readonly symbol: string;
readonly logoURI?: string;
readonly tags?: string[];
readonly extensions?: TokenExtensions;
};
export type FullTokenInfo = FullLegacyTokenInfo & {
readonly verified: boolean;
};
type FullLegacyTokenInfoList = {
tokens: FullLegacyTokenInfo[];
};
function getChainId(cluster: Cluster): ChainId | undefined {
if (cluster === Cluster.MainnetBeta) return ChainId.MAINNET;
else if (cluster === Cluster.Testnet) return ChainId.TESTNET;
else if (cluster === Cluster.Devnet) return ChainId.DEVNET;
else return undefined;
}
function makeUtlClient(cluster: Cluster, connectionString: string): Client | undefined {
const chainId = getChainId(cluster);
if (!chainId) return undefined;
const config: UtlConfig = new UtlConfig({
chainId,
connection: new Connection(connectionString),
});
return new Client(config);
}
export function getTokenInfoSwrKey(address: string, cluster: Cluster, connectionString: string) {
return ['get-token-info', address, cluster, connectionString];
}
export async function getTokenInfo(
address: PublicKey,
cluster: Cluster,
connectionString: string
): Promise<Token | undefined> {
const client = makeUtlClient(cluster, connectionString);
if (!client) return undefined;
let token;
try {
token = await client.fetchMint(address);
} catch (error){
console.error(error);
}
return token;
}
type UtlApiResponse = {
content: Token[]
}
export async function getTokenInfoWithoutOnChainFallback(
address: PublicKey,
cluster: Cluster
): Promise<Token | undefined> {
const chainId = getChainId(cluster);
if (!chainId) return undefined;
// Request token info directly from UTL API
// We don't use the SDK here because we don't want it to fallback to an on-chain request
const response = await fetch(`https://token-list-api.solana.cloud/v1/mints?chainId=${chainId}`, {
body: JSON.stringify({ addresses: [address.toBase58()] }),
headers: {
"Content-Type": "application/json",
},
method: 'POST',
});
if (response.status >= 400) {
console.error(`Error calling UTL API for address ${address} on chain ID ${chainId}. Status ${response.status}`);
return undefined;
}
const fetchedData = await response.json() as UtlApiResponse;
return fetchedData.content[0];
}
async function getFullLegacyTokenInfoUsingCdn(
address: PublicKey,
chainId: ChainId
): Promise<FullLegacyTokenInfo | undefined> {
const tokenListResponse = await fetch(
'https://cdn.jsdelivr.net/gh/solana-labs/token-list@latest/src/tokens/solana.tokenlist.json'
);
if (tokenListResponse.status >= 400) {
console.error(new Error('Error fetching token list from CDN'));
return undefined;
}
const { tokens } = (await tokenListResponse.json()) as FullLegacyTokenInfoList;
const tokenInfo = tokens.find(t => t.address === address.toString() && t.chainId === chainId);
return tokenInfo;
}
/**
* Get the full token info from a CDN with the legacy token list
* The UTL SDK only returns the most common fields, we sometimes need eg extensions
* @param address Public key of the token
* @param cluster Cluster to fetch the token info for
*/
export async function getFullTokenInfo(
address: PublicKey,
cluster: Cluster,
connectionString: string
): Promise<FullTokenInfo | undefined> {
const chainId = getChainId(cluster);
if (!chainId) return undefined;
const [legacyCdnTokenInfo, sdkTokenInfo] = await Promise.all([
getFullLegacyTokenInfoUsingCdn(address, chainId),
getTokenInfo(address, cluster, connectionString),
]);
if (!sdkTokenInfo) {
return legacyCdnTokenInfo
? {
...legacyCdnTokenInfo,
verified: true,
}
: undefined;
}
// Merge the fields, prioritising the sdk ones which are more up to date
let tags: string[] = [];
if (sdkTokenInfo.tags) tags = Array.from(sdkTokenInfo.tags);
else if (legacyCdnTokenInfo?.tags) tags = legacyCdnTokenInfo.tags;
return {
address: sdkTokenInfo.address,
chainId,
decimals: sdkTokenInfo.decimals ?? 0,
extensions: legacyCdnTokenInfo?.extensions,
logoURI: sdkTokenInfo.logoURI ?? undefined,
name: sdkTokenInfo.name,
symbol: sdkTokenInfo.symbol,
tags,
verified: sdkTokenInfo.verified ?? false,
};
}
export async function getTokenInfos(
addresses: PublicKey[],
cluster: Cluster,
connectionString: string
): Promise<Token[] | undefined> {
const client = makeUtlClient(cluster, connectionString);
if (!client) return undefined;
const tokens = await client.fetchMints(addresses);
return tokens;
}