tx-indexer 0.5.0 → 0.5.2

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
@@ -49,6 +49,57 @@ function getTokenInfo(mint) {
49
49
  return TOKEN_INFO[mint];
50
50
  }
51
51
 
52
+ // ../solana/src/constants/program-ids.ts
53
+ var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
54
+ var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
55
+ var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
56
+ var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
57
+ var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
58
+ var TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
59
+ var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
60
+ var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
61
+ var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
62
+ var JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
63
+ var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
64
+ var JUPITER_ORDER_ENGINE_PROGRAM_ID = "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH";
65
+ var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
66
+ var RAYDIUM_CLMM_PROGRAM_ID = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
67
+ var RAYDIUM_CPMM_PROGRAM_ID = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
68
+ var RAYDIUM_STABLE_PROGRAM_ID = "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h";
69
+ var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
70
+ var ORCA_TOKEN_SWAP_V1_PROGRAM_ID = "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP";
71
+ var OPENBOOK_V2_PROGRAM_ID = "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb";
72
+ var PHOENIX_PROGRAM_ID = "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY";
73
+ var SABER_STABLE_SWAP_PROGRAM_ID = "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ";
74
+ var MERCURIAL_STABLE_SWAP_PROGRAM_ID = "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky";
75
+ var METEORA_DLMM_PROGRAM_ID = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo";
76
+ var METEORA_POOLS_PROGRAM_ID = "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB";
77
+ var PUMPFUN_AMM_PROGRAM_ID = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA";
78
+ var PUMPFUN_BONDING_CURVE_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
79
+ var LIFINITY_V2_PROGRAM_ID = "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c";
80
+ var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
81
+ var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
82
+ var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
83
+ var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
84
+ var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
85
+ var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
86
+ var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
87
+ var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
88
+ var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
89
+ var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
90
+ var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
91
+ var KNOWN_FACILITATORS = [PAYAI_FACILITATOR];
92
+ function detectFacilitator(accountKeys) {
93
+ for (const facilitator of KNOWN_FACILITATORS) {
94
+ if (accountKeys.includes(facilitator)) {
95
+ if (facilitator === PAYAI_FACILITATOR) {
96
+ return "payai";
97
+ }
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
52
103
  // ../solana/src/fetcher/balances.ts
53
104
  async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
54
105
  const balanceResponse = await rpc.getBalance(walletAddress).send();
@@ -66,12 +117,17 @@ async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
66
117
  }
67
118
  async function fetchTokenAccounts(rpc, walletAddress) {
68
119
  const accountsMap = /* @__PURE__ */ new Map();
69
- try {
70
- const response = await rpc.getTokenAccountsByOwner(
71
- walletAddress,
72
- { programId: address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") },
73
- { encoding: "jsonParsed" }
74
- ).send();
120
+ const tokenPrograms = [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID];
121
+ const responses = await Promise.all(
122
+ tokenPrograms.map(
123
+ (programId) => rpc.getTokenAccountsByOwner(
124
+ walletAddress,
125
+ { programId: address(programId) },
126
+ { encoding: "jsonParsed" }
127
+ ).send().catch(() => ({ value: [] }))
128
+ )
129
+ );
130
+ for (const response of responses) {
75
131
  for (const account of response.value) {
76
132
  const parsedInfo = account.account.data.parsed.info;
77
133
  const mint = parsedInfo.mint;
@@ -88,8 +144,6 @@ async function fetchTokenAccounts(rpc, walletAddress) {
88
144
  symbol
89
145
  });
90
146
  }
91
- } catch (error) {
92
- console.error("Error fetching token accounts:", error);
93
147
  }
94
148
  return accountsMap;
95
149
  }
@@ -133,44 +187,6 @@ function extractProgramIds(transaction) {
133
187
  return Array.from(programIds);
134
188
  }
135
189
 
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
190
  // ../../node_modules/.bun/base-x@5.0.1/node_modules/base-x/src/esm/index.js
175
191
  function base(ALPHABET2) {
176
192
  if (ALPHABET2.length >= 255) {
@@ -380,6 +396,189 @@ function isSolanaPayTransaction(programIds, memo) {
380
396
  return hasMemoProgram && memo !== null && memo !== void 0;
381
397
  }
382
398
 
399
+ // ../solana/src/rpc/retry.ts
400
+ var DEFAULT_CONFIG = {
401
+ maxAttempts: 3,
402
+ baseDelayMs: 1e3,
403
+ maxDelayMs: 1e4
404
+ };
405
+ function isRetryableError(error) {
406
+ if (error instanceof Error) {
407
+ const message = error.message.toLowerCase();
408
+ 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");
409
+ }
410
+ return false;
411
+ }
412
+ function calculateDelay(attempt, baseDelayMs, maxDelayMs) {
413
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
414
+ const jitter = Math.random() * 0.3 * exponentialDelay;
415
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
416
+ }
417
+ function sleep(ms) {
418
+ return new Promise((resolve) => setTimeout(resolve, ms));
419
+ }
420
+ async function withRetry(fn, config = {}) {
421
+ const { maxAttempts, baseDelayMs, maxDelayMs } = {
422
+ ...DEFAULT_CONFIG,
423
+ ...config
424
+ };
425
+ let lastError;
426
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
427
+ try {
428
+ return await fn();
429
+ } catch (error) {
430
+ lastError = error;
431
+ const isLastAttempt = attempt === maxAttempts;
432
+ const shouldRetry = !isLastAttempt && isRetryableError(error);
433
+ if (!shouldRetry) {
434
+ throw error;
435
+ }
436
+ const delay = calculateDelay(attempt, baseDelayMs, maxDelayMs);
437
+ await sleep(delay);
438
+ }
439
+ }
440
+ throw lastError;
441
+ }
442
+
443
+ // ../../node_modules/.bun/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
444
+ var Node = class {
445
+ value;
446
+ next;
447
+ constructor(value) {
448
+ this.value = value;
449
+ }
450
+ };
451
+ var Queue = class {
452
+ #head;
453
+ #tail;
454
+ #size;
455
+ constructor() {
456
+ this.clear();
457
+ }
458
+ enqueue(value) {
459
+ const node = new Node(value);
460
+ if (this.#head) {
461
+ this.#tail.next = node;
462
+ this.#tail = node;
463
+ } else {
464
+ this.#head = node;
465
+ this.#tail = node;
466
+ }
467
+ this.#size++;
468
+ }
469
+ dequeue() {
470
+ const current = this.#head;
471
+ if (!current) {
472
+ return;
473
+ }
474
+ this.#head = this.#head.next;
475
+ this.#size--;
476
+ if (!this.#head) {
477
+ this.#tail = void 0;
478
+ }
479
+ return current.value;
480
+ }
481
+ peek() {
482
+ if (!this.#head) {
483
+ return;
484
+ }
485
+ return this.#head.value;
486
+ }
487
+ clear() {
488
+ this.#head = void 0;
489
+ this.#tail = void 0;
490
+ this.#size = 0;
491
+ }
492
+ get size() {
493
+ return this.#size;
494
+ }
495
+ *[Symbol.iterator]() {
496
+ let current = this.#head;
497
+ while (current) {
498
+ yield current.value;
499
+ current = current.next;
500
+ }
501
+ }
502
+ *drain() {
503
+ while (this.#head) {
504
+ yield this.dequeue();
505
+ }
506
+ }
507
+ };
508
+
509
+ // ../../node_modules/.bun/p-limit@6.2.0/node_modules/p-limit/index.js
510
+ function pLimit(concurrency) {
511
+ validateConcurrency(concurrency);
512
+ const queue = new Queue();
513
+ let activeCount = 0;
514
+ const resumeNext = () => {
515
+ if (activeCount < concurrency && queue.size > 0) {
516
+ queue.dequeue()();
517
+ activeCount++;
518
+ }
519
+ };
520
+ const next = () => {
521
+ activeCount--;
522
+ resumeNext();
523
+ };
524
+ const run = async (function_, resolve, arguments_) => {
525
+ const result = (async () => function_(...arguments_))();
526
+ resolve(result);
527
+ try {
528
+ await result;
529
+ } catch {
530
+ }
531
+ next();
532
+ };
533
+ const enqueue = (function_, resolve, arguments_) => {
534
+ new Promise((internalResolve) => {
535
+ queue.enqueue(internalResolve);
536
+ }).then(
537
+ run.bind(void 0, function_, resolve, arguments_)
538
+ );
539
+ (async () => {
540
+ await Promise.resolve();
541
+ if (activeCount < concurrency) {
542
+ resumeNext();
543
+ }
544
+ })();
545
+ };
546
+ const generator = (function_, ...arguments_) => new Promise((resolve) => {
547
+ enqueue(function_, resolve, arguments_);
548
+ });
549
+ Object.defineProperties(generator, {
550
+ activeCount: {
551
+ get: () => activeCount
552
+ },
553
+ pendingCount: {
554
+ get: () => queue.size
555
+ },
556
+ clearQueue: {
557
+ value() {
558
+ queue.clear();
559
+ }
560
+ },
561
+ concurrency: {
562
+ get: () => concurrency,
563
+ set(newConcurrency) {
564
+ validateConcurrency(newConcurrency);
565
+ concurrency = newConcurrency;
566
+ queueMicrotask(() => {
567
+ while (activeCount < concurrency && queue.size > 0) {
568
+ resumeNext();
569
+ }
570
+ });
571
+ }
572
+ }
573
+ });
574
+ return generator;
575
+ }
576
+ function validateConcurrency(concurrency) {
577
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
578
+ throw new TypeError("Expected `concurrency` to be a number from 1 and up");
579
+ }
580
+ }
581
+
383
582
  // ../solana/src/fetcher/transactions.ts
384
583
  async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
385
584
  const { limit = 100, before, until } = config;
@@ -398,12 +597,16 @@ async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
398
597
  memo: sig.memo || null
399
598
  }));
400
599
  }
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();
600
+ async function fetchTransaction(rpc, signature2, options = {}) {
601
+ const { commitment = "confirmed", retry } = options;
602
+ const response = await withRetry(
603
+ () => rpc.getTransaction(signature2, {
604
+ commitment,
605
+ maxSupportedTransactionVersion: 0,
606
+ encoding: "json"
607
+ }).send(),
608
+ retry
609
+ );
407
610
  if (!response) {
408
611
  return null;
409
612
  }
@@ -450,10 +653,29 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
450
653
  memo
451
654
  };
452
655
  }
453
- async function fetchTransactionsBatch(rpc, signatures, commitment = "confirmed") {
454
- const promises = signatures.map(
455
- (sig) => fetchTransaction(rpc, sig, commitment)
456
- );
656
+ async function fetchTransactionsBatch(rpc, signatures, options = {}) {
657
+ const {
658
+ commitment = "confirmed",
659
+ concurrency = 10,
660
+ retry,
661
+ onFetchError
662
+ } = options;
663
+ if (signatures.length === 0) {
664
+ return [];
665
+ }
666
+ const limit = pLimit(concurrency);
667
+ const safeFetch = async (sig) => {
668
+ try {
669
+ return await fetchTransaction(rpc, sig, { commitment, retry });
670
+ } catch (error) {
671
+ onFetchError?.(
672
+ sig,
673
+ error instanceof Error ? error : new Error(String(error))
674
+ );
675
+ return null;
676
+ }
677
+ };
678
+ const promises = signatures.map((sig) => limit(() => safeFetch(sig)));
457
679
  const results = await Promise.all(promises);
458
680
  return results.filter((tx) => tx !== null);
459
681
  }
@@ -598,7 +820,7 @@ function transactionToLegs(tx) {
598
820
  amountRaw: change.change.toString().replace("-", ""),
599
821
  amountUi: Math.abs(change.changeUi)
600
822
  },
601
- role: determineSolRole(change, tx, feePayer)
823
+ role: determineSolRole(change, tx)
602
824
  });
603
825
  }
604
826
  const networkFee = totalSolDebits - totalSolCredits;
@@ -655,13 +877,8 @@ function transactionToLegs(tx) {
655
877
  }
656
878
  return legs;
657
879
  }
658
- function determineSolRole(change, tx, feePayer) {
659
- const isFeePayer = feePayer ? change.address.toLowerCase() === feePayer : false;
880
+ function determineSolRole(change, tx, _feePayer) {
660
881
  const isPositive = change.change > 0n;
661
- const amountSol = Math.abs(change.changeUi);
662
- if (isFeePayer && !isPositive && amountSol < 0.01) {
663
- return "fee";
664
- }
665
882
  if (isPositive) {
666
883
  if (tx.protocol?.id === "stake") {
667
884
  return "reward";
@@ -721,6 +938,7 @@ var TransferClassifier = class {
721
938
 
722
939
  // ../classification/src/protocols/detector.ts
723
940
  var KNOWN_PROGRAMS = {
941
+ // Jupiter aggregator
724
942
  [JUPITER_V6_PROGRAM_ID]: {
725
943
  id: "jupiter",
726
944
  name: "Jupiter"
@@ -729,10 +947,19 @@ var KNOWN_PROGRAMS = {
729
947
  id: "jupiter-v4",
730
948
  name: "Jupiter V4"
731
949
  },
950
+ [JUPITER_ORDER_ENGINE_PROGRAM_ID]: {
951
+ id: "jupiter-limit-order",
952
+ name: "Jupiter Limit Order"
953
+ },
954
+ // Core token programs
732
955
  [TOKEN_PROGRAM_ID]: {
733
956
  id: "spl-token",
734
957
  name: "Token Program"
735
958
  },
959
+ [TOKEN_2022_PROGRAM_ID]: {
960
+ id: "token-2022",
961
+ name: "Token-2022 Program"
962
+ },
736
963
  [SYSTEM_PROGRAM_ID]: {
737
964
  id: "system",
738
965
  name: "System Program"
@@ -745,25 +972,86 @@ var KNOWN_PROGRAMS = {
745
972
  id: "associated-token",
746
973
  name: "Associated Token Program"
747
974
  },
748
- [METAPLEX_PROGRAM_ID]: {
749
- id: "metaplex",
750
- name: "Metaplex"
975
+ // Memo programs
976
+ [SPL_MEMO_PROGRAM_ID]: {
977
+ id: "memo",
978
+ name: "Memo Program"
751
979
  },
752
- [ORCA_WHIRLPOOL_PROGRAM_ID]: {
753
- id: "orca-whirlpool",
754
- name: "Orca Whirlpool"
980
+ [MEMO_V1_PROGRAM_ID]: {
981
+ id: "memo-v1",
982
+ name: "Memo Program V1"
755
983
  },
984
+ // Raydium AMMs
756
985
  [RAYDIUM_PROGRAM_ID]: {
757
986
  id: "raydium",
758
987
  name: "Raydium"
759
988
  },
760
- [STAKE_PROGRAM_ID]: {
761
- id: "stake",
762
- name: "Stake Program"
989
+ [RAYDIUM_CLMM_PROGRAM_ID]: {
990
+ id: "raydium-clmm",
991
+ name: "Raydium CLMM"
763
992
  },
764
- [STAKE_POOL_PROGRAM_ID]: {
765
- id: "stake-pool",
766
- name: "Stake Pool Program"
993
+ [RAYDIUM_CPMM_PROGRAM_ID]: {
994
+ id: "raydium-cpmm",
995
+ name: "Raydium CPMM"
996
+ },
997
+ [RAYDIUM_STABLE_PROGRAM_ID]: {
998
+ id: "raydium-stable",
999
+ name: "Raydium Stable"
1000
+ },
1001
+ // Orca
1002
+ [ORCA_WHIRLPOOL_PROGRAM_ID]: {
1003
+ id: "orca-whirlpool",
1004
+ name: "Orca Whirlpool"
1005
+ },
1006
+ [ORCA_TOKEN_SWAP_V1_PROGRAM_ID]: {
1007
+ id: "orca-v1",
1008
+ name: "Orca Token Swap V1"
1009
+ },
1010
+ // CLOBs (Central Limit Order Books)
1011
+ [OPENBOOK_V2_PROGRAM_ID]: {
1012
+ id: "openbook",
1013
+ name: "OpenBook"
1014
+ },
1015
+ [PHOENIX_PROGRAM_ID]: {
1016
+ id: "phoenix",
1017
+ name: "Phoenix"
1018
+ },
1019
+ // Stableswap protocols
1020
+ [SABER_STABLE_SWAP_PROGRAM_ID]: {
1021
+ id: "saber",
1022
+ name: "Saber"
1023
+ },
1024
+ [MERCURIAL_STABLE_SWAP_PROGRAM_ID]: {
1025
+ id: "mercurial",
1026
+ name: "Mercurial"
1027
+ },
1028
+ // Meteora
1029
+ [METEORA_DLMM_PROGRAM_ID]: {
1030
+ id: "meteora-dlmm",
1031
+ name: "Meteora DLMM"
1032
+ },
1033
+ [METEORA_POOLS_PROGRAM_ID]: {
1034
+ id: "meteora-pools",
1035
+ name: "Meteora Pools"
1036
+ },
1037
+ // Pump.fun
1038
+ [PUMPFUN_AMM_PROGRAM_ID]: {
1039
+ id: "pumpfun",
1040
+ name: "Pump.fun"
1041
+ },
1042
+ [PUMPFUN_BONDING_CURVE_PROGRAM_ID]: {
1043
+ id: "pumpfun-bonding",
1044
+ name: "Pump.fun Bonding Curve"
1045
+ },
1046
+ // Lifinity
1047
+ [LIFINITY_V2_PROGRAM_ID]: {
1048
+ id: "lifinity",
1049
+ name: "Lifinity"
1050
+ },
1051
+ // NFT programs
1052
+ [METAPLEX_PROGRAM_ID]: {
1053
+ id: "metaplex",
1054
+ name: "Metaplex"
767
1055
  },
768
1056
  [CANDY_GUARD_PROGRAM_ID]: {
769
1057
  id: "candy-guard",
@@ -781,6 +1069,16 @@ var KNOWN_PROGRAMS = {
781
1069
  id: "magic-eden-candy-machine",
782
1070
  name: "Nft Candy Machine Program (Magic Eden)"
783
1071
  },
1072
+ // Staking programs
1073
+ [STAKE_PROGRAM_ID]: {
1074
+ id: "stake",
1075
+ name: "Stake Program"
1076
+ },
1077
+ [STAKE_POOL_PROGRAM_ID]: {
1078
+ id: "stake-pool",
1079
+ name: "Stake Pool Program"
1080
+ },
1081
+ // Bridge programs
784
1082
  [WORMHOLE_PROGRAM_ID]: {
785
1083
  id: "wormhole",
786
1084
  name: "Wormhole"
@@ -803,27 +1101,79 @@ var KNOWN_PROGRAMS = {
803
1101
  }
804
1102
  };
805
1103
  var PRIORITY_ORDER = [
1104
+ // Bridge protocols (highest priority - cross-chain operations)
806
1105
  "wormhole",
807
1106
  "wormhole-token-bridge",
808
1107
  "degods-bridge",
809
1108
  "debridge",
810
1109
  "allbridge",
1110
+ // DEX aggregators (route through multiple DEXes)
811
1111
  "jupiter",
812
1112
  "jupiter-v4",
1113
+ "jupiter-limit-order",
1114
+ // AMMs and DEXes
813
1115
  "raydium",
1116
+ "raydium-clmm",
1117
+ "raydium-cpmm",
1118
+ "raydium-stable",
814
1119
  "orca-whirlpool",
1120
+ "orca-v1",
1121
+ "meteora-dlmm",
1122
+ "meteora-pools",
1123
+ "lifinity",
1124
+ "pumpfun",
1125
+ "pumpfun-bonding",
1126
+ // CLOBs
1127
+ "openbook",
1128
+ "phoenix",
1129
+ // Stableswap
1130
+ "saber",
1131
+ "mercurial",
1132
+ // NFT
815
1133
  "metaplex",
1134
+ "candy-guard",
1135
+ "candy-machine-v3",
1136
+ "bubblegum",
1137
+ "magic-eden-candy-machine",
1138
+ // Staking
816
1139
  "stake",
1140
+ "stake-pool",
1141
+ // Infrastructure (lowest priority)
1142
+ "memo",
1143
+ "memo-v1",
817
1144
  "associated-token",
818
1145
  "spl-token",
1146
+ "token-2022",
819
1147
  "compute-budget",
820
1148
  "system"
821
1149
  ];
822
1150
  var DEX_PROTOCOL_IDS2 = /* @__PURE__ */ new Set([
1151
+ // Jupiter aggregator
823
1152
  "jupiter",
824
1153
  "jupiter-v4",
1154
+ "jupiter-limit-order",
1155
+ // Raydium AMMs
825
1156
  "raydium",
826
- "orca-whirlpool"
1157
+ "raydium-clmm",
1158
+ "raydium-cpmm",
1159
+ "raydium-stable",
1160
+ // Orca
1161
+ "orca-whirlpool",
1162
+ "orca-v1",
1163
+ // CLOBs
1164
+ "openbook",
1165
+ "phoenix",
1166
+ // Stableswap
1167
+ "saber",
1168
+ "mercurial",
1169
+ // Meteora
1170
+ "meteora-dlmm",
1171
+ "meteora-pools",
1172
+ // Pump.fun
1173
+ "pumpfun",
1174
+ "pumpfun-bonding",
1175
+ // Lifinity
1176
+ "lifinity"
827
1177
  ]);
828
1178
  var NFT_MINT_PROTOCOL_IDS = /* @__PURE__ */ new Set([
829
1179
  "metaplex",
@@ -872,41 +1222,71 @@ function detectProtocol(programIds) {
872
1222
  }
873
1223
 
874
1224
  // ../classification/src/classifiers/swap-classifier.ts
1225
+ function findSwapPair(tokensOut, tokensIn) {
1226
+ let bestPair = null;
1227
+ let bestScore = 0;
1228
+ for (const out of tokensOut) {
1229
+ for (const inLeg of tokensIn) {
1230
+ if (out.amount.token.symbol !== inLeg.amount.token.symbol) {
1231
+ const score = Math.max(out.amount.amountUi, inLeg.amount.amountUi);
1232
+ if (score > bestScore) {
1233
+ bestScore = score;
1234
+ bestPair = { initiatorOut: out, initiatorIn: inLeg };
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+ return bestPair;
1240
+ }
875
1241
  var SwapClassifier = class {
876
1242
  name = "swap";
877
1243
  priority = 80;
878
1244
  classify(context) {
879
- const { legs, tx } = context;
880
- const feeLeg = legs.find(
881
- (leg) => leg.role === "fee" && leg.side === "debit"
882
- );
883
- const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
1245
+ const { legs, tx, walletAddress } = context;
1246
+ const initiator = tx.accountKeys?.[0] ?? null;
884
1247
  if (!initiator) {
885
1248
  return null;
886
1249
  }
887
1250
  const initiatorAccountId = `external:${initiator}`;
888
- const tokensOut = legs.filter(
1251
+ const initiatorTokensOut = legs.filter(
889
1252
  (leg) => leg.accountId === initiatorAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
890
1253
  );
891
- const tokensIn = legs.filter(
1254
+ const initiatorTokensIn = legs.filter(
892
1255
  (leg) => leg.accountId === initiatorAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
893
1256
  );
894
- if (tokensOut.length === 0 || tokensIn.length === 0) {
1257
+ if (initiatorTokensOut.length === 0 || initiatorTokensIn.length === 0) {
895
1258
  return null;
896
1259
  }
897
- const tokenOut = tokensOut[0];
898
- const tokenIn = tokensIn[0];
899
- if (tokenOut.amount.token.symbol === tokenIn.amount.token.symbol) {
1260
+ const swapPair = findSwapPair(initiatorTokensOut, initiatorTokensIn);
1261
+ if (!swapPair) {
900
1262
  return null;
901
1263
  }
1264
+ const { initiatorOut, initiatorIn } = swapPair;
1265
+ let tokenOut = initiatorOut;
1266
+ let tokenIn = initiatorIn;
1267
+ let perspectiveWallet = initiator;
1268
+ if (walletAddress) {
1269
+ const walletAccountId = `external:${walletAddress}`;
1270
+ const walletOut = legs.find(
1271
+ (leg) => leg.accountId === walletAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
1272
+ );
1273
+ const walletIn = legs.find(
1274
+ (leg) => leg.accountId === walletAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
1275
+ );
1276
+ if (walletOut && walletIn && walletOut.amount.token.symbol !== walletIn.amount.token.symbol) {
1277
+ tokenOut = walletOut;
1278
+ tokenIn = walletIn;
1279
+ perspectiveWallet = walletAddress;
1280
+ }
1281
+ }
902
1282
  const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
903
1283
  const confidence = hasDexProtocol ? 0.95 : 0.75;
904
1284
  return {
905
1285
  primaryType: "swap",
906
1286
  primaryAmount: tokenOut.amount,
907
1287
  secondaryAmount: tokenIn.amount,
908
- sender: initiator,
909
- receiver: initiator,
1288
+ sender: perspectiveWallet,
1289
+ receiver: perspectiveWallet,
910
1290
  counterparty: null,
911
1291
  confidence,
912
1292
  isRelevant: true,
@@ -1248,8 +1628,8 @@ var ClassificationService = class {
1248
1628
  this.classifiers.push(classifier);
1249
1629
  this.classifiers.sort((a, b) => b.priority - a.priority);
1250
1630
  }
1251
- classify(legs, tx) {
1252
- const context = { legs, tx };
1631
+ classify(legs, tx, walletAddress) {
1632
+ const context = { legs, tx, walletAddress };
1253
1633
  for (const classifier of this.classifiers) {
1254
1634
  const result = classifier.classify(context);
1255
1635
  if (result && result.isRelevant) {
@@ -1270,19 +1650,19 @@ var ClassificationService = class {
1270
1650
  }
1271
1651
  };
1272
1652
  var classificationService = new ClassificationService();
1273
- function classifyTransaction(legs, tx) {
1274
- return classificationService.classify(legs, tx);
1653
+ function classifyTransaction(legs, tx, walletAddress) {
1654
+ return classificationService.classify(legs, tx, walletAddress);
1275
1655
  }
1276
1656
 
1277
1657
  // ../domain/src/tx/spam-filter.ts
1278
- var DEFAULT_CONFIG = {
1658
+ var DEFAULT_CONFIG2 = {
1279
1659
  minSolAmount: 1e-3,
1280
1660
  minTokenAmountUsd: 0.01,
1281
1661
  minConfidence: 0.5,
1282
1662
  allowFailed: false
1283
1663
  };
1284
1664
  function isSpamTransaction(tx, classification, config = {}) {
1285
- const cfg = { ...DEFAULT_CONFIG, ...config };
1665
+ const cfg = { ...DEFAULT_CONFIG2, ...config };
1286
1666
  if (!cfg.allowFailed && tx.err) {
1287
1667
  return true;
1288
1668
  }
@@ -1448,10 +1828,11 @@ function createIndexer(options) {
1448
1828
  client.rpc,
1449
1829
  signatureObjects
1450
1830
  );
1831
+ const walletAddressStr2 = walletAddress.toString();
1451
1832
  const classified = transactions.map((tx) => {
1452
1833
  tx.protocol = detectProtocol(tx.programIds);
1453
1834
  const legs = transactionToLegs(tx);
1454
- const classification = classifyTransaction(legs, tx);
1835
+ const classification = classifyTransaction(legs, tx, walletAddressStr2);
1455
1836
  return { tx, classification, legs };
1456
1837
  });
1457
1838
  return enrichBatch(classified);
@@ -1460,6 +1841,7 @@ function createIndexer(options) {
1460
1841
  let currentBefore = before;
1461
1842
  const MAX_ITERATIONS = 10;
1462
1843
  let iteration = 0;
1844
+ const walletAddressStr = walletAddress.toString();
1463
1845
  while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
1464
1846
  iteration++;
1465
1847
  const batchSize = iteration === 1 ? limit : limit * 2;
@@ -1481,7 +1863,7 @@ function createIndexer(options) {
1481
1863
  const classified = transactions.map((tx) => {
1482
1864
  tx.protocol = detectProtocol(tx.programIds);
1483
1865
  const legs = transactionToLegs(tx);
1484
- const classification = classifyTransaction(legs, tx);
1866
+ const classification = classifyTransaction(legs, tx, walletAddressStr);
1485
1867
  return { tx, classification, legs };
1486
1868
  });
1487
1869
  const nonSpam = filterSpamTransactions(classified, spamConfig);