@deserialize/multi-vm-wallet 1.2.10 → 1.2.21

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.
Files changed (47) hide show
  1. package/dist/IChainWallet.d.ts +5 -1
  2. package/dist/IChainWallet.js.map +1 -1
  3. package/dist/constant.d.ts +16 -0
  4. package/dist/constant.js +19 -3
  5. package/dist/constant.js.map +1 -1
  6. package/dist/evm/evm.d.ts +6 -1
  7. package/dist/evm/evm.js +36 -45
  8. package/dist/evm/evm.js.map +1 -1
  9. package/dist/evm/transactionParsing.d.ts +3687 -0
  10. package/dist/evm/transactionParsing.js +441 -0
  11. package/dist/evm/transactionParsing.js.map +1 -0
  12. package/dist/evm/utils.d.ts +2 -9
  13. package/dist/evm/utils.js +17 -16
  14. package/dist/evm/utils.js.map +1 -1
  15. package/dist/helpers/index.d.ts +4 -0
  16. package/dist/helpers/index.js +13 -0
  17. package/dist/helpers/index.js.map +1 -0
  18. package/dist/svm/constant.d.ts +15 -0
  19. package/dist/svm/constant.js +25 -0
  20. package/dist/svm/constant.js.map +1 -0
  21. package/dist/svm/svm.d.ts +5 -2
  22. package/dist/svm/svm.js +10 -0
  23. package/dist/svm/svm.js.map +1 -1
  24. package/dist/svm/transactionParsing.d.ts +28 -0
  25. package/dist/svm/transactionParsing.js +207 -0
  26. package/dist/svm/transactionParsing.js.map +1 -0
  27. package/dist/svm/utils.d.ts +4 -3
  28. package/dist/svm/utils.js +82 -9
  29. package/dist/svm/utils.js.map +1 -1
  30. package/dist/test.d.ts +1 -1
  31. package/dist/test.js +47 -9
  32. package/dist/test.js.map +1 -1
  33. package/dist/types.d.ts +5 -1
  34. package/dist/types.js.map +1 -1
  35. package/package.json +4 -2
  36. package/utils/IChainWallet.ts +6 -2
  37. package/utils/constant.ts +22 -4
  38. package/utils/evm/evm.ts +53 -48
  39. package/utils/evm/transactionParsing.ts +639 -0
  40. package/utils/evm/utils.ts +26 -25
  41. package/utils/helpers/index.ts +11 -0
  42. package/utils/svm/constant.ts +29 -0
  43. package/utils/svm/svm.ts +14 -2
  44. package/utils/svm/transactionParsing.ts +294 -0
  45. package/utils/svm/utils.ts +104 -13
  46. package/utils/test.ts +56 -6
  47. package/utils/types.ts +6 -1
@@ -0,0 +1,639 @@
1
+ import {
2
+ createPublicClient,
3
+ http,
4
+ type PublicClient,
5
+ type Address,
6
+ type Hash,
7
+ type Transaction,
8
+ type TransactionReceipt,
9
+ formatEther,
10
+ formatUnits,
11
+ decodeEventLog,
12
+ parseAbi
13
+ } from 'viem';
14
+ import { TRANSACTION_TYPE, TransactionType } from '../constant';
15
+
16
+ export interface EVMTransactionHistoryItem {
17
+ hash: string;
18
+ timestamp: number | null;
19
+ status: 'success' | 'failed' | 'pending';
20
+ fee: string; // in ETH
21
+ type: TransactionType;
22
+ from: string;
23
+ to: string | null;
24
+
25
+
26
+
27
+ blockNumber: bigint;
28
+ gasUsed: bigint;
29
+ gasPrice: string; // in gwei
30
+ value: string; // in ETH
31
+ method?: string;
32
+ tokenTransfers?: TokenTransfer[];
33
+ nftTransfers?: NFTTransfer[];
34
+ }
35
+
36
+ export interface TokenTransfer {
37
+ type: 'ERC20';
38
+ from: string;
39
+ to: string;
40
+ amount: string;
41
+ tokenAddress: string;
42
+ tokenSymbol?: string;
43
+ tokenDecimals?: number;
44
+ }
45
+
46
+ export interface NFTTransfer {
47
+ type: 'ERC721' | 'ERC1155';
48
+ from: string;
49
+ to: string;
50
+ tokenId: string;
51
+ amount?: string; // for ERC1155
52
+ tokenAddress: string;
53
+ collectionName?: string;
54
+ }
55
+
56
+ export interface TransactionHistoryOptions {
57
+ startBlock?: bigint;
58
+ endBlock?: bigint;
59
+ includeTokenTransfers?: boolean;
60
+ includeNFTTransfers?: boolean;
61
+ }
62
+
63
+ // ERC20 Transfer event signature
64
+ const ERC20_TRANSFER_EVENT = parseAbi([
65
+ 'event Transfer(address indexed from, address indexed to, uint256 value)'
66
+ ]);
67
+
68
+ // ERC721 Transfer event signature
69
+ const ERC721_TRANSFER_EVENT = parseAbi([
70
+ 'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)'
71
+ ]);
72
+
73
+ // ERC1155 TransferSingle event signature
74
+ const ERC1155_TRANSFER_SINGLE_EVENT = parseAbi([
75
+ 'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)'
76
+ ]);
77
+
78
+ // ERC1155 TransferBatch event signature
79
+ const ERC1155_TRANSFER_BATCH_EVENT = parseAbi([
80
+ 'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)'
81
+ ]);
82
+
83
+ /**
84
+ * Fetches and parses transaction history for an EVM wallet address
85
+ * @param client - Viem public client
86
+ * @param walletAddress - Ethereum address
87
+ * @param options - Optional parameters for filtering and features
88
+ * @returns Array of parsed transaction history items
89
+ */
90
+ export async function getEVMTransactionHistory(
91
+ client: PublicClient,
92
+ walletAddress: Address,
93
+ options: TransactionHistoryOptions = {}
94
+ ): Promise<EVMTransactionHistoryItem[]> {
95
+ const {
96
+ startBlock = 0n,
97
+ endBlock,
98
+ includeTokenTransfers = true,
99
+ includeNFTTransfers = true,
100
+ } = options;
101
+
102
+ try {
103
+ const currentBlock = await client.getBlockNumber();
104
+ const toBlock = endBlock || currentBlock;
105
+
106
+ // For wallet UI, we typically want recent transactions
107
+ // Start scanning from current block backwards
108
+ const scanStartBlock = startBlock > 0n ? startBlock : (currentBlock > 5000n ? currentBlock - 5000n : 0n);
109
+
110
+ console.log(`Fetching recent transactions from block ${scanStartBlock} to ${toBlock}`);
111
+
112
+ // Get transaction hashes for the address (max 15 transactions)
113
+ const txHashes = await getRecentTransactionHashes(
114
+ client,
115
+ walletAddress,
116
+ scanStartBlock,
117
+ toBlock,
118
+ 15 // max transactions to fetch
119
+ );
120
+
121
+ console.log(`Found ${txHashes.length} unique transactions`);
122
+
123
+ // Fetch full transaction details in batches
124
+ const transactions = await fetchTransactionsInBatches(client, txHashes, 10);
125
+
126
+ // Parse each transaction
127
+ const history: EVMTransactionHistoryItem[] = [];
128
+
129
+ for (const { tx, receipt, block } of transactions) {
130
+ if (!tx || !receipt) continue;
131
+
132
+ const parsed = await parseEVMTransaction(
133
+ client,
134
+ tx,
135
+ receipt,
136
+ block?.timestamp || null,
137
+ walletAddress,
138
+ includeTokenTransfers,
139
+ includeNFTTransfers
140
+ );
141
+
142
+ history.push(parsed);
143
+ }
144
+
145
+ // Sort by block number (newest first)
146
+ history.sort((a, b) => Number(b.blockNumber - a.blockNumber));
147
+
148
+ return history;
149
+ } catch (error) {
150
+ console.error('Error fetching EVM transaction history:', error);
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Gets recent transaction hashes by scanning blocks backwards
157
+ * Uses batched parallel requests for better performance
158
+ */
159
+ async function getRecentTransactionHashes(
160
+ client: PublicClient,
161
+ address: Address,
162
+ startBlock: bigint,
163
+ endBlock: bigint,
164
+ maxTransactions: number = 15,
165
+ batchSize: number = 9
166
+ ): Promise<Hash[]> {
167
+ const txHashes: Hash[] = [];
168
+ const seenHashes = new Map<Hash, boolean>();
169
+ const addressLower = address.toLowerCase();
170
+ let currentBlock = endBlock;
171
+ let blocksScanned = 0n;
172
+
173
+ console.log(`Scanning blocks backwards from ${endBlock}...`);
174
+
175
+ // Iterate backwards in batches
176
+ while (currentBlock >= startBlock && txHashes.length < maxTransactions) {
177
+ // Create batch of block numbers to fetch
178
+ const blockNumbers: bigint[] = [];
179
+ for (let i = 0; i < batchSize && currentBlock >= startBlock; i++) {
180
+ blockNumbers.push(currentBlock);
181
+ currentBlock--;
182
+ }
183
+
184
+ try {
185
+ // Fetch all blocks in parallel
186
+ const blocks = await Promise.all(
187
+ blockNumbers.map(blockNumber =>
188
+ client.getBlock({
189
+ blockNumber,
190
+ includeTransactions: true,
191
+ }).catch(error => {
192
+ console.error(`Error fetching block ${blockNumber}:`, error);
193
+ return null;
194
+ })
195
+ )
196
+ );
197
+ blocks.length
198
+ console.log('blocks.length: ', blocks.length);
199
+ // Process blocks in order (newest to oldest)
200
+ for (const block of blocks) {
201
+ if (!block || !block.transactions) continue;
202
+
203
+ for (let i = 0; i < block.transactions.length && txHashes.length < maxTransactions; i++) {
204
+ const tx = block.transactions[i];
205
+
206
+ if (typeof tx === 'object') {
207
+ // Check if wallet is sender or receiver
208
+ const isSender = tx.from.toLowerCase() === addressLower;
209
+ const isReceiver = tx.to?.toLowerCase() === addressLower;
210
+
211
+ if ((isSender || isReceiver) && !seenHashes.has(tx.hash)) {
212
+ seenHashes.set(tx.hash, true);
213
+ txHashes.push(tx.hash);
214
+ }
215
+ } else {
216
+ console.log("Transaction is not an object: ", tx);
217
+ }
218
+ }
219
+
220
+ // Early exit if we found enough transactions
221
+ if (txHashes.length >= maxTransactions) break;
222
+ }
223
+
224
+ blocksScanned += BigInt(blockNumbers.length);
225
+
226
+ // Log progress
227
+ if (blocksScanned % 50n === 0n || blocksScanned < 50n) {
228
+ console.log(`Scanned ${blocksScanned} blocks, found ${txHashes.length} transactions`);
229
+ }
230
+
231
+ } catch (error) {
232
+ console.error(`Error fetching block batch:`, error);
233
+ continue;
234
+ }
235
+ }
236
+
237
+ console.log(`Found ${txHashes.length} transactions after scanning ${blocksScanned} blocks`);
238
+ return txHashes;
239
+ }
240
+
241
+ /**
242
+ * Alternative function that uses Etherscan-like API
243
+ * This is the recommended approach for production use
244
+ */
245
+ export async function getEVMTransactionHistoryWithAPI(
246
+ client: PublicClient,
247
+ walletAddress: Address,
248
+ apiEndpoint: string,
249
+ apiKey: string,
250
+ options: TransactionHistoryOptions = {}
251
+ ): Promise<EVMTransactionHistoryItem[]> {
252
+ const { includeTokenTransfers = true, includeNFTTransfers = true } = options;
253
+
254
+ try {
255
+ // Fetch normal transactions
256
+ const normalTxResponse = await fetch(
257
+ `${apiEndpoint}?module=account&action=txlist&address=${walletAddress}&startblock=0&endblock=99999999&sort=desc&apikey=${apiKey}`
258
+ );
259
+ const normalTxData = await normalTxResponse.json();
260
+
261
+ const history: EVMTransactionHistoryItem[] = [];
262
+
263
+ if (normalTxData.status === '1' && normalTxData.result) {
264
+ for (const tx of normalTxData.result.slice(0, 50)) { // Limit to 50 most recent
265
+ const receipt = await client.getTransactionReceipt({ hash: tx.hash as Hash });
266
+
267
+ const parsed = await parseEVMTransaction(
268
+ client,
269
+ {
270
+ hash: tx.hash as Hash,
271
+ from: tx.from as Address,
272
+ to: tx.to as Address | null,
273
+ value: BigInt(tx.value),
274
+ blockNumber: BigInt(tx.blockNumber),
275
+ input: tx.input as Hash,
276
+ nonce: parseInt(tx.nonce),
277
+ gas: BigInt(tx.gas),
278
+ gasPrice: tx.gasPrice ? BigInt(tx.gasPrice) : undefined,
279
+ } as Transaction,
280
+ receipt,
281
+ BigInt(tx.timeStamp),
282
+ walletAddress,
283
+ includeTokenTransfers,
284
+ includeNFTTransfers
285
+ );
286
+
287
+ history.push(parsed);
288
+ }
289
+ }
290
+
291
+ return history;
292
+ } catch (error) {
293
+ console.error('Error fetching EVM transaction history with API:', error);
294
+ throw error;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Fetches transaction hashes for an address by iterating through blocks
300
+ * Efficient for recent transactions (last 10-15 txs)
301
+ */
302
+ async function getTransactionHashesByAddress(
303
+ client: PublicClient,
304
+ address: Address,
305
+ startBlock: bigint,
306
+ endBlock: bigint,
307
+ direction: 'from' | 'to',
308
+ maxTransactions: number = 15
309
+ ): Promise<Hash[]> {
310
+ const txHashes: Hash[] = [];
311
+ const addressLower = address.toLowerCase();
312
+ let currentBlock = endBlock;
313
+
314
+ console.log(`Scanning blocks backwards from ${endBlock} to ${startBlock}...`);
315
+
316
+ // Iterate backwards from most recent block
317
+ while (currentBlock >= startBlock && txHashes.length < maxTransactions) {
318
+ try {
319
+ const block = await client.getBlock({
320
+ blockNumber: currentBlock,
321
+ includeTransactions: true,
322
+ });
323
+
324
+ if (block.transactions) {
325
+ for (const tx of block.transactions) {
326
+ // Check if transaction matches the direction filter
327
+ if (typeof tx === 'object') {
328
+ const matchesDirection =
329
+ (direction === 'from' && tx.from.toLowerCase() === addressLower) ||
330
+ (direction === 'to' && tx.to?.toLowerCase() === addressLower) ||
331
+ (direction === 'from' && tx.from.toLowerCase() === addressLower) ||
332
+ (direction === 'to' && tx.to?.toLowerCase() === addressLower);
333
+
334
+ if (matchesDirection) {
335
+ txHashes.push(tx.hash);
336
+
337
+ // Stop if we've found enough transactions
338
+ if (txHashes.length >= maxTransactions) {
339
+ break;
340
+ }
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ currentBlock--;
347
+
348
+ // Log progress every 100 blocks
349
+ if ((endBlock - currentBlock) % 100n === 0n) {
350
+ console.log(`Scanned ${endBlock - currentBlock} blocks, found ${txHashes.length} transactions`);
351
+ }
352
+
353
+ } catch (error) {
354
+ console.error(`Error fetching block ${currentBlock}:`, error);
355
+ currentBlock--;
356
+ continue;
357
+ }
358
+ }
359
+
360
+ console.log(`Found ${txHashes.length} transactions after scanning ${endBlock - currentBlock} blocks`);
361
+ return txHashes;
362
+ }
363
+
364
+ /**
365
+ * Fetches full transaction details in batches
366
+ */
367
+ async function fetchTransactionsInBatches(
368
+ client: PublicClient,
369
+ hashes: Hash[],
370
+ batchSize: number
371
+ ): Promise<Array<{
372
+ tx: Transaction | null;
373
+ receipt: TransactionReceipt | null;
374
+ block: { timestamp: bigint } | null;
375
+ }>> {
376
+ const results = [];
377
+
378
+ for (let i = 0; i < hashes.length; i += batchSize) {
379
+ const batch = hashes.slice(i, i + batchSize);
380
+
381
+ const batchResults = await Promise.all(
382
+ batch.map(async (hash) => {
383
+ try {
384
+ const [tx, receipt] = await Promise.all([
385
+ client.getTransaction({ hash }),
386
+ client.getTransactionReceipt({ hash }),
387
+ ]);
388
+
389
+ let block = null;
390
+ if (tx?.blockNumber) {
391
+ block = await client.getBlock({ blockNumber: tx.blockNumber });
392
+ }
393
+
394
+ return { tx, receipt, block };
395
+ } catch (error) {
396
+ console.error(`Error fetching transaction ${hash}:`, error);
397
+ return { tx: null, receipt: null, block: null };
398
+ }
399
+ })
400
+ );
401
+
402
+ results.push(...batchResults);
403
+
404
+ // Small delay to avoid rate limiting
405
+ if (i + batchSize < hashes.length) {
406
+ await new Promise(resolve => setTimeout(resolve, 100));
407
+ }
408
+ }
409
+
410
+ return results;
411
+ }
412
+
413
+ /**
414
+ * Parses a single EVM transaction
415
+ */
416
+ async function parseEVMTransaction(
417
+ client: PublicClient,
418
+ tx: Transaction,
419
+ receipt: TransactionReceipt,
420
+ timestamp: bigint | null,
421
+ walletAddress: Address,
422
+ includeTokenTransfers: boolean,
423
+ includeNFTTransfers: boolean
424
+ ): Promise<EVMTransactionHistoryItem> {
425
+ const gasUsed = receipt.gasUsed;
426
+ const effectiveGasPrice = receipt.effectiveGasPrice || tx.gasPrice || 0n;
427
+ const fee = formatEther(gasUsed * effectiveGasPrice);
428
+ const gasPrice = formatUnits(effectiveGasPrice, 9); // gwei
429
+
430
+ // Determine transaction type
431
+ const type = determineTransactionType(tx, receipt);
432
+
433
+ // Extract method signature
434
+ const method = tx.input && tx.input.length >= 10
435
+ ? tx.input.slice(0, 10)
436
+ : undefined;
437
+
438
+ // Parse token transfers from logs
439
+ let tokenTransfers: TokenTransfer[] = [];
440
+ let nftTransfers: NFTTransfer[] = [];
441
+
442
+ if (includeTokenTransfers || includeNFTTransfers) {
443
+ const transfers = await parseTransferLogs(
444
+ client,
445
+ receipt.logs,
446
+ walletAddress,
447
+ includeTokenTransfers,
448
+ includeNFTTransfers
449
+ );
450
+ tokenTransfers = transfers.tokens;
451
+ nftTransfers = transfers.nfts;
452
+ }
453
+
454
+ return {
455
+ hash: tx.hash,
456
+ timestamp: timestamp ? Number(timestamp) : null,
457
+ blockNumber: tx.blockNumber || 0n,
458
+ status: receipt.status === 'success' ? 'success' : 'failed',
459
+ fee,
460
+ gasUsed,
461
+ gasPrice,
462
+ type,
463
+ from: tx.from,
464
+ to: tx.to || null,
465
+ value: formatEther(tx.value || 0n),
466
+ method,
467
+ tokenTransfers: tokenTransfers.length > 0 ? tokenTransfers : undefined,
468
+ nftTransfers: nftTransfers.length > 0 ? nftTransfers : undefined,
469
+ };
470
+ }
471
+
472
+ /**
473
+ * Determines the transaction type
474
+ */
475
+ function determineTransactionType(tx: Transaction, receipt: TransactionReceipt): TransactionType {
476
+ // Contract creation
477
+ if (!tx.to) return TRANSACTION_TYPE.CONTRACT_CREATION;
478
+
479
+ // Check if it's a token/NFT transfer based on method signature
480
+ const methodSig = tx.input?.slice(0, 10);
481
+
482
+ if (methodSig === '0xa9059cbb') return TRANSACTION_TYPE.TOKEN_TRANSFER; // ERC20 transfer
483
+ if (methodSig === '0x23b872dd') return TRANSACTION_TYPE.TOKEN_TRANSFER; // ERC20 transferFrom
484
+ if (methodSig === '0x42842e0e') return TRANSACTION_TYPE.NFT_TRANSFER; // ERC721 safeTransferFrom
485
+ if (methodSig === '0xf242432a') return TRANSACTION_TYPE.NFT_TRANSFER; // ERC1155 safeTransferFrom
486
+
487
+ // Check value
488
+ if (tx.value && tx.value > 0n) return TRANSACTION_TYPE.NATIVE_TRANSFER;
489
+
490
+ // Check logs for common patterns
491
+ if (receipt.logs.some(log => log.topics[0]?.includes('Swap'))) return TRANSACTION_TYPE.SWAP;
492
+ if (receipt.logs.some(log => log.topics[0]?.includes('Deposit'))) return TRANSACTION_TYPE.DEPOSIT;
493
+ if (receipt.logs.some(log => log.topics[0]?.includes('Withdraw'))) return TRANSACTION_TYPE.WITHDRAWAL;
494
+
495
+ return TRANSACTION_TYPE.CONTRACT_INTERACTION;
496
+ }
497
+
498
+ /**
499
+ * Parses transfer events from transaction logs
500
+ */
501
+ async function parseTransferLogs(
502
+ client: PublicClient,
503
+ logs: TransactionReceipt['logs'],
504
+ walletAddress: Address,
505
+ includeTokenTransfers: boolean,
506
+ includeNFTTransfers: boolean
507
+ ): Promise<{ tokens: TokenTransfer[]; nfts: NFTTransfer[] }> {
508
+ const tokens: TokenTransfer[] = [];
509
+ const nfts: NFTTransfer[] = [];
510
+
511
+ for (const log of logs) {
512
+ try {
513
+ // Try ERC20 Transfer
514
+ if (includeTokenTransfers && log.topics.length === 3) {
515
+ try {
516
+ const decoded = decodeEventLog({
517
+ abi: ERC20_TRANSFER_EVENT,
518
+ data: log.data,
519
+ topics: log.topics,
520
+ });
521
+
522
+ if (decoded.eventName === 'Transfer') {
523
+ const { from, to, value } = decoded.args as any;
524
+
525
+ // Only include if wallet is involved
526
+ if (from.toLowerCase() === walletAddress.toLowerCase() ||
527
+ to.toLowerCase() === walletAddress.toLowerCase()) {
528
+
529
+ // Try to get token info (this might fail for non-standard tokens)
530
+ let decimals = 18;
531
+ try {
532
+ decimals = await client.readContract({
533
+ address: log.address,
534
+ abi: parseAbi(['function decimals() view returns (uint8)']),
535
+ functionName: 'decimals',
536
+ }) as number;
537
+ } catch { }
538
+
539
+ tokens.push({
540
+ type: 'ERC20',
541
+ from,
542
+ to,
543
+ amount: formatUnits(value, decimals),
544
+ tokenAddress: log.address,
545
+ tokenDecimals: decimals,
546
+ });
547
+ }
548
+ }
549
+ } catch { }
550
+ }
551
+
552
+ // Try ERC721 Transfer (has indexed tokenId)
553
+ if (includeNFTTransfers && log.topics.length === 4) {
554
+ try {
555
+ const decoded = decodeEventLog({
556
+ abi: ERC721_TRANSFER_EVENT,
557
+ data: log.data,
558
+ topics: log.topics,
559
+ });
560
+
561
+ if (decoded.eventName === 'Transfer') {
562
+ const { from, to, tokenId } = decoded.args as any;
563
+
564
+ if (from.toLowerCase() === walletAddress.toLowerCase() ||
565
+ to.toLowerCase() === walletAddress.toLowerCase()) {
566
+ nfts.push({
567
+ type: 'ERC721',
568
+ from,
569
+ to,
570
+ tokenId: tokenId.toString(),
571
+ tokenAddress: log.address,
572
+ });
573
+ }
574
+ }
575
+ } catch { }
576
+ }
577
+
578
+ // Try ERC1155 TransferSingle
579
+ if (includeNFTTransfers) {
580
+ try {
581
+ const decoded = decodeEventLog({
582
+ abi: ERC1155_TRANSFER_SINGLE_EVENT,
583
+ data: log.data,
584
+ topics: log.topics,
585
+ });
586
+
587
+ if (decoded.eventName === 'TransferSingle') {
588
+ const { from, to, id, value } = decoded.args as any;
589
+
590
+ if (from.toLowerCase() === walletAddress.toLowerCase() ||
591
+ to.toLowerCase() === walletAddress.toLowerCase()) {
592
+ nfts.push({
593
+ type: 'ERC1155',
594
+ from,
595
+ to,
596
+ tokenId: id.toString(),
597
+ amount: value.toString(),
598
+ tokenAddress: log.address,
599
+ });
600
+ }
601
+ }
602
+ } catch { }
603
+ }
604
+ } catch (error) {
605
+ // Skip logs that don't match our patterns
606
+ continue;
607
+ }
608
+ }
609
+
610
+ return { tokens, nfts };
611
+ }
612
+
613
+ /**
614
+ * Helper function to create a client
615
+ */
616
+ export function createEVMClient(rpcUrl: string) {
617
+ return createPublicClient({
618
+ transport: http(rpcUrl),
619
+ });
620
+ }
621
+
622
+ // Example usage:
623
+ /*
624
+ import { mainnet } from 'viem/chains';
625
+
626
+ const client = createPublicClient({
627
+ chain: mainnet,
628
+ transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'),
629
+ });
630
+
631
+ const history = await getEVMTransactionHistoryWithAPI(
632
+ client,
633
+ '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' as Address,
634
+ 'https://api.etherscan.io/api',
635
+ 'YOUR_ETHERSCAN_API_KEY'
636
+ );
637
+
638
+ console.log(history);
639
+ */