tx-indexer 0.4.0 → 0.4.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
@@ -28,8 +28,42 @@ const txs = await indexer.getTransactions("YourWalletAddress...", {
28
28
  filterSpam: true
29
29
  });
30
30
 
31
- // Get single transaction
32
- const tx = await indexer.getTransaction("signature...", "walletAddress...");
31
+ // Get single transaction (no wallet required)
32
+ const tx = await indexer.getTransaction("signature...");
33
+
34
+ // Classification includes sender/receiver
35
+ console.log(tx.classification.primaryType); // "transfer", "swap", "nft_mint", etc.
36
+ console.log(tx.classification.sender); // sender address
37
+ console.log(tx.classification.receiver); // receiver address
38
+ ```
39
+
40
+ ## Transaction Types
41
+
42
+ - `transfer` - Wallet-to-wallet transfers
43
+ - `swap` - Token exchanges (Jupiter, Raydium, Orca)
44
+ - `nft_mint` - NFT minting (Metaplex, Candy Machine, Bubblegum)
45
+ - `stake_deposit` - SOL staking deposits
46
+ - `stake_withdraw` - SOL staking withdrawals
47
+ - `bridge_in` - Receiving from bridge (Wormhole, deBridge, Allbridge)
48
+ - `bridge_out` - Sending to bridge
49
+ - `airdrop` - Token distributions
50
+ - `fee_only` - Transactions with only network fees
51
+
52
+ ## Frontend Integration
53
+
54
+ Classification is wallet-agnostic. Determine perspective in your frontend:
55
+
56
+ ```typescript
57
+ const { classification } = await indexer.getTransaction(signature);
58
+ const connectedWallet = wallet?.address;
59
+
60
+ if (connectedWallet === classification.sender) {
61
+ // "You sent..."
62
+ } else if (connectedWallet === classification.receiver) {
63
+ // "You received..."
64
+ } else {
65
+ // "Address X sent to Address Y"
66
+ }
33
67
  ```
34
68
 
35
69
  ## Bundle Size
@@ -78,4 +112,3 @@ bun run size:why
78
112
  ## License
79
113
 
80
114
  MIT
81
-
@@ -8,11 +8,11 @@ declare const TxDirectionSchema: z.ZodEnum<{
8
8
  neutral: "neutral";
9
9
  }>;
10
10
  declare const TxPrimaryTypeSchema: z.ZodEnum<{
11
- transfer: "transfer";
12
- swap: "swap";
11
+ nft_mint: "nft_mint";
13
12
  nft_purchase: "nft_purchase";
14
13
  nft_sale: "nft_sale";
15
- nft_mint: "nft_mint";
14
+ transfer: "transfer";
15
+ swap: "swap";
16
16
  stake_deposit: "stake_deposit";
17
17
  stake_withdraw: "stake_withdraw";
18
18
  token_deposit: "token_deposit";
@@ -50,6 +50,7 @@ declare const RawTransactionSchema: z.ZodObject<{
50
50
  signature: z.ZodCustom<Signature, Signature>;
51
51
  slot: z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>;
52
52
  blockTime: z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodBigInt]>>;
53
+ fee: z.ZodOptional<z.ZodNumber>;
53
54
  err: z.ZodNullable<z.ZodAny>;
54
55
  programIds: z.ZodArray<z.ZodString>;
55
56
  protocol: z.ZodNullable<z.ZodObject<{
@@ -91,11 +92,11 @@ declare const TxLegSideSchema: z.ZodEnum<{
91
92
  credit: "credit";
92
93
  }>;
93
94
  declare const TxLegRoleSchema: z.ZodEnum<{
94
- unknown: "unknown";
95
95
  reward: "reward";
96
+ unknown: "unknown";
97
+ fee: "fee";
96
98
  sent: "sent";
97
99
  received: "received";
98
- fee: "fee";
99
100
  protocol_deposit: "protocol_deposit";
100
101
  protocol_withdraw: "protocol_withdraw";
101
102
  principal: "principal";
@@ -143,11 +144,11 @@ declare const TxLegSchema: z.ZodObject<{
143
144
  } | undefined;
144
145
  }>;
145
146
  role: z.ZodEnum<{
146
- unknown: "unknown";
147
147
  reward: "reward";
148
+ unknown: "unknown";
149
+ fee: "fee";
148
150
  sent: "sent";
149
151
  received: "received";
150
- fee: "fee";
151
152
  protocol_deposit: "protocol_deposit";
152
153
  protocol_withdraw: "protocol_withdraw";
153
154
  principal: "principal";
@@ -165,11 +166,11 @@ type TxLeg = z.infer<typeof TxLegSchema>;
165
166
 
166
167
  declare const TransactionClassificationSchema: z.ZodObject<{
167
168
  primaryType: z.ZodEnum<{
168
- transfer: "transfer";
169
- swap: "swap";
169
+ nft_mint: "nft_mint";
170
170
  nft_purchase: "nft_purchase";
171
171
  nft_sale: "nft_sale";
172
- nft_mint: "nft_mint";
172
+ transfer: "transfer";
173
+ swap: "swap";
173
174
  stake_deposit: "stake_deposit";
174
175
  stake_withdraw: "stake_withdraw";
175
176
  token_deposit: "token_deposit";
@@ -187,12 +188,12 @@ declare const TransactionClassificationSchema: z.ZodObject<{
187
188
  receiver: z.ZodOptional<z.ZodNullable<z.ZodString>>;
188
189
  counterparty: z.ZodNullable<z.ZodObject<{
189
190
  type: z.ZodEnum<{
190
- unknown: "unknown";
191
- protocol: "protocol";
192
191
  person: "person";
193
192
  merchant: "merchant";
194
193
  exchange: "exchange";
194
+ protocol: "protocol";
195
195
  own_wallet: "own_wallet";
196
+ unknown: "unknown";
196
197
  }>;
197
198
  address: z.ZodString;
198
199
  name: z.ZodOptional<z.ZodString>;
@@ -1,5 +1,5 @@
1
1
  import { Rpc, GetBalanceApi, GetTokenAccountsByOwnerApi, GetSignaturesForAddressApi, GetTransactionApi, RpcSubscriptions, Address, Signature } from '@solana/kit';
2
- import { R as RawTransaction, T as TxLeg, a as TransactionClassification } from './classification.types-DlJe6bDZ.js';
2
+ import { R as RawTransaction, T as TxLeg, a as TransactionClassification } from './classification.types-Cn9IGtEC.js';
3
3
 
4
4
  /**
5
5
  * Union type of all RPC APIs used by the transaction indexer.
@@ -152,6 +152,36 @@ interface TokenAccountBalance {
152
152
  */
153
153
  declare function fetchWalletBalance(rpc: Rpc<GetBalanceApi & GetTokenAccountsByOwnerApi>, walletAddress: Address, tokenMints?: readonly string[]): Promise<WalletBalance>;
154
154
 
155
+ interface NftMetadata {
156
+ mint: string;
157
+ name: string;
158
+ symbol: string;
159
+ image: string;
160
+ cdnImage?: string;
161
+ description?: string;
162
+ collection?: string;
163
+ attributes?: Array<{
164
+ trait_type: string;
165
+ value: string;
166
+ }>;
167
+ }
168
+ /**
169
+ * Fetches NFT metadata from Helius DAS API.
170
+ *
171
+ * @param rpcUrl - Helius RPC endpoint URL
172
+ * @param mintAddress - NFT mint address
173
+ * @returns NFT metadata including name, image, collection, and attributes, or null if not found
174
+ */
175
+ declare function fetchNftMetadata(rpcUrl: string, mintAddress: string): Promise<NftMetadata | null>;
176
+ /**
177
+ * Fetches NFT metadata for multiple mints in parallel.
178
+ *
179
+ * @param rpcUrl - Helius RPC endpoint URL
180
+ * @param mintAddresses - Array of NFT mint addresses
181
+ * @returns Map of mint address to NFT metadata (only includes successful fetches)
182
+ */
183
+ declare function fetchNftMetadataBatch(rpcUrl: string, mintAddresses: string[]): Promise<Map<string, NftMetadata>>;
184
+
155
185
  type TxIndexerOptions = {
156
186
  rpcUrl: string;
157
187
  wsUrl?: string;
@@ -164,6 +194,10 @@ interface GetTransactionsOptions {
164
194
  until?: Signature;
165
195
  filterSpam?: boolean;
166
196
  spamConfig?: SpamFilterConfig;
197
+ enrichNftMetadata?: boolean;
198
+ }
199
+ interface GetTransactionOptions {
200
+ enrichNftMetadata?: boolean;
167
201
  }
168
202
  interface ClassifiedTransaction {
169
203
  tx: RawTransaction;
@@ -174,9 +208,11 @@ interface TxIndexer {
174
208
  rpc: ReturnType<typeof createSolanaClient>["rpc"];
175
209
  getBalance(walletAddress: Address, tokenMints?: readonly string[]): Promise<WalletBalance>;
176
210
  getTransactions(walletAddress: Address, options?: GetTransactionsOptions): Promise<ClassifiedTransaction[]>;
177
- getTransaction(signature: Signature): Promise<ClassifiedTransaction | null>;
211
+ getTransaction(signature: Signature, options?: GetTransactionOptions): Promise<ClassifiedTransaction | null>;
178
212
  getRawTransaction(signature: Signature): Promise<RawTransaction | null>;
213
+ getNftMetadata(mintAddress: string): Promise<NftMetadata | null>;
214
+ getNftMetadataBatch(mintAddresses: string[]): Promise<Map<string, NftMetadata>>;
179
215
  }
180
216
  declare function createIndexer(options: TxIndexerOptions): TxIndexer;
181
217
 
182
- export { type ClassifiedTransaction as C, type FetchTransactionsConfig as F, type GetTransactionsOptions as G, type IndexerRpcApi as I, type SolanaClient as S, type TxIndexer as T, type WalletBalance as W, type TxIndexerOptions as a, createSolanaClient as b, createIndexer as c, parseSignature as d, type TokenAccountBalance as e, fetchWalletBalance as f, fetchWalletSignatures as g, fetchTransaction as h, fetchTransactionsBatch as i, isSpamTransaction as j, filterSpamTransactions as k, type SpamFilterConfig as l, parseAddress as p, transactionToLegs as t };
218
+ export { type ClassifiedTransaction as C, type FetchTransactionsConfig as F, type GetTransactionsOptions as G, type IndexerRpcApi as I, type NftMetadata as N, type SolanaClient as S, type TxIndexer as T, type WalletBalance as W, type TxIndexerOptions as a, type GetTransactionOptions as b, createIndexer as c, createSolanaClient as d, parseSignature as e, fetchWalletBalance as f, type TokenAccountBalance as g, fetchWalletSignatures as h, fetchTransaction as i, fetchTransactionsBatch as j, isSpamTransaction as k, filterSpamTransactions as l, type SpamFilterConfig as m, fetchNftMetadata as n, fetchNftMetadataBatch as o, parseAddress as p, transactionToLegs as t };
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import '@solana/kit';
2
- export { C as ClassifiedTransaction, F as FetchTransactionsConfig, G as GetTransactionsOptions, T as TxIndexer, a as TxIndexerOptions, c as createIndexer } from './client-yGDWPKKf.js';
3
- import './classification.types-DlJe6bDZ.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-iLW2_DnL.js';
3
+ import './classification.types-Cn9IGtEC.js';
4
4
  import 'zod';
package/dist/client.js CHANGED
@@ -416,6 +416,7 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
416
416
  signature: signature2,
417
417
  slot: response.slot,
418
418
  blockTime: response.blockTime,
419
+ fee: Number(response.meta?.fee ?? 0),
419
420
  err: response.meta?.err ?? null,
420
421
  programIds: extractProgramIds(response.transaction),
421
422
  protocol: null,
@@ -1314,8 +1315,81 @@ function filterSpamTransactions(transactions, config) {
1314
1315
  );
1315
1316
  }
1316
1317
 
1318
+ // src/nft.ts
1319
+ async function fetchNftMetadata(rpcUrl, mintAddress) {
1320
+ const response = await fetch(rpcUrl, {
1321
+ method: "POST",
1322
+ headers: { "Content-Type": "application/json" },
1323
+ body: JSON.stringify({
1324
+ jsonrpc: "2.0",
1325
+ id: "get-asset",
1326
+ method: "getAsset",
1327
+ params: { id: mintAddress }
1328
+ })
1329
+ });
1330
+ const data = await response.json();
1331
+ if (data.error || !data.result?.content?.metadata) {
1332
+ return null;
1333
+ }
1334
+ const { result } = data;
1335
+ const { content, grouping } = result;
1336
+ return {
1337
+ mint: mintAddress,
1338
+ name: content.metadata.name,
1339
+ symbol: content.metadata.symbol,
1340
+ image: content.links.image ?? content.files?.[0]?.uri ?? "",
1341
+ cdnImage: content.files?.[0]?.cdn_uri,
1342
+ description: content.metadata.description,
1343
+ collection: grouping?.find((g) => g.group_key === "collection")?.group_value,
1344
+ attributes: content.metadata.attributes
1345
+ };
1346
+ }
1347
+ async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
1348
+ const results = await Promise.all(
1349
+ mintAddresses.map((mint) => fetchNftMetadata(rpcUrl, mint))
1350
+ );
1351
+ const map = /* @__PURE__ */ new Map();
1352
+ results.forEach((metadata, index) => {
1353
+ if (metadata) {
1354
+ map.set(mintAddresses[index], metadata);
1355
+ }
1356
+ });
1357
+ return map;
1358
+ }
1359
+
1317
1360
  // src/client.ts
1361
+ var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
1362
+ async function enrichNftClassification(rpcUrl, classified) {
1363
+ const { classification } = classified;
1364
+ if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
1365
+ return classified;
1366
+ }
1367
+ const nftMint = classification.metadata?.nft_mint;
1368
+ if (!nftMint) {
1369
+ return classified;
1370
+ }
1371
+ const nftData = await fetchNftMetadata(rpcUrl, nftMint);
1372
+ if (!nftData) {
1373
+ return classified;
1374
+ }
1375
+ return {
1376
+ ...classified,
1377
+ classification: {
1378
+ ...classification,
1379
+ metadata: {
1380
+ ...classification.metadata,
1381
+ nft_name: nftData.name,
1382
+ nft_image: nftData.image,
1383
+ nft_cdn_image: nftData.cdnImage,
1384
+ nft_collection: nftData.collection,
1385
+ nft_symbol: nftData.symbol,
1386
+ nft_attributes: nftData.attributes
1387
+ }
1388
+ }
1389
+ };
1390
+ }
1318
1391
  function createIndexer(options) {
1392
+ const rpcUrl = "client" in options ? "" : options.rpcUrl;
1319
1393
  const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
1320
1394
  return {
1321
1395
  rpc: client.rpc,
@@ -1323,7 +1397,39 @@ function createIndexer(options) {
1323
1397
  return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
1324
1398
  },
1325
1399
  async getTransactions(walletAddress, options2 = {}) {
1326
- const { limit = 10, before, until, filterSpam = true, spamConfig } = options2;
1400
+ const { limit = 10, before, until, filterSpam = true, spamConfig, enrichNftMetadata = true } = options2;
1401
+ async function enrichBatch(transactions) {
1402
+ if (!enrichNftMetadata || !rpcUrl) {
1403
+ return transactions;
1404
+ }
1405
+ const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
1406
+ if (nftMints.length === 0) {
1407
+ return transactions;
1408
+ }
1409
+ const nftMetadataMap = await fetchNftMetadataBatch(rpcUrl, nftMints);
1410
+ return transactions.map((t) => {
1411
+ const nftMint = t.classification.metadata?.nft_mint;
1412
+ if (!nftMint || !nftMetadataMap.has(nftMint)) {
1413
+ return t;
1414
+ }
1415
+ const nftData = nftMetadataMap.get(nftMint);
1416
+ return {
1417
+ ...t,
1418
+ classification: {
1419
+ ...t.classification,
1420
+ metadata: {
1421
+ ...t.classification.metadata,
1422
+ nft_name: nftData.name,
1423
+ nft_image: nftData.image,
1424
+ nft_cdn_image: nftData.cdnImage,
1425
+ nft_collection: nftData.collection,
1426
+ nft_symbol: nftData.symbol,
1427
+ nft_attributes: nftData.attributes
1428
+ }
1429
+ }
1430
+ };
1431
+ });
1432
+ }
1327
1433
  if (!filterSpam) {
1328
1434
  const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
1329
1435
  limit,
@@ -1346,7 +1452,7 @@ function createIndexer(options) {
1346
1452
  const classification = classifyTransaction(legs, tx);
1347
1453
  return { tx, classification, legs };
1348
1454
  });
1349
- return classified;
1455
+ return enrichBatch(classified);
1350
1456
  }
1351
1457
  const accumulated = [];
1352
1458
  let currentBefore = before;
@@ -1385,9 +1491,11 @@ function createIndexer(options) {
1385
1491
  break;
1386
1492
  }
1387
1493
  }
1388
- return accumulated.slice(0, limit);
1494
+ const result = accumulated.slice(0, limit);
1495
+ return enrichBatch(result);
1389
1496
  },
1390
- async getTransaction(signature2) {
1497
+ async getTransaction(signature2, options2 = {}) {
1498
+ const { enrichNftMetadata = true } = options2;
1391
1499
  const tx = await fetchTransaction(client.rpc, signature2);
1392
1500
  if (!tx) {
1393
1501
  return null;
@@ -1395,10 +1503,26 @@ function createIndexer(options) {
1395
1503
  tx.protocol = detectProtocol(tx.programIds);
1396
1504
  const legs = transactionToLegs(tx);
1397
1505
  const classification = classifyTransaction(legs, tx);
1398
- return { tx, classification, legs };
1506
+ let classified = { tx, classification, legs };
1507
+ if (enrichNftMetadata && rpcUrl) {
1508
+ classified = await enrichNftClassification(rpcUrl, classified);
1509
+ }
1510
+ return classified;
1399
1511
  },
1400
1512
  async getRawTransaction(signature2) {
1401
1513
  return fetchTransaction(client.rpc, signature2);
1514
+ },
1515
+ async getNftMetadata(mintAddress) {
1516
+ if (!rpcUrl) {
1517
+ throw new Error("getNftMetadata requires rpcUrl to be set");
1518
+ }
1519
+ return fetchNftMetadata(rpcUrl, mintAddress);
1520
+ },
1521
+ async getNftMetadataBatch(mintAddresses) {
1522
+ if (!rpcUrl) {
1523
+ throw new Error("getNftMetadataBatch requires rpcUrl to be set");
1524
+ }
1525
+ return fetchNftMetadataBatch(rpcUrl, mintAddresses);
1402
1526
  }
1403
1527
  };
1404
1528
  }