tx-indexer 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -12,12 +12,40 @@ function parseSignature(sig) {
12
12
 
13
13
  // ../domain/src/money/token-registry.ts
14
14
  var KNOWN_TOKENS = {
15
+ // Native
15
16
  SOL: "So11111111111111111111111111111111111111112",
17
+ // Stablecoins
16
18
  USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
19
+ USDT: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
20
+ PYUSD: "2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo",
21
+ USDG: "2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH",
17
22
  USDC_BRIDGED: "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM",
18
- USDG: "2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH"
19
- };
23
+ DAI: "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCCi3Z4dPuFhh",
24
+ // Major tokens
25
+ JUP: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
26
+ JTO: "jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL",
27
+ PYTH: "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3",
28
+ BONK: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
29
+ WIF: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm",
30
+ RENDER: "rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof",
31
+ HNT: "hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux",
32
+ RAY: "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R",
33
+ ORCA: "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE",
34
+ MNGO: "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac",
35
+ MSOL: "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So",
36
+ JITOSOL: "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn",
37
+ BSOL: "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1",
38
+ // Memecoins
39
+ POPCAT: "7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr",
40
+ MEW: "MEW1gQWJ3nEXg2qgERiKu7FAFj79PHvQVREQUzScPP5",
41
+ PNUT: "2qEHjDLDLbuBgRYvsxhc5D6uDWAivNFZGan56P1tpump",
42
+ FARTCOIN: "9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump",
43
+ AI16Z: "HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC",
44
+ // Wrapped tokens
45
+ WBTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh",
46
+ WETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"};
20
47
  var TOKEN_INFO = {
48
+ // Native SOL
21
49
  [KNOWN_TOKENS.SOL]: {
22
50
  mint: KNOWN_TOKENS.SOL,
23
51
  symbol: "SOL",
@@ -25,6 +53,7 @@ var TOKEN_INFO = {
25
53
  decimals: 9,
26
54
  logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png"
27
55
  },
56
+ // Stablecoins
28
57
  [KNOWN_TOKENS.USDC]: {
29
58
  mint: KNOWN_TOKENS.USDC,
30
59
  symbol: "USDC",
@@ -32,22 +61,194 @@ var TOKEN_INFO = {
32
61
  decimals: 6,
33
62
  logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png"
34
63
  },
35
- [KNOWN_TOKENS.USDC_BRIDGED]: {
36
- mint: KNOWN_TOKENS.USDC_BRIDGED,
37
- symbol: "USDCet",
38
- name: "USDC (Bridged)",
39
- decimals: 6
64
+ [KNOWN_TOKENS.USDT]: {
65
+ mint: KNOWN_TOKENS.USDT,
66
+ symbol: "USDT",
67
+ name: "Tether USD",
68
+ decimals: 6,
69
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB/logo.svg"
70
+ },
71
+ [KNOWN_TOKENS.PYUSD]: {
72
+ mint: KNOWN_TOKENS.PYUSD,
73
+ symbol: "PYUSD",
74
+ name: "PayPal USD",
75
+ decimals: 6,
76
+ logoURI: "https://img.fotofolio.xyz/?url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolana-labs%2Ftoken-list%2Fmain%2Fassets%2Fmainnet%2F2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo%2Flogo.png"
40
77
  },
41
78
  [KNOWN_TOKENS.USDG]: {
42
79
  mint: KNOWN_TOKENS.USDG,
43
80
  symbol: "USDG",
44
81
  name: "USD Glitter",
45
82
  decimals: 6
83
+ },
84
+ [KNOWN_TOKENS.USDC_BRIDGED]: {
85
+ mint: KNOWN_TOKENS.USDC_BRIDGED,
86
+ symbol: "USDCet",
87
+ name: "USDC (Wormhole)",
88
+ decimals: 6
89
+ },
90
+ [KNOWN_TOKENS.DAI]: {
91
+ mint: KNOWN_TOKENS.DAI,
92
+ symbol: "DAI",
93
+ name: "DAI (Wormhole)",
94
+ decimals: 8
95
+ },
96
+ // Major tokens
97
+ [KNOWN_TOKENS.JUP]: {
98
+ mint: KNOWN_TOKENS.JUP,
99
+ symbol: "JUP",
100
+ name: "Jupiter",
101
+ decimals: 6,
102
+ logoURI: "https://static.jup.ag/jup/icon.png"
103
+ },
104
+ [KNOWN_TOKENS.JTO]: {
105
+ mint: KNOWN_TOKENS.JTO,
106
+ symbol: "JTO",
107
+ name: "Jito",
108
+ decimals: 9,
109
+ logoURI: "https://metadata.jito.network/token/jto/image"
110
+ },
111
+ [KNOWN_TOKENS.PYTH]: {
112
+ mint: KNOWN_TOKENS.PYTH,
113
+ symbol: "PYTH",
114
+ name: "Pyth Network",
115
+ decimals: 6,
116
+ logoURI: "https://pyth.network/token.svg"
117
+ },
118
+ [KNOWN_TOKENS.BONK]: {
119
+ mint: KNOWN_TOKENS.BONK,
120
+ symbol: "BONK",
121
+ name: "Bonk",
122
+ decimals: 5,
123
+ logoURI: "https://arweave.net/hQiPZOsRZXGXBJd_82PhVdlM_hACsT_q6wqwf5cSY7I"
124
+ },
125
+ [KNOWN_TOKENS.WIF]: {
126
+ mint: KNOWN_TOKENS.WIF,
127
+ symbol: "WIF",
128
+ name: "dogwifhat",
129
+ decimals: 6,
130
+ logoURI: "https://bafkreibk3covs5ltyqxa272uodhber6kcclnlgrbwjee4kyqhstwmjqpfa.ipfs.nftstorage.link/"
131
+ },
132
+ [KNOWN_TOKENS.RENDER]: {
133
+ mint: KNOWN_TOKENS.RENDER,
134
+ symbol: "RENDER",
135
+ name: "Render Token",
136
+ decimals: 8,
137
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof/logo.png"
138
+ },
139
+ [KNOWN_TOKENS.HNT]: {
140
+ mint: KNOWN_TOKENS.HNT,
141
+ symbol: "HNT",
142
+ name: "Helium",
143
+ decimals: 8,
144
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux/logo.png"
145
+ },
146
+ [KNOWN_TOKENS.RAY]: {
147
+ mint: KNOWN_TOKENS.RAY,
148
+ symbol: "RAY",
149
+ name: "Raydium",
150
+ decimals: 6,
151
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R/logo.png"
152
+ },
153
+ [KNOWN_TOKENS.ORCA]: {
154
+ mint: KNOWN_TOKENS.ORCA,
155
+ symbol: "ORCA",
156
+ name: "Orca",
157
+ decimals: 6,
158
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE/logo.png"
159
+ },
160
+ [KNOWN_TOKENS.MNGO]: {
161
+ mint: KNOWN_TOKENS.MNGO,
162
+ symbol: "MNGO",
163
+ name: "Mango",
164
+ decimals: 6,
165
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac/logo.png"
166
+ },
167
+ // Liquid staking tokens
168
+ [KNOWN_TOKENS.MSOL]: {
169
+ mint: KNOWN_TOKENS.MSOL,
170
+ symbol: "mSOL",
171
+ name: "Marinade Staked SOL",
172
+ decimals: 9,
173
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So/logo.png"
174
+ },
175
+ [KNOWN_TOKENS.JITOSOL]: {
176
+ mint: KNOWN_TOKENS.JITOSOL,
177
+ symbol: "JitoSOL",
178
+ name: "Jito Staked SOL",
179
+ decimals: 9,
180
+ logoURI: "https://storage.googleapis.com/token-metadata/JitoSOL-256.png"
181
+ },
182
+ [KNOWN_TOKENS.BSOL]: {
183
+ mint: KNOWN_TOKENS.BSOL,
184
+ symbol: "bSOL",
185
+ name: "BlazeStake Staked SOL",
186
+ decimals: 9,
187
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1/logo.png"
188
+ },
189
+ // Memecoins
190
+ [KNOWN_TOKENS.POPCAT]: {
191
+ mint: KNOWN_TOKENS.POPCAT,
192
+ symbol: "POPCAT",
193
+ name: "Popcat",
194
+ decimals: 9,
195
+ logoURI: "https://bafkreidvkvuzyslw5jh5z242lgzwzhbi2kxxnpkm73fkuqzasyr34jj2o4.ipfs.nftstorage.link/"
196
+ },
197
+ [KNOWN_TOKENS.MEW]: {
198
+ mint: KNOWN_TOKENS.MEW,
199
+ symbol: "MEW",
200
+ name: "cat in a dogs world",
201
+ decimals: 5,
202
+ logoURI: "https://bafkreidlwyr565dxtao2ipsze6bmzpszqzybz7sqi2zaet5fs7k53henju.ipfs.nftstorage.link/"
203
+ },
204
+ [KNOWN_TOKENS.PNUT]: {
205
+ mint: KNOWN_TOKENS.PNUT,
206
+ symbol: "Peanut",
207
+ name: "Peanut the Squirrel",
208
+ decimals: 6,
209
+ logoURI: "https://ipfs.io/ipfs/QmNS3Hdb6pMheFzRdwXr3PPCrXcBohzhLrKHqEUV1n3HnJ"
210
+ },
211
+ [KNOWN_TOKENS.FARTCOIN]: {
212
+ mint: KNOWN_TOKENS.FARTCOIN,
213
+ symbol: "FARTCOIN",
214
+ name: "Fartcoin",
215
+ decimals: 6,
216
+ logoURI: "https://ipfs.io/ipfs/QmQHY6t8TxucH3F9LGPBBqqRLbyWx7NxWvrnoZKcq9ErrR"
217
+ },
218
+ [KNOWN_TOKENS.AI16Z]: {
219
+ mint: KNOWN_TOKENS.AI16Z,
220
+ symbol: "ai16z",
221
+ name: "ai16z",
222
+ decimals: 9,
223
+ logoURI: "https://ipfs.io/ipfs/QmRbm1mprqHmJ7PvCTrSNydkquLi5r41wq8kWbHvoRm3FX"
224
+ },
225
+ // Wrapped tokens
226
+ [KNOWN_TOKENS.WBTC]: {
227
+ mint: KNOWN_TOKENS.WBTC,
228
+ symbol: "WBTC",
229
+ name: "Wrapped BTC (Wormhole)",
230
+ decimals: 8,
231
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh/logo.png"
232
+ },
233
+ [KNOWN_TOKENS.WETH]: {
234
+ mint: KNOWN_TOKENS.WETH,
235
+ symbol: "WETH",
236
+ name: "Wrapped ETH (Wormhole)",
237
+ decimals: 8,
238
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs/logo.png"
46
239
  }
47
240
  };
48
241
  function getTokenInfo(mint) {
49
242
  return TOKEN_INFO[mint];
50
243
  }
244
+ function createUnknownToken(mint, decimals) {
245
+ return {
246
+ mint,
247
+ symbol: mint.slice(0, 8),
248
+ name: `Unknown Token (${mint.slice(0, 8)}...)`,
249
+ decimals
250
+ };
251
+ }
51
252
 
52
253
  // ../solana/src/constants/program-ids.ts
53
254
  var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
@@ -900,21 +1101,38 @@ function determineTokenRole(change, tx, feePayer) {
900
1101
  }
901
1102
 
902
1103
  // ../classification/src/classifiers/transfer-classifier.ts
1104
+ function findBestTransferPair(legs) {
1105
+ const senderLegs = legs.filter(
1106
+ (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
1107
+ );
1108
+ if (senderLegs.length === 0) return null;
1109
+ let bestPair = null;
1110
+ let bestAmount = 0;
1111
+ for (const senderLeg of senderLegs) {
1112
+ const receiverLeg = legs.find(
1113
+ (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
1114
+ // Must be different account
1115
+ );
1116
+ if (receiverLeg) {
1117
+ const amount = senderLeg.amount.amountUi;
1118
+ if (amount > bestAmount) {
1119
+ bestAmount = amount;
1120
+ bestPair = { senderLeg, receiverLeg };
1121
+ }
1122
+ }
1123
+ }
1124
+ return bestPair;
1125
+ }
903
1126
  var TransferClassifier = class {
904
1127
  name = "transfer";
905
1128
  priority = 20;
906
1129
  classify(context) {
907
1130
  const { legs, tx } = context;
908
1131
  const facilitator = tx.accountKeys ? detectFacilitator(tx.accountKeys) : null;
909
- const senderLeg = legs.find(
910
- (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
911
- );
912
- if (!senderLeg) return null;
1132
+ const pair = findBestTransferPair(legs);
1133
+ if (!pair) return null;
1134
+ const { senderLeg, receiverLeg } = pair;
913
1135
  const sender = senderLeg.accountId.replace("external:", "");
914
- const receiverLeg = legs.find(
915
- (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint
916
- );
917
- if (!receiverLeg) return null;
918
1136
  const receiver = receiverLeg.accountId.replace("external:", "");
919
1137
  return {
920
1138
  primaryType: "transfer",
@@ -1393,6 +1611,27 @@ var FeeOnlyClassifier = class {
1393
1611
  };
1394
1612
 
1395
1613
  // ../classification/src/classifiers/solana-pay-classifier.ts
1614
+ function findBestTransferPair2(legs) {
1615
+ const senderLegs = legs.filter(
1616
+ (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
1617
+ );
1618
+ if (senderLegs.length === 0) return null;
1619
+ let bestPair = null;
1620
+ let bestAmount = 0;
1621
+ for (const senderLeg of senderLegs) {
1622
+ const receiverLeg = legs.find(
1623
+ (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
1624
+ );
1625
+ if (receiverLeg) {
1626
+ const amount = senderLeg.amount.amountUi;
1627
+ if (amount > bestAmount) {
1628
+ bestAmount = amount;
1629
+ bestPair = { senderLeg, receiverLeg };
1630
+ }
1631
+ }
1632
+ }
1633
+ return bestPair;
1634
+ }
1396
1635
  var SolanaPayClassifier = class {
1397
1636
  name = "solana-pay";
1398
1637
  priority = 95;
@@ -1402,12 +1641,9 @@ var SolanaPayClassifier = class {
1402
1641
  return null;
1403
1642
  }
1404
1643
  const memo = parseSolanaPayMemo(tx.memo);
1405
- const senderLeg = legs.find(
1406
- (leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.role === "sent"
1407
- );
1408
- const receiverLeg = legs.find(
1409
- (leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.role === "received"
1410
- );
1644
+ const pair = findBestTransferPair2(legs);
1645
+ const senderLeg = pair?.senderLeg ?? null;
1646
+ const receiverLeg = pair?.receiverLeg ?? null;
1411
1647
  const sender = senderLeg?.accountId.replace("external:", "") ?? null;
1412
1648
  const receiver = receiverLeg?.accountId.replace("external:", "") ?? null;
1413
1649
  const primaryAmount = senderLeg?.amount ?? receiverLeg?.amount ?? null;
@@ -1697,6 +1933,118 @@ function filterSpamTransactions(transactions, config) {
1697
1933
  );
1698
1934
  }
1699
1935
 
1936
+ // ../domain/src/money/token-fetcher.ts
1937
+ var DEFAULT_JUPITER_API_URL = "https://tokens.jup.ag/tokens?tags=verified";
1938
+ var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
1939
+ function createTokenFetcher(options = {}) {
1940
+ const {
1941
+ jupiterApiUrl = DEFAULT_JUPITER_API_URL,
1942
+ cacheTtlMs = DEFAULT_CACHE_TTL_MS,
1943
+ prefetch = false
1944
+ } = options;
1945
+ const jupiterCache = /* @__PURE__ */ new Map();
1946
+ let lastFetchTime = 0;
1947
+ let fetchPromise = null;
1948
+ async function fetchJupiterTokens() {
1949
+ if (fetchPromise) {
1950
+ return fetchPromise;
1951
+ }
1952
+ if (Date.now() - lastFetchTime < cacheTtlMs && jupiterCache.size > 0) {
1953
+ return;
1954
+ }
1955
+ fetchPromise = (async () => {
1956
+ try {
1957
+ const response = await fetch(jupiterApiUrl);
1958
+ if (!response.ok) {
1959
+ console.warn(
1960
+ `Jupiter API returned ${response.status}: ${response.statusText}`
1961
+ );
1962
+ return;
1963
+ }
1964
+ const tokens = await response.json();
1965
+ jupiterCache.clear();
1966
+ for (const token of tokens) {
1967
+ jupiterCache.set(token.address, {
1968
+ mint: token.address,
1969
+ symbol: token.symbol,
1970
+ name: token.name,
1971
+ decimals: token.decimals,
1972
+ logoURI: token.logoURI
1973
+ });
1974
+ }
1975
+ lastFetchTime = Date.now();
1976
+ } catch (error) {
1977
+ console.warn("Failed to fetch Jupiter tokens:", error);
1978
+ } finally {
1979
+ fetchPromise = null;
1980
+ }
1981
+ })();
1982
+ return fetchPromise;
1983
+ }
1984
+ async function getToken(mint, decimals = 9) {
1985
+ const staticToken = TOKEN_INFO[mint];
1986
+ if (staticToken) {
1987
+ return staticToken;
1988
+ }
1989
+ const cachedToken = jupiterCache.get(mint);
1990
+ if (cachedToken) {
1991
+ return cachedToken;
1992
+ }
1993
+ await fetchJupiterTokens();
1994
+ const fetchedToken = jupiterCache.get(mint);
1995
+ if (fetchedToken) {
1996
+ return fetchedToken;
1997
+ }
1998
+ return createUnknownToken(mint, decimals);
1999
+ }
2000
+ async function getTokens(mints, defaultDecimals = 9) {
2001
+ const result = /* @__PURE__ */ new Map();
2002
+ const missingMints = [];
2003
+ for (const mint of mints) {
2004
+ const staticToken = TOKEN_INFO[mint];
2005
+ if (staticToken) {
2006
+ result.set(mint, staticToken);
2007
+ continue;
2008
+ }
2009
+ const cachedToken = jupiterCache.get(mint);
2010
+ if (cachedToken) {
2011
+ result.set(mint, cachedToken);
2012
+ continue;
2013
+ }
2014
+ missingMints.push(mint);
2015
+ }
2016
+ if (missingMints.length > 0) {
2017
+ await fetchJupiterTokens();
2018
+ for (const mint of missingMints) {
2019
+ const fetchedToken = jupiterCache.get(mint);
2020
+ if (fetchedToken) {
2021
+ result.set(mint, fetchedToken);
2022
+ } else {
2023
+ result.set(mint, createUnknownToken(mint, defaultDecimals));
2024
+ }
2025
+ }
2026
+ }
2027
+ return result;
2028
+ }
2029
+ async function refresh() {
2030
+ lastFetchTime = 0;
2031
+ await fetchJupiterTokens();
2032
+ }
2033
+ function getCacheSize() {
2034
+ return jupiterCache.size;
2035
+ }
2036
+ if (prefetch) {
2037
+ fetchJupiterTokens().catch(() => {
2038
+ });
2039
+ }
2040
+ return {
2041
+ getToken,
2042
+ getTokens,
2043
+ refresh,
2044
+ getCacheSize
2045
+ };
2046
+ }
2047
+
1700
2048
  // src/nft.ts
1701
2049
  async function fetchNftMetadata(rpcUrl, mintAddress) {
1702
2050
  const response = await fetch(rpcUrl, {
@@ -1741,6 +2089,102 @@ async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
1741
2089
 
1742
2090
  // src/client.ts
1743
2091
  var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
2092
+ async function enrichTokenMetadata(tokenFetcher, classified) {
2093
+ const mints = /* @__PURE__ */ new Set();
2094
+ const decimalsMap = /* @__PURE__ */ new Map();
2095
+ for (const leg of classified.legs) {
2096
+ const mint = leg.amount.token.mint;
2097
+ mints.add(mint);
2098
+ decimalsMap.set(mint, leg.amount.token.decimals);
2099
+ }
2100
+ if (classified.classification.primaryAmount?.token.mint) {
2101
+ const mint = classified.classification.primaryAmount.token.mint;
2102
+ mints.add(mint);
2103
+ decimalsMap.set(
2104
+ mint,
2105
+ classified.classification.primaryAmount.token.decimals
2106
+ );
2107
+ }
2108
+ if (classified.classification.secondaryAmount?.token.mint) {
2109
+ const mint = classified.classification.secondaryAmount.token.mint;
2110
+ mints.add(mint);
2111
+ decimalsMap.set(
2112
+ mint,
2113
+ classified.classification.secondaryAmount.token.decimals
2114
+ );
2115
+ }
2116
+ if (mints.size === 0) {
2117
+ return classified;
2118
+ }
2119
+ const tokenInfoMap = await tokenFetcher.getTokens(
2120
+ Array.from(mints),
2121
+ 9
2122
+ // default decimals
2123
+ );
2124
+ function enrichAmount(amount) {
2125
+ if (!amount) return amount;
2126
+ const enrichedToken = tokenInfoMap.get(amount.token.mint);
2127
+ if (!enrichedToken || enrichedToken.symbol === amount.token.symbol) {
2128
+ return amount;
2129
+ }
2130
+ return {
2131
+ ...amount,
2132
+ token: {
2133
+ ...enrichedToken,
2134
+ // Keep the decimals from the original (from RPC) as they're authoritative
2135
+ decimals: amount.token.decimals
2136
+ }
2137
+ };
2138
+ }
2139
+ function enrichLeg(leg) {
2140
+ const enrichedToken = tokenInfoMap.get(leg.amount.token.mint);
2141
+ if (!enrichedToken || enrichedToken.symbol === leg.amount.token.symbol) {
2142
+ return leg;
2143
+ }
2144
+ return {
2145
+ ...leg,
2146
+ amount: {
2147
+ ...leg.amount,
2148
+ token: {
2149
+ ...enrichedToken,
2150
+ decimals: leg.amount.token.decimals
2151
+ }
2152
+ }
2153
+ };
2154
+ }
2155
+ const enrichedLegs = classified.legs.map(enrichLeg);
2156
+ const enrichedClassification = {
2157
+ ...classified.classification,
2158
+ primaryAmount: enrichAmount(classified.classification.primaryAmount) ?? null,
2159
+ secondaryAmount: enrichAmount(classified.classification.secondaryAmount)
2160
+ };
2161
+ return {
2162
+ tx: classified.tx,
2163
+ legs: enrichedLegs,
2164
+ classification: enrichedClassification
2165
+ };
2166
+ }
2167
+ async function enrichTokenMetadataBatch(tokenFetcher, transactions) {
2168
+ const mints = /* @__PURE__ */ new Set();
2169
+ for (const classified of transactions) {
2170
+ for (const leg of classified.legs) {
2171
+ mints.add(leg.amount.token.mint);
2172
+ }
2173
+ if (classified.classification.primaryAmount?.token.mint) {
2174
+ mints.add(classified.classification.primaryAmount.token.mint);
2175
+ }
2176
+ if (classified.classification.secondaryAmount?.token.mint) {
2177
+ mints.add(classified.classification.secondaryAmount.token.mint);
2178
+ }
2179
+ }
2180
+ if (mints.size === 0) {
2181
+ return transactions;
2182
+ }
2183
+ await tokenFetcher.getTokens(Array.from(mints));
2184
+ return Promise.all(
2185
+ transactions.map((tx) => enrichTokenMetadata(tokenFetcher, tx))
2186
+ );
2187
+ }
1744
2188
  async function enrichNftClassification(rpcUrl, classified) {
1745
2189
  const { classification } = classified;
1746
2190
  if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
@@ -1773,51 +2217,74 @@ async function enrichNftClassification(rpcUrl, classified) {
1773
2217
  function createIndexer(options) {
1774
2218
  const rpcUrl = "client" in options ? "" : options.rpcUrl;
1775
2219
  const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
2220
+ const tokenFetcher = createTokenFetcher();
1776
2221
  return {
1777
2222
  rpc: client.rpc,
1778
2223
  async getBalance(walletAddress, tokenMints) {
1779
2224
  return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
1780
2225
  },
1781
2226
  async getTransactions(walletAddress, options2 = {}) {
1782
- const { limit = 10, before, until, filterSpam = true, spamConfig, enrichNftMetadata = true } = options2;
2227
+ const {
2228
+ limit = 10,
2229
+ before,
2230
+ until,
2231
+ filterSpam = true,
2232
+ spamConfig,
2233
+ enrichNftMetadata = true,
2234
+ enrichTokenMetadata: enrichTokens = true
2235
+ } = options2;
1783
2236
  async function enrichBatch(transactions) {
1784
- if (!enrichNftMetadata || !rpcUrl) {
1785
- return transactions;
1786
- }
1787
- const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
1788
- if (nftMints.length === 0) {
1789
- return transactions;
2237
+ let result2 = transactions;
2238
+ if (enrichTokens) {
2239
+ result2 = await enrichTokenMetadataBatch(tokenFetcher, result2);
1790
2240
  }
1791
- const nftMetadataMap = await fetchNftMetadataBatch(rpcUrl, nftMints);
1792
- return transactions.map((t) => {
1793
- const nftMint = t.classification.metadata?.nft_mint;
1794
- if (!nftMint || !nftMetadataMap.has(nftMint)) {
1795
- return t;
1796
- }
1797
- const nftData = nftMetadataMap.get(nftMint);
1798
- return {
1799
- ...t,
1800
- classification: {
1801
- ...t.classification,
1802
- metadata: {
1803
- ...t.classification.metadata,
1804
- nft_name: nftData.name,
1805
- nft_image: nftData.image,
1806
- nft_cdn_image: nftData.cdnImage,
1807
- nft_collection: nftData.collection,
1808
- nft_symbol: nftData.symbol,
1809
- nft_attributes: nftData.attributes
2241
+ if (enrichNftMetadata && rpcUrl) {
2242
+ const nftMints = result2.filter(
2243
+ (t) => NFT_TRANSACTION_TYPES.includes(
2244
+ t.classification.primaryType
2245
+ )
2246
+ ).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
2247
+ if (nftMints.length > 0) {
2248
+ const nftMetadataMap = await fetchNftMetadataBatch(
2249
+ rpcUrl,
2250
+ nftMints
2251
+ );
2252
+ result2 = result2.map((t) => {
2253
+ const nftMint = t.classification.metadata?.nft_mint;
2254
+ if (!nftMint || !nftMetadataMap.has(nftMint)) {
2255
+ return t;
1810
2256
  }
1811
- }
1812
- };
1813
- });
2257
+ const nftData = nftMetadataMap.get(nftMint);
2258
+ return {
2259
+ ...t,
2260
+ classification: {
2261
+ ...t.classification,
2262
+ metadata: {
2263
+ ...t.classification.metadata,
2264
+ nft_name: nftData.name,
2265
+ nft_image: nftData.image,
2266
+ nft_cdn_image: nftData.cdnImage,
2267
+ nft_collection: nftData.collection,
2268
+ nft_symbol: nftData.symbol,
2269
+ nft_attributes: nftData.attributes
2270
+ }
2271
+ }
2272
+ };
2273
+ });
2274
+ }
2275
+ }
2276
+ return result2;
1814
2277
  }
1815
2278
  if (!filterSpam) {
1816
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1817
- limit,
1818
- before,
1819
- until
1820
- });
2279
+ const signatures = await fetchWalletSignatures(
2280
+ client.rpc,
2281
+ walletAddress,
2282
+ {
2283
+ limit,
2284
+ before,
2285
+ until
2286
+ }
2287
+ );
1821
2288
  if (signatures.length === 0) {
1822
2289
  return [];
1823
2290
  }
@@ -1832,7 +2299,11 @@ function createIndexer(options) {
1832
2299
  const classified = transactions.map((tx) => {
1833
2300
  tx.protocol = detectProtocol(tx.programIds);
1834
2301
  const legs = transactionToLegs(tx);
1835
- const classification = classifyTransaction(legs, tx, walletAddressStr2);
2302
+ const classification = classifyTransaction(
2303
+ legs,
2304
+ tx,
2305
+ walletAddressStr2
2306
+ );
1836
2307
  return { tx, classification, legs };
1837
2308
  });
1838
2309
  return enrichBatch(classified);
@@ -1845,11 +2316,15 @@ function createIndexer(options) {
1845
2316
  while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
1846
2317
  iteration++;
1847
2318
  const batchSize = iteration === 1 ? limit : limit * 2;
1848
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1849
- limit: batchSize,
1850
- before: currentBefore,
1851
- until
1852
- });
2319
+ const signatures = await fetchWalletSignatures(
2320
+ client.rpc,
2321
+ walletAddress,
2322
+ {
2323
+ limit: batchSize,
2324
+ before: currentBefore,
2325
+ until
2326
+ }
2327
+ );
1853
2328
  if (signatures.length === 0) {
1854
2329
  break;
1855
2330
  }
@@ -1863,7 +2338,11 @@ function createIndexer(options) {
1863
2338
  const classified = transactions.map((tx) => {
1864
2339
  tx.protocol = detectProtocol(tx.programIds);
1865
2340
  const legs = transactionToLegs(tx);
1866
- const classification = classifyTransaction(legs, tx, walletAddressStr);
2341
+ const classification = classifyTransaction(
2342
+ legs,
2343
+ tx,
2344
+ walletAddressStr
2345
+ );
1867
2346
  return { tx, classification, legs };
1868
2347
  });
1869
2348
  const nonSpam = filterSpamTransactions(classified, spamConfig);
@@ -1879,7 +2358,10 @@ function createIndexer(options) {
1879
2358
  return enrichBatch(result);
1880
2359
  },
1881
2360
  async getTransaction(signature2, options2 = {}) {
1882
- const { enrichNftMetadata = true } = options2;
2361
+ const {
2362
+ enrichNftMetadata = true,
2363
+ enrichTokenMetadata: enrichTokens = true
2364
+ } = options2;
1883
2365
  const tx = await fetchTransaction(client.rpc, signature2);
1884
2366
  if (!tx) {
1885
2367
  return null;
@@ -1888,6 +2370,9 @@ function createIndexer(options) {
1888
2370
  const legs = transactionToLegs(tx);
1889
2371
  const classification = classifyTransaction(legs, tx);
1890
2372
  let classified = { tx, classification, legs };
2373
+ if (enrichTokens) {
2374
+ classified = await enrichTokenMetadata(tokenFetcher, classified);
2375
+ }
1891
2376
  if (enrichNftMetadata && rpcUrl) {
1892
2377
  classified = await enrichNftClassification(rpcUrl, classified);
1893
2378
  }