tx-indexer 0.4.0 → 0.5.0

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.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { C as ClassifiedTransaction, F as FetchTransactionsConfig, G as GetTransactionsOptions, I as IndexerRpcApi, S as SolanaClient, l as SpamFilterConfig, e as TokenAccountBalance, T as TxIndexer, a as TxIndexerOptions, W as WalletBalance, c as createIndexer, b as createSolanaClient, h as fetchTransaction, i as fetchTransactionsBatch, f as fetchWalletBalance, g as fetchWalletSignatures, k as filterSpamTransactions, j as isSpamTransaction, p as parseAddress, d as parseSignature, t as transactionToLegs } from './client-yGDWPKKf.js';
2
- import { T as TxLeg, R as RawTransaction, a as TransactionClassification } from './classification.types-DlJe6bDZ.js';
3
- export { f as RawTransactionSchema, k as TokenBalance, e as TokenBalanceSchema, n as TransactionClassificationSchema, j as TxCategory, d as TxCategorySchema, h as TxDirection, b as TxDirectionSchema, m as TxLegRole, g as TxLegSchema, l as TxLegSide, i as TxPrimaryType, c as TxPrimaryTypeSchema } from './classification.types-DlJe6bDZ.js';
1
+ export { C as ClassifiedTransaction, F as FetchTransactionsConfig, b as GetTransactionOptions, G as GetTransactionsOptions, I as IndexerRpcApi, N as NftMetadata, S as SolanaClient, m as SpamFilterConfig, g as TokenAccountBalance, T as TxIndexer, a as TxIndexerOptions, W as WalletBalance, c as createIndexer, d as createSolanaClient, n as fetchNftMetadata, o as fetchNftMetadataBatch, i as fetchTransaction, j as fetchTransactionsBatch, f as fetchWalletBalance, h as fetchWalletSignatures, l as filterSpamTransactions, k as isSpamTransaction, p as parseAddress, e as parseSignature, t as transactionToLegs } from './client-iLW2_DnL.js';
2
+ import { T as TxLeg, R as RawTransaction, a as TransactionClassification } from './classification.types-Cn9IGtEC.js';
3
+ export { f as RawTransactionSchema, k as TokenBalance, e as TokenBalanceSchema, n as TransactionClassificationSchema, j as TxCategory, d as TxCategorySchema, h as TxDirection, b as TxDirectionSchema, m as TxLegRole, g as TxLegSchema, l as TxLegSide, i as TxPrimaryType, c as TxPrimaryTypeSchema } from './classification.types-Cn9IGtEC.js';
4
4
  import { ProtocolInfo, TokenInfo } from './types.js';
5
5
  export { Categorization, CategorizationSchema, Counterparty, CounterpartySchema, FiatValue, MoneyAmount, ProtocolInfoSchema } from './types.js';
6
6
  import { z } from 'zod';
package/dist/index.js CHANGED
@@ -428,6 +428,7 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
428
428
  signature: signature2,
429
429
  slot: response.slot,
430
430
  blockTime: response.blockTime,
431
+ fee: Number(response.meta?.fee ?? 0),
431
432
  err: response.meta?.err ?? null,
432
433
  programIds: extractProgramIds(response.transaction),
433
434
  protocol: null,
@@ -747,7 +748,7 @@ var TransferClassifier = class {
747
748
  sender,
748
749
  receiver,
749
750
  counterparty: {
750
- type: "wallet",
751
+ type: "unknown",
751
752
  address: receiver,
752
753
  name: `${receiver.slice(0, 8)}...`
753
754
  },
@@ -918,19 +919,19 @@ var SwapClassifier = class {
918
919
  priority = 80;
919
920
  classify(context) {
920
921
  const { legs, tx } = context;
921
- const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
922
- if (!hasDexProtocol) {
923
- return null;
924
- }
925
922
  const feeLeg = legs.find(
926
923
  (leg) => leg.role === "fee" && leg.side === "debit"
927
924
  );
928
925
  const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
926
+ if (!initiator) {
927
+ return null;
928
+ }
929
+ const initiatorAccountId = `external:${initiator}`;
929
930
  const tokensOut = legs.filter(
930
- (leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
931
+ (leg) => leg.accountId === initiatorAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
931
932
  );
932
933
  const tokensIn = legs.filter(
933
- (leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
934
+ (leg) => leg.accountId === initiatorAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
934
935
  );
935
936
  if (tokensOut.length === 0 || tokensIn.length === 0) {
936
937
  return null;
@@ -940,6 +941,8 @@ var SwapClassifier = class {
940
941
  if (tokenOut.amount.token.symbol === tokenIn.amount.token.symbol) {
941
942
  return null;
942
943
  }
944
+ const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
945
+ const confidence = hasDexProtocol ? 0.95 : 0.75;
943
946
  return {
944
947
  primaryType: "swap",
945
948
  primaryAmount: tokenOut.amount,
@@ -947,7 +950,7 @@ var SwapClassifier = class {
947
950
  sender: initiator,
948
951
  receiver: initiator,
949
952
  counterparty: null,
950
- confidence: 0.9,
953
+ confidence,
951
954
  isRelevant: true,
952
955
  metadata: {
953
956
  swap_type: "token_to_token",
@@ -1079,7 +1082,7 @@ var SolanaPayClassifier = class {
1079
1082
  counterparty: receiver ? {
1080
1083
  address: receiver,
1081
1084
  name: memo.merchant ?? void 0,
1082
- type: memo.merchant ? "merchant" : "wallet"
1085
+ type: memo.merchant ? "merchant" : "unknown"
1083
1086
  } : null,
1084
1087
  confidence: 0.98,
1085
1088
  isRelevant: true,
@@ -1356,8 +1359,81 @@ function filterSpamTransactions(transactions, config) {
1356
1359
  );
1357
1360
  }
1358
1361
 
1362
+ // src/nft.ts
1363
+ async function fetchNftMetadata(rpcUrl, mintAddress) {
1364
+ const response = await fetch(rpcUrl, {
1365
+ method: "POST",
1366
+ headers: { "Content-Type": "application/json" },
1367
+ body: JSON.stringify({
1368
+ jsonrpc: "2.0",
1369
+ id: "get-asset",
1370
+ method: "getAsset",
1371
+ params: { id: mintAddress }
1372
+ })
1373
+ });
1374
+ const data = await response.json();
1375
+ if (data.error || !data.result?.content?.metadata) {
1376
+ return null;
1377
+ }
1378
+ const { result } = data;
1379
+ const { content, grouping } = result;
1380
+ return {
1381
+ mint: mintAddress,
1382
+ name: content.metadata.name,
1383
+ symbol: content.metadata.symbol,
1384
+ image: content.links.image ?? content.files?.[0]?.uri ?? "",
1385
+ cdnImage: content.files?.[0]?.cdn_uri,
1386
+ description: content.metadata.description,
1387
+ collection: grouping?.find((g) => g.group_key === "collection")?.group_value,
1388
+ attributes: content.metadata.attributes
1389
+ };
1390
+ }
1391
+ async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
1392
+ const results = await Promise.all(
1393
+ mintAddresses.map((mint) => fetchNftMetadata(rpcUrl, mint))
1394
+ );
1395
+ const map = /* @__PURE__ */ new Map();
1396
+ results.forEach((metadata, index) => {
1397
+ if (metadata) {
1398
+ map.set(mintAddresses[index], metadata);
1399
+ }
1400
+ });
1401
+ return map;
1402
+ }
1403
+
1359
1404
  // src/client.ts
1405
+ var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
1406
+ async function enrichNftClassification(rpcUrl, classified) {
1407
+ const { classification } = classified;
1408
+ if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
1409
+ return classified;
1410
+ }
1411
+ const nftMint = classification.metadata?.nft_mint;
1412
+ if (!nftMint) {
1413
+ return classified;
1414
+ }
1415
+ const nftData = await fetchNftMetadata(rpcUrl, nftMint);
1416
+ if (!nftData) {
1417
+ return classified;
1418
+ }
1419
+ return {
1420
+ ...classified,
1421
+ classification: {
1422
+ ...classification,
1423
+ metadata: {
1424
+ ...classification.metadata,
1425
+ nft_name: nftData.name,
1426
+ nft_image: nftData.image,
1427
+ nft_cdn_image: nftData.cdnImage,
1428
+ nft_collection: nftData.collection,
1429
+ nft_symbol: nftData.symbol,
1430
+ nft_attributes: nftData.attributes
1431
+ }
1432
+ }
1433
+ };
1434
+ }
1360
1435
  function createIndexer(options) {
1436
+ const rpcUrl = "client" in options ? "" : options.rpcUrl;
1361
1437
  const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
1362
1438
  return {
1363
1439
  rpc: client.rpc,
@@ -1365,7 +1441,39 @@ function createIndexer(options) {
1365
1441
  return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
1366
1442
  },
1367
1443
  async getTransactions(walletAddress, options2 = {}) {
1368
- const { limit = 10, before, until, filterSpam = true, spamConfig } = options2;
1444
+ const { limit = 10, before, until, filterSpam = true, spamConfig, enrichNftMetadata = true } = options2;
1445
+ async function enrichBatch(transactions) {
1446
+ if (!enrichNftMetadata || !rpcUrl) {
1447
+ return transactions;
1448
+ }
1449
+ const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
1450
+ if (nftMints.length === 0) {
1451
+ return transactions;
1452
+ }
1453
+ const nftMetadataMap = await fetchNftMetadataBatch(rpcUrl, nftMints);
1454
+ return transactions.map((t) => {
1455
+ const nftMint = t.classification.metadata?.nft_mint;
1456
+ if (!nftMint || !nftMetadataMap.has(nftMint)) {
1457
+ return t;
1458
+ }
1459
+ const nftData = nftMetadataMap.get(nftMint);
1460
+ return {
1461
+ ...t,
1462
+ classification: {
1463
+ ...t.classification,
1464
+ metadata: {
1465
+ ...t.classification.metadata,
1466
+ nft_name: nftData.name,
1467
+ nft_image: nftData.image,
1468
+ nft_cdn_image: nftData.cdnImage,
1469
+ nft_collection: nftData.collection,
1470
+ nft_symbol: nftData.symbol,
1471
+ nft_attributes: nftData.attributes
1472
+ }
1473
+ }
1474
+ };
1475
+ });
1476
+ }
1369
1477
  if (!filterSpam) {
1370
1478
  const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1371
1479
  limit,
@@ -1388,7 +1496,7 @@ function createIndexer(options) {
1388
1496
  const classification = classifyTransaction(legs, tx);
1389
1497
  return { tx, classification, legs };
1390
1498
  });
1391
- return classified;
1499
+ return enrichBatch(classified);
1392
1500
  }
1393
1501
  const accumulated = [];
1394
1502
  let currentBefore = before;
@@ -1427,9 +1535,11 @@ function createIndexer(options) {
1427
1535
  break;
1428
1536
  }
1429
1537
  }
1430
- return accumulated.slice(0, limit);
1538
+ const result = accumulated.slice(0, limit);
1539
+ return enrichBatch(result);
1431
1540
  },
1432
- async getTransaction(signature2) {
1541
+ async getTransaction(signature2, options2 = {}) {
1542
+ const { enrichNftMetadata = true } = options2;
1433
1543
  const tx = await fetchTransaction(client.rpc, signature2);
1434
1544
  if (!tx) {
1435
1545
  return null;
@@ -1437,10 +1547,26 @@ function createIndexer(options) {
1437
1547
  tx.protocol = detectProtocol(tx.programIds);
1438
1548
  const legs = transactionToLegs(tx);
1439
1549
  const classification = classifyTransaction(legs, tx);
1440
- return { tx, classification, legs };
1550
+ let classified = { tx, classification, legs };
1551
+ if (enrichNftMetadata && rpcUrl) {
1552
+ classified = await enrichNftClassification(rpcUrl, classified);
1553
+ }
1554
+ return classified;
1441
1555
  },
1442
1556
  async getRawTransaction(signature2) {
1443
1557
  return fetchTransaction(client.rpc, signature2);
1558
+ },
1559
+ async getNftMetadata(mintAddress) {
1560
+ if (!rpcUrl) {
1561
+ throw new Error("getNftMetadata requires rpcUrl to be set");
1562
+ }
1563
+ return fetchNftMetadata(rpcUrl, mintAddress);
1564
+ },
1565
+ async getNftMetadataBatch(mintAddresses) {
1566
+ if (!rpcUrl) {
1567
+ throw new Error("getNftMetadataBatch requires rpcUrl to be set");
1568
+ }
1569
+ return fetchNftMetadataBatch(rpcUrl, mintAddresses);
1444
1570
  }
1445
1571
  };
1446
1572
  }
@@ -1492,6 +1618,6 @@ function groupLegsByToken(legs) {
1492
1618
  return grouped;
1493
1619
  }
1494
1620
 
1495
- export { JUPITER_V4_PROGRAM_ID, JUPITER_V6_PROGRAM_ID, KNOWN_TOKENS, SPL_MEMO_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_INFO, TOKEN_PROGRAM_ID, buildAccountId, classifyTransaction, createIndexer, createSolanaClient, detectFacilitator, detectProtocol, extractMemo, fetchTransaction, fetchTransactionsBatch, fetchWalletBalance, fetchWalletSignatures, filterSpamTransactions, getTokenInfo, groupLegsByAccount, groupLegsByToken, isSolanaPayTransaction, isSpamTransaction, parseAccountId, parseAddress, parseSignature, parseSolanaPayMemo, transactionToLegs, validateLegsBalance };
1621
+ export { JUPITER_V4_PROGRAM_ID, JUPITER_V6_PROGRAM_ID, KNOWN_TOKENS, SPL_MEMO_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_INFO, TOKEN_PROGRAM_ID, buildAccountId, classifyTransaction, createIndexer, createSolanaClient, detectFacilitator, detectProtocol, extractMemo, fetchNftMetadata, fetchNftMetadataBatch, fetchTransaction, fetchTransactionsBatch, fetchWalletBalance, fetchWalletSignatures, filterSpamTransactions, getTokenInfo, groupLegsByAccount, groupLegsByToken, isSolanaPayTransaction, isSpamTransaction, parseAccountId, parseAddress, parseSignature, parseSolanaPayMemo, transactionToLegs, validateLegsBalance };
1496
1622
  //# sourceMappingURL=index.js.map
1497
1623
  //# sourceMappingURL=index.js.map