tx-indexer 0.4.1 → 0.5.1

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/README.md CHANGED
@@ -37,10 +37,22 @@ console.log(tx.classification.sender); // sender address
37
37
  console.log(tx.classification.receiver); // receiver address
38
38
  ```
39
39
 
40
+ ## RPC Compatibility
41
+
42
+ The SDK works with any Solana RPC for core features (transactions, balances, classification).
43
+
44
+ NFT metadata enrichment requires a DAS-compatible RPC (Helius, Triton, etc.). If using a standard RPC, disable it:
45
+
46
+ ```typescript
47
+ const txs = await indexer.getTransactions(address, {
48
+ enrichNftMetadata: false
49
+ });
50
+ ```
51
+
40
52
  ## Transaction Types
41
53
 
42
54
  - `transfer` - Wallet-to-wallet transfers
43
- - `swap` - Token exchanges (Jupiter, Raydium, Orca)
55
+ - `swap` - Token exchanges (pattern-based detection works with any DEX, higher confidence for known protocols like Jupiter, Raydium, Orca)
44
56
  - `nft_mint` - NFT minting (Metaplex, Candy Machine, Bubblegum)
45
57
  - `stake_deposit` - SOL staking deposits
46
58
  - `stake_withdraw` - SOL staking withdrawals
package/dist/client.js CHANGED
@@ -706,7 +706,7 @@ var TransferClassifier = class {
706
706
  sender,
707
707
  receiver,
708
708
  counterparty: {
709
- type: "wallet",
709
+ type: "unknown",
710
710
  address: receiver,
711
711
  name: `${receiver.slice(0, 8)}...`
712
712
  },
@@ -876,37 +876,56 @@ var SwapClassifier = class {
876
876
  name = "swap";
877
877
  priority = 80;
878
878
  classify(context) {
879
- const { legs, tx } = context;
880
- const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
881
- if (!hasDexProtocol) {
882
- return null;
883
- }
879
+ const { legs, tx, walletAddress } = context;
884
880
  const feeLeg = legs.find(
885
881
  (leg) => leg.role === "fee" && leg.side === "debit"
886
882
  );
887
883
  const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
888
- const tokensOut = legs.filter(
889
- (leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
884
+ if (!initiator) {
885
+ return null;
886
+ }
887
+ const initiatorAccountId = `external:${initiator}`;
888
+ const initiatorTokensOut = legs.filter(
889
+ (leg) => leg.accountId === initiatorAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
890
890
  );
891
- const tokensIn = legs.filter(
892
- (leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
891
+ const initiatorTokensIn = legs.filter(
892
+ (leg) => leg.accountId === initiatorAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
893
893
  );
894
- if (tokensOut.length === 0 || tokensIn.length === 0) {
894
+ if (initiatorTokensOut.length === 0 || initiatorTokensIn.length === 0) {
895
895
  return null;
896
896
  }
897
- const tokenOut = tokensOut[0];
898
- const tokenIn = tokensIn[0];
899
- if (tokenOut.amount.token.symbol === tokenIn.amount.token.symbol) {
897
+ const initiatorOut = initiatorTokensOut[0];
898
+ const initiatorIn = initiatorTokensIn[0];
899
+ if (initiatorOut.amount.token.symbol === initiatorIn.amount.token.symbol) {
900
900
  return null;
901
901
  }
902
+ let tokenOut = initiatorOut;
903
+ let tokenIn = initiatorIn;
904
+ let perspectiveWallet = initiator;
905
+ if (walletAddress) {
906
+ const walletAccountId = `external:${walletAddress}`;
907
+ const walletOut = legs.find(
908
+ (leg) => leg.accountId === walletAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
909
+ );
910
+ const walletIn = legs.find(
911
+ (leg) => leg.accountId === walletAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
912
+ );
913
+ if (walletOut && walletIn && walletOut.amount.token.symbol !== walletIn.amount.token.symbol) {
914
+ tokenOut = walletOut;
915
+ tokenIn = walletIn;
916
+ perspectiveWallet = walletAddress;
917
+ }
918
+ }
919
+ const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
920
+ const confidence = hasDexProtocol ? 0.95 : 0.75;
902
921
  return {
903
922
  primaryType: "swap",
904
923
  primaryAmount: tokenOut.amount,
905
924
  secondaryAmount: tokenIn.amount,
906
- sender: initiator,
907
- receiver: initiator,
925
+ sender: perspectiveWallet,
926
+ receiver: perspectiveWallet,
908
927
  counterparty: null,
909
- confidence: 0.9,
928
+ confidence,
910
929
  isRelevant: true,
911
930
  metadata: {
912
931
  swap_type: "token_to_token",
@@ -1038,7 +1057,7 @@ var SolanaPayClassifier = class {
1038
1057
  counterparty: receiver ? {
1039
1058
  address: receiver,
1040
1059
  name: memo.merchant ?? void 0,
1041
- type: memo.merchant ? "merchant" : "wallet"
1060
+ type: memo.merchant ? "merchant" : "unknown"
1042
1061
  } : null,
1043
1062
  confidence: 0.98,
1044
1063
  isRelevant: true,
@@ -1246,8 +1265,8 @@ var ClassificationService = class {
1246
1265
  this.classifiers.push(classifier);
1247
1266
  this.classifiers.sort((a, b) => b.priority - a.priority);
1248
1267
  }
1249
- classify(legs, tx) {
1250
- const context = { legs, tx };
1268
+ classify(legs, tx, walletAddress) {
1269
+ const context = { legs, tx, walletAddress };
1251
1270
  for (const classifier of this.classifiers) {
1252
1271
  const result = classifier.classify(context);
1253
1272
  if (result && result.isRelevant) {
@@ -1268,8 +1287,8 @@ var ClassificationService = class {
1268
1287
  }
1269
1288
  };
1270
1289
  var classificationService = new ClassificationService();
1271
- function classifyTransaction(legs, tx) {
1272
- return classificationService.classify(legs, tx);
1290
+ function classifyTransaction(legs, tx, walletAddress) {
1291
+ return classificationService.classify(legs, tx, walletAddress);
1273
1292
  }
1274
1293
 
1275
1294
  // ../domain/src/tx/spam-filter.ts
@@ -1446,10 +1465,11 @@ function createIndexer(options) {
1446
1465
  client.rpc,
1447
1466
  signatureObjects
1448
1467
  );
1468
+ const walletAddressStr2 = walletAddress.toString();
1449
1469
  const classified = transactions.map((tx) => {
1450
1470
  tx.protocol = detectProtocol(tx.programIds);
1451
1471
  const legs = transactionToLegs(tx);
1452
- const classification = classifyTransaction(legs, tx);
1472
+ const classification = classifyTransaction(legs, tx, walletAddressStr2);
1453
1473
  return { tx, classification, legs };
1454
1474
  });
1455
1475
  return enrichBatch(classified);
@@ -1458,6 +1478,7 @@ function createIndexer(options) {
1458
1478
  let currentBefore = before;
1459
1479
  const MAX_ITERATIONS = 10;
1460
1480
  let iteration = 0;
1481
+ const walletAddressStr = walletAddress.toString();
1461
1482
  while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
1462
1483
  iteration++;
1463
1484
  const batchSize = iteration === 1 ? limit : limit * 2;
@@ -1479,7 +1500,7 @@ function createIndexer(options) {
1479
1500
  const classified = transactions.map((tx) => {
1480
1501
  tx.protocol = detectProtocol(tx.programIds);
1481
1502
  const legs = transactionToLegs(tx);
1482
- const classification = classifyTransaction(legs, tx);
1503
+ const classification = classifyTransaction(legs, tx, walletAddressStr);
1483
1504
  return { tx, classification, legs };
1484
1505
  });
1485
1506
  const nonSpam = filterSpamTransactions(classified, spamConfig);