tx-indexer 0.5.2 → 0.5.3

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";
@@ -1697,6 +1898,118 @@ function filterSpamTransactions(transactions, config) {
1697
1898
  );
1698
1899
  }
1699
1900
 
1901
+ // ../domain/src/money/token-fetcher.ts
1902
+ var DEFAULT_JUPITER_API_URL = "https://tokens.jup.ag/tokens?tags=verified";
1903
+ var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
1904
+ function createTokenFetcher(options = {}) {
1905
+ const {
1906
+ jupiterApiUrl = DEFAULT_JUPITER_API_URL,
1907
+ cacheTtlMs = DEFAULT_CACHE_TTL_MS,
1908
+ prefetch = false
1909
+ } = options;
1910
+ const jupiterCache = /* @__PURE__ */ new Map();
1911
+ let lastFetchTime = 0;
1912
+ let fetchPromise = null;
1913
+ async function fetchJupiterTokens() {
1914
+ if (fetchPromise) {
1915
+ return fetchPromise;
1916
+ }
1917
+ if (Date.now() - lastFetchTime < cacheTtlMs && jupiterCache.size > 0) {
1918
+ return;
1919
+ }
1920
+ fetchPromise = (async () => {
1921
+ try {
1922
+ const response = await fetch(jupiterApiUrl);
1923
+ if (!response.ok) {
1924
+ console.warn(
1925
+ `Jupiter API returned ${response.status}: ${response.statusText}`
1926
+ );
1927
+ return;
1928
+ }
1929
+ const tokens = await response.json();
1930
+ jupiterCache.clear();
1931
+ for (const token of tokens) {
1932
+ jupiterCache.set(token.address, {
1933
+ mint: token.address,
1934
+ symbol: token.symbol,
1935
+ name: token.name,
1936
+ decimals: token.decimals,
1937
+ logoURI: token.logoURI
1938
+ });
1939
+ }
1940
+ lastFetchTime = Date.now();
1941
+ } catch (error) {
1942
+ console.warn("Failed to fetch Jupiter tokens:", error);
1943
+ } finally {
1944
+ fetchPromise = null;
1945
+ }
1946
+ })();
1947
+ return fetchPromise;
1948
+ }
1949
+ async function getToken(mint, decimals = 9) {
1950
+ const staticToken = TOKEN_INFO[mint];
1951
+ if (staticToken) {
1952
+ return staticToken;
1953
+ }
1954
+ const cachedToken = jupiterCache.get(mint);
1955
+ if (cachedToken) {
1956
+ return cachedToken;
1957
+ }
1958
+ await fetchJupiterTokens();
1959
+ const fetchedToken = jupiterCache.get(mint);
1960
+ if (fetchedToken) {
1961
+ return fetchedToken;
1962
+ }
1963
+ return createUnknownToken(mint, decimals);
1964
+ }
1965
+ async function getTokens(mints, defaultDecimals = 9) {
1966
+ const result = /* @__PURE__ */ new Map();
1967
+ const missingMints = [];
1968
+ for (const mint of mints) {
1969
+ const staticToken = TOKEN_INFO[mint];
1970
+ if (staticToken) {
1971
+ result.set(mint, staticToken);
1972
+ continue;
1973
+ }
1974
+ const cachedToken = jupiterCache.get(mint);
1975
+ if (cachedToken) {
1976
+ result.set(mint, cachedToken);
1977
+ continue;
1978
+ }
1979
+ missingMints.push(mint);
1980
+ }
1981
+ if (missingMints.length > 0) {
1982
+ await fetchJupiterTokens();
1983
+ for (const mint of missingMints) {
1984
+ const fetchedToken = jupiterCache.get(mint);
1985
+ if (fetchedToken) {
1986
+ result.set(mint, fetchedToken);
1987
+ } else {
1988
+ result.set(mint, createUnknownToken(mint, defaultDecimals));
1989
+ }
1990
+ }
1991
+ }
1992
+ return result;
1993
+ }
1994
+ async function refresh() {
1995
+ lastFetchTime = 0;
1996
+ await fetchJupiterTokens();
1997
+ }
1998
+ function getCacheSize() {
1999
+ return jupiterCache.size;
2000
+ }
2001
+ if (prefetch) {
2002
+ fetchJupiterTokens().catch(() => {
2003
+ });
2004
+ }
2005
+ return {
2006
+ getToken,
2007
+ getTokens,
2008
+ refresh,
2009
+ getCacheSize
2010
+ };
2011
+ }
2012
+
1700
2013
  // src/nft.ts
1701
2014
  async function fetchNftMetadata(rpcUrl, mintAddress) {
1702
2015
  const response = await fetch(rpcUrl, {
@@ -1741,6 +2054,102 @@ async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
1741
2054
 
1742
2055
  // src/client.ts
1743
2056
  var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
2057
+ async function enrichTokenMetadata(tokenFetcher, classified) {
2058
+ const mints = /* @__PURE__ */ new Set();
2059
+ const decimalsMap = /* @__PURE__ */ new Map();
2060
+ for (const leg of classified.legs) {
2061
+ const mint = leg.amount.token.mint;
2062
+ mints.add(mint);
2063
+ decimalsMap.set(mint, leg.amount.token.decimals);
2064
+ }
2065
+ if (classified.classification.primaryAmount?.token.mint) {
2066
+ const mint = classified.classification.primaryAmount.token.mint;
2067
+ mints.add(mint);
2068
+ decimalsMap.set(
2069
+ mint,
2070
+ classified.classification.primaryAmount.token.decimals
2071
+ );
2072
+ }
2073
+ if (classified.classification.secondaryAmount?.token.mint) {
2074
+ const mint = classified.classification.secondaryAmount.token.mint;
2075
+ mints.add(mint);
2076
+ decimalsMap.set(
2077
+ mint,
2078
+ classified.classification.secondaryAmount.token.decimals
2079
+ );
2080
+ }
2081
+ if (mints.size === 0) {
2082
+ return classified;
2083
+ }
2084
+ const tokenInfoMap = await tokenFetcher.getTokens(
2085
+ Array.from(mints),
2086
+ 9
2087
+ // default decimals
2088
+ );
2089
+ function enrichAmount(amount) {
2090
+ if (!amount) return amount;
2091
+ const enrichedToken = tokenInfoMap.get(amount.token.mint);
2092
+ if (!enrichedToken || enrichedToken.symbol === amount.token.symbol) {
2093
+ return amount;
2094
+ }
2095
+ return {
2096
+ ...amount,
2097
+ token: {
2098
+ ...enrichedToken,
2099
+ // Keep the decimals from the original (from RPC) as they're authoritative
2100
+ decimals: amount.token.decimals
2101
+ }
2102
+ };
2103
+ }
2104
+ function enrichLeg(leg) {
2105
+ const enrichedToken = tokenInfoMap.get(leg.amount.token.mint);
2106
+ if (!enrichedToken || enrichedToken.symbol === leg.amount.token.symbol) {
2107
+ return leg;
2108
+ }
2109
+ return {
2110
+ ...leg,
2111
+ amount: {
2112
+ ...leg.amount,
2113
+ token: {
2114
+ ...enrichedToken,
2115
+ decimals: leg.amount.token.decimals
2116
+ }
2117
+ }
2118
+ };
2119
+ }
2120
+ const enrichedLegs = classified.legs.map(enrichLeg);
2121
+ const enrichedClassification = {
2122
+ ...classified.classification,
2123
+ primaryAmount: enrichAmount(classified.classification.primaryAmount) ?? null,
2124
+ secondaryAmount: enrichAmount(classified.classification.secondaryAmount)
2125
+ };
2126
+ return {
2127
+ tx: classified.tx,
2128
+ legs: enrichedLegs,
2129
+ classification: enrichedClassification
2130
+ };
2131
+ }
2132
+ async function enrichTokenMetadataBatch(tokenFetcher, transactions) {
2133
+ const mints = /* @__PURE__ */ new Set();
2134
+ for (const classified of transactions) {
2135
+ for (const leg of classified.legs) {
2136
+ mints.add(leg.amount.token.mint);
2137
+ }
2138
+ if (classified.classification.primaryAmount?.token.mint) {
2139
+ mints.add(classified.classification.primaryAmount.token.mint);
2140
+ }
2141
+ if (classified.classification.secondaryAmount?.token.mint) {
2142
+ mints.add(classified.classification.secondaryAmount.token.mint);
2143
+ }
2144
+ }
2145
+ if (mints.size === 0) {
2146
+ return transactions;
2147
+ }
2148
+ await tokenFetcher.getTokens(Array.from(mints));
2149
+ return Promise.all(
2150
+ transactions.map((tx) => enrichTokenMetadata(tokenFetcher, tx))
2151
+ );
2152
+ }
1744
2153
  async function enrichNftClassification(rpcUrl, classified) {
1745
2154
  const { classification } = classified;
1746
2155
  if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
@@ -1773,51 +2182,74 @@ async function enrichNftClassification(rpcUrl, classified) {
1773
2182
  function createIndexer(options) {
1774
2183
  const rpcUrl = "client" in options ? "" : options.rpcUrl;
1775
2184
  const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
2185
+ const tokenFetcher = createTokenFetcher();
1776
2186
  return {
1777
2187
  rpc: client.rpc,
1778
2188
  async getBalance(walletAddress, tokenMints) {
1779
2189
  return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
1780
2190
  },
1781
2191
  async getTransactions(walletAddress, options2 = {}) {
1782
- const { limit = 10, before, until, filterSpam = true, spamConfig, enrichNftMetadata = true } = options2;
2192
+ const {
2193
+ limit = 10,
2194
+ before,
2195
+ until,
2196
+ filterSpam = true,
2197
+ spamConfig,
2198
+ enrichNftMetadata = true,
2199
+ enrichTokenMetadata: enrichTokens = true
2200
+ } = options2;
1783
2201
  async function enrichBatch(transactions) {
1784
- if (!enrichNftMetadata || !rpcUrl) {
1785
- return transactions;
2202
+ let result2 = transactions;
2203
+ if (enrichTokens) {
2204
+ result2 = await enrichTokenMetadataBatch(tokenFetcher, result2);
1786
2205
  }
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;
1790
- }
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
2206
+ if (enrichNftMetadata && rpcUrl) {
2207
+ const nftMints = result2.filter(
2208
+ (t) => NFT_TRANSACTION_TYPES.includes(
2209
+ t.classification.primaryType
2210
+ )
2211
+ ).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
2212
+ if (nftMints.length > 0) {
2213
+ const nftMetadataMap = await fetchNftMetadataBatch(
2214
+ rpcUrl,
2215
+ nftMints
2216
+ );
2217
+ result2 = result2.map((t) => {
2218
+ const nftMint = t.classification.metadata?.nft_mint;
2219
+ if (!nftMint || !nftMetadataMap.has(nftMint)) {
2220
+ return t;
1810
2221
  }
1811
- }
1812
- };
1813
- });
2222
+ const nftData = nftMetadataMap.get(nftMint);
2223
+ return {
2224
+ ...t,
2225
+ classification: {
2226
+ ...t.classification,
2227
+ metadata: {
2228
+ ...t.classification.metadata,
2229
+ nft_name: nftData.name,
2230
+ nft_image: nftData.image,
2231
+ nft_cdn_image: nftData.cdnImage,
2232
+ nft_collection: nftData.collection,
2233
+ nft_symbol: nftData.symbol,
2234
+ nft_attributes: nftData.attributes
2235
+ }
2236
+ }
2237
+ };
2238
+ });
2239
+ }
2240
+ }
2241
+ return result2;
1814
2242
  }
1815
2243
  if (!filterSpam) {
1816
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1817
- limit,
1818
- before,
1819
- until
1820
- });
2244
+ const signatures = await fetchWalletSignatures(
2245
+ client.rpc,
2246
+ walletAddress,
2247
+ {
2248
+ limit,
2249
+ before,
2250
+ until
2251
+ }
2252
+ );
1821
2253
  if (signatures.length === 0) {
1822
2254
  return [];
1823
2255
  }
@@ -1832,7 +2264,11 @@ function createIndexer(options) {
1832
2264
  const classified = transactions.map((tx) => {
1833
2265
  tx.protocol = detectProtocol(tx.programIds);
1834
2266
  const legs = transactionToLegs(tx);
1835
- const classification = classifyTransaction(legs, tx, walletAddressStr2);
2267
+ const classification = classifyTransaction(
2268
+ legs,
2269
+ tx,
2270
+ walletAddressStr2
2271
+ );
1836
2272
  return { tx, classification, legs };
1837
2273
  });
1838
2274
  return enrichBatch(classified);
@@ -1845,11 +2281,15 @@ function createIndexer(options) {
1845
2281
  while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
1846
2282
  iteration++;
1847
2283
  const batchSize = iteration === 1 ? limit : limit * 2;
1848
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1849
- limit: batchSize,
1850
- before: currentBefore,
1851
- until
1852
- });
2284
+ const signatures = await fetchWalletSignatures(
2285
+ client.rpc,
2286
+ walletAddress,
2287
+ {
2288
+ limit: batchSize,
2289
+ before: currentBefore,
2290
+ until
2291
+ }
2292
+ );
1853
2293
  if (signatures.length === 0) {
1854
2294
  break;
1855
2295
  }
@@ -1863,7 +2303,11 @@ function createIndexer(options) {
1863
2303
  const classified = transactions.map((tx) => {
1864
2304
  tx.protocol = detectProtocol(tx.programIds);
1865
2305
  const legs = transactionToLegs(tx);
1866
- const classification = classifyTransaction(legs, tx, walletAddressStr);
2306
+ const classification = classifyTransaction(
2307
+ legs,
2308
+ tx,
2309
+ walletAddressStr
2310
+ );
1867
2311
  return { tx, classification, legs };
1868
2312
  });
1869
2313
  const nonSpam = filterSpamTransactions(classified, spamConfig);
@@ -1879,7 +2323,10 @@ function createIndexer(options) {
1879
2323
  return enrichBatch(result);
1880
2324
  },
1881
2325
  async getTransaction(signature2, options2 = {}) {
1882
- const { enrichNftMetadata = true } = options2;
2326
+ const {
2327
+ enrichNftMetadata = true,
2328
+ enrichTokenMetadata: enrichTokens = true
2329
+ } = options2;
1883
2330
  const tx = await fetchTransaction(client.rpc, signature2);
1884
2331
  if (!tx) {
1885
2332
  return null;
@@ -1888,6 +2335,9 @@ function createIndexer(options) {
1888
2335
  const legs = transactionToLegs(tx);
1889
2336
  const classification = classifyTransaction(legs, tx);
1890
2337
  let classified = { tx, classification, legs };
2338
+ if (enrichTokens) {
2339
+ classified = await enrichTokenMetadata(tokenFetcher, classified);
2340
+ }
1891
2341
  if (enrichNftMetadata && rpcUrl) {
1892
2342
  classified = await enrichNftClassification(rpcUrl, classified);
1893
2343
  }