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/index.js CHANGED
@@ -61,6 +61,57 @@ function getTokenInfo(mint) {
61
61
  KNOWN_TOKENS.USDC
62
62
  ];
63
63
 
64
+ // ../solana/src/constants/program-ids.ts
65
+ var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
66
+ var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
67
+ var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
68
+ var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
69
+ var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
70
+ var TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
71
+ var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
72
+ var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
73
+ var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
74
+ var JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
75
+ var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
76
+ var JUPITER_ORDER_ENGINE_PROGRAM_ID = "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH";
77
+ var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
78
+ var RAYDIUM_CLMM_PROGRAM_ID = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
79
+ var RAYDIUM_CPMM_PROGRAM_ID = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
80
+ var RAYDIUM_STABLE_PROGRAM_ID = "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h";
81
+ var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
82
+ var ORCA_TOKEN_SWAP_V1_PROGRAM_ID = "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP";
83
+ var OPENBOOK_V2_PROGRAM_ID = "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb";
84
+ var PHOENIX_PROGRAM_ID = "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY";
85
+ var SABER_STABLE_SWAP_PROGRAM_ID = "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ";
86
+ var MERCURIAL_STABLE_SWAP_PROGRAM_ID = "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky";
87
+ var METEORA_DLMM_PROGRAM_ID = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo";
88
+ var METEORA_POOLS_PROGRAM_ID = "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB";
89
+ var PUMPFUN_AMM_PROGRAM_ID = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA";
90
+ var PUMPFUN_BONDING_CURVE_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
91
+ var LIFINITY_V2_PROGRAM_ID = "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c";
92
+ var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
93
+ var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
94
+ var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
95
+ var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
96
+ var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
97
+ var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
98
+ var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
99
+ var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
100
+ var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
101
+ var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
102
+ var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
103
+ var KNOWN_FACILITATORS = [PAYAI_FACILITATOR];
104
+ function detectFacilitator(accountKeys) {
105
+ for (const facilitator of KNOWN_FACILITATORS) {
106
+ if (accountKeys.includes(facilitator)) {
107
+ if (facilitator === PAYAI_FACILITATOR) {
108
+ return "payai";
109
+ }
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+
64
115
  // ../solana/src/fetcher/balances.ts
65
116
  async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
66
117
  const balanceResponse = await rpc.getBalance(walletAddress).send();
@@ -78,12 +129,17 @@ async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
78
129
  }
79
130
  async function fetchTokenAccounts(rpc, walletAddress) {
80
131
  const accountsMap = /* @__PURE__ */ new Map();
81
- try {
82
- const response = await rpc.getTokenAccountsByOwner(
83
- walletAddress,
84
- { programId: address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") },
85
- { encoding: "jsonParsed" }
86
- ).send();
132
+ const tokenPrograms = [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID];
133
+ const responses = await Promise.all(
134
+ tokenPrograms.map(
135
+ (programId) => rpc.getTokenAccountsByOwner(
136
+ walletAddress,
137
+ { programId: address(programId) },
138
+ { encoding: "jsonParsed" }
139
+ ).send().catch(() => ({ value: [] }))
140
+ )
141
+ );
142
+ for (const response of responses) {
87
143
  for (const account of response.value) {
88
144
  const parsedInfo = account.account.data.parsed.info;
89
145
  const mint = parsedInfo.mint;
@@ -100,8 +156,6 @@ async function fetchTokenAccounts(rpc, walletAddress) {
100
156
  symbol
101
157
  });
102
158
  }
103
- } catch (error) {
104
- console.error("Error fetching token accounts:", error);
105
159
  }
106
160
  return accountsMap;
107
161
  }
@@ -145,44 +199,6 @@ function extractProgramIds(transaction) {
145
199
  return Array.from(programIds);
146
200
  }
147
201
 
148
- // ../solana/src/constants/program-ids.ts
149
- var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
150
- var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
151
- var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
152
- var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
153
- var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
154
- var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
155
- var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
156
- var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
157
- var JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
158
- var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
159
- var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
160
- var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
161
- var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
162
- var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
163
- var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
164
- var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
165
- var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
166
- var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
167
- var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
168
- var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
169
- var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
170
- var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
171
- var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
172
- var KNOWN_FACILITATORS = [
173
- PAYAI_FACILITATOR
174
- ];
175
- function detectFacilitator(accountKeys) {
176
- for (const facilitator of KNOWN_FACILITATORS) {
177
- if (accountKeys.includes(facilitator)) {
178
- if (facilitator === PAYAI_FACILITATOR) {
179
- return "payai";
180
- }
181
- }
182
- }
183
- return null;
184
- }
185
-
186
202
  // ../../node_modules/.bun/base-x@5.0.1/node_modules/base-x/src/esm/index.js
187
203
  function base(ALPHABET2) {
188
204
  if (ALPHABET2.length >= 255) {
@@ -392,6 +408,189 @@ function isSolanaPayTransaction(programIds, memo) {
392
408
  return hasMemoProgram && memo !== null && memo !== void 0;
393
409
  }
394
410
 
411
+ // ../solana/src/rpc/retry.ts
412
+ var DEFAULT_CONFIG = {
413
+ maxAttempts: 3,
414
+ baseDelayMs: 1e3,
415
+ maxDelayMs: 1e4
416
+ };
417
+ function isRetryableError(error) {
418
+ if (error instanceof Error) {
419
+ const message = error.message.toLowerCase();
420
+ 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");
421
+ }
422
+ return false;
423
+ }
424
+ function calculateDelay(attempt, baseDelayMs, maxDelayMs) {
425
+ const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
426
+ const jitter = Math.random() * 0.3 * exponentialDelay;
427
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
428
+ }
429
+ function sleep(ms) {
430
+ return new Promise((resolve) => setTimeout(resolve, ms));
431
+ }
432
+ async function withRetry(fn, config = {}) {
433
+ const { maxAttempts, baseDelayMs, maxDelayMs } = {
434
+ ...DEFAULT_CONFIG,
435
+ ...config
436
+ };
437
+ let lastError;
438
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
439
+ try {
440
+ return await fn();
441
+ } catch (error) {
442
+ lastError = error;
443
+ const isLastAttempt = attempt === maxAttempts;
444
+ const shouldRetry = !isLastAttempt && isRetryableError(error);
445
+ if (!shouldRetry) {
446
+ throw error;
447
+ }
448
+ const delay = calculateDelay(attempt, baseDelayMs, maxDelayMs);
449
+ await sleep(delay);
450
+ }
451
+ }
452
+ throw lastError;
453
+ }
454
+
455
+ // ../../node_modules/.bun/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
456
+ var Node = class {
457
+ value;
458
+ next;
459
+ constructor(value) {
460
+ this.value = value;
461
+ }
462
+ };
463
+ var Queue = class {
464
+ #head;
465
+ #tail;
466
+ #size;
467
+ constructor() {
468
+ this.clear();
469
+ }
470
+ enqueue(value) {
471
+ const node = new Node(value);
472
+ if (this.#head) {
473
+ this.#tail.next = node;
474
+ this.#tail = node;
475
+ } else {
476
+ this.#head = node;
477
+ this.#tail = node;
478
+ }
479
+ this.#size++;
480
+ }
481
+ dequeue() {
482
+ const current = this.#head;
483
+ if (!current) {
484
+ return;
485
+ }
486
+ this.#head = this.#head.next;
487
+ this.#size--;
488
+ if (!this.#head) {
489
+ this.#tail = void 0;
490
+ }
491
+ return current.value;
492
+ }
493
+ peek() {
494
+ if (!this.#head) {
495
+ return;
496
+ }
497
+ return this.#head.value;
498
+ }
499
+ clear() {
500
+ this.#head = void 0;
501
+ this.#tail = void 0;
502
+ this.#size = 0;
503
+ }
504
+ get size() {
505
+ return this.#size;
506
+ }
507
+ *[Symbol.iterator]() {
508
+ let current = this.#head;
509
+ while (current) {
510
+ yield current.value;
511
+ current = current.next;
512
+ }
513
+ }
514
+ *drain() {
515
+ while (this.#head) {
516
+ yield this.dequeue();
517
+ }
518
+ }
519
+ };
520
+
521
+ // ../../node_modules/.bun/p-limit@6.2.0/node_modules/p-limit/index.js
522
+ function pLimit(concurrency) {
523
+ validateConcurrency(concurrency);
524
+ const queue = new Queue();
525
+ let activeCount = 0;
526
+ const resumeNext = () => {
527
+ if (activeCount < concurrency && queue.size > 0) {
528
+ queue.dequeue()();
529
+ activeCount++;
530
+ }
531
+ };
532
+ const next = () => {
533
+ activeCount--;
534
+ resumeNext();
535
+ };
536
+ const run = async (function_, resolve, arguments_) => {
537
+ const result = (async () => function_(...arguments_))();
538
+ resolve(result);
539
+ try {
540
+ await result;
541
+ } catch {
542
+ }
543
+ next();
544
+ };
545
+ const enqueue = (function_, resolve, arguments_) => {
546
+ new Promise((internalResolve) => {
547
+ queue.enqueue(internalResolve);
548
+ }).then(
549
+ run.bind(void 0, function_, resolve, arguments_)
550
+ );
551
+ (async () => {
552
+ await Promise.resolve();
553
+ if (activeCount < concurrency) {
554
+ resumeNext();
555
+ }
556
+ })();
557
+ };
558
+ const generator = (function_, ...arguments_) => new Promise((resolve) => {
559
+ enqueue(function_, resolve, arguments_);
560
+ });
561
+ Object.defineProperties(generator, {
562
+ activeCount: {
563
+ get: () => activeCount
564
+ },
565
+ pendingCount: {
566
+ get: () => queue.size
567
+ },
568
+ clearQueue: {
569
+ value() {
570
+ queue.clear();
571
+ }
572
+ },
573
+ concurrency: {
574
+ get: () => concurrency,
575
+ set(newConcurrency) {
576
+ validateConcurrency(newConcurrency);
577
+ concurrency = newConcurrency;
578
+ queueMicrotask(() => {
579
+ while (activeCount < concurrency && queue.size > 0) {
580
+ resumeNext();
581
+ }
582
+ });
583
+ }
584
+ }
585
+ });
586
+ return generator;
587
+ }
588
+ function validateConcurrency(concurrency) {
589
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
590
+ throw new TypeError("Expected `concurrency` to be a number from 1 and up");
591
+ }
592
+ }
593
+
395
594
  // ../solana/src/fetcher/transactions.ts
396
595
  async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
397
596
  const { limit = 100, before, until } = config;
@@ -410,12 +609,16 @@ async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
410
609
  memo: sig.memo || null
411
610
  }));
412
611
  }
413
- async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
414
- const response = await rpc.getTransaction(signature2, {
415
- commitment,
416
- maxSupportedTransactionVersion: 0,
417
- encoding: "json"
418
- }).send();
612
+ async function fetchTransaction(rpc, signature2, options = {}) {
613
+ const { commitment = "confirmed", retry } = options;
614
+ const response = await withRetry(
615
+ () => rpc.getTransaction(signature2, {
616
+ commitment,
617
+ maxSupportedTransactionVersion: 0,
618
+ encoding: "json"
619
+ }).send(),
620
+ retry
621
+ );
419
622
  if (!response) {
420
623
  return null;
421
624
  }
@@ -462,10 +665,29 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
462
665
  memo
463
666
  };
464
667
  }
465
- async function fetchTransactionsBatch(rpc, signatures, commitment = "confirmed") {
466
- const promises = signatures.map(
467
- (sig) => fetchTransaction(rpc, sig, commitment)
468
- );
668
+ async function fetchTransactionsBatch(rpc, signatures, options = {}) {
669
+ const {
670
+ commitment = "confirmed",
671
+ concurrency = 10,
672
+ retry,
673
+ onFetchError
674
+ } = options;
675
+ if (signatures.length === 0) {
676
+ return [];
677
+ }
678
+ const limit = pLimit(concurrency);
679
+ const safeFetch = async (sig) => {
680
+ try {
681
+ return await fetchTransaction(rpc, sig, { commitment, retry });
682
+ } catch (error) {
683
+ onFetchError?.(
684
+ sig,
685
+ error instanceof Error ? error : new Error(String(error))
686
+ );
687
+ return null;
688
+ }
689
+ };
690
+ const promises = signatures.map((sig) => limit(() => safeFetch(sig)));
469
691
  const results = await Promise.all(promises);
470
692
  return results.filter((tx) => tx !== null);
471
693
  }
@@ -640,7 +862,7 @@ function transactionToLegs(tx) {
640
862
  amountRaw: change.change.toString().replace("-", ""),
641
863
  amountUi: Math.abs(change.changeUi)
642
864
  },
643
- role: determineSolRole(change, tx, feePayer)
865
+ role: determineSolRole(change, tx)
644
866
  });
645
867
  }
646
868
  const networkFee = totalSolDebits - totalSolCredits;
@@ -697,13 +919,8 @@ function transactionToLegs(tx) {
697
919
  }
698
920
  return legs;
699
921
  }
700
- function determineSolRole(change, tx, feePayer) {
701
- const isFeePayer = feePayer ? change.address.toLowerCase() === feePayer : false;
922
+ function determineSolRole(change, tx, _feePayer) {
702
923
  const isPositive = change.change > 0n;
703
- const amountSol = Math.abs(change.changeUi);
704
- if (isFeePayer && !isPositive && amountSol < 0.01) {
705
- return "fee";
706
- }
707
924
  if (isPositive) {
708
925
  if (tx.protocol?.id === "stake") {
709
926
  return "reward";
@@ -763,6 +980,7 @@ var TransferClassifier = class {
763
980
 
764
981
  // ../classification/src/protocols/detector.ts
765
982
  var KNOWN_PROGRAMS = {
983
+ // Jupiter aggregator
766
984
  [JUPITER_V6_PROGRAM_ID]: {
767
985
  id: "jupiter",
768
986
  name: "Jupiter"
@@ -771,10 +989,19 @@ var KNOWN_PROGRAMS = {
771
989
  id: "jupiter-v4",
772
990
  name: "Jupiter V4"
773
991
  },
992
+ [JUPITER_ORDER_ENGINE_PROGRAM_ID]: {
993
+ id: "jupiter-limit-order",
994
+ name: "Jupiter Limit Order"
995
+ },
996
+ // Core token programs
774
997
  [TOKEN_PROGRAM_ID]: {
775
998
  id: "spl-token",
776
999
  name: "Token Program"
777
1000
  },
1001
+ [TOKEN_2022_PROGRAM_ID]: {
1002
+ id: "token-2022",
1003
+ name: "Token-2022 Program"
1004
+ },
778
1005
  [SYSTEM_PROGRAM_ID]: {
779
1006
  id: "system",
780
1007
  name: "System Program"
@@ -787,25 +1014,86 @@ var KNOWN_PROGRAMS = {
787
1014
  id: "associated-token",
788
1015
  name: "Associated Token Program"
789
1016
  },
790
- [METAPLEX_PROGRAM_ID]: {
791
- id: "metaplex",
792
- name: "Metaplex"
1017
+ // Memo programs
1018
+ [SPL_MEMO_PROGRAM_ID]: {
1019
+ id: "memo",
1020
+ name: "Memo Program"
793
1021
  },
794
- [ORCA_WHIRLPOOL_PROGRAM_ID]: {
795
- id: "orca-whirlpool",
796
- name: "Orca Whirlpool"
1022
+ [MEMO_V1_PROGRAM_ID]: {
1023
+ id: "memo-v1",
1024
+ name: "Memo Program V1"
797
1025
  },
1026
+ // Raydium AMMs
798
1027
  [RAYDIUM_PROGRAM_ID]: {
799
1028
  id: "raydium",
800
1029
  name: "Raydium"
801
1030
  },
802
- [STAKE_PROGRAM_ID]: {
803
- id: "stake",
804
- name: "Stake Program"
1031
+ [RAYDIUM_CLMM_PROGRAM_ID]: {
1032
+ id: "raydium-clmm",
1033
+ name: "Raydium CLMM"
805
1034
  },
806
- [STAKE_POOL_PROGRAM_ID]: {
807
- id: "stake-pool",
808
- name: "Stake Pool Program"
1035
+ [RAYDIUM_CPMM_PROGRAM_ID]: {
1036
+ id: "raydium-cpmm",
1037
+ name: "Raydium CPMM"
1038
+ },
1039
+ [RAYDIUM_STABLE_PROGRAM_ID]: {
1040
+ id: "raydium-stable",
1041
+ name: "Raydium Stable"
1042
+ },
1043
+ // Orca
1044
+ [ORCA_WHIRLPOOL_PROGRAM_ID]: {
1045
+ id: "orca-whirlpool",
1046
+ name: "Orca Whirlpool"
1047
+ },
1048
+ [ORCA_TOKEN_SWAP_V1_PROGRAM_ID]: {
1049
+ id: "orca-v1",
1050
+ name: "Orca Token Swap V1"
1051
+ },
1052
+ // CLOBs (Central Limit Order Books)
1053
+ [OPENBOOK_V2_PROGRAM_ID]: {
1054
+ id: "openbook",
1055
+ name: "OpenBook"
1056
+ },
1057
+ [PHOENIX_PROGRAM_ID]: {
1058
+ id: "phoenix",
1059
+ name: "Phoenix"
1060
+ },
1061
+ // Stableswap protocols
1062
+ [SABER_STABLE_SWAP_PROGRAM_ID]: {
1063
+ id: "saber",
1064
+ name: "Saber"
1065
+ },
1066
+ [MERCURIAL_STABLE_SWAP_PROGRAM_ID]: {
1067
+ id: "mercurial",
1068
+ name: "Mercurial"
1069
+ },
1070
+ // Meteora
1071
+ [METEORA_DLMM_PROGRAM_ID]: {
1072
+ id: "meteora-dlmm",
1073
+ name: "Meteora DLMM"
1074
+ },
1075
+ [METEORA_POOLS_PROGRAM_ID]: {
1076
+ id: "meteora-pools",
1077
+ name: "Meteora Pools"
1078
+ },
1079
+ // Pump.fun
1080
+ [PUMPFUN_AMM_PROGRAM_ID]: {
1081
+ id: "pumpfun",
1082
+ name: "Pump.fun"
1083
+ },
1084
+ [PUMPFUN_BONDING_CURVE_PROGRAM_ID]: {
1085
+ id: "pumpfun-bonding",
1086
+ name: "Pump.fun Bonding Curve"
1087
+ },
1088
+ // Lifinity
1089
+ [LIFINITY_V2_PROGRAM_ID]: {
1090
+ id: "lifinity",
1091
+ name: "Lifinity"
1092
+ },
1093
+ // NFT programs
1094
+ [METAPLEX_PROGRAM_ID]: {
1095
+ id: "metaplex",
1096
+ name: "Metaplex"
809
1097
  },
810
1098
  [CANDY_GUARD_PROGRAM_ID]: {
811
1099
  id: "candy-guard",
@@ -823,6 +1111,16 @@ var KNOWN_PROGRAMS = {
823
1111
  id: "magic-eden-candy-machine",
824
1112
  name: "Nft Candy Machine Program (Magic Eden)"
825
1113
  },
1114
+ // Staking programs
1115
+ [STAKE_PROGRAM_ID]: {
1116
+ id: "stake",
1117
+ name: "Stake Program"
1118
+ },
1119
+ [STAKE_POOL_PROGRAM_ID]: {
1120
+ id: "stake-pool",
1121
+ name: "Stake Pool Program"
1122
+ },
1123
+ // Bridge programs
826
1124
  [WORMHOLE_PROGRAM_ID]: {
827
1125
  id: "wormhole",
828
1126
  name: "Wormhole"
@@ -845,27 +1143,79 @@ var KNOWN_PROGRAMS = {
845
1143
  }
846
1144
  };
847
1145
  var PRIORITY_ORDER = [
1146
+ // Bridge protocols (highest priority - cross-chain operations)
848
1147
  "wormhole",
849
1148
  "wormhole-token-bridge",
850
1149
  "degods-bridge",
851
1150
  "debridge",
852
1151
  "allbridge",
1152
+ // DEX aggregators (route through multiple DEXes)
853
1153
  "jupiter",
854
1154
  "jupiter-v4",
1155
+ "jupiter-limit-order",
1156
+ // AMMs and DEXes
855
1157
  "raydium",
1158
+ "raydium-clmm",
1159
+ "raydium-cpmm",
1160
+ "raydium-stable",
856
1161
  "orca-whirlpool",
1162
+ "orca-v1",
1163
+ "meteora-dlmm",
1164
+ "meteora-pools",
1165
+ "lifinity",
1166
+ "pumpfun",
1167
+ "pumpfun-bonding",
1168
+ // CLOBs
1169
+ "openbook",
1170
+ "phoenix",
1171
+ // Stableswap
1172
+ "saber",
1173
+ "mercurial",
1174
+ // NFT
857
1175
  "metaplex",
1176
+ "candy-guard",
1177
+ "candy-machine-v3",
1178
+ "bubblegum",
1179
+ "magic-eden-candy-machine",
1180
+ // Staking
858
1181
  "stake",
1182
+ "stake-pool",
1183
+ // Infrastructure (lowest priority)
1184
+ "memo",
1185
+ "memo-v1",
859
1186
  "associated-token",
860
1187
  "spl-token",
1188
+ "token-2022",
861
1189
  "compute-budget",
862
1190
  "system"
863
1191
  ];
864
1192
  var DEX_PROTOCOL_IDS2 = /* @__PURE__ */ new Set([
1193
+ // Jupiter aggregator
865
1194
  "jupiter",
866
1195
  "jupiter-v4",
1196
+ "jupiter-limit-order",
1197
+ // Raydium AMMs
867
1198
  "raydium",
868
- "orca-whirlpool"
1199
+ "raydium-clmm",
1200
+ "raydium-cpmm",
1201
+ "raydium-stable",
1202
+ // Orca
1203
+ "orca-whirlpool",
1204
+ "orca-v1",
1205
+ // CLOBs
1206
+ "openbook",
1207
+ "phoenix",
1208
+ // Stableswap
1209
+ "saber",
1210
+ "mercurial",
1211
+ // Meteora
1212
+ "meteora-dlmm",
1213
+ "meteora-pools",
1214
+ // Pump.fun
1215
+ "pumpfun",
1216
+ "pumpfun-bonding",
1217
+ // Lifinity
1218
+ "lifinity"
869
1219
  ]);
870
1220
  var NFT_MINT_PROTOCOL_IDS = /* @__PURE__ */ new Set([
871
1221
  "metaplex",
@@ -914,41 +1264,71 @@ function detectProtocol(programIds) {
914
1264
  }
915
1265
 
916
1266
  // ../classification/src/classifiers/swap-classifier.ts
1267
+ function findSwapPair(tokensOut, tokensIn) {
1268
+ let bestPair = null;
1269
+ let bestScore = 0;
1270
+ for (const out of tokensOut) {
1271
+ for (const inLeg of tokensIn) {
1272
+ if (out.amount.token.symbol !== inLeg.amount.token.symbol) {
1273
+ const score = Math.max(out.amount.amountUi, inLeg.amount.amountUi);
1274
+ if (score > bestScore) {
1275
+ bestScore = score;
1276
+ bestPair = { initiatorOut: out, initiatorIn: inLeg };
1277
+ }
1278
+ }
1279
+ }
1280
+ }
1281
+ return bestPair;
1282
+ }
917
1283
  var SwapClassifier = class {
918
1284
  name = "swap";
919
1285
  priority = 80;
920
1286
  classify(context) {
921
- const { legs, tx } = context;
922
- const feeLeg = legs.find(
923
- (leg) => leg.role === "fee" && leg.side === "debit"
924
- );
925
- const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
1287
+ const { legs, tx, walletAddress } = context;
1288
+ const initiator = tx.accountKeys?.[0] ?? null;
926
1289
  if (!initiator) {
927
1290
  return null;
928
1291
  }
929
1292
  const initiatorAccountId = `external:${initiator}`;
930
- const tokensOut = legs.filter(
1293
+ const initiatorTokensOut = legs.filter(
931
1294
  (leg) => leg.accountId === initiatorAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
932
1295
  );
933
- const tokensIn = legs.filter(
1296
+ const initiatorTokensIn = legs.filter(
934
1297
  (leg) => leg.accountId === initiatorAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
935
1298
  );
936
- if (tokensOut.length === 0 || tokensIn.length === 0) {
1299
+ if (initiatorTokensOut.length === 0 || initiatorTokensIn.length === 0) {
937
1300
  return null;
938
1301
  }
939
- const tokenOut = tokensOut[0];
940
- const tokenIn = tokensIn[0];
941
- if (tokenOut.amount.token.symbol === tokenIn.amount.token.symbol) {
1302
+ const swapPair = findSwapPair(initiatorTokensOut, initiatorTokensIn);
1303
+ if (!swapPair) {
942
1304
  return null;
943
1305
  }
1306
+ const { initiatorOut, initiatorIn } = swapPair;
1307
+ let tokenOut = initiatorOut;
1308
+ let tokenIn = initiatorIn;
1309
+ let perspectiveWallet = initiator;
1310
+ if (walletAddress) {
1311
+ const walletAccountId = `external:${walletAddress}`;
1312
+ const walletOut = legs.find(
1313
+ (leg) => leg.accountId === walletAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
1314
+ );
1315
+ const walletIn = legs.find(
1316
+ (leg) => leg.accountId === walletAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
1317
+ );
1318
+ if (walletOut && walletIn && walletOut.amount.token.symbol !== walletIn.amount.token.symbol) {
1319
+ tokenOut = walletOut;
1320
+ tokenIn = walletIn;
1321
+ perspectiveWallet = walletAddress;
1322
+ }
1323
+ }
944
1324
  const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
945
1325
  const confidence = hasDexProtocol ? 0.95 : 0.75;
946
1326
  return {
947
1327
  primaryType: "swap",
948
1328
  primaryAmount: tokenOut.amount,
949
1329
  secondaryAmount: tokenIn.amount,
950
- sender: initiator,
951
- receiver: initiator,
1330
+ sender: perspectiveWallet,
1331
+ receiver: perspectiveWallet,
952
1332
  counterparty: null,
953
1333
  confidence,
954
1334
  isRelevant: true,
@@ -1290,8 +1670,8 @@ var ClassificationService = class {
1290
1670
  this.classifiers.push(classifier);
1291
1671
  this.classifiers.sort((a, b) => b.priority - a.priority);
1292
1672
  }
1293
- classify(legs, tx) {
1294
- const context = { legs, tx };
1673
+ classify(legs, tx, walletAddress) {
1674
+ const context = { legs, tx, walletAddress };
1295
1675
  for (const classifier of this.classifiers) {
1296
1676
  const result = classifier.classify(context);
1297
1677
  if (result && result.isRelevant) {
@@ -1312,19 +1692,19 @@ var ClassificationService = class {
1312
1692
  }
1313
1693
  };
1314
1694
  var classificationService = new ClassificationService();
1315
- function classifyTransaction(legs, tx) {
1316
- return classificationService.classify(legs, tx);
1695
+ function classifyTransaction(legs, tx, walletAddress) {
1696
+ return classificationService.classify(legs, tx, walletAddress);
1317
1697
  }
1318
1698
 
1319
1699
  // ../domain/src/tx/spam-filter.ts
1320
- var DEFAULT_CONFIG = {
1700
+ var DEFAULT_CONFIG2 = {
1321
1701
  minSolAmount: 1e-3,
1322
1702
  minTokenAmountUsd: 0.01,
1323
1703
  minConfidence: 0.5,
1324
1704
  allowFailed: false
1325
1705
  };
1326
1706
  function isSpamTransaction(tx, classification, config = {}) {
1327
- const cfg = { ...DEFAULT_CONFIG, ...config };
1707
+ const cfg = { ...DEFAULT_CONFIG2, ...config };
1328
1708
  if (!cfg.allowFailed && tx.err) {
1329
1709
  return true;
1330
1710
  }
@@ -1490,10 +1870,11 @@ function createIndexer(options) {
1490
1870
  client.rpc,
1491
1871
  signatureObjects
1492
1872
  );
1873
+ const walletAddressStr2 = walletAddress.toString();
1493
1874
  const classified = transactions.map((tx) => {
1494
1875
  tx.protocol = detectProtocol(tx.programIds);
1495
1876
  const legs = transactionToLegs(tx);
1496
- const classification = classifyTransaction(legs, tx);
1877
+ const classification = classifyTransaction(legs, tx, walletAddressStr2);
1497
1878
  return { tx, classification, legs };
1498
1879
  });
1499
1880
  return enrichBatch(classified);
@@ -1502,6 +1883,7 @@ function createIndexer(options) {
1502
1883
  let currentBefore = before;
1503
1884
  const MAX_ITERATIONS = 10;
1504
1885
  let iteration = 0;
1886
+ const walletAddressStr = walletAddress.toString();
1505
1887
  while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
1506
1888
  iteration++;
1507
1889
  const batchSize = iteration === 1 ? limit : limit * 2;
@@ -1523,7 +1905,7 @@ function createIndexer(options) {
1523
1905
  const classified = transactions.map((tx) => {
1524
1906
  tx.protocol = detectProtocol(tx.programIds);
1525
1907
  const legs = transactionToLegs(tx);
1526
- const classification = classifyTransaction(legs, tx);
1908
+ const classification = classifyTransaction(legs, tx, walletAddressStr);
1527
1909
  return { tx, classification, legs };
1528
1910
  });
1529
1911
  const nonSpam = filterSpamTransactions(classified, spamConfig);