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/index.js CHANGED
@@ -15,12 +15,43 @@ function parseSignature(sig) {
15
15
 
16
16
  // ../domain/src/money/token-registry.ts
17
17
  var KNOWN_TOKENS = {
18
+ // Native
18
19
  SOL: "So11111111111111111111111111111111111111112",
20
+ // Stablecoins
19
21
  USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
22
+ USDT: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
23
+ PYUSD: "2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo",
24
+ USDG: "2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH",
20
25
  USDC_BRIDGED: "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM",
21
- USDG: "2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH"
26
+ DAI: "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCCi3Z4dPuFhh",
27
+ // Major tokens
28
+ JUP: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
29
+ JTO: "jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL",
30
+ PYTH: "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3",
31
+ BONK: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
32
+ WIF: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm",
33
+ RENDER: "rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof",
34
+ HNT: "hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux",
35
+ RAY: "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R",
36
+ ORCA: "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE",
37
+ MNGO: "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac",
38
+ MSOL: "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So",
39
+ JITOSOL: "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn",
40
+ BSOL: "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1",
41
+ // Memecoins
42
+ POPCAT: "7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr",
43
+ MEW: "MEW1gQWJ3nEXg2qgERiKu7FAFj79PHvQVREQUzScPP5",
44
+ PNUT: "2qEHjDLDLbuBgRYvsxhc5D6uDWAivNFZGan56P1tpump",
45
+ FARTCOIN: "9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump",
46
+ AI16Z: "HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC",
47
+ // Wrapped tokens
48
+ WBTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh",
49
+ WETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs",
50
+ WSOL: "So11111111111111111111111111111111111111112"
51
+ // Same as SOL (wrapped)
22
52
  };
23
53
  var TOKEN_INFO = {
54
+ // Native SOL
24
55
  [KNOWN_TOKENS.SOL]: {
25
56
  mint: KNOWN_TOKENS.SOL,
26
57
  symbol: "SOL",
@@ -28,6 +59,7 @@ var TOKEN_INFO = {
28
59
  decimals: 9,
29
60
  logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png"
30
61
  },
62
+ // Stablecoins
31
63
  [KNOWN_TOKENS.USDC]: {
32
64
  mint: KNOWN_TOKENS.USDC,
33
65
  symbol: "USDC",
@@ -35,30 +67,211 @@ var TOKEN_INFO = {
35
67
  decimals: 6,
36
68
  logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png"
37
69
  },
38
- [KNOWN_TOKENS.USDC_BRIDGED]: {
39
- mint: KNOWN_TOKENS.USDC_BRIDGED,
40
- symbol: "USDCet",
41
- name: "USDC (Bridged)",
42
- decimals: 6
70
+ [KNOWN_TOKENS.USDT]: {
71
+ mint: KNOWN_TOKENS.USDT,
72
+ symbol: "USDT",
73
+ name: "Tether USD",
74
+ decimals: 6,
75
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB/logo.svg"
76
+ },
77
+ [KNOWN_TOKENS.PYUSD]: {
78
+ mint: KNOWN_TOKENS.PYUSD,
79
+ symbol: "PYUSD",
80
+ name: "PayPal USD",
81
+ decimals: 6,
82
+ logoURI: "https://img.fotofolio.xyz/?url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolana-labs%2Ftoken-list%2Fmain%2Fassets%2Fmainnet%2F2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo%2Flogo.png"
43
83
  },
44
84
  [KNOWN_TOKENS.USDG]: {
45
85
  mint: KNOWN_TOKENS.USDG,
46
86
  symbol: "USDG",
47
87
  name: "USD Glitter",
48
88
  decimals: 6
89
+ },
90
+ [KNOWN_TOKENS.USDC_BRIDGED]: {
91
+ mint: KNOWN_TOKENS.USDC_BRIDGED,
92
+ symbol: "USDCet",
93
+ name: "USDC (Wormhole)",
94
+ decimals: 6
95
+ },
96
+ [KNOWN_TOKENS.DAI]: {
97
+ mint: KNOWN_TOKENS.DAI,
98
+ symbol: "DAI",
99
+ name: "DAI (Wormhole)",
100
+ decimals: 8
101
+ },
102
+ // Major tokens
103
+ [KNOWN_TOKENS.JUP]: {
104
+ mint: KNOWN_TOKENS.JUP,
105
+ symbol: "JUP",
106
+ name: "Jupiter",
107
+ decimals: 6,
108
+ logoURI: "https://static.jup.ag/jup/icon.png"
109
+ },
110
+ [KNOWN_TOKENS.JTO]: {
111
+ mint: KNOWN_TOKENS.JTO,
112
+ symbol: "JTO",
113
+ name: "Jito",
114
+ decimals: 9,
115
+ logoURI: "https://metadata.jito.network/token/jto/image"
116
+ },
117
+ [KNOWN_TOKENS.PYTH]: {
118
+ mint: KNOWN_TOKENS.PYTH,
119
+ symbol: "PYTH",
120
+ name: "Pyth Network",
121
+ decimals: 6,
122
+ logoURI: "https://pyth.network/token.svg"
123
+ },
124
+ [KNOWN_TOKENS.BONK]: {
125
+ mint: KNOWN_TOKENS.BONK,
126
+ symbol: "BONK",
127
+ name: "Bonk",
128
+ decimals: 5,
129
+ logoURI: "https://arweave.net/hQiPZOsRZXGXBJd_82PhVdlM_hACsT_q6wqwf5cSY7I"
130
+ },
131
+ [KNOWN_TOKENS.WIF]: {
132
+ mint: KNOWN_TOKENS.WIF,
133
+ symbol: "WIF",
134
+ name: "dogwifhat",
135
+ decimals: 6,
136
+ logoURI: "https://bafkreibk3covs5ltyqxa272uodhber6kcclnlgrbwjee4kyqhstwmjqpfa.ipfs.nftstorage.link/"
137
+ },
138
+ [KNOWN_TOKENS.RENDER]: {
139
+ mint: KNOWN_TOKENS.RENDER,
140
+ symbol: "RENDER",
141
+ name: "Render Token",
142
+ decimals: 8,
143
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof/logo.png"
144
+ },
145
+ [KNOWN_TOKENS.HNT]: {
146
+ mint: KNOWN_TOKENS.HNT,
147
+ symbol: "HNT",
148
+ name: "Helium",
149
+ decimals: 8,
150
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux/logo.png"
151
+ },
152
+ [KNOWN_TOKENS.RAY]: {
153
+ mint: KNOWN_TOKENS.RAY,
154
+ symbol: "RAY",
155
+ name: "Raydium",
156
+ decimals: 6,
157
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R/logo.png"
158
+ },
159
+ [KNOWN_TOKENS.ORCA]: {
160
+ mint: KNOWN_TOKENS.ORCA,
161
+ symbol: "ORCA",
162
+ name: "Orca",
163
+ decimals: 6,
164
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE/logo.png"
165
+ },
166
+ [KNOWN_TOKENS.MNGO]: {
167
+ mint: KNOWN_TOKENS.MNGO,
168
+ symbol: "MNGO",
169
+ name: "Mango",
170
+ decimals: 6,
171
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac/logo.png"
172
+ },
173
+ // Liquid staking tokens
174
+ [KNOWN_TOKENS.MSOL]: {
175
+ mint: KNOWN_TOKENS.MSOL,
176
+ symbol: "mSOL",
177
+ name: "Marinade Staked SOL",
178
+ decimals: 9,
179
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So/logo.png"
180
+ },
181
+ [KNOWN_TOKENS.JITOSOL]: {
182
+ mint: KNOWN_TOKENS.JITOSOL,
183
+ symbol: "JitoSOL",
184
+ name: "Jito Staked SOL",
185
+ decimals: 9,
186
+ logoURI: "https://storage.googleapis.com/token-metadata/JitoSOL-256.png"
187
+ },
188
+ [KNOWN_TOKENS.BSOL]: {
189
+ mint: KNOWN_TOKENS.BSOL,
190
+ symbol: "bSOL",
191
+ name: "BlazeStake Staked SOL",
192
+ decimals: 9,
193
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1/logo.png"
194
+ },
195
+ // Memecoins
196
+ [KNOWN_TOKENS.POPCAT]: {
197
+ mint: KNOWN_TOKENS.POPCAT,
198
+ symbol: "POPCAT",
199
+ name: "Popcat",
200
+ decimals: 9,
201
+ logoURI: "https://bafkreidvkvuzyslw5jh5z242lgzwzhbi2kxxnpkm73fkuqzasyr34jj2o4.ipfs.nftstorage.link/"
202
+ },
203
+ [KNOWN_TOKENS.MEW]: {
204
+ mint: KNOWN_TOKENS.MEW,
205
+ symbol: "MEW",
206
+ name: "cat in a dogs world",
207
+ decimals: 5,
208
+ logoURI: "https://bafkreidlwyr565dxtao2ipsze6bmzpszqzybz7sqi2zaet5fs7k53henju.ipfs.nftstorage.link/"
209
+ },
210
+ [KNOWN_TOKENS.PNUT]: {
211
+ mint: KNOWN_TOKENS.PNUT,
212
+ symbol: "Peanut",
213
+ name: "Peanut the Squirrel",
214
+ decimals: 6,
215
+ logoURI: "https://ipfs.io/ipfs/QmNS3Hdb6pMheFzRdwXr3PPCrXcBohzhLrKHqEUV1n3HnJ"
216
+ },
217
+ [KNOWN_TOKENS.FARTCOIN]: {
218
+ mint: KNOWN_TOKENS.FARTCOIN,
219
+ symbol: "FARTCOIN",
220
+ name: "Fartcoin",
221
+ decimals: 6,
222
+ logoURI: "https://ipfs.io/ipfs/QmQHY6t8TxucH3F9LGPBBqqRLbyWx7NxWvrnoZKcq9ErrR"
223
+ },
224
+ [KNOWN_TOKENS.AI16Z]: {
225
+ mint: KNOWN_TOKENS.AI16Z,
226
+ symbol: "ai16z",
227
+ name: "ai16z",
228
+ decimals: 9,
229
+ logoURI: "https://ipfs.io/ipfs/QmRbm1mprqHmJ7PvCTrSNydkquLi5r41wq8kWbHvoRm3FX"
230
+ },
231
+ // Wrapped tokens
232
+ [KNOWN_TOKENS.WBTC]: {
233
+ mint: KNOWN_TOKENS.WBTC,
234
+ symbol: "WBTC",
235
+ name: "Wrapped BTC (Wormhole)",
236
+ decimals: 8,
237
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh/logo.png"
238
+ },
239
+ [KNOWN_TOKENS.WETH]: {
240
+ mint: KNOWN_TOKENS.WETH,
241
+ symbol: "WETH",
242
+ name: "Wrapped ETH (Wormhole)",
243
+ decimals: 8,
244
+ logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs/logo.png"
49
245
  }
50
246
  };
51
247
  function getTokenInfo(mint) {
52
248
  return TOKEN_INFO[mint];
53
249
  }
54
- [
250
+ function createUnknownToken(mint, decimals) {
251
+ return {
252
+ mint,
253
+ symbol: mint.slice(0, 8),
254
+ name: `Unknown Token (${mint.slice(0, 8)}...)`,
255
+ decimals
256
+ };
257
+ }
258
+ var SUPPORTED_STABLECOINS = [
55
259
  KNOWN_TOKENS.USDC,
260
+ KNOWN_TOKENS.USDT,
261
+ KNOWN_TOKENS.PYUSD,
262
+ KNOWN_TOKENS.USDG,
56
263
  KNOWN_TOKENS.USDC_BRIDGED,
57
- KNOWN_TOKENS.USDG
264
+ KNOWN_TOKENS.DAI
58
265
  ];
59
266
  [
60
267
  KNOWN_TOKENS.SOL,
61
- KNOWN_TOKENS.USDC
268
+ KNOWN_TOKENS.USDC,
269
+ KNOWN_TOKENS.USDT
270
+ ];
271
+ var LIQUID_STAKING_TOKENS = [
272
+ KNOWN_TOKENS.MSOL,
273
+ KNOWN_TOKENS.JITOSOL,
274
+ KNOWN_TOKENS.BSOL
62
275
  ];
63
276
 
64
277
  // ../solana/src/constants/program-ids.ts
@@ -942,21 +1155,38 @@ function determineTokenRole(change, tx, feePayer) {
942
1155
  }
943
1156
 
944
1157
  // ../classification/src/classifiers/transfer-classifier.ts
1158
+ function findBestTransferPair(legs) {
1159
+ const senderLegs = legs.filter(
1160
+ (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
1161
+ );
1162
+ if (senderLegs.length === 0) return null;
1163
+ let bestPair = null;
1164
+ let bestAmount = 0;
1165
+ for (const senderLeg of senderLegs) {
1166
+ const receiverLeg = legs.find(
1167
+ (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
1168
+ // Must be different account
1169
+ );
1170
+ if (receiverLeg) {
1171
+ const amount = senderLeg.amount.amountUi;
1172
+ if (amount > bestAmount) {
1173
+ bestAmount = amount;
1174
+ bestPair = { senderLeg, receiverLeg };
1175
+ }
1176
+ }
1177
+ }
1178
+ return bestPair;
1179
+ }
945
1180
  var TransferClassifier = class {
946
1181
  name = "transfer";
947
1182
  priority = 20;
948
1183
  classify(context) {
949
1184
  const { legs, tx } = context;
950
1185
  const facilitator = tx.accountKeys ? detectFacilitator(tx.accountKeys) : null;
951
- const senderLeg = legs.find(
952
- (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
953
- );
954
- if (!senderLeg) return null;
1186
+ const pair = findBestTransferPair(legs);
1187
+ if (!pair) return null;
1188
+ const { senderLeg, receiverLeg } = pair;
955
1189
  const sender = senderLeg.accountId.replace("external:", "");
956
- const receiverLeg = legs.find(
957
- (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint
958
- );
959
- if (!receiverLeg) return null;
960
1190
  const receiver = receiverLeg.accountId.replace("external:", "");
961
1191
  return {
962
1192
  primaryType: "transfer",
@@ -1435,6 +1665,27 @@ var FeeOnlyClassifier = class {
1435
1665
  };
1436
1666
 
1437
1667
  // ../classification/src/classifiers/solana-pay-classifier.ts
1668
+ function findBestTransferPair2(legs) {
1669
+ const senderLegs = legs.filter(
1670
+ (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
1671
+ );
1672
+ if (senderLegs.length === 0) return null;
1673
+ let bestPair = null;
1674
+ let bestAmount = 0;
1675
+ for (const senderLeg of senderLegs) {
1676
+ const receiverLeg = legs.find(
1677
+ (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
1678
+ );
1679
+ if (receiverLeg) {
1680
+ const amount = senderLeg.amount.amountUi;
1681
+ if (amount > bestAmount) {
1682
+ bestAmount = amount;
1683
+ bestPair = { senderLeg, receiverLeg };
1684
+ }
1685
+ }
1686
+ }
1687
+ return bestPair;
1688
+ }
1438
1689
  var SolanaPayClassifier = class {
1439
1690
  name = "solana-pay";
1440
1691
  priority = 95;
@@ -1444,12 +1695,9 @@ var SolanaPayClassifier = class {
1444
1695
  return null;
1445
1696
  }
1446
1697
  const memo = parseSolanaPayMemo(tx.memo);
1447
- const senderLeg = legs.find(
1448
- (leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.role === "sent"
1449
- );
1450
- const receiverLeg = legs.find(
1451
- (leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.role === "received"
1452
- );
1698
+ const pair = findBestTransferPair2(legs);
1699
+ const senderLeg = pair?.senderLeg ?? null;
1700
+ const receiverLeg = pair?.receiverLeg ?? null;
1453
1701
  const sender = senderLeg?.accountId.replace("external:", "") ?? null;
1454
1702
  const receiver = receiverLeg?.accountId.replace("external:", "") ?? null;
1455
1703
  const primaryAmount = senderLeg?.amount ?? receiverLeg?.amount ?? null;
@@ -1739,6 +1987,125 @@ function filterSpamTransactions(transactions, config) {
1739
1987
  );
1740
1988
  }
1741
1989
 
1990
+ // ../domain/src/money/token-fetcher.ts
1991
+ var DEFAULT_JUPITER_API_URL = "https://tokens.jup.ag/tokens?tags=verified";
1992
+ var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
1993
+ function createTokenFetcher(options = {}) {
1994
+ const {
1995
+ jupiterApiUrl = DEFAULT_JUPITER_API_URL,
1996
+ cacheTtlMs = DEFAULT_CACHE_TTL_MS,
1997
+ prefetch = false
1998
+ } = options;
1999
+ const jupiterCache = /* @__PURE__ */ new Map();
2000
+ let lastFetchTime = 0;
2001
+ let fetchPromise = null;
2002
+ async function fetchJupiterTokens() {
2003
+ if (fetchPromise) {
2004
+ return fetchPromise;
2005
+ }
2006
+ if (Date.now() - lastFetchTime < cacheTtlMs && jupiterCache.size > 0) {
2007
+ return;
2008
+ }
2009
+ fetchPromise = (async () => {
2010
+ try {
2011
+ const response = await fetch(jupiterApiUrl);
2012
+ if (!response.ok) {
2013
+ console.warn(
2014
+ `Jupiter API returned ${response.status}: ${response.statusText}`
2015
+ );
2016
+ return;
2017
+ }
2018
+ const tokens = await response.json();
2019
+ jupiterCache.clear();
2020
+ for (const token of tokens) {
2021
+ jupiterCache.set(token.address, {
2022
+ mint: token.address,
2023
+ symbol: token.symbol,
2024
+ name: token.name,
2025
+ decimals: token.decimals,
2026
+ logoURI: token.logoURI
2027
+ });
2028
+ }
2029
+ lastFetchTime = Date.now();
2030
+ } catch (error) {
2031
+ console.warn("Failed to fetch Jupiter tokens:", error);
2032
+ } finally {
2033
+ fetchPromise = null;
2034
+ }
2035
+ })();
2036
+ return fetchPromise;
2037
+ }
2038
+ async function getToken(mint, decimals = 9) {
2039
+ const staticToken = TOKEN_INFO[mint];
2040
+ if (staticToken) {
2041
+ return staticToken;
2042
+ }
2043
+ const cachedToken = jupiterCache.get(mint);
2044
+ if (cachedToken) {
2045
+ return cachedToken;
2046
+ }
2047
+ await fetchJupiterTokens();
2048
+ const fetchedToken = jupiterCache.get(mint);
2049
+ if (fetchedToken) {
2050
+ return fetchedToken;
2051
+ }
2052
+ return createUnknownToken(mint, decimals);
2053
+ }
2054
+ async function getTokens(mints, defaultDecimals = 9) {
2055
+ const result = /* @__PURE__ */ new Map();
2056
+ const missingMints = [];
2057
+ for (const mint of mints) {
2058
+ const staticToken = TOKEN_INFO[mint];
2059
+ if (staticToken) {
2060
+ result.set(mint, staticToken);
2061
+ continue;
2062
+ }
2063
+ const cachedToken = jupiterCache.get(mint);
2064
+ if (cachedToken) {
2065
+ result.set(mint, cachedToken);
2066
+ continue;
2067
+ }
2068
+ missingMints.push(mint);
2069
+ }
2070
+ if (missingMints.length > 0) {
2071
+ await fetchJupiterTokens();
2072
+ for (const mint of missingMints) {
2073
+ const fetchedToken = jupiterCache.get(mint);
2074
+ if (fetchedToken) {
2075
+ result.set(mint, fetchedToken);
2076
+ } else {
2077
+ result.set(mint, createUnknownToken(mint, defaultDecimals));
2078
+ }
2079
+ }
2080
+ }
2081
+ return result;
2082
+ }
2083
+ async function refresh() {
2084
+ lastFetchTime = 0;
2085
+ await fetchJupiterTokens();
2086
+ }
2087
+ function getCacheSize() {
2088
+ return jupiterCache.size;
2089
+ }
2090
+ if (prefetch) {
2091
+ fetchJupiterTokens().catch(() => {
2092
+ });
2093
+ }
2094
+ return {
2095
+ getToken,
2096
+ getTokens,
2097
+ refresh,
2098
+ getCacheSize
2099
+ };
2100
+ }
2101
+ var defaultFetcher = null;
2102
+ function getDefaultTokenFetcher() {
2103
+ if (!defaultFetcher) {
2104
+ defaultFetcher = createTokenFetcher();
2105
+ }
2106
+ return defaultFetcher;
2107
+ }
2108
+
1742
2109
  // src/nft.ts
1743
2110
  async function fetchNftMetadata(rpcUrl, mintAddress) {
1744
2111
  const response = await fetch(rpcUrl, {
@@ -1783,6 +2150,102 @@ async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
1783
2150
 
1784
2151
  // src/client.ts
1785
2152
  var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
2153
+ async function enrichTokenMetadata(tokenFetcher, classified) {
2154
+ const mints = /* @__PURE__ */ new Set();
2155
+ const decimalsMap = /* @__PURE__ */ new Map();
2156
+ for (const leg of classified.legs) {
2157
+ const mint = leg.amount.token.mint;
2158
+ mints.add(mint);
2159
+ decimalsMap.set(mint, leg.amount.token.decimals);
2160
+ }
2161
+ if (classified.classification.primaryAmount?.token.mint) {
2162
+ const mint = classified.classification.primaryAmount.token.mint;
2163
+ mints.add(mint);
2164
+ decimalsMap.set(
2165
+ mint,
2166
+ classified.classification.primaryAmount.token.decimals
2167
+ );
2168
+ }
2169
+ if (classified.classification.secondaryAmount?.token.mint) {
2170
+ const mint = classified.classification.secondaryAmount.token.mint;
2171
+ mints.add(mint);
2172
+ decimalsMap.set(
2173
+ mint,
2174
+ classified.classification.secondaryAmount.token.decimals
2175
+ );
2176
+ }
2177
+ if (mints.size === 0) {
2178
+ return classified;
2179
+ }
2180
+ const tokenInfoMap = await tokenFetcher.getTokens(
2181
+ Array.from(mints),
2182
+ 9
2183
+ // default decimals
2184
+ );
2185
+ function enrichAmount(amount) {
2186
+ if (!amount) return amount;
2187
+ const enrichedToken = tokenInfoMap.get(amount.token.mint);
2188
+ if (!enrichedToken || enrichedToken.symbol === amount.token.symbol) {
2189
+ return amount;
2190
+ }
2191
+ return {
2192
+ ...amount,
2193
+ token: {
2194
+ ...enrichedToken,
2195
+ // Keep the decimals from the original (from RPC) as they're authoritative
2196
+ decimals: amount.token.decimals
2197
+ }
2198
+ };
2199
+ }
2200
+ function enrichLeg(leg) {
2201
+ const enrichedToken = tokenInfoMap.get(leg.amount.token.mint);
2202
+ if (!enrichedToken || enrichedToken.symbol === leg.amount.token.symbol) {
2203
+ return leg;
2204
+ }
2205
+ return {
2206
+ ...leg,
2207
+ amount: {
2208
+ ...leg.amount,
2209
+ token: {
2210
+ ...enrichedToken,
2211
+ decimals: leg.amount.token.decimals
2212
+ }
2213
+ }
2214
+ };
2215
+ }
2216
+ const enrichedLegs = classified.legs.map(enrichLeg);
2217
+ const enrichedClassification = {
2218
+ ...classified.classification,
2219
+ primaryAmount: enrichAmount(classified.classification.primaryAmount) ?? null,
2220
+ secondaryAmount: enrichAmount(classified.classification.secondaryAmount)
2221
+ };
2222
+ return {
2223
+ tx: classified.tx,
2224
+ legs: enrichedLegs,
2225
+ classification: enrichedClassification
2226
+ };
2227
+ }
2228
+ async function enrichTokenMetadataBatch(tokenFetcher, transactions) {
2229
+ const mints = /* @__PURE__ */ new Set();
2230
+ for (const classified of transactions) {
2231
+ for (const leg of classified.legs) {
2232
+ mints.add(leg.amount.token.mint);
2233
+ }
2234
+ if (classified.classification.primaryAmount?.token.mint) {
2235
+ mints.add(classified.classification.primaryAmount.token.mint);
2236
+ }
2237
+ if (classified.classification.secondaryAmount?.token.mint) {
2238
+ mints.add(classified.classification.secondaryAmount.token.mint);
2239
+ }
2240
+ }
2241
+ if (mints.size === 0) {
2242
+ return transactions;
2243
+ }
2244
+ await tokenFetcher.getTokens(Array.from(mints));
2245
+ return Promise.all(
2246
+ transactions.map((tx) => enrichTokenMetadata(tokenFetcher, tx))
2247
+ );
2248
+ }
1786
2249
  async function enrichNftClassification(rpcUrl, classified) {
1787
2250
  const { classification } = classified;
1788
2251
  if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
@@ -1815,51 +2278,74 @@ async function enrichNftClassification(rpcUrl, classified) {
1815
2278
  function createIndexer(options) {
1816
2279
  const rpcUrl = "client" in options ? "" : options.rpcUrl;
1817
2280
  const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
2281
+ const tokenFetcher = createTokenFetcher();
1818
2282
  return {
1819
2283
  rpc: client.rpc,
1820
2284
  async getBalance(walletAddress, tokenMints) {
1821
2285
  return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
1822
2286
  },
1823
2287
  async getTransactions(walletAddress, options2 = {}) {
1824
- const { limit = 10, before, until, filterSpam = true, spamConfig, enrichNftMetadata = true } = options2;
2288
+ const {
2289
+ limit = 10,
2290
+ before,
2291
+ until,
2292
+ filterSpam = true,
2293
+ spamConfig,
2294
+ enrichNftMetadata = true,
2295
+ enrichTokenMetadata: enrichTokens = true
2296
+ } = options2;
1825
2297
  async function enrichBatch(transactions) {
1826
- if (!enrichNftMetadata || !rpcUrl) {
1827
- return transactions;
1828
- }
1829
- const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
1830
- if (nftMints.length === 0) {
1831
- return transactions;
2298
+ let result2 = transactions;
2299
+ if (enrichTokens) {
2300
+ result2 = await enrichTokenMetadataBatch(tokenFetcher, result2);
1832
2301
  }
1833
- const nftMetadataMap = await fetchNftMetadataBatch(rpcUrl, nftMints);
1834
- return transactions.map((t) => {
1835
- const nftMint = t.classification.metadata?.nft_mint;
1836
- if (!nftMint || !nftMetadataMap.has(nftMint)) {
1837
- return t;
1838
- }
1839
- const nftData = nftMetadataMap.get(nftMint);
1840
- return {
1841
- ...t,
1842
- classification: {
1843
- ...t.classification,
1844
- metadata: {
1845
- ...t.classification.metadata,
1846
- nft_name: nftData.name,
1847
- nft_image: nftData.image,
1848
- nft_cdn_image: nftData.cdnImage,
1849
- nft_collection: nftData.collection,
1850
- nft_symbol: nftData.symbol,
1851
- nft_attributes: nftData.attributes
2302
+ if (enrichNftMetadata && rpcUrl) {
2303
+ const nftMints = result2.filter(
2304
+ (t) => NFT_TRANSACTION_TYPES.includes(
2305
+ t.classification.primaryType
2306
+ )
2307
+ ).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
2308
+ if (nftMints.length > 0) {
2309
+ const nftMetadataMap = await fetchNftMetadataBatch(
2310
+ rpcUrl,
2311
+ nftMints
2312
+ );
2313
+ result2 = result2.map((t) => {
2314
+ const nftMint = t.classification.metadata?.nft_mint;
2315
+ if (!nftMint || !nftMetadataMap.has(nftMint)) {
2316
+ return t;
1852
2317
  }
1853
- }
1854
- };
1855
- });
2318
+ const nftData = nftMetadataMap.get(nftMint);
2319
+ return {
2320
+ ...t,
2321
+ classification: {
2322
+ ...t.classification,
2323
+ metadata: {
2324
+ ...t.classification.metadata,
2325
+ nft_name: nftData.name,
2326
+ nft_image: nftData.image,
2327
+ nft_cdn_image: nftData.cdnImage,
2328
+ nft_collection: nftData.collection,
2329
+ nft_symbol: nftData.symbol,
2330
+ nft_attributes: nftData.attributes
2331
+ }
2332
+ }
2333
+ };
2334
+ });
2335
+ }
2336
+ }
2337
+ return result2;
1856
2338
  }
1857
2339
  if (!filterSpam) {
1858
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1859
- limit,
1860
- before,
1861
- until
1862
- });
2340
+ const signatures = await fetchWalletSignatures(
2341
+ client.rpc,
2342
+ walletAddress,
2343
+ {
2344
+ limit,
2345
+ before,
2346
+ until
2347
+ }
2348
+ );
1863
2349
  if (signatures.length === 0) {
1864
2350
  return [];
1865
2351
  }
@@ -1874,7 +2360,11 @@ function createIndexer(options) {
1874
2360
  const classified = transactions.map((tx) => {
1875
2361
  tx.protocol = detectProtocol(tx.programIds);
1876
2362
  const legs = transactionToLegs(tx);
1877
- const classification = classifyTransaction(legs, tx, walletAddressStr2);
2363
+ const classification = classifyTransaction(
2364
+ legs,
2365
+ tx,
2366
+ walletAddressStr2
2367
+ );
1878
2368
  return { tx, classification, legs };
1879
2369
  });
1880
2370
  return enrichBatch(classified);
@@ -1887,11 +2377,15 @@ function createIndexer(options) {
1887
2377
  while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
1888
2378
  iteration++;
1889
2379
  const batchSize = iteration === 1 ? limit : limit * 2;
1890
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1891
- limit: batchSize,
1892
- before: currentBefore,
1893
- until
1894
- });
2380
+ const signatures = await fetchWalletSignatures(
2381
+ client.rpc,
2382
+ walletAddress,
2383
+ {
2384
+ limit: batchSize,
2385
+ before: currentBefore,
2386
+ until
2387
+ }
2388
+ );
1895
2389
  if (signatures.length === 0) {
1896
2390
  break;
1897
2391
  }
@@ -1905,7 +2399,11 @@ function createIndexer(options) {
1905
2399
  const classified = transactions.map((tx) => {
1906
2400
  tx.protocol = detectProtocol(tx.programIds);
1907
2401
  const legs = transactionToLegs(tx);
1908
- const classification = classifyTransaction(legs, tx, walletAddressStr);
2402
+ const classification = classifyTransaction(
2403
+ legs,
2404
+ tx,
2405
+ walletAddressStr
2406
+ );
1909
2407
  return { tx, classification, legs };
1910
2408
  });
1911
2409
  const nonSpam = filterSpamTransactions(classified, spamConfig);
@@ -1921,7 +2419,10 @@ function createIndexer(options) {
1921
2419
  return enrichBatch(result);
1922
2420
  },
1923
2421
  async getTransaction(signature2, options2 = {}) {
1924
- const { enrichNftMetadata = true } = options2;
2422
+ const {
2423
+ enrichNftMetadata = true,
2424
+ enrichTokenMetadata: enrichTokens = true
2425
+ } = options2;
1925
2426
  const tx = await fetchTransaction(client.rpc, signature2);
1926
2427
  if (!tx) {
1927
2428
  return null;
@@ -1930,6 +2431,9 @@ function createIndexer(options) {
1930
2431
  const legs = transactionToLegs(tx);
1931
2432
  const classification = classifyTransaction(legs, tx);
1932
2433
  let classified = { tx, classification, legs };
2434
+ if (enrichTokens) {
2435
+ classified = await enrichTokenMetadata(tokenFetcher, classified);
2436
+ }
1933
2437
  if (enrichNftMetadata && rpcUrl) {
1934
2438
  classified = await enrichNftClassification(rpcUrl, classified);
1935
2439
  }
@@ -2000,6 +2504,6 @@ function groupLegsByToken(legs) {
2000
2504
  return grouped;
2001
2505
  }
2002
2506
 
2003
- export { JUPITER_V4_PROGRAM_ID, JUPITER_V6_PROGRAM_ID, KNOWN_TOKENS, SPL_MEMO_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_INFO, TOKEN_PROGRAM_ID, buildAccountId, classifyTransaction, createIndexer, createSolanaClient, detectFacilitator, detectProtocol, extractMemo, fetchNftMetadata, fetchNftMetadataBatch, fetchTransaction, fetchTransactionsBatch, fetchWalletBalance, fetchWalletSignatures, filterSpamTransactions, getTokenInfo, groupLegsByAccount, groupLegsByToken, isSolanaPayTransaction, isSpamTransaction, parseAccountId, parseAddress, parseSignature, parseSolanaPayMemo, transactionToLegs, validateLegsBalance };
2507
+ export { JUPITER_V4_PROGRAM_ID, JUPITER_V6_PROGRAM_ID, KNOWN_TOKENS, LIQUID_STAKING_TOKENS, SPL_MEMO_PROGRAM_ID, SUPPORTED_STABLECOINS, SYSTEM_PROGRAM_ID, TOKEN_INFO, TOKEN_PROGRAM_ID, buildAccountId, classifyTransaction, createIndexer, createSolanaClient, createTokenFetcher, createUnknownToken, detectFacilitator, detectProtocol, extractMemo, fetchNftMetadata, fetchNftMetadataBatch, fetchTransaction, fetchTransactionsBatch, fetchWalletBalance, fetchWalletSignatures, filterSpamTransactions, getDefaultTokenFetcher, getTokenInfo, groupLegsByAccount, groupLegsByToken, isSolanaPayTransaction, isSpamTransaction, parseAccountId, parseAddress, parseSignature, parseSolanaPayMemo, transactionToLegs, validateLegsBalance };
2004
2508
  //# sourceMappingURL=index.js.map
2005
2509
  //# sourceMappingURL=index.js.map