tx-indexer 0.5.1 → 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,15 +1222,28 @@ 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
1245
  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;
1246
+ const initiator = tx.accountKeys?.[0] ?? null;
884
1247
  if (!initiator) {
885
1248
  return null;
886
1249
  }
@@ -894,11 +1257,11 @@ var SwapClassifier = class {
894
1257
  if (initiatorTokensOut.length === 0 || initiatorTokensIn.length === 0) {
895
1258
  return null;
896
1259
  }
897
- const initiatorOut = initiatorTokensOut[0];
898
- const initiatorIn = initiatorTokensIn[0];
899
- if (initiatorOut.amount.token.symbol === initiatorIn.amount.token.symbol) {
1260
+ const swapPair = findSwapPair(initiatorTokensOut, initiatorTokensIn);
1261
+ if (!swapPair) {
900
1262
  return null;
901
1263
  }
1264
+ const { initiatorOut, initiatorIn } = swapPair;
902
1265
  let tokenOut = initiatorOut;
903
1266
  let tokenIn = initiatorIn;
904
1267
  let perspectiveWallet = initiator;
@@ -1292,14 +1655,14 @@ function classifyTransaction(legs, tx, walletAddress) {
1292
1655
  }
1293
1656
 
1294
1657
  // ../domain/src/tx/spam-filter.ts
1295
- var DEFAULT_CONFIG = {
1658
+ var DEFAULT_CONFIG2 = {
1296
1659
  minSolAmount: 1e-3,
1297
1660
  minTokenAmountUsd: 0.01,
1298
1661
  minConfidence: 0.5,
1299
1662
  allowFailed: false
1300
1663
  };
1301
1664
  function isSpamTransaction(tx, classification, config = {}) {
1302
- const cfg = { ...DEFAULT_CONFIG, ...config };
1665
+ const cfg = { ...DEFAULT_CONFIG2, ...config };
1303
1666
  if (!cfg.allowFailed && tx.err) {
1304
1667
  return true;
1305
1668
  }