tx-indexer 0.5.3 → 0.5.5

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.
@@ -110,27 +110,32 @@ interface SpamFilterConfig {
110
110
  * - Has low classification confidence
111
111
  * - Is not relevant to the wallet
112
112
  * - Involves dust amounts below configured thresholds
113
+ * - The wallet only received dust amounts (spam airdrops)
113
114
  *
114
115
  * @param tx - Raw transaction data
115
116
  * @param classification - Transaction classification result
116
117
  * @param config - Optional spam filter configuration (uses defaults if omitted)
118
+ * @param legs - Optional legs to check wallet involvement
119
+ * @param walletAddress - Optional wallet address to check involvement
117
120
  * @returns True if the transaction should be filtered as spam
118
121
  */
119
- declare function isSpamTransaction(tx: RawTransaction, classification: TransactionClassification, config?: SpamFilterConfig): boolean;
122
+ declare function isSpamTransaction(tx: RawTransaction, classification: TransactionClassification, config?: SpamFilterConfig, legs?: TxLeg[], walletAddress?: string): boolean;
120
123
  /**
121
124
  * Filters an array of transactions to remove spam and dust transactions.
122
125
  *
123
126
  * Applies spam detection criteria to each transaction while preserving
124
127
  * additional properties in the returned array items.
125
128
  *
126
- * @param transactions - Array of transaction objects with tx and classification
129
+ * @param transactions - Array of transaction objects with tx, classification, and legs
127
130
  * @param config - Optional spam filter configuration
131
+ * @param walletAddress - Optional wallet address to check for dust airdrops
128
132
  * @returns Filtered array with spam transactions removed
129
133
  */
130
134
  declare function filterSpamTransactions<T extends {
131
135
  tx: RawTransaction;
132
136
  classification: TransactionClassification;
133
- }>(transactions: T[], config?: SpamFilterConfig): T[];
137
+ legs?: TxLeg[];
138
+ }>(transactions: T[], config?: SpamFilterConfig, walletAddress?: string): T[];
134
139
 
135
140
  interface WalletBalance {
136
141
  address: string;
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import '@solana/kit';
2
- export { C as ClassifiedTransaction, F as FetchTransactionsConfig, b as GetTransactionOptions, G as GetTransactionsOptions, T as TxIndexer, a as TxIndexerOptions, c as createIndexer } from './client-xmDVjOy4.js';
2
+ export { C as ClassifiedTransaction, F as FetchTransactionsConfig, b as GetTransactionOptions, G as GetTransactionsOptions, T as TxIndexer, a as TxIndexerOptions, c as createIndexer } from './client-D8iyrH5w.js';
3
3
  import './classification.types-h046WjuF.js';
4
4
  import 'zod';
package/dist/client.js CHANGED
@@ -1101,21 +1101,38 @@ function determineTokenRole(change, tx, feePayer) {
1101
1101
  }
1102
1102
 
1103
1103
  // ../classification/src/classifiers/transfer-classifier.ts
1104
+ function findBestTransferPair(legs) {
1105
+ const senderLegs = legs.filter(
1106
+ (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
1107
+ );
1108
+ if (senderLegs.length === 0) return null;
1109
+ let bestPair = null;
1110
+ let bestAmount = 0;
1111
+ for (const senderLeg of senderLegs) {
1112
+ const receiverLeg = legs.find(
1113
+ (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
1114
+ // Must be different account
1115
+ );
1116
+ if (receiverLeg) {
1117
+ const amount = senderLeg.amount.amountUi;
1118
+ if (amount > bestAmount) {
1119
+ bestAmount = amount;
1120
+ bestPair = { senderLeg, receiverLeg };
1121
+ }
1122
+ }
1123
+ }
1124
+ return bestPair;
1125
+ }
1104
1126
  var TransferClassifier = class {
1105
1127
  name = "transfer";
1106
1128
  priority = 20;
1107
1129
  classify(context) {
1108
1130
  const { legs, tx } = context;
1109
1131
  const facilitator = tx.accountKeys ? detectFacilitator(tx.accountKeys) : null;
1110
- const senderLeg = legs.find(
1111
- (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
1112
- );
1113
- if (!senderLeg) return null;
1132
+ const pair = findBestTransferPair(legs);
1133
+ if (!pair) return null;
1134
+ const { senderLeg, receiverLeg } = pair;
1114
1135
  const sender = senderLeg.accountId.replace("external:", "");
1115
- const receiverLeg = legs.find(
1116
- (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint
1117
- );
1118
- if (!receiverLeg) return null;
1119
1136
  const receiver = receiverLeg.accountId.replace("external:", "");
1120
1137
  return {
1121
1138
  primaryType: "transfer",
@@ -1594,6 +1611,27 @@ var FeeOnlyClassifier = class {
1594
1611
  };
1595
1612
 
1596
1613
  // ../classification/src/classifiers/solana-pay-classifier.ts
1614
+ function findBestTransferPair2(legs) {
1615
+ const senderLegs = legs.filter(
1616
+ (l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
1617
+ );
1618
+ if (senderLegs.length === 0) return null;
1619
+ let bestPair = null;
1620
+ let bestAmount = 0;
1621
+ for (const senderLeg of senderLegs) {
1622
+ const receiverLeg = legs.find(
1623
+ (l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
1624
+ );
1625
+ if (receiverLeg) {
1626
+ const amount = senderLeg.amount.amountUi;
1627
+ if (amount > bestAmount) {
1628
+ bestAmount = amount;
1629
+ bestPair = { senderLeg, receiverLeg };
1630
+ }
1631
+ }
1632
+ }
1633
+ return bestPair;
1634
+ }
1597
1635
  var SolanaPayClassifier = class {
1598
1636
  name = "solana-pay";
1599
1637
  priority = 95;
@@ -1603,12 +1641,9 @@ var SolanaPayClassifier = class {
1603
1641
  return null;
1604
1642
  }
1605
1643
  const memo = parseSolanaPayMemo(tx.memo);
1606
- const senderLeg = legs.find(
1607
- (leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.role === "sent"
1608
- );
1609
- const receiverLeg = legs.find(
1610
- (leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.role === "received"
1611
- );
1644
+ const pair = findBestTransferPair2(legs);
1645
+ const senderLeg = pair?.senderLeg ?? null;
1646
+ const receiverLeg = pair?.receiverLeg ?? null;
1612
1647
  const sender = senderLeg?.accountId.replace("external:", "") ?? null;
1613
1648
  const receiver = receiverLeg?.accountId.replace("external:", "") ?? null;
1614
1649
  const primaryAmount = senderLeg?.amount ?? receiverLeg?.amount ?? null;
@@ -1862,7 +1897,7 @@ var DEFAULT_CONFIG2 = {
1862
1897
  minConfidence: 0.5,
1863
1898
  allowFailed: false
1864
1899
  };
1865
- function isSpamTransaction(tx, classification, config = {}) {
1900
+ function isSpamTransaction(tx, classification, config = {}, legs, walletAddress) {
1866
1901
  const cfg = { ...DEFAULT_CONFIG2, ...config };
1867
1902
  if (!cfg.allowFailed && tx.err) {
1868
1903
  return true;
@@ -1876,6 +1911,11 @@ function isSpamTransaction(tx, classification, config = {}) {
1876
1911
  if (isDustTransaction(classification, cfg)) {
1877
1912
  return true;
1878
1913
  }
1914
+ if (legs && walletAddress) {
1915
+ if (isWalletDustOnly(legs, walletAddress, cfg)) {
1916
+ return true;
1917
+ }
1918
+ }
1879
1919
  return false;
1880
1920
  }
1881
1921
  function isDustTransaction(classification, config) {
@@ -1892,9 +1932,40 @@ function isDustTransaction(classification, config) {
1892
1932
  }
1893
1933
  return Math.abs(amountUi) < config.minTokenAmountUsd;
1894
1934
  }
1895
- function filterSpamTransactions(transactions, config) {
1935
+ function isWalletDustOnly(legs, walletAddress, config) {
1936
+ const walletAccountId = `external:${walletAddress}`;
1937
+ const walletLegs = legs.filter((leg) => leg.accountId === walletAccountId);
1938
+ if (walletLegs.length === 0) {
1939
+ return false;
1940
+ }
1941
+ const sentLegs = walletLegs.filter(
1942
+ (leg) => leg.side === "debit" && leg.role === "sent"
1943
+ );
1944
+ for (const leg of sentLegs) {
1945
+ if (!isDustAmount(leg.amount.token.symbol, leg.amount.amountUi, config)) {
1946
+ return false;
1947
+ }
1948
+ }
1949
+ const receivedLegs = walletLegs.filter(
1950
+ (leg) => leg.side === "credit" && leg.role === "received"
1951
+ );
1952
+ for (const leg of receivedLegs) {
1953
+ if (!isDustAmount(leg.amount.token.symbol, leg.amount.amountUi, config)) {
1954
+ return false;
1955
+ }
1956
+ }
1957
+ return receivedLegs.length > 0;
1958
+ }
1959
+ function isDustAmount(symbol, amountUi, config) {
1960
+ const absAmount = Math.abs(amountUi);
1961
+ if (symbol === "SOL" || symbol === "WSOL") {
1962
+ return absAmount < config.minSolAmount;
1963
+ }
1964
+ return absAmount < config.minTokenAmountUsd;
1965
+ }
1966
+ function filterSpamTransactions(transactions, config, walletAddress) {
1896
1967
  return transactions.filter(
1897
- ({ tx, classification }) => !isSpamTransaction(tx, classification, config)
1968
+ ({ tx, classification, legs }) => !isSpamTransaction(tx, classification, config, legs, walletAddress)
1898
1969
  );
1899
1970
  }
1900
1971
 
@@ -2310,7 +2381,11 @@ function createIndexer(options) {
2310
2381
  );
2311
2382
  return { tx, classification, legs };
2312
2383
  });
2313
- const nonSpam = filterSpamTransactions(classified, spamConfig);
2384
+ const nonSpam = filterSpamTransactions(
2385
+ classified,
2386
+ spamConfig,
2387
+ walletAddressStr
2388
+ );
2314
2389
  accumulated.push(...nonSpam);
2315
2390
  const lastSignature = signatures[signatures.length - 1];
2316
2391
  if (lastSignature) {