@deserialize/multi-vm-wallet 1.2.21 → 1.2.22

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.
@@ -0,0 +1,824 @@
1
+ import {
2
+ type PublicClient,
3
+ type Address,
4
+ type Hex,
5
+ isHex,
6
+ parseAbi,
7
+ decodeFunctionData,
8
+ getContract,
9
+ } from 'viem';
10
+
11
+ // ============================================================================
12
+ // Types & Constants
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Enum representing different transaction types
17
+ */
18
+ export enum TransactionType {
19
+ // Token operations
20
+ tokenMethodApprove = 'approve',
21
+ tokenMethodSetApprovalForAll = 'setApprovalForAll',
22
+ tokenMethodTransfer = 'transfer',
23
+ tokenMethodTransferFrom = 'transferFrom',
24
+ tokenMethodIncreaseAllowance = 'increaseAllowance',
25
+ tokenMethodSafeTransferFrom = 'safeTransferFrom',
26
+
27
+ // Contract operations
28
+ contractInteraction = 'contractInteraction',
29
+ deployContract = 'deployContract',
30
+
31
+ // Simple transfers
32
+ simpleSend = 'simpleSend',
33
+
34
+ // Special operations
35
+ cancel = 'cancel',
36
+ retry = 'retry',
37
+ swap = 'swap',
38
+ }
39
+
40
+ /**
41
+ * Enum representing asset types
42
+ */
43
+ export enum AssetType {
44
+ native = 'NATIVE',
45
+ token = 'TOKEN',
46
+ NFT = 'NFT',
47
+ unknown = 'UNKNOWN',
48
+ }
49
+
50
+ /**
51
+ * Enum representing token standards
52
+ */
53
+ export enum TokenStandard {
54
+ ERC20 = 'ERC20',
55
+ ERC721 = 'ERC721',
56
+ ERC1155 = 'ERC1155',
57
+ none = 'none',
58
+ }
59
+
60
+ /**
61
+ * Transaction parameters interface
62
+ */
63
+ export interface TransactionParams {
64
+ from: Address;
65
+ to?: Address;
66
+ value?: bigint | string;
67
+ data?: Hex;
68
+ gas?: bigint | string;
69
+ gasPrice?: bigint | string;
70
+ maxFeePerGas?: bigint | string;
71
+ maxPriorityFeePerGas?: bigint | string;
72
+ nonce?: number;
73
+ }
74
+
75
+ /**
76
+ * Transaction metadata interface
77
+ */
78
+ export interface TransactionMeta {
79
+ type?: TransactionType;
80
+ txParams: TransactionParams;
81
+ dappSuggestedGasFees?: {
82
+ gasPrice?: bigint | string;
83
+ maxFeePerGas?: bigint | string;
84
+ maxPriorityFeePerGas?: bigint | string;
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Result of transaction type inference
90
+ */
91
+ export interface InferTransactionTypeResult {
92
+ type: TransactionType;
93
+ contractCode: Hex | null;
94
+ }
95
+
96
+ /**
97
+ * Parsed approval transaction data
98
+ */
99
+ export interface ParsedApprovalData {
100
+ amountOrTokenId?: bigint;
101
+ isApproveAll?: boolean;
102
+ isRevokeAll?: boolean;
103
+ name: string;
104
+ tokenAddress?: Address;
105
+ spender?: Address;
106
+ }
107
+
108
+ /**
109
+ * Token details interface
110
+ */
111
+ export interface TokenDetails {
112
+ decimals?: number;
113
+ balance?: bigint;
114
+ symbol?: string;
115
+ standard?: TokenStandard;
116
+ }
117
+
118
+ // ============================================================================
119
+ // ABIs
120
+ // ============================================================================
121
+
122
+ /**
123
+ * ERC20 token ABI (essential methods only)
124
+ */
125
+ const ERC20_ABI = parseAbi([
126
+ 'function approve(address spender, uint256 amount) returns (bool)',
127
+ 'function transfer(address to, uint256 amount) returns (bool)',
128
+ 'function transferFrom(address from, address to, uint256 amount) returns (bool)',
129
+ 'function increaseAllowance(address spender, uint256 addedValue) returns (bool)',
130
+ 'function decreaseAllowance(address spender, uint256 subtractedValue) returns (bool)',
131
+ 'function decimals() view returns (uint8)',
132
+ 'function symbol() view returns (string)',
133
+ 'function balanceOf(address account) view returns (uint256)',
134
+ ]);
135
+
136
+ /**
137
+ * ERC721 token ABI (essential methods only)
138
+ */
139
+ const ERC721_ABI = parseAbi([
140
+ 'function approve(address to, uint256 tokenId)',
141
+ 'function setApprovalForAll(address operator, bool approved)',
142
+ 'function transferFrom(address from, address to, uint256 tokenId)',
143
+ 'function safeTransferFrom(address from, address to, uint256 tokenId)',
144
+ 'function safeTransferFrom(address from, address to, uint256 tokenId, bytes data)',
145
+ 'function supportsInterface(bytes4 interfaceId) view returns (bool)',
146
+ ]);
147
+
148
+ /**
149
+ * ERC1155 token ABI (essential methods only)
150
+ */
151
+ const ERC1155_ABI = parseAbi([
152
+ 'function setApprovalForAll(address operator, bool approved)',
153
+ 'function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)',
154
+ 'function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)',
155
+ 'function supportsInterface(bytes4 interfaceId) view returns (bool)',
156
+ ]);
157
+
158
+ /**
159
+ * USDC (Fiat Token V2) specific methods
160
+ */
161
+ const FIAT_TOKEN_V2_ABI = parseAbi([
162
+ 'function increaseAllowance(address spender, uint256 increment) returns (bool)',
163
+ 'function decreaseAllowance(address spender, uint256 decrement) returns (bool)',
164
+ ]);
165
+
166
+ /**
167
+ * Permit2 contract ABI
168
+ */
169
+ const PERMIT2_ABI = parseAbi([
170
+ 'function approve(address token, address spender, uint160 amount, uint48 expiration)',
171
+ ]);
172
+
173
+ /**
174
+ * List of transaction types that can be inferred from transaction data
175
+ */
176
+ const INFERRABLE_TRANSACTION_TYPES: TransactionType[] = [
177
+ TransactionType.tokenMethodApprove,
178
+ TransactionType.tokenMethodSetApprovalForAll,
179
+ TransactionType.tokenMethodTransfer,
180
+ TransactionType.tokenMethodTransferFrom,
181
+ TransactionType.tokenMethodIncreaseAllowance,
182
+ TransactionType.contractInteraction,
183
+ TransactionType.simpleSend,
184
+ ];
185
+
186
+ /**
187
+ * List of approval method names
188
+ */
189
+ const APPROVAL_METHOD_NAMES = [
190
+ 'approve',
191
+ 'setApprovalForAll',
192
+ 'increaseAllowance',
193
+ 'decreaseAllowance',
194
+ ];
195
+
196
+ /**
197
+ * Interface IDs for token standards (EIP-165)
198
+ */
199
+ const INTERFACE_IDS = {
200
+ ERC721: '0x80ac58cd',
201
+ ERC1155: '0xd9b67a26',
202
+ ERC165: '0x01ffc9a7',
203
+ };
204
+
205
+ // ============================================================================
206
+ // Gas Fee Utilities
207
+ // ============================================================================
208
+
209
+ /**
210
+ * Determines if a transaction uses EIP-1559 gas fields
211
+ *
212
+ * EIP-1559 transactions use maxFeePerGas and maxPriorityFeePerGas instead of gasPrice
213
+ *
214
+ * @param transactionMeta - Transaction metadata to check
215
+ * @returns True if transaction uses valid EIP-1559 fields
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * const isEIP1559 = isEIP1559Transaction({
220
+ * txParams: {
221
+ * maxFeePerGas: '0x59682f00',
222
+ * maxPriorityFeePerGas: '0x3b9aca00'
223
+ * }
224
+ * });
225
+ * ```
226
+ */
227
+ export function isEIP1559Transaction(transactionMeta: TransactionMeta): boolean {
228
+ const { maxFeePerGas, maxPriorityFeePerGas } = transactionMeta.txParams;
229
+ return (
230
+ isHex(String(maxFeePerGas ?? '')) &&
231
+ isHex(String(maxPriorityFeePerGas ?? ''))
232
+ );
233
+ }
234
+
235
+ /**
236
+ * Determines if a transaction uses legacy gas fields (gasPrice)
237
+ *
238
+ * Legacy transactions use gasPrice instead of EIP-1559 fields
239
+ *
240
+ * @param transactionMeta - Transaction metadata to check
241
+ * @returns True if transaction uses valid legacy gas fields
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * const isLegacy = isLegacyTransaction({
246
+ * txParams: {
247
+ * gasPrice: '0x3b9aca00'
248
+ * }
249
+ * });
250
+ * ```
251
+ */
252
+ export function isLegacyTransaction(transactionMeta: TransactionMeta): boolean {
253
+ const { maxFeePerGas, maxPriorityFeePerGas, gasPrice } = transactionMeta.txParams;
254
+ return (
255
+ typeof maxFeePerGas === 'undefined' &&
256
+ typeof maxPriorityFeePerGas === 'undefined' &&
257
+ (typeof gasPrice === 'undefined' || isHex(String(gasPrice)))
258
+ );
259
+ }
260
+
261
+ /**
262
+ * Checks if transaction gas fees match dApp suggested fees
263
+ *
264
+ * Useful for determining if the user has modified the dApp's suggested gas fees
265
+ *
266
+ * @param transactionMeta - Transaction metadata to check
267
+ * @returns True if txParams match dappSuggestedGasFees
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const isUnmodified = txParamsAreDappSuggested({
272
+ * txParams: { gasPrice: '0x3b9aca00' },
273
+ * dappSuggestedGasFees: { gasPrice: '0x3b9aca00' }
274
+ * });
275
+ * ```
276
+ */
277
+ export function txParamsAreDappSuggested(transactionMeta: TransactionMeta): boolean {
278
+ const { gasPrice, maxPriorityFeePerGas, maxFeePerGas } = transactionMeta.txParams;
279
+ const suggested = transactionMeta.dappSuggestedGasFees;
280
+
281
+ if (!suggested) return false;
282
+
283
+ return Boolean(
284
+ (gasPrice && gasPrice === suggested.gasPrice) ||
285
+ (maxPriorityFeePerGas &&
286
+ maxFeePerGas &&
287
+ suggested.maxPriorityFeePerGas === maxPriorityFeePerGas &&
288
+ suggested.maxFeePerGas === maxFeePerGas)
289
+ );
290
+ }
291
+
292
+ // ============================================================================
293
+ // Transaction Data Parsing
294
+ // ============================================================================
295
+
296
+ /**
297
+ * Attempts to decode transaction data using standard token ABIs
298
+ *
299
+ * Tries to decode the data against ERC20, ERC721, ERC1155, USDC, and Permit2 ABIs
300
+ *
301
+ * @param data - Encoded transaction data
302
+ * @returns Decoded function data or undefined if decoding fails
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * const parsed = parseStandardTokenTransactionData('0xa9059cbb...');
307
+ * if (parsed) {
308
+ * console.log('Function:', parsed.functionName);
309
+ * console.log('Args:', parsed.args);
310
+ * }
311
+ * ```
312
+ */
313
+ export function parseStandardTokenTransactionData(data: Hex) {
314
+ const abis = [
315
+ ERC20_ABI,
316
+ ERC721_ABI,
317
+ ERC1155_ABI,
318
+ FIAT_TOKEN_V2_ABI,
319
+ PERMIT2_ABI,
320
+ ];
321
+
322
+ for (const abi of abis) {
323
+ try {
324
+ return decodeFunctionData({ abi, data });
325
+ } catch {
326
+ // Continue to next ABI
327
+ }
328
+ }
329
+
330
+ return undefined;
331
+ }
332
+
333
+ /**
334
+ * Checks if transaction has meaningful data
335
+ *
336
+ * @param transactionData - Transaction data field
337
+ * @returns True if data exists and is not empty
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * hasTransactionData('0x') // false
342
+ * hasTransactionData('0xa9059cbb...') // true
343
+ * ```
344
+ */
345
+ export function hasTransactionData(transactionData?: Hex): boolean {
346
+ return Boolean(
347
+ transactionData?.length && transactionData.toLowerCase() !== '0x'
348
+ );
349
+ }
350
+
351
+ /**
352
+ * Parses approval transaction data to extract relevant information
353
+ *
354
+ * Supports:
355
+ * - ERC20: approve, increaseAllowance
356
+ * - ERC721/ERC1155: setApprovalForAll
357
+ * - Permit2: approve
358
+ * - USDC: increaseAllowance
359
+ *
360
+ * @param data - Transaction data to parse
361
+ * @returns Parsed approval data or undefined if not an approval transaction
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const approval = parseApprovalTransactionData('0x095ea7b3...');
366
+ * if (approval) {
367
+ * console.log('Spender:', approval.spender);
368
+ * console.log('Amount:', approval.amountOrTokenId);
369
+ * console.log('Is approval for all?', approval.isApproveAll);
370
+ * }
371
+ * ```
372
+ */
373
+ export function parseApprovalTransactionData(data: Hex): ParsedApprovalData | undefined {
374
+ const decoded = parseStandardTokenTransactionData(data);
375
+
376
+ if (!decoded) return undefined;
377
+
378
+ const { functionName, args } = decoded;
379
+
380
+ if (!functionName || !APPROVAL_METHOD_NAMES.includes(functionName)) {
381
+ return undefined;
382
+ }
383
+
384
+ // Extract amount or token ID based on function
385
+ const rawAmountOrTokenId =
386
+ (args as any)?._value ?? // ERC-20 approve
387
+ (args as any)?.increment ?? // Fiat Token V2 increaseAllowance
388
+ (args as any)?.amount ?? // Permit2 approve
389
+ (args as any)?.addedValue; // ERC-20 increaseAllowance
390
+
391
+ const amountOrTokenId = rawAmountOrTokenId
392
+ ? BigInt(rawAmountOrTokenId.toString())
393
+ : undefined;
394
+
395
+ // Extract spender address
396
+ const spender =
397
+ (args as any)?.spender ??
398
+ (args as any)?._spender ??
399
+ (args as any)?.[0];
400
+
401
+ // Check if it's an approval for all (ERC721/ERC1155)
402
+ const isApproveAll =
403
+ functionName === 'setApprovalForAll' && (args as any)?._approved === true;
404
+ const isRevokeAll =
405
+ functionName === 'setApprovalForAll' && (args as any)?._approved === false;
406
+
407
+ // Extract token address for Permit2
408
+ const tokenAddress =
409
+ functionName === 'approve' ? (args as any)?.token : undefined;
410
+
411
+ return {
412
+ amountOrTokenId,
413
+ isApproveAll,
414
+ isRevokeAll,
415
+ name: functionName,
416
+ tokenAddress,
417
+ spender,
418
+ };
419
+ }
420
+
421
+ // ============================================================================
422
+ // Contract Utilities
423
+ // ============================================================================
424
+
425
+ /**
426
+ * Reads an address to determine if it's a contract
427
+ *
428
+ * @param client - Viem public client
429
+ * @param address - Address to check
430
+ * @returns Contract code and whether it's a contract address
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * const { isContractAddress, contractCode } = await readAddressAsContract(
435
+ * client,
436
+ * '0x...'
437
+ * );
438
+ * ```
439
+ */
440
+ export async function readAddressAsContract(
441
+ client: PublicClient,
442
+ address: Address
443
+ ): Promise<{ isContractAddress: boolean; contractCode: Hex | null }> {
444
+ try {
445
+ const code = await client.getCode({ address });
446
+ const isContract = Boolean(code && code !== '0x' && code !== '0x0');
447
+
448
+ return {
449
+ isContractAddress: isContract,
450
+ contractCode: code || null,
451
+ };
452
+ } catch (error) {
453
+ console.error('Error reading contract code:', error);
454
+ return {
455
+ isContractAddress: false,
456
+ contractCode: null,
457
+ };
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Determines the token standard by checking EIP-165 interface support
463
+ *
464
+ * @param client - Viem public client
465
+ * @param address - Token contract address
466
+ * @returns Token standard (ERC721, ERC1155, ERC20, or none)
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * const standard = await getTokenStandard(client, '0x...');
471
+ * console.log('Token standard:', standard); // 'ERC721'
472
+ * ```
473
+ */
474
+ export async function getTokenStandard(
475
+ client: PublicClient,
476
+ address: Address
477
+ ): Promise<TokenStandard> {
478
+ // Try ERC165 supportsInterface for ERC721/ERC1155
479
+ try {
480
+ const contract = getContract({
481
+ address,
482
+ abi: parseAbi(['function supportsInterface(bytes4) view returns (bool)']),
483
+ client,
484
+ });
485
+
486
+ // Check ERC721
487
+ const isERC721 = await contract.read.supportsInterface([INTERFACE_IDS.ERC721 as Hex]);
488
+ if (isERC721) return TokenStandard.ERC721;
489
+
490
+ // Check ERC1155
491
+ const isERC1155 = await contract.read.supportsInterface([INTERFACE_IDS.ERC1155 as Hex]);
492
+ if (isERC1155) return TokenStandard.ERC1155;
493
+ } catch {
494
+ // Not ERC721/ERC1155, try ERC20
495
+ }
496
+
497
+ // Try ERC20 by checking for decimals function
498
+ try {
499
+ const contract = getContract({
500
+ address,
501
+ abi: parseAbi(['function decimals() view returns (uint8)']),
502
+ client,
503
+ });
504
+
505
+ await contract.read.decimals();
506
+ return TokenStandard.ERC20;
507
+ } catch {
508
+ // Not a recognized token standard
509
+ }
510
+
511
+ return TokenStandard.none;
512
+ }
513
+
514
+ /**
515
+ * Gets token details including standard, decimals, symbol, and balance
516
+ *
517
+ * @param client - Viem public client
518
+ * @param address - Token contract address
519
+ * @param holderAddress - Optional address to check balance for
520
+ * @returns Token details
521
+ *
522
+ * @example
523
+ * ```typescript
524
+ * const details = await getTokenStandardAndDetails(
525
+ * client,
526
+ * '0x...', // token address
527
+ * '0x...' // holder address
528
+ * );
529
+ * console.log(details.symbol, details.decimals, details.balance);
530
+ * ```
531
+ */
532
+ export async function getTokenStandardAndDetails(
533
+ client: PublicClient,
534
+ address: Address,
535
+ holderAddress?: Address
536
+ ): Promise<TokenDetails> {
537
+ const standard = await getTokenStandard(client, address);
538
+
539
+ if (standard === TokenStandard.none) {
540
+ return { standard };
541
+ }
542
+
543
+ const details: TokenDetails = { standard };
544
+
545
+ // Get ERC20 details
546
+ if (standard === TokenStandard.ERC20) {
547
+ try {
548
+ const contract = getContract({
549
+ address,
550
+ abi: ERC20_ABI,
551
+ client,
552
+ });
553
+
554
+ const [decimals, symbol, balance] = await Promise.all([
555
+ contract.read.decimals().catch(() => undefined),
556
+ contract.read.symbol().catch(() => undefined),
557
+ holderAddress
558
+ ? contract.read.balanceOf([holderAddress]).catch(() => undefined)
559
+ : Promise.resolve(undefined),
560
+ ]);
561
+
562
+ details.decimals = decimals;
563
+ details.symbol = symbol;
564
+ details.balance = balance;
565
+ } catch (error) {
566
+ console.error('Error fetching ERC20 details:', error);
567
+ }
568
+ }
569
+
570
+ return details;
571
+ }
572
+
573
+ // ============================================================================
574
+ // Transaction Type Determination
575
+ // ============================================================================
576
+
577
+ /**
578
+ * Determines the type of transaction by analyzing its parameters
579
+ *
580
+ * This function analyzes the transaction to determine if it's:
581
+ * - A contract deployment
582
+ * - A token transfer/approval
583
+ * - A contract interaction
584
+ * - A simple ETH transfer
585
+ *
586
+ * @param txParams - Transaction parameters
587
+ * @param client - Viem public client
588
+ * @returns Transaction type and contract code
589
+ *
590
+ * @example
591
+ * ```typescript
592
+ * const result = await determineTransactionType(
593
+ * {
594
+ * from: '0x...',
595
+ * to: '0x...',
596
+ * data: '0xa9059cbb...'
597
+ * },
598
+ * client
599
+ * );
600
+ * console.log('Transaction type:', result.type);
601
+ * ```
602
+ */
603
+ export async function determineTransactionType(
604
+ txParams: TransactionParams,
605
+ client: PublicClient
606
+ ): Promise<InferTransactionTypeResult> {
607
+ const { data, to, value } = txParams;
608
+
609
+ // Contract deployment (no 'to' address)
610
+ if (data && !to) {
611
+ return {
612
+ type: TransactionType.deployContract,
613
+ contractCode: null,
614
+ };
615
+ }
616
+
617
+ // Check if 'to' is a contract
618
+ if (to) {
619
+ const { contractCode, isContractAddress } = await readAddressAsContract(client, to);
620
+
621
+ if (isContractAddress) {
622
+ const hasValue = value && BigInt(value) !== 0n;
623
+
624
+ // Try to parse the transaction data
625
+ let functionName = '';
626
+ try {
627
+ const parsed = data ? parseStandardTokenTransactionData(data) : undefined;
628
+ if (parsed?.functionName) {
629
+ functionName = parsed.functionName;
630
+ }
631
+ } catch (error) {
632
+ console.debug('Failed to parse transaction data:', error);
633
+ }
634
+
635
+ // Check if it's a known token method
636
+ const tokenMethodName = [
637
+ TransactionType.tokenMethodApprove,
638
+ TransactionType.tokenMethodSetApprovalForAll,
639
+ TransactionType.tokenMethodTransfer,
640
+ TransactionType.tokenMethodTransferFrom,
641
+ TransactionType.tokenMethodIncreaseAllowance,
642
+ TransactionType.tokenMethodSafeTransferFrom,
643
+ ].find((methodName) =>
644
+ methodName.toLowerCase() === functionName.toLowerCase()
645
+ );
646
+
647
+ // Return token method if found and no ETH value
648
+ if (data && tokenMethodName && !hasValue) {
649
+ return {
650
+ type: tokenMethodName,
651
+ contractCode,
652
+ };
653
+ }
654
+
655
+ // Otherwise it's a general contract interaction
656
+ return {
657
+ type: TransactionType.contractInteraction,
658
+ contractCode,
659
+ };
660
+ }
661
+ }
662
+
663
+ // Simple ETH transfer
664
+ return {
665
+ type: TransactionType.simpleSend,
666
+ contractCode: null,
667
+ };
668
+ }
669
+
670
+ /**
671
+ * Determines the asset type and token standard for a transaction
672
+ *
673
+ * This function analyzes the transaction to determine what type of asset
674
+ * is being transferred and what standard it follows (if applicable)
675
+ *
676
+ * @param txMeta - Transaction metadata
677
+ * @param client - Viem public client
678
+ * @returns Asset type and token standard
679
+ *
680
+ * @example
681
+ * ```typescript
682
+ * const { assetType, tokenStandard } = await determineTransactionAssetType(
683
+ * txMeta,
684
+ * client
685
+ * );
686
+ *
687
+ * if (assetType === AssetType.token) {
688
+ * console.log('ERC20 token transfer');
689
+ * }
690
+ * ```
691
+ */
692
+ export async function determineTransactionAssetType(
693
+ txMeta: TransactionMeta,
694
+ client: PublicClient
695
+ ): Promise<{
696
+ assetType: AssetType;
697
+ tokenStandard: TokenStandard;
698
+ }> {
699
+ // Use existing type if it's already inferrable
700
+ let inferrableType = txMeta.type;
701
+
702
+ if (txMeta.type && !INFERRABLE_TRANSACTION_TYPES.includes(txMeta.type)) {
703
+ // Get an inferrable type for special transactions (like swaps)
704
+ const result = await determineTransactionType(txMeta.txParams, client);
705
+ inferrableType = result.type;
706
+ }
707
+
708
+ // Check if it's a token method
709
+ const isTokenMethod = [
710
+ TransactionType.tokenMethodApprove,
711
+ TransactionType.tokenMethodSetApprovalForAll,
712
+ TransactionType.tokenMethodTransfer,
713
+ TransactionType.tokenMethodTransferFrom,
714
+ TransactionType.tokenMethodIncreaseAllowance,
715
+ ].includes(inferrableType as TransactionType);
716
+
717
+ // Try to get token standard for token methods or contract interactions
718
+ if (isTokenMethod || inferrableType === TransactionType.contractInteraction) {
719
+ if (txMeta.txParams.to) {
720
+ try {
721
+ const details = await getTokenStandardAndDetails(
722
+ client,
723
+ txMeta.txParams.to
724
+ );
725
+
726
+ if (details.standard && details.standard !== TokenStandard.none) {
727
+ return {
728
+ assetType:
729
+ details.standard === TokenStandard.ERC20
730
+ ? AssetType.token
731
+ : AssetType.NFT,
732
+ tokenStandard: details.standard,
733
+ };
734
+ }
735
+ } catch {
736
+ // Failed to get token details, continue
737
+ }
738
+ }
739
+ }
740
+
741
+ // Contract interaction with unknown asset type
742
+ if (inferrableType === TransactionType.contractInteraction) {
743
+ return {
744
+ assetType: AssetType.unknown,
745
+ tokenStandard: TokenStandard.none,
746
+ };
747
+ }
748
+
749
+ // Native ETH transfer
750
+ return {
751
+ assetType: AssetType.native,
752
+ tokenStandard: TokenStandard.none,
753
+ };
754
+ }
755
+
756
+ // ============================================================================
757
+ // Typed Data Parsing
758
+ // ============================================================================
759
+
760
+ /**
761
+ * Regex to extract large number values from typed data messages
762
+ * Handles cases where JSON.parse would lose precision
763
+ */
764
+ const REGEX_MESSAGE_VALUE_LARGE = /"message"\s*:\s*\{[^}]*"value"\s*:\s*(\d{15,})/u;
765
+
766
+ /**
767
+ * Extracts large number value from a stringified message
768
+ *
769
+ * @param dataToParse - Stringified data to parse
770
+ * @returns Extracted large number or undefined
771
+ */
772
+ function extractLargeMessageValue(dataToParse: string): string | undefined {
773
+ if (typeof dataToParse !== 'string') {
774
+ return undefined;
775
+ }
776
+ return dataToParse.match(REGEX_MESSAGE_VALUE_LARGE)?.[1];
777
+ }
778
+
779
+ /**
780
+ * Parses typed data message while preserving large number precision
781
+ *
782
+ * JSON.parse can lose precision for numbers > Number.MAX_SAFE_INTEGER
783
+ * This function extracts large values and preserves them as strings
784
+ *
785
+ * @param dataToParse - Data to parse (object or string)
786
+ * @returns Parsed data with preserved precision
787
+ *
788
+ * @example
789
+ * ```typescript
790
+ * const parsed = parseTypedDataMessage(signatureRequest.data);
791
+ * console.log(parsed.message.value); // Preserved as string
792
+ * ```
793
+ */
794
+ export function parseTypedDataMessage(
795
+ dataToParse: object | string | number | boolean
796
+ ): any {
797
+ const result =
798
+ typeof dataToParse === 'object'
799
+ ? dataToParse
800
+ : JSON.parse(String(dataToParse));
801
+
802
+ const messageValue = extractLargeMessageValue(String(dataToParse));
803
+
804
+ if (result.message?.value) {
805
+ result.message.value = messageValue || String(result.message.value);
806
+ }
807
+
808
+ return result;
809
+ }
810
+
811
+ // ============================================================================
812
+ // Helper Utilities
813
+ // ============================================================================
814
+
815
+ /**
816
+ * Case-insensitive string comparison
817
+ *
818
+ * @param str1 - First string
819
+ * @param str2 - Second string
820
+ * @returns True if strings are equal (case-insensitive)
821
+ */
822
+ export function isEqualCaseInsensitive(str1: string, str2: string): boolean {
823
+ return str1.toLowerCase() === str2.toLowerCase();
824
+ }