tx-indexer 0.5.1 → 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,245 @@ 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
+ }
252
+
253
+ // ../solana/src/constants/program-ids.ts
254
+ var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
255
+ var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
256
+ var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
257
+ var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
258
+ var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
259
+ var TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
260
+ var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
261
+ var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
262
+ var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
263
+ var JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
264
+ var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
265
+ var JUPITER_ORDER_ENGINE_PROGRAM_ID = "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH";
266
+ var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
267
+ var RAYDIUM_CLMM_PROGRAM_ID = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
268
+ var RAYDIUM_CPMM_PROGRAM_ID = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
269
+ var RAYDIUM_STABLE_PROGRAM_ID = "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h";
270
+ var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
271
+ var ORCA_TOKEN_SWAP_V1_PROGRAM_ID = "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP";
272
+ var OPENBOOK_V2_PROGRAM_ID = "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb";
273
+ var PHOENIX_PROGRAM_ID = "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY";
274
+ var SABER_STABLE_SWAP_PROGRAM_ID = "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ";
275
+ var MERCURIAL_STABLE_SWAP_PROGRAM_ID = "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky";
276
+ var METEORA_DLMM_PROGRAM_ID = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo";
277
+ var METEORA_POOLS_PROGRAM_ID = "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB";
278
+ var PUMPFUN_AMM_PROGRAM_ID = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA";
279
+ var PUMPFUN_BONDING_CURVE_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
280
+ var LIFINITY_V2_PROGRAM_ID = "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c";
281
+ var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
282
+ var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
283
+ var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
284
+ var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
285
+ var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
286
+ var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
287
+ var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
288
+ var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
289
+ var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
290
+ var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
291
+ var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
292
+ var KNOWN_FACILITATORS = [PAYAI_FACILITATOR];
293
+ function detectFacilitator(accountKeys) {
294
+ for (const facilitator of KNOWN_FACILITATORS) {
295
+ if (accountKeys.includes(facilitator)) {
296
+ if (facilitator === PAYAI_FACILITATOR) {
297
+ return "payai";
298
+ }
299
+ }
300
+ }
301
+ return null;
302
+ }
51
303
 
52
304
  // ../solana/src/fetcher/balances.ts
53
305
  async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
@@ -66,12 +318,17 @@ async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
66
318
  }
67
319
  async function fetchTokenAccounts(rpc, walletAddress) {
68
320
  const accountsMap = /* @__PURE__ */ new Map();
69
- try {
70
- const response = await rpc.getTokenAccountsByOwner(
71
- walletAddress,
72
- { programId: address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") },
73
- { encoding: "jsonParsed" }
74
- ).send();
321
+ const tokenPrograms = [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID];
322
+ const responses = await Promise.all(
323
+ tokenPrograms.map(
324
+ (programId) => rpc.getTokenAccountsByOwner(
325
+ walletAddress,
326
+ { programId: address(programId) },
327
+ { encoding: "jsonParsed" }
328
+ ).send().catch(() => ({ value: [] }))
329
+ )
330
+ );
331
+ for (const response of responses) {
75
332
  for (const account of response.value) {
76
333
  const parsedInfo = account.account.data.parsed.info;
77
334
  const mint = parsedInfo.mint;
@@ -88,8 +345,6 @@ async function fetchTokenAccounts(rpc, walletAddress) {
88
345
  symbol
89
346
  });
90
347
  }
91
- } catch (error) {
92
- console.error("Error fetching token accounts:", error);
93
348
  }
94
349
  return accountsMap;
95
350
  }
@@ -133,44 +388,6 @@ function extractProgramIds(transaction) {
133
388
  return Array.from(programIds);
134
389
  }
135
390
 
136
- // ../solana/src/constants/program-ids.ts
137
- var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
138
- var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
139
- var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
140
- var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
141
- var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
142
- var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
143
- var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
144
- var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
145
- var JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
146
- var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
147
- var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
148
- var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
149
- var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
150
- var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
151
- var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
152
- var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
153
- var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
154
- var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
155
- var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
156
- var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
157
- var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
158
- var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
159
- var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
160
- var KNOWN_FACILITATORS = [
161
- PAYAI_FACILITATOR
162
- ];
163
- function detectFacilitator(accountKeys) {
164
- for (const facilitator of KNOWN_FACILITATORS) {
165
- if (accountKeys.includes(facilitator)) {
166
- if (facilitator === PAYAI_FACILITATOR) {
167
- return "payai";
168
- }
169
- }
170
- }
171
- return null;
172
- }
173
-
174
391
  // ../../node_modules/.bun/base-x@5.0.1/node_modules/base-x/src/esm/index.js
175
392
  function base(ALPHABET2) {
176
393
  if (ALPHABET2.length >= 255) {
@@ -380,6 +597,189 @@ function isSolanaPayTransaction(programIds, memo) {
380
597
  return hasMemoProgram && memo !== null && memo !== void 0;
381
598
  }
382
599
 
600
+ // ../solana/src/rpc/retry.ts
601
+ var DEFAULT_CONFIG = {
602
+ maxAttempts: 3,
603
+ baseDelayMs: 1e3,
604
+ maxDelayMs: 1e4
605
+ };
606
+ function isRetryableError(error) {
607
+ if (error instanceof Error) {
608
+ const message = error.message.toLowerCase();
609
+ return message.includes("timeout") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("socket hang up") || message.includes("network") || message.includes("429") || message.includes("rate limit") || message.includes("too many requests") || message.includes("503") || message.includes("502") || message.includes("504");
610
+ }
611
+ return false;
612
+ }
613
+ function calculateDelay(attempt, baseDelayMs, maxDelayMs) {
614
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
615
+ const jitter = Math.random() * 0.3 * exponentialDelay;
616
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
617
+ }
618
+ function sleep(ms) {
619
+ return new Promise((resolve) => setTimeout(resolve, ms));
620
+ }
621
+ async function withRetry(fn, config = {}) {
622
+ const { maxAttempts, baseDelayMs, maxDelayMs } = {
623
+ ...DEFAULT_CONFIG,
624
+ ...config
625
+ };
626
+ let lastError;
627
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
628
+ try {
629
+ return await fn();
630
+ } catch (error) {
631
+ lastError = error;
632
+ const isLastAttempt = attempt === maxAttempts;
633
+ const shouldRetry = !isLastAttempt && isRetryableError(error);
634
+ if (!shouldRetry) {
635
+ throw error;
636
+ }
637
+ const delay = calculateDelay(attempt, baseDelayMs, maxDelayMs);
638
+ await sleep(delay);
639
+ }
640
+ }
641
+ throw lastError;
642
+ }
643
+
644
+ // ../../node_modules/.bun/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
645
+ var Node = class {
646
+ value;
647
+ next;
648
+ constructor(value) {
649
+ this.value = value;
650
+ }
651
+ };
652
+ var Queue = class {
653
+ #head;
654
+ #tail;
655
+ #size;
656
+ constructor() {
657
+ this.clear();
658
+ }
659
+ enqueue(value) {
660
+ const node = new Node(value);
661
+ if (this.#head) {
662
+ this.#tail.next = node;
663
+ this.#tail = node;
664
+ } else {
665
+ this.#head = node;
666
+ this.#tail = node;
667
+ }
668
+ this.#size++;
669
+ }
670
+ dequeue() {
671
+ const current = this.#head;
672
+ if (!current) {
673
+ return;
674
+ }
675
+ this.#head = this.#head.next;
676
+ this.#size--;
677
+ if (!this.#head) {
678
+ this.#tail = void 0;
679
+ }
680
+ return current.value;
681
+ }
682
+ peek() {
683
+ if (!this.#head) {
684
+ return;
685
+ }
686
+ return this.#head.value;
687
+ }
688
+ clear() {
689
+ this.#head = void 0;
690
+ this.#tail = void 0;
691
+ this.#size = 0;
692
+ }
693
+ get size() {
694
+ return this.#size;
695
+ }
696
+ *[Symbol.iterator]() {
697
+ let current = this.#head;
698
+ while (current) {
699
+ yield current.value;
700
+ current = current.next;
701
+ }
702
+ }
703
+ *drain() {
704
+ while (this.#head) {
705
+ yield this.dequeue();
706
+ }
707
+ }
708
+ };
709
+
710
+ // ../../node_modules/.bun/p-limit@6.2.0/node_modules/p-limit/index.js
711
+ function pLimit(concurrency) {
712
+ validateConcurrency(concurrency);
713
+ const queue = new Queue();
714
+ let activeCount = 0;
715
+ const resumeNext = () => {
716
+ if (activeCount < concurrency && queue.size > 0) {
717
+ queue.dequeue()();
718
+ activeCount++;
719
+ }
720
+ };
721
+ const next = () => {
722
+ activeCount--;
723
+ resumeNext();
724
+ };
725
+ const run = async (function_, resolve, arguments_) => {
726
+ const result = (async () => function_(...arguments_))();
727
+ resolve(result);
728
+ try {
729
+ await result;
730
+ } catch {
731
+ }
732
+ next();
733
+ };
734
+ const enqueue = (function_, resolve, arguments_) => {
735
+ new Promise((internalResolve) => {
736
+ queue.enqueue(internalResolve);
737
+ }).then(
738
+ run.bind(void 0, function_, resolve, arguments_)
739
+ );
740
+ (async () => {
741
+ await Promise.resolve();
742
+ if (activeCount < concurrency) {
743
+ resumeNext();
744
+ }
745
+ })();
746
+ };
747
+ const generator = (function_, ...arguments_) => new Promise((resolve) => {
748
+ enqueue(function_, resolve, arguments_);
749
+ });
750
+ Object.defineProperties(generator, {
751
+ activeCount: {
752
+ get: () => activeCount
753
+ },
754
+ pendingCount: {
755
+ get: () => queue.size
756
+ },
757
+ clearQueue: {
758
+ value() {
759
+ queue.clear();
760
+ }
761
+ },
762
+ concurrency: {
763
+ get: () => concurrency,
764
+ set(newConcurrency) {
765
+ validateConcurrency(newConcurrency);
766
+ concurrency = newConcurrency;
767
+ queueMicrotask(() => {
768
+ while (activeCount < concurrency && queue.size > 0) {
769
+ resumeNext();
770
+ }
771
+ });
772
+ }
773
+ }
774
+ });
775
+ return generator;
776
+ }
777
+ function validateConcurrency(concurrency) {
778
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
779
+ throw new TypeError("Expected `concurrency` to be a number from 1 and up");
780
+ }
781
+ }
782
+
383
783
  // ../solana/src/fetcher/transactions.ts
384
784
  async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
385
785
  const { limit = 100, before, until } = config;
@@ -398,12 +798,16 @@ async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
398
798
  memo: sig.memo || null
399
799
  }));
400
800
  }
401
- async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
402
- const response = await rpc.getTransaction(signature2, {
403
- commitment,
404
- maxSupportedTransactionVersion: 0,
405
- encoding: "json"
406
- }).send();
801
+ async function fetchTransaction(rpc, signature2, options = {}) {
802
+ const { commitment = "confirmed", retry } = options;
803
+ const response = await withRetry(
804
+ () => rpc.getTransaction(signature2, {
805
+ commitment,
806
+ maxSupportedTransactionVersion: 0,
807
+ encoding: "json"
808
+ }).send(),
809
+ retry
810
+ );
407
811
  if (!response) {
408
812
  return null;
409
813
  }
@@ -450,10 +854,29 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
450
854
  memo
451
855
  };
452
856
  }
453
- async function fetchTransactionsBatch(rpc, signatures, commitment = "confirmed") {
454
- const promises = signatures.map(
455
- (sig) => fetchTransaction(rpc, sig, commitment)
456
- );
857
+ async function fetchTransactionsBatch(rpc, signatures, options = {}) {
858
+ const {
859
+ commitment = "confirmed",
860
+ concurrency = 10,
861
+ retry,
862
+ onFetchError
863
+ } = options;
864
+ if (signatures.length === 0) {
865
+ return [];
866
+ }
867
+ const limit = pLimit(concurrency);
868
+ const safeFetch = async (sig) => {
869
+ try {
870
+ return await fetchTransaction(rpc, sig, { commitment, retry });
871
+ } catch (error) {
872
+ onFetchError?.(
873
+ sig,
874
+ error instanceof Error ? error : new Error(String(error))
875
+ );
876
+ return null;
877
+ }
878
+ };
879
+ const promises = signatures.map((sig) => limit(() => safeFetch(sig)));
457
880
  const results = await Promise.all(promises);
458
881
  return results.filter((tx) => tx !== null);
459
882
  }
@@ -598,7 +1021,7 @@ function transactionToLegs(tx) {
598
1021
  amountRaw: change.change.toString().replace("-", ""),
599
1022
  amountUi: Math.abs(change.changeUi)
600
1023
  },
601
- role: determineSolRole(change, tx, feePayer)
1024
+ role: determineSolRole(change, tx)
602
1025
  });
603
1026
  }
604
1027
  const networkFee = totalSolDebits - totalSolCredits;
@@ -655,13 +1078,8 @@ function transactionToLegs(tx) {
655
1078
  }
656
1079
  return legs;
657
1080
  }
658
- function determineSolRole(change, tx, feePayer) {
659
- const isFeePayer = feePayer ? change.address.toLowerCase() === feePayer : false;
1081
+ function determineSolRole(change, tx, _feePayer) {
660
1082
  const isPositive = change.change > 0n;
661
- const amountSol = Math.abs(change.changeUi);
662
- if (isFeePayer && !isPositive && amountSol < 0.01) {
663
- return "fee";
664
- }
665
1083
  if (isPositive) {
666
1084
  if (tx.protocol?.id === "stake") {
667
1085
  return "reward";
@@ -721,6 +1139,7 @@ var TransferClassifier = class {
721
1139
 
722
1140
  // ../classification/src/protocols/detector.ts
723
1141
  var KNOWN_PROGRAMS = {
1142
+ // Jupiter aggregator
724
1143
  [JUPITER_V6_PROGRAM_ID]: {
725
1144
  id: "jupiter",
726
1145
  name: "Jupiter"
@@ -729,10 +1148,19 @@ var KNOWN_PROGRAMS = {
729
1148
  id: "jupiter-v4",
730
1149
  name: "Jupiter V4"
731
1150
  },
1151
+ [JUPITER_ORDER_ENGINE_PROGRAM_ID]: {
1152
+ id: "jupiter-limit-order",
1153
+ name: "Jupiter Limit Order"
1154
+ },
1155
+ // Core token programs
732
1156
  [TOKEN_PROGRAM_ID]: {
733
1157
  id: "spl-token",
734
1158
  name: "Token Program"
735
1159
  },
1160
+ [TOKEN_2022_PROGRAM_ID]: {
1161
+ id: "token-2022",
1162
+ name: "Token-2022 Program"
1163
+ },
736
1164
  [SYSTEM_PROGRAM_ID]: {
737
1165
  id: "system",
738
1166
  name: "System Program"
@@ -745,25 +1173,86 @@ var KNOWN_PROGRAMS = {
745
1173
  id: "associated-token",
746
1174
  name: "Associated Token Program"
747
1175
  },
748
- [METAPLEX_PROGRAM_ID]: {
749
- id: "metaplex",
750
- name: "Metaplex"
1176
+ // Memo programs
1177
+ [SPL_MEMO_PROGRAM_ID]: {
1178
+ id: "memo",
1179
+ name: "Memo Program"
751
1180
  },
752
- [ORCA_WHIRLPOOL_PROGRAM_ID]: {
753
- id: "orca-whirlpool",
754
- name: "Orca Whirlpool"
1181
+ [MEMO_V1_PROGRAM_ID]: {
1182
+ id: "memo-v1",
1183
+ name: "Memo Program V1"
755
1184
  },
1185
+ // Raydium AMMs
756
1186
  [RAYDIUM_PROGRAM_ID]: {
757
1187
  id: "raydium",
758
1188
  name: "Raydium"
759
1189
  },
760
- [STAKE_PROGRAM_ID]: {
761
- id: "stake",
762
- name: "Stake Program"
1190
+ [RAYDIUM_CLMM_PROGRAM_ID]: {
1191
+ id: "raydium-clmm",
1192
+ name: "Raydium CLMM"
763
1193
  },
764
- [STAKE_POOL_PROGRAM_ID]: {
765
- id: "stake-pool",
766
- name: "Stake Pool Program"
1194
+ [RAYDIUM_CPMM_PROGRAM_ID]: {
1195
+ id: "raydium-cpmm",
1196
+ name: "Raydium CPMM"
1197
+ },
1198
+ [RAYDIUM_STABLE_PROGRAM_ID]: {
1199
+ id: "raydium-stable",
1200
+ name: "Raydium Stable"
1201
+ },
1202
+ // Orca
1203
+ [ORCA_WHIRLPOOL_PROGRAM_ID]: {
1204
+ id: "orca-whirlpool",
1205
+ name: "Orca Whirlpool"
1206
+ },
1207
+ [ORCA_TOKEN_SWAP_V1_PROGRAM_ID]: {
1208
+ id: "orca-v1",
1209
+ name: "Orca Token Swap V1"
1210
+ },
1211
+ // CLOBs (Central Limit Order Books)
1212
+ [OPENBOOK_V2_PROGRAM_ID]: {
1213
+ id: "openbook",
1214
+ name: "OpenBook"
1215
+ },
1216
+ [PHOENIX_PROGRAM_ID]: {
1217
+ id: "phoenix",
1218
+ name: "Phoenix"
1219
+ },
1220
+ // Stableswap protocols
1221
+ [SABER_STABLE_SWAP_PROGRAM_ID]: {
1222
+ id: "saber",
1223
+ name: "Saber"
1224
+ },
1225
+ [MERCURIAL_STABLE_SWAP_PROGRAM_ID]: {
1226
+ id: "mercurial",
1227
+ name: "Mercurial"
1228
+ },
1229
+ // Meteora
1230
+ [METEORA_DLMM_PROGRAM_ID]: {
1231
+ id: "meteora-dlmm",
1232
+ name: "Meteora DLMM"
1233
+ },
1234
+ [METEORA_POOLS_PROGRAM_ID]: {
1235
+ id: "meteora-pools",
1236
+ name: "Meteora Pools"
1237
+ },
1238
+ // Pump.fun
1239
+ [PUMPFUN_AMM_PROGRAM_ID]: {
1240
+ id: "pumpfun",
1241
+ name: "Pump.fun"
1242
+ },
1243
+ [PUMPFUN_BONDING_CURVE_PROGRAM_ID]: {
1244
+ id: "pumpfun-bonding",
1245
+ name: "Pump.fun Bonding Curve"
1246
+ },
1247
+ // Lifinity
1248
+ [LIFINITY_V2_PROGRAM_ID]: {
1249
+ id: "lifinity",
1250
+ name: "Lifinity"
1251
+ },
1252
+ // NFT programs
1253
+ [METAPLEX_PROGRAM_ID]: {
1254
+ id: "metaplex",
1255
+ name: "Metaplex"
767
1256
  },
768
1257
  [CANDY_GUARD_PROGRAM_ID]: {
769
1258
  id: "candy-guard",
@@ -781,6 +1270,16 @@ var KNOWN_PROGRAMS = {
781
1270
  id: "magic-eden-candy-machine",
782
1271
  name: "Nft Candy Machine Program (Magic Eden)"
783
1272
  },
1273
+ // Staking programs
1274
+ [STAKE_PROGRAM_ID]: {
1275
+ id: "stake",
1276
+ name: "Stake Program"
1277
+ },
1278
+ [STAKE_POOL_PROGRAM_ID]: {
1279
+ id: "stake-pool",
1280
+ name: "Stake Pool Program"
1281
+ },
1282
+ // Bridge programs
784
1283
  [WORMHOLE_PROGRAM_ID]: {
785
1284
  id: "wormhole",
786
1285
  name: "Wormhole"
@@ -803,27 +1302,79 @@ var KNOWN_PROGRAMS = {
803
1302
  }
804
1303
  };
805
1304
  var PRIORITY_ORDER = [
1305
+ // Bridge protocols (highest priority - cross-chain operations)
806
1306
  "wormhole",
807
1307
  "wormhole-token-bridge",
808
1308
  "degods-bridge",
809
1309
  "debridge",
810
1310
  "allbridge",
1311
+ // DEX aggregators (route through multiple DEXes)
811
1312
  "jupiter",
812
1313
  "jupiter-v4",
1314
+ "jupiter-limit-order",
1315
+ // AMMs and DEXes
813
1316
  "raydium",
1317
+ "raydium-clmm",
1318
+ "raydium-cpmm",
1319
+ "raydium-stable",
814
1320
  "orca-whirlpool",
1321
+ "orca-v1",
1322
+ "meteora-dlmm",
1323
+ "meteora-pools",
1324
+ "lifinity",
1325
+ "pumpfun",
1326
+ "pumpfun-bonding",
1327
+ // CLOBs
1328
+ "openbook",
1329
+ "phoenix",
1330
+ // Stableswap
1331
+ "saber",
1332
+ "mercurial",
1333
+ // NFT
815
1334
  "metaplex",
1335
+ "candy-guard",
1336
+ "candy-machine-v3",
1337
+ "bubblegum",
1338
+ "magic-eden-candy-machine",
1339
+ // Staking
816
1340
  "stake",
1341
+ "stake-pool",
1342
+ // Infrastructure (lowest priority)
1343
+ "memo",
1344
+ "memo-v1",
817
1345
  "associated-token",
818
1346
  "spl-token",
1347
+ "token-2022",
819
1348
  "compute-budget",
820
1349
  "system"
821
1350
  ];
822
1351
  var DEX_PROTOCOL_IDS2 = /* @__PURE__ */ new Set([
1352
+ // Jupiter aggregator
823
1353
  "jupiter",
824
1354
  "jupiter-v4",
1355
+ "jupiter-limit-order",
1356
+ // Raydium AMMs
825
1357
  "raydium",
826
- "orca-whirlpool"
1358
+ "raydium-clmm",
1359
+ "raydium-cpmm",
1360
+ "raydium-stable",
1361
+ // Orca
1362
+ "orca-whirlpool",
1363
+ "orca-v1",
1364
+ // CLOBs
1365
+ "openbook",
1366
+ "phoenix",
1367
+ // Stableswap
1368
+ "saber",
1369
+ "mercurial",
1370
+ // Meteora
1371
+ "meteora-dlmm",
1372
+ "meteora-pools",
1373
+ // Pump.fun
1374
+ "pumpfun",
1375
+ "pumpfun-bonding",
1376
+ // Lifinity
1377
+ "lifinity"
827
1378
  ]);
828
1379
  var NFT_MINT_PROTOCOL_IDS = /* @__PURE__ */ new Set([
829
1380
  "metaplex",
@@ -872,15 +1423,28 @@ function detectProtocol(programIds) {
872
1423
  }
873
1424
 
874
1425
  // ../classification/src/classifiers/swap-classifier.ts
1426
+ function findSwapPair(tokensOut, tokensIn) {
1427
+ let bestPair = null;
1428
+ let bestScore = 0;
1429
+ for (const out of tokensOut) {
1430
+ for (const inLeg of tokensIn) {
1431
+ if (out.amount.token.symbol !== inLeg.amount.token.symbol) {
1432
+ const score = Math.max(out.amount.amountUi, inLeg.amount.amountUi);
1433
+ if (score > bestScore) {
1434
+ bestScore = score;
1435
+ bestPair = { initiatorOut: out, initiatorIn: inLeg };
1436
+ }
1437
+ }
1438
+ }
1439
+ }
1440
+ return bestPair;
1441
+ }
875
1442
  var SwapClassifier = class {
876
1443
  name = "swap";
877
1444
  priority = 80;
878
1445
  classify(context) {
879
1446
  const { legs, tx, walletAddress } = context;
880
- const feeLeg = legs.find(
881
- (leg) => leg.role === "fee" && leg.side === "debit"
882
- );
883
- const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
1447
+ const initiator = tx.accountKeys?.[0] ?? null;
884
1448
  if (!initiator) {
885
1449
  return null;
886
1450
  }
@@ -894,11 +1458,11 @@ var SwapClassifier = class {
894
1458
  if (initiatorTokensOut.length === 0 || initiatorTokensIn.length === 0) {
895
1459
  return null;
896
1460
  }
897
- const initiatorOut = initiatorTokensOut[0];
898
- const initiatorIn = initiatorTokensIn[0];
899
- if (initiatorOut.amount.token.symbol === initiatorIn.amount.token.symbol) {
1461
+ const swapPair = findSwapPair(initiatorTokensOut, initiatorTokensIn);
1462
+ if (!swapPair) {
900
1463
  return null;
901
1464
  }
1465
+ const { initiatorOut, initiatorIn } = swapPair;
902
1466
  let tokenOut = initiatorOut;
903
1467
  let tokenIn = initiatorIn;
904
1468
  let perspectiveWallet = initiator;
@@ -1292,14 +1856,14 @@ function classifyTransaction(legs, tx, walletAddress) {
1292
1856
  }
1293
1857
 
1294
1858
  // ../domain/src/tx/spam-filter.ts
1295
- var DEFAULT_CONFIG = {
1859
+ var DEFAULT_CONFIG2 = {
1296
1860
  minSolAmount: 1e-3,
1297
1861
  minTokenAmountUsd: 0.01,
1298
1862
  minConfidence: 0.5,
1299
1863
  allowFailed: false
1300
1864
  };
1301
1865
  function isSpamTransaction(tx, classification, config = {}) {
1302
- const cfg = { ...DEFAULT_CONFIG, ...config };
1866
+ const cfg = { ...DEFAULT_CONFIG2, ...config };
1303
1867
  if (!cfg.allowFailed && tx.err) {
1304
1868
  return true;
1305
1869
  }
@@ -1334,6 +1898,118 @@ function filterSpamTransactions(transactions, config) {
1334
1898
  );
1335
1899
  }
1336
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
+
1337
2013
  // src/nft.ts
1338
2014
  async function fetchNftMetadata(rpcUrl, mintAddress) {
1339
2015
  const response = await fetch(rpcUrl, {
@@ -1378,6 +2054,102 @@ async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
1378
2054
 
1379
2055
  // src/client.ts
1380
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
+ }
1381
2153
  async function enrichNftClassification(rpcUrl, classified) {
1382
2154
  const { classification } = classified;
1383
2155
  if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
@@ -1410,51 +2182,74 @@ async function enrichNftClassification(rpcUrl, classified) {
1410
2182
  function createIndexer(options) {
1411
2183
  const rpcUrl = "client" in options ? "" : options.rpcUrl;
1412
2184
  const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
2185
+ const tokenFetcher = createTokenFetcher();
1413
2186
  return {
1414
2187
  rpc: client.rpc,
1415
2188
  async getBalance(walletAddress, tokenMints) {
1416
2189
  return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
1417
2190
  },
1418
2191
  async getTransactions(walletAddress, options2 = {}) {
1419
- 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;
1420
2201
  async function enrichBatch(transactions) {
1421
- if (!enrichNftMetadata || !rpcUrl) {
1422
- return transactions;
2202
+ let result2 = transactions;
2203
+ if (enrichTokens) {
2204
+ result2 = await enrichTokenMetadataBatch(tokenFetcher, result2);
1423
2205
  }
1424
- const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
1425
- if (nftMints.length === 0) {
1426
- return transactions;
1427
- }
1428
- const nftMetadataMap = await fetchNftMetadataBatch(rpcUrl, nftMints);
1429
- return transactions.map((t) => {
1430
- const nftMint = t.classification.metadata?.nft_mint;
1431
- if (!nftMint || !nftMetadataMap.has(nftMint)) {
1432
- return t;
1433
- }
1434
- const nftData = nftMetadataMap.get(nftMint);
1435
- return {
1436
- ...t,
1437
- classification: {
1438
- ...t.classification,
1439
- metadata: {
1440
- ...t.classification.metadata,
1441
- nft_name: nftData.name,
1442
- nft_image: nftData.image,
1443
- nft_cdn_image: nftData.cdnImage,
1444
- nft_collection: nftData.collection,
1445
- nft_symbol: nftData.symbol,
1446
- 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;
1447
2221
  }
1448
- }
1449
- };
1450
- });
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;
1451
2242
  }
1452
2243
  if (!filterSpam) {
1453
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1454
- limit,
1455
- before,
1456
- until
1457
- });
2244
+ const signatures = await fetchWalletSignatures(
2245
+ client.rpc,
2246
+ walletAddress,
2247
+ {
2248
+ limit,
2249
+ before,
2250
+ until
2251
+ }
2252
+ );
1458
2253
  if (signatures.length === 0) {
1459
2254
  return [];
1460
2255
  }
@@ -1469,7 +2264,11 @@ function createIndexer(options) {
1469
2264
  const classified = transactions.map((tx) => {
1470
2265
  tx.protocol = detectProtocol(tx.programIds);
1471
2266
  const legs = transactionToLegs(tx);
1472
- const classification = classifyTransaction(legs, tx, walletAddressStr2);
2267
+ const classification = classifyTransaction(
2268
+ legs,
2269
+ tx,
2270
+ walletAddressStr2
2271
+ );
1473
2272
  return { tx, classification, legs };
1474
2273
  });
1475
2274
  return enrichBatch(classified);
@@ -1482,11 +2281,15 @@ function createIndexer(options) {
1482
2281
  while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
1483
2282
  iteration++;
1484
2283
  const batchSize = iteration === 1 ? limit : limit * 2;
1485
- const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1486
- limit: batchSize,
1487
- before: currentBefore,
1488
- until
1489
- });
2284
+ const signatures = await fetchWalletSignatures(
2285
+ client.rpc,
2286
+ walletAddress,
2287
+ {
2288
+ limit: batchSize,
2289
+ before: currentBefore,
2290
+ until
2291
+ }
2292
+ );
1490
2293
  if (signatures.length === 0) {
1491
2294
  break;
1492
2295
  }
@@ -1500,7 +2303,11 @@ function createIndexer(options) {
1500
2303
  const classified = transactions.map((tx) => {
1501
2304
  tx.protocol = detectProtocol(tx.programIds);
1502
2305
  const legs = transactionToLegs(tx);
1503
- const classification = classifyTransaction(legs, tx, walletAddressStr);
2306
+ const classification = classifyTransaction(
2307
+ legs,
2308
+ tx,
2309
+ walletAddressStr
2310
+ );
1504
2311
  return { tx, classification, legs };
1505
2312
  });
1506
2313
  const nonSpam = filterSpamTransactions(classified, spamConfig);
@@ -1516,7 +2323,10 @@ function createIndexer(options) {
1516
2323
  return enrichBatch(result);
1517
2324
  },
1518
2325
  async getTransaction(signature2, options2 = {}) {
1519
- const { enrichNftMetadata = true } = options2;
2326
+ const {
2327
+ enrichNftMetadata = true,
2328
+ enrichTokenMetadata: enrichTokens = true
2329
+ } = options2;
1520
2330
  const tx = await fetchTransaction(client.rpc, signature2);
1521
2331
  if (!tx) {
1522
2332
  return null;
@@ -1525,6 +2335,9 @@ function createIndexer(options) {
1525
2335
  const legs = transactionToLegs(tx);
1526
2336
  const classification = classifyTransaction(legs, tx);
1527
2337
  let classified = { tx, classification, legs };
2338
+ if (enrichTokens) {
2339
+ classified = await enrichTokenMetadata(tokenFetcher, classified);
2340
+ }
1528
2341
  if (enrichNftMetadata && rpcUrl) {
1529
2342
  classified = await enrichNftClassification(rpcUrl, classified);
1530
2343
  }