signer-test-sdk-react 0.0.12 → 0.0.14
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/src/AbstraxnProvider.d.ts +2 -5
- package/dist/src/AbstraxnProvider.js +266 -31
- package/dist/src/AbstraxnProvider.js.map +1 -1
- package/dist/src/ExternalWalletButtons.js +37 -10
- package/dist/src/ExternalWalletButtons.js.map +1 -1
- package/dist/src/WalletModal.css +547 -21
- package/dist/src/WalletModal.js +98 -164
- package/dist/src/WalletModal.js.map +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +7 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +169 -20
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -1
- package/dist/src/components/WalletModal/components/ChainSelector.css +180 -0
- package/dist/src/components/WalletModal/components/ChainSelector.d.ts +10 -0
- package/dist/src/components/WalletModal/components/ChainSelector.js +34 -0
- package/dist/src/components/WalletModal/components/ChainSelector.js.map +1 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.css +133 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.d.ts +9 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.js +31 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.css +2 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.d.ts +11 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.js +18 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.css +160 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.d.ts +12 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.js +21 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/PreviewTransactionModal.css +128 -0
- package/dist/src/components/WalletModal/components/PreviewTransactionModal.d.ts +17 -0
- package/dist/src/components/WalletModal/components/PreviewTransactionModal.js +10 -0
- package/dist/src/components/WalletModal/components/PreviewTransactionModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/ReceiveModal.css +101 -0
- package/dist/src/components/WalletModal/components/ReceiveModal.d.ts +8 -0
- package/dist/src/components/WalletModal/components/ReceiveModal.js +22 -0
- package/dist/src/components/WalletModal/components/ReceiveModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/SendModal.css +234 -0
- package/dist/src/components/WalletModal/components/SendModal.d.ts +18 -0
- package/dist/src/components/WalletModal/components/SendModal.js +127 -0
- package/dist/src/components/WalletModal/components/SendModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/SuccessModal.css +86 -0
- package/dist/src/components/WalletModal/components/SuccessModal.d.ts +13 -0
- package/dist/src/components/WalletModal/components/SuccessModal.js +8 -0
- package/dist/src/components/WalletModal/components/SuccessModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/UserAvatar.d.ts +9 -0
- package/dist/src/components/WalletModal/components/UserAvatar.js +31 -0
- package/dist/src/components/WalletModal/components/UserAvatar.js.map +1 -0
- package/dist/src/components/WalletModal/components/index.d.ts +21 -0
- package/dist/src/components/WalletModal/components/index.js +13 -0
- package/dist/src/components/WalletModal/components/index.js.map +1 -0
- package/dist/src/components/WalletModal/hooks/index.d.ts +6 -0
- package/dist/src/components/WalletModal/hooks/index.js +7 -0
- package/dist/src/components/WalletModal/hooks/index.js.map +1 -0
- package/dist/src/components/WalletModal/hooks/useAddressValidation.d.ts +4 -0
- package/dist/src/components/WalletModal/hooks/useAddressValidation.js +17 -0
- package/dist/src/components/WalletModal/hooks/useAddressValidation.js.map +1 -0
- package/dist/src/components/WalletModal/hooks/useAmountValidation.d.ts +4 -0
- package/dist/src/components/WalletModal/hooks/useAmountValidation.js +29 -0
- package/dist/src/components/WalletModal/hooks/useAmountValidation.js.map +1 -0
- package/dist/src/components/WalletModal/hooks/useSendTransaction.d.ts +20 -0
- package/dist/src/components/WalletModal/hooks/useSendTransaction.js +55 -0
- package/dist/src/components/WalletModal/hooks/useSendTransaction.js.map +1 -0
- package/dist/src/components/WalletModal/index.d.ts +5 -0
- package/dist/src/components/WalletModal/index.js +7 -0
- package/dist/src/components/WalletModal/index.js.map +1 -0
- package/dist/src/components/WalletModal/utils/addressUtils.d.ts +19 -0
- package/dist/src/components/WalletModal/utils/addressUtils.js +62 -0
- package/dist/src/components/WalletModal/utils/addressUtils.js.map +1 -0
- package/dist/src/components/WalletModal/utils/formatUtils.d.ts +20 -0
- package/dist/src/components/WalletModal/utils/formatUtils.js +47 -0
- package/dist/src/components/WalletModal/utils/formatUtils.js.map +1 -0
- package/dist/src/components/WalletModal/utils/index.d.ts +5 -0
- package/dist/src/components/WalletModal/utils/index.js +6 -0
- package/dist/src/components/WalletModal/utils/index.js.map +1 -0
- package/dist/src/hooks.d.ts +8937 -0
- package/dist/src/hooks.js +776 -1
- package/dist/src/hooks.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/wagmiConfig.d.ts +2 -1
- package/dist/src/wagmiConfig.js +11 -3
- package/dist/src/wagmiConfig.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -3
package/dist/src/hooks.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* React Hooks for Abstraxn Wallet SDK
|
|
3
3
|
*/
|
|
4
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
4
|
+
import { useState, useCallback, useEffect, useMemo } from 'react';
|
|
5
5
|
import { useAbstraxnWallet } from './AbstraxnProvider';
|
|
6
|
+
import { createPublicClient, createWalletClient, http, getContract, serializeTransaction, parseEther, encodeFunctionData } from 'viem';
|
|
7
|
+
import { useConnectorClient, useAccount } from 'wagmi';
|
|
6
8
|
/**
|
|
7
9
|
* Hook to check if wallet is connected
|
|
8
10
|
*/
|
|
@@ -391,4 +393,777 @@ export function useExternalWalletInfo() {
|
|
|
391
393
|
walletClient,
|
|
392
394
|
};
|
|
393
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Hook to create a publicClient using viem
|
|
398
|
+
* Works for all wallet types: external wallet, Google, email OTP, Discord, X, passkey
|
|
399
|
+
*
|
|
400
|
+
* @param chain - Viem chain object (can use from 'viem/chains' or create custom)
|
|
401
|
+
* @param rpcUrl - RPC URL for the chain
|
|
402
|
+
* @returns Object with publicClient instance from viem
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```tsx
|
|
406
|
+
* import { usePublicClient } from 'signer-test-sdk-react';
|
|
407
|
+
* import { polygonAmoy } from 'viem/chains';
|
|
408
|
+
*
|
|
409
|
+
* function MyComponent() {
|
|
410
|
+
* const { publicClient } = usePublicClient(
|
|
411
|
+
* polygonAmoy,
|
|
412
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
413
|
+
* );
|
|
414
|
+
*
|
|
415
|
+
* // Read operations
|
|
416
|
+
* const balance = await publicClient.getBalance({ address: '0x...' });
|
|
417
|
+
* const tokenBalance = await publicClient.readContract({
|
|
418
|
+
* address: '0x...',
|
|
419
|
+
* abi: erc20Abi,
|
|
420
|
+
* functionName: 'balanceOf',
|
|
421
|
+
* args: ['0x...'],
|
|
422
|
+
* });
|
|
423
|
+
* }
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
export function usePublicClient(chain, rpcUrl) {
|
|
427
|
+
const publicClient = useMemo(() => {
|
|
428
|
+
if (!chain || !rpcUrl) {
|
|
429
|
+
throw new Error('Chain and RPC URL are required');
|
|
430
|
+
}
|
|
431
|
+
return createPublicClient({
|
|
432
|
+
chain,
|
|
433
|
+
transport: http(rpcUrl),
|
|
434
|
+
});
|
|
435
|
+
}, [chain, rpcUrl]);
|
|
436
|
+
return { publicClient };
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Hook to create a walletClient using viem
|
|
440
|
+
* Used ONLY to broadcast signed transactions. No private key involved.
|
|
441
|
+
* Works for all wallet types: external wallet, Google, email OTP, Discord, X, passkey
|
|
442
|
+
*
|
|
443
|
+
* @param chain - Viem chain object (can use from 'viem/chains' or create custom)
|
|
444
|
+
* @param rpcUrl - RPC URL for the chain (optional, will use chain's default RPC if not provided)
|
|
445
|
+
* @returns Object with walletClient instance from viem
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```tsx
|
|
449
|
+
* import { useWalletClient } from 'signer-test-sdk-react';
|
|
450
|
+
* import { polygonAmoy } from 'viem/chains';
|
|
451
|
+
*
|
|
452
|
+
* function MyComponent() {
|
|
453
|
+
* const { walletClient } = useWalletClient(
|
|
454
|
+
* polygonAmoy,
|
|
455
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
456
|
+
* );
|
|
457
|
+
*
|
|
458
|
+
* // Broadcast signed transaction
|
|
459
|
+
* const hash = await walletClient.sendRawTransaction({
|
|
460
|
+
* serializedTransaction: signedTx,
|
|
461
|
+
* });
|
|
462
|
+
* }
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
export function useWalletClient(chain, rpcUrl) {
|
|
466
|
+
const walletClient = useMemo(() => {
|
|
467
|
+
if (!chain) {
|
|
468
|
+
throw new Error('Chain is required');
|
|
469
|
+
}
|
|
470
|
+
// Extract RPC URL from chain if not provided
|
|
471
|
+
let finalRpcUrl = rpcUrl;
|
|
472
|
+
if (!finalRpcUrl) {
|
|
473
|
+
if (chain.rpcUrls?.default?.http) {
|
|
474
|
+
finalRpcUrl = Array.isArray(chain.rpcUrls.default.http)
|
|
475
|
+
? chain.rpcUrls.default.http[0]
|
|
476
|
+
: chain.rpcUrls.default.http;
|
|
477
|
+
}
|
|
478
|
+
else if (chain.rpcUrls?.public?.http) {
|
|
479
|
+
finalRpcUrl = Array.isArray(chain.rpcUrls.public.http)
|
|
480
|
+
? chain.rpcUrls.public.http[0]
|
|
481
|
+
: chain.rpcUrls.public.http;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!finalRpcUrl) {
|
|
485
|
+
throw new Error('RPC URL is required. Either provide it as a parameter or ensure the chain has an RPC URL configured.');
|
|
486
|
+
}
|
|
487
|
+
return createWalletClient({
|
|
488
|
+
chain: chain,
|
|
489
|
+
transport: http(finalRpcUrl),
|
|
490
|
+
});
|
|
491
|
+
}, [chain, rpcUrl]);
|
|
492
|
+
return { walletClient };
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Hook to create a contract instance using viem
|
|
496
|
+
* Works for all wallet types: external wallet, Google, email OTP, Discord, X, passkey
|
|
497
|
+
*
|
|
498
|
+
* @param address - Contract address
|
|
499
|
+
* @param abi - Contract ABI
|
|
500
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
501
|
+
* @returns Object with contract instance from viem
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```tsx
|
|
505
|
+
* import { useContract, usePublicClient } from 'signer-test-sdk-react';
|
|
506
|
+
* import { polygonAmoy } from 'viem/chains';
|
|
507
|
+
* import erc20Abi from './erc20Abi.json';
|
|
508
|
+
*
|
|
509
|
+
* function MyComponent() {
|
|
510
|
+
* const { publicClient } = usePublicClient(
|
|
511
|
+
* polygonAmoy,
|
|
512
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
513
|
+
* );
|
|
514
|
+
*
|
|
515
|
+
* const { contract } = useContract({
|
|
516
|
+
* address: '0x...',
|
|
517
|
+
* abi: erc20Abi,
|
|
518
|
+
* provider: publicClient,
|
|
519
|
+
* });
|
|
520
|
+
*
|
|
521
|
+
* // Read from contract
|
|
522
|
+
* const balance = await contract.read.balanceOf(['0x...']);
|
|
523
|
+
*
|
|
524
|
+
* // Write to contract (requires walletClient)
|
|
525
|
+
* const hash = await contract.write.transfer(['0x...', '1000000000000000000']);
|
|
526
|
+
* }
|
|
527
|
+
* ```
|
|
528
|
+
*/
|
|
529
|
+
export function useContract({ address, abi, provider, }) {
|
|
530
|
+
const contract = useMemo(() => {
|
|
531
|
+
if (!address) {
|
|
532
|
+
throw new Error('Contract address is required');
|
|
533
|
+
}
|
|
534
|
+
if (!abi) {
|
|
535
|
+
throw new Error('Contract ABI is required');
|
|
536
|
+
}
|
|
537
|
+
if (!provider) {
|
|
538
|
+
throw new Error('Provider (publicClient) is required');
|
|
539
|
+
}
|
|
540
|
+
return getContract({
|
|
541
|
+
address,
|
|
542
|
+
abi,
|
|
543
|
+
client: provider,
|
|
544
|
+
});
|
|
545
|
+
}, [address, abi, provider]);
|
|
546
|
+
return { contract };
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Hook to prepare raw transaction data with encoding support
|
|
550
|
+
* Returns { to, value, data } for both native transfers and contract calls
|
|
551
|
+
* Can encode function data internally when ABI, functionName, and args are provided
|
|
552
|
+
*
|
|
553
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
554
|
+
* @returns Object with prepareRawTxn function
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```tsx
|
|
558
|
+
* // Native transfer
|
|
559
|
+
* const { prepareRawTxn } = usePrepareRawTxn(publicClient);
|
|
560
|
+
* const nativeTx = await prepareRawTxn({
|
|
561
|
+
* from: address!,
|
|
562
|
+
* to: '0x...',
|
|
563
|
+
* value: '0.001', // ETH amount
|
|
564
|
+
* });
|
|
565
|
+
* // Returns: { to: '0x...', value: '0x...', data: '0x' }
|
|
566
|
+
*
|
|
567
|
+
* // Contract call with encoding (recommended)
|
|
568
|
+
* const contractTx = await prepareRawTxn({
|
|
569
|
+
* from: address!,
|
|
570
|
+
* to: '0x...', // Contract address
|
|
571
|
+
* abi: erc20Abi,
|
|
572
|
+
* functionName: 'transfer',
|
|
573
|
+
* args: ['0x...', '0.001'], // Automatically converts ETH to wei
|
|
574
|
+
* value: '0', // Optional ETH value
|
|
575
|
+
* });
|
|
576
|
+
* // Returns: { to: '0x...', value: '0x', data: '0xa9059cbb...' }
|
|
577
|
+
*
|
|
578
|
+
* // Contract call with pre-encoded data
|
|
579
|
+
* const contractTx2 = await prepareRawTxn({
|
|
580
|
+
* from: address!,
|
|
581
|
+
* to: '0x...',
|
|
582
|
+
* data: '0xa9059cbb...', // Already encoded
|
|
583
|
+
* });
|
|
584
|
+
* // Returns: { to: '0x...', value: '0x', data: '0xa9059cbb...' }
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
587
|
+
export function usePrepareRawTxn(provider) {
|
|
588
|
+
const prepareRawTxn = useCallback(async ({ from, to, value, data, abi, functionName, args, }) => {
|
|
589
|
+
if (!provider) {
|
|
590
|
+
throw new Error('Provider (publicClient) is required');
|
|
591
|
+
}
|
|
592
|
+
if (!from) {
|
|
593
|
+
throw new Error('From address is required');
|
|
594
|
+
}
|
|
595
|
+
if (!to) {
|
|
596
|
+
throw new Error('To address is required');
|
|
597
|
+
}
|
|
598
|
+
let finalValue;
|
|
599
|
+
let finalData;
|
|
600
|
+
// Check if we need to encode function data
|
|
601
|
+
const needsEncoding = abi && functionName;
|
|
602
|
+
const hasPreEncodedData = data && data !== '0x';
|
|
603
|
+
if (needsEncoding && hasPreEncodedData) {
|
|
604
|
+
throw new Error('Cannot provide both encoded data and encoding parameters (abi/functionName). Use one or the other.');
|
|
605
|
+
}
|
|
606
|
+
// Determine if this is a native transfer
|
|
607
|
+
const isNativeTransfer = !needsEncoding && !hasPreEncodedData;
|
|
608
|
+
if (isNativeTransfer) {
|
|
609
|
+
// Native transfer: convert value to wei and set data to '0x'
|
|
610
|
+
if (!value || value === '0' || value === 0) {
|
|
611
|
+
throw new Error('Value is required for native transfer');
|
|
612
|
+
}
|
|
613
|
+
// Convert value to wei
|
|
614
|
+
const valueStr = typeof value === 'string' ? value.trim() : String(value);
|
|
615
|
+
let valueInWei;
|
|
616
|
+
try {
|
|
617
|
+
valueInWei = parseEther(valueStr);
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
throw new Error(`Invalid value format: "${valueStr}". Value must be a valid number string (e.g., "0.001").`);
|
|
621
|
+
}
|
|
622
|
+
// Convert to hex string
|
|
623
|
+
finalValue = `0x${valueInWei.toString(16)}`;
|
|
624
|
+
finalData = '0x';
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
// Contract call: encode data if needed, or use provided data
|
|
628
|
+
if (needsEncoding) {
|
|
629
|
+
// Encode function data
|
|
630
|
+
if (!abi) {
|
|
631
|
+
throw new Error('ABI is required for encoding function data');
|
|
632
|
+
}
|
|
633
|
+
if (!functionName) {
|
|
634
|
+
throw new Error('Function name is required for encoding function data');
|
|
635
|
+
}
|
|
636
|
+
// Process args to convert ETH amounts to wei for transfer functions
|
|
637
|
+
const processedArgs = (args || []).map((arg, index) => {
|
|
638
|
+
// Check if this is likely an amount parameter for transfer/transferFrom functions
|
|
639
|
+
const isAmountParam = (functionName === 'transfer' || functionName === 'transferFrom') && index === 1;
|
|
640
|
+
if (isAmountParam) {
|
|
641
|
+
// If arg is a string or number that looks like ETH (has decimal point)
|
|
642
|
+
if (typeof arg === 'string' && arg.includes('.')) {
|
|
643
|
+
try {
|
|
644
|
+
// Convert ETH to wei
|
|
645
|
+
const weiAmount = parseEther(arg);
|
|
646
|
+
return weiAmount.toString();
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
throw new Error(`Failed to convert amount "${arg}" to wei. Make sure it's a valid number string (e.g., "0.001").`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
else if (typeof arg === 'number' && arg % 1 !== 0) {
|
|
653
|
+
// Number with decimal places - convert to wei
|
|
654
|
+
try {
|
|
655
|
+
const weiAmount = parseEther(arg.toString());
|
|
656
|
+
return weiAmount.toString();
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
throw new Error(`Failed to convert amount ${arg} to wei. Make sure it's a valid number.`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
// For other args, just return as-is
|
|
664
|
+
return arg;
|
|
665
|
+
});
|
|
666
|
+
try {
|
|
667
|
+
finalData = encodeFunctionData({
|
|
668
|
+
abi: abi,
|
|
669
|
+
functionName: functionName,
|
|
670
|
+
args: processedArgs,
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
if (error instanceof Error && error.message.includes('BigInt')) {
|
|
675
|
+
throw new Error(`Failed to encode function data: ${error.message}. Make sure all numeric arguments are in the correct format (e.g., use wei for amounts, not ETH).`);
|
|
676
|
+
}
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
else if (hasPreEncodedData) {
|
|
681
|
+
// Use provided encoded data
|
|
682
|
+
finalData = data;
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
throw new Error('Either provide encoded data, or provide abi/functionName/args for encoding.');
|
|
686
|
+
}
|
|
687
|
+
// Handle value for contract calls
|
|
688
|
+
if (value && value !== '0' && value !== 0) {
|
|
689
|
+
// Convert value to wei if provided
|
|
690
|
+
const valueStr = typeof value === 'string' ? value.trim() : String(value);
|
|
691
|
+
let valueInWei;
|
|
692
|
+
try {
|
|
693
|
+
valueInWei = parseEther(valueStr);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
throw new Error(`Invalid value format: "${valueStr}". Value must be a valid number string (e.g., "0.001").`);
|
|
697
|
+
}
|
|
698
|
+
finalValue = `0x${valueInWei.toString(16)}`;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
finalValue = '0x0';
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
to,
|
|
706
|
+
value: finalValue,
|
|
707
|
+
data: finalData,
|
|
708
|
+
};
|
|
709
|
+
}, [provider]);
|
|
710
|
+
return { prepareRawTxn };
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Hook to sign transaction using Turnkey API
|
|
714
|
+
* Accepts raw transaction data from usePrepareRawTxn, serializes it, and signs via API
|
|
715
|
+
*
|
|
716
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
717
|
+
* @returns Object with signTxn function
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```tsx
|
|
721
|
+
* const { prepareRawTxn } = usePrepareRawTxn(publicClient);
|
|
722
|
+
* const { signTxn } = useSignTxn(publicClient);
|
|
723
|
+
*
|
|
724
|
+
* // Prepare transaction
|
|
725
|
+
* const rawTx = await prepareRawTxn({
|
|
726
|
+
* from: address!,
|
|
727
|
+
* to: '0x...',
|
|
728
|
+
* value: '0.001',
|
|
729
|
+
* });
|
|
730
|
+
*
|
|
731
|
+
* // Sign transaction
|
|
732
|
+
* const signedTx = await signTxn({
|
|
733
|
+
* from: address!,
|
|
734
|
+
* ...rawTx, // Spread to, value, data from prepareRawTxn
|
|
735
|
+
* });
|
|
736
|
+
* ```
|
|
737
|
+
*/
|
|
738
|
+
export function useSignTxn(provider) {
|
|
739
|
+
const { wallet, isConnected, address, signTransactionViaAPI } = useAbstraxnWallet();
|
|
740
|
+
const signTxn = useCallback(async ({ from, to, value, data, chainId, }) => {
|
|
741
|
+
if (!isConnected || !wallet) {
|
|
742
|
+
throw new Error('Wallet is not connected');
|
|
743
|
+
}
|
|
744
|
+
if (!provider) {
|
|
745
|
+
throw new Error('Provider (publicClient) is required');
|
|
746
|
+
}
|
|
747
|
+
if (!from) {
|
|
748
|
+
throw new Error('From address is required');
|
|
749
|
+
}
|
|
750
|
+
if (!to) {
|
|
751
|
+
throw new Error('To address is required');
|
|
752
|
+
}
|
|
753
|
+
// Get chain ID from provider or use provided one
|
|
754
|
+
const targetChainId = chainId || provider.chain?.id;
|
|
755
|
+
if (!targetChainId) {
|
|
756
|
+
throw new Error('Chain ID is required. Either provide it or ensure provider has a chain configured.');
|
|
757
|
+
}
|
|
758
|
+
// Get nonce
|
|
759
|
+
const nonce = await provider.getTransactionCount({ address: from });
|
|
760
|
+
// Convert hex value to bigint
|
|
761
|
+
// Handle empty hex string "0x" by converting to "0x0"
|
|
762
|
+
const normalizedValue = value === '0x' ? '0x0' : value;
|
|
763
|
+
const valueInWei = BigInt(normalizedValue);
|
|
764
|
+
// Estimate gas
|
|
765
|
+
const gas = await provider.estimateGas({
|
|
766
|
+
account: from,
|
|
767
|
+
to,
|
|
768
|
+
data,
|
|
769
|
+
value: valueInWei,
|
|
770
|
+
});
|
|
771
|
+
// Estimate fees
|
|
772
|
+
const fee = await provider.estimateFeesPerGas();
|
|
773
|
+
// Create unsigned transaction
|
|
774
|
+
const unsignedTx = {
|
|
775
|
+
chainId: targetChainId,
|
|
776
|
+
from,
|
|
777
|
+
to,
|
|
778
|
+
data,
|
|
779
|
+
value: valueInWei,
|
|
780
|
+
nonce,
|
|
781
|
+
gas,
|
|
782
|
+
maxFeePerGas: fee.maxFeePerGas,
|
|
783
|
+
maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
|
|
784
|
+
type: 'eip1559',
|
|
785
|
+
};
|
|
786
|
+
// Serialize transaction
|
|
787
|
+
const serializedTx = serializeTransaction(unsignedTx);
|
|
788
|
+
// Sign transaction via Turnkey API
|
|
789
|
+
const signResult = await signTransactionViaAPI(serializedTx, from);
|
|
790
|
+
return {
|
|
791
|
+
unsignedTransaction: serializedTx,
|
|
792
|
+
signedTransaction: signResult.signedTransaction,
|
|
793
|
+
};
|
|
794
|
+
}, [provider, isConnected, wallet, address, signTransactionViaAPI]);
|
|
795
|
+
return { signTxn, isConnected, address };
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Hook to sign and send transaction using Turnkey API
|
|
799
|
+
* Same as useSignTxn but also sends the transaction on blockchain using publicClient
|
|
800
|
+
*
|
|
801
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
802
|
+
* @returns Object with signAndSendTxn function
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```tsx
|
|
806
|
+
* const { prepareRawTxn } = usePrepareRawTxn(publicClient);
|
|
807
|
+
* const { signAndSendTxn } = useSignAndSendTxn(publicClient);
|
|
808
|
+
*
|
|
809
|
+
* // Prepare transaction
|
|
810
|
+
* const rawTx = await prepareRawTxn({
|
|
811
|
+
* from: address!,
|
|
812
|
+
* to: '0x...',
|
|
813
|
+
* value: '0.001',
|
|
814
|
+
* });
|
|
815
|
+
*
|
|
816
|
+
* // Sign and send transaction
|
|
817
|
+
* const result = await signAndSendTxn({
|
|
818
|
+
* from: address!,
|
|
819
|
+
* ...rawTx, // Spread to, value, data from prepareRawTxn
|
|
820
|
+
* });
|
|
821
|
+
*
|
|
822
|
+
* console.log('Transaction hash:', result.hash);
|
|
823
|
+
* ```
|
|
824
|
+
*/
|
|
825
|
+
export function useSignAndSendTxn(provider) {
|
|
826
|
+
const { wallet, isConnected, address, signTransactionViaAPI } = useAbstraxnWallet();
|
|
827
|
+
const signAndSendTxn = useCallback(async ({ from, to, value, data, chainId, }) => {
|
|
828
|
+
if (!isConnected || !wallet) {
|
|
829
|
+
throw new Error('Wallet is not connected');
|
|
830
|
+
}
|
|
831
|
+
if (!provider) {
|
|
832
|
+
throw new Error('Provider (publicClient) is required');
|
|
833
|
+
}
|
|
834
|
+
if (!from) {
|
|
835
|
+
throw new Error('From address is required');
|
|
836
|
+
}
|
|
837
|
+
if (!to) {
|
|
838
|
+
throw new Error('To address is required');
|
|
839
|
+
}
|
|
840
|
+
// Get chain ID from provider or use provided one
|
|
841
|
+
const targetChainId = chainId || provider.chain?.id;
|
|
842
|
+
if (!targetChainId) {
|
|
843
|
+
throw new Error('Chain ID is required. Either provide it or ensure provider has a chain configured.');
|
|
844
|
+
}
|
|
845
|
+
// Get nonce
|
|
846
|
+
const nonce = await provider.getTransactionCount({ address: from });
|
|
847
|
+
// Convert hex value to bigint
|
|
848
|
+
// Handle empty hex string "0x" by converting to "0x0"
|
|
849
|
+
const normalizedValue = value === '0x' ? '0x0' : value;
|
|
850
|
+
const valueInWei = BigInt(normalizedValue);
|
|
851
|
+
// Estimate gas
|
|
852
|
+
const gas = await provider.estimateGas({
|
|
853
|
+
account: from,
|
|
854
|
+
to,
|
|
855
|
+
data,
|
|
856
|
+
value: valueInWei,
|
|
857
|
+
});
|
|
858
|
+
// Estimate fees
|
|
859
|
+
const fee = await provider.estimateFeesPerGas();
|
|
860
|
+
// Create unsigned transaction
|
|
861
|
+
const unsignedTx = {
|
|
862
|
+
chainId: targetChainId,
|
|
863
|
+
from,
|
|
864
|
+
to,
|
|
865
|
+
data,
|
|
866
|
+
value: valueInWei,
|
|
867
|
+
nonce,
|
|
868
|
+
gas,
|
|
869
|
+
maxFeePerGas: fee.maxFeePerGas,
|
|
870
|
+
maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
|
|
871
|
+
type: 'eip1559',
|
|
872
|
+
};
|
|
873
|
+
// Serialize transaction
|
|
874
|
+
const serializedTx = serializeTransaction(unsignedTx);
|
|
875
|
+
// Sign transaction via Turnkey API
|
|
876
|
+
const signResult = await signTransactionViaAPI(serializedTx, from);
|
|
877
|
+
// Send the signed transaction
|
|
878
|
+
const hash = await provider.sendRawTransaction({
|
|
879
|
+
serializedTransaction: signResult.signedTransaction,
|
|
880
|
+
});
|
|
881
|
+
return {
|
|
882
|
+
hash,
|
|
883
|
+
unsignedTransaction: serializedTx,
|
|
884
|
+
signedTransaction: signResult.signedTransaction,
|
|
885
|
+
};
|
|
886
|
+
}, [provider, isConnected, wallet, address, signTransactionViaAPI]);
|
|
887
|
+
return { signAndSendTxn, isConnected, address };
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Hook to wait for transaction receipt
|
|
891
|
+
* Waits for a transaction to be mined and returns the receipt
|
|
892
|
+
*
|
|
893
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
894
|
+
* @returns Object with waitForTxnReceipt function
|
|
895
|
+
*
|
|
896
|
+
* @example
|
|
897
|
+
* ```tsx
|
|
898
|
+
* const { publicClient } = usePublicClient(
|
|
899
|
+
* polygonAmoy,
|
|
900
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
901
|
+
* );
|
|
902
|
+
* const { waitForTxnReceipt } = useWaitForTxnReceipt(publicClient);
|
|
903
|
+
*
|
|
904
|
+
* // After sending a transaction
|
|
905
|
+
* const hash = '0x...'; // Transaction hash
|
|
906
|
+
*
|
|
907
|
+
* // Wait for receipt
|
|
908
|
+
* const receipt = await waitForTxnReceipt({
|
|
909
|
+
* hash,
|
|
910
|
+
* confirmations: 1, // Optional: number of confirmations to wait for
|
|
911
|
+
* });
|
|
912
|
+
*
|
|
913
|
+
* console.log('Transaction confirmed:', receipt);
|
|
914
|
+
* ```
|
|
915
|
+
*/
|
|
916
|
+
export function useWaitForTxnReceipt(provider) {
|
|
917
|
+
const waitForTxnReceipt = useCallback(async ({ hash, confirmations, timeout, }) => {
|
|
918
|
+
if (!provider) {
|
|
919
|
+
throw new Error('Provider (publicClient) is required');
|
|
920
|
+
}
|
|
921
|
+
if (!hash) {
|
|
922
|
+
throw new Error('Transaction hash is required');
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
// waitForTransactionReceipt is a method on PublicClient
|
|
926
|
+
const receipt = await provider.waitForTransactionReceipt({
|
|
927
|
+
hash,
|
|
928
|
+
confirmations,
|
|
929
|
+
timeout,
|
|
930
|
+
});
|
|
931
|
+
return receipt;
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
if (error instanceof Error) {
|
|
935
|
+
throw new Error(`Failed to wait for transaction receipt: ${error.message}`);
|
|
936
|
+
}
|
|
937
|
+
throw error;
|
|
938
|
+
}
|
|
939
|
+
}, [provider]);
|
|
940
|
+
return { waitForTxnReceipt };
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Hook to read from contract
|
|
944
|
+
* Reads contract data using publicClient.readContract
|
|
945
|
+
*
|
|
946
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
947
|
+
* @returns Object with readContract function
|
|
948
|
+
*
|
|
949
|
+
* @example
|
|
950
|
+
* ```tsx
|
|
951
|
+
* const { publicClient } = usePublicClient(
|
|
952
|
+
* polygonAmoy,
|
|
953
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
954
|
+
* );
|
|
955
|
+
* const { readContract } = useReadContract(publicClient);
|
|
956
|
+
*
|
|
957
|
+
* // Read contract data
|
|
958
|
+
* const balance = await readContract({
|
|
959
|
+
* address: '0x...',
|
|
960
|
+
* abi: erc20Abi,
|
|
961
|
+
* functionName: 'balanceOf',
|
|
962
|
+
* args: ['0x...'],
|
|
963
|
+
* });
|
|
964
|
+
*
|
|
965
|
+
* const name = await readContract({
|
|
966
|
+
* address: '0x...',
|
|
967
|
+
* abi: erc20Abi,
|
|
968
|
+
* functionName: 'name',
|
|
969
|
+
* });
|
|
970
|
+
* ```
|
|
971
|
+
*/
|
|
972
|
+
export function useReadContract(provider) {
|
|
973
|
+
const readContract = useCallback(({ address, abi, functionName, args, }) => {
|
|
974
|
+
if (!provider) {
|
|
975
|
+
throw new Error('Provider (publicClient) is required');
|
|
976
|
+
}
|
|
977
|
+
if (!address) {
|
|
978
|
+
throw new Error('Contract address is required');
|
|
979
|
+
}
|
|
980
|
+
if (!abi) {
|
|
981
|
+
throw new Error('Contract ABI is required');
|
|
982
|
+
}
|
|
983
|
+
if (!functionName) {
|
|
984
|
+
throw new Error('Function name is required');
|
|
985
|
+
}
|
|
986
|
+
try {
|
|
987
|
+
return provider.readContract({
|
|
988
|
+
address,
|
|
989
|
+
abi: abi,
|
|
990
|
+
functionName: functionName,
|
|
991
|
+
args: args,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
catch (error) {
|
|
995
|
+
if (error instanceof Error) {
|
|
996
|
+
throw new Error(`Failed to read contract: ${error.message}`);
|
|
997
|
+
}
|
|
998
|
+
throw error;
|
|
999
|
+
}
|
|
1000
|
+
}, [provider]);
|
|
1001
|
+
return { readContract };
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Hook to get walletClient for external wallets (MetaMask, WalletConnect, etc.)
|
|
1005
|
+
* Uses wagmi's useConnectorClient to get the walletClient
|
|
1006
|
+
*
|
|
1007
|
+
* @returns Object with walletClient instance (null if not connected)
|
|
1008
|
+
*
|
|
1009
|
+
* @example
|
|
1010
|
+
* ```tsx
|
|
1011
|
+
* import { useExternalWalletClient } from 'signer-test-sdk-react';
|
|
1012
|
+
*
|
|
1013
|
+
* function MyComponent() {
|
|
1014
|
+
* const { walletClient, isConnected } = useExternalWalletClient();
|
|
1015
|
+
*
|
|
1016
|
+
* if (!walletClient || !isConnected) {
|
|
1017
|
+
* return <div>Please connect your wallet</div>;
|
|
1018
|
+
* }
|
|
1019
|
+
*
|
|
1020
|
+
* // Use walletClient for transactions
|
|
1021
|
+
* }
|
|
1022
|
+
* ```
|
|
1023
|
+
*/
|
|
1024
|
+
export function useExternalWalletClient() {
|
|
1025
|
+
const { isExternalWalletConnected } = useExternalWallet();
|
|
1026
|
+
const { data: connectorClient, isPending, isError } = useConnectorClient();
|
|
1027
|
+
const { isConnected: wagmiIsConnected } = useAccount();
|
|
1028
|
+
// Check both our internal state and wagmi's state
|
|
1029
|
+
// useConnectorClient might return undefined even when connected, so we check both
|
|
1030
|
+
const isActuallyConnected = isExternalWalletConnected && wagmiIsConnected;
|
|
1031
|
+
const walletClient = (isActuallyConnected && connectorClient) ? connectorClient : null;
|
|
1032
|
+
return {
|
|
1033
|
+
walletClient,
|
|
1034
|
+
isConnected: isActuallyConnected && !!connectorClient,
|
|
1035
|
+
isPending,
|
|
1036
|
+
isError,
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Hook to write to contract (for external wallets like MetaMask, WalletConnect, etc.)
|
|
1041
|
+
* Uses walletClient from useExternalWalletClient to send transactions
|
|
1042
|
+
*
|
|
1043
|
+
* @returns Object with writeContract function
|
|
1044
|
+
*
|
|
1045
|
+
* @example
|
|
1046
|
+
* ```tsx
|
|
1047
|
+
* import { useWriteContract } from 'signer-test-sdk-react';
|
|
1048
|
+
* import erc20Abi from './erc20Abi.json';
|
|
1049
|
+
*
|
|
1050
|
+
* function MyComponent() {
|
|
1051
|
+
* const { writeContract } = useWriteContract();
|
|
1052
|
+
*
|
|
1053
|
+
* const handleTransfer = async () => {
|
|
1054
|
+
* try {
|
|
1055
|
+
* // Write to contract
|
|
1056
|
+
* const hash = await writeContract({
|
|
1057
|
+
* address: '0x...', // Contract address
|
|
1058
|
+
* abi: erc20Abi,
|
|
1059
|
+
* functionName: 'transfer',
|
|
1060
|
+
* args: ['0x...', '1000000000000000000'], // to, amount in wei
|
|
1061
|
+
* });
|
|
1062
|
+
*
|
|
1063
|
+
* console.log('Transaction hash:', hash);
|
|
1064
|
+
* } catch (error) {
|
|
1065
|
+
* console.error('Transaction failed:', error);
|
|
1066
|
+
* }
|
|
1067
|
+
* };
|
|
1068
|
+
*
|
|
1069
|
+
* // With ETH value
|
|
1070
|
+
* const handleDeposit = async () => {
|
|
1071
|
+
* const hash = await writeContract({
|
|
1072
|
+
* address: '0x...',
|
|
1073
|
+
* abi: contractAbi,
|
|
1074
|
+
* functionName: 'deposit',
|
|
1075
|
+
* value: parseEther('0.1'), // Send 0.1 ETH with the transaction
|
|
1076
|
+
* });
|
|
1077
|
+
* };
|
|
1078
|
+
*
|
|
1079
|
+
* return <button onClick={handleTransfer}>Transfer Tokens</button>;
|
|
1080
|
+
* }
|
|
1081
|
+
* ```
|
|
1082
|
+
*/
|
|
1083
|
+
export function useWriteContract() {
|
|
1084
|
+
const { walletClient } = useExternalWalletClient();
|
|
1085
|
+
const writeContract = useCallback(({ address, abi, functionName, args, value, account, gas, gasPrice, maxFeePerGas, maxPriorityFeePerGas, nonce, }) => {
|
|
1086
|
+
if (!walletClient) {
|
|
1087
|
+
throw new Error('WalletClient is not available. Please connect an external wallet (MetaMask, WalletConnect, etc.).');
|
|
1088
|
+
}
|
|
1089
|
+
if (!address) {
|
|
1090
|
+
throw new Error('Contract address is required');
|
|
1091
|
+
}
|
|
1092
|
+
if (!abi) {
|
|
1093
|
+
throw new Error('Contract ABI is required');
|
|
1094
|
+
}
|
|
1095
|
+
if (!functionName) {
|
|
1096
|
+
throw new Error('Function name is required');
|
|
1097
|
+
}
|
|
1098
|
+
try {
|
|
1099
|
+
// Cast to any to avoid type issues with different viem versions
|
|
1100
|
+
const walletClientAny = walletClient;
|
|
1101
|
+
// Try using writeContract method directly if available
|
|
1102
|
+
if (walletClientAny && typeof walletClientAny.writeContract === 'function') {
|
|
1103
|
+
return walletClientAny.writeContract({
|
|
1104
|
+
address,
|
|
1105
|
+
abi: abi,
|
|
1106
|
+
functionName: functionName,
|
|
1107
|
+
args: args,
|
|
1108
|
+
value,
|
|
1109
|
+
account,
|
|
1110
|
+
gas,
|
|
1111
|
+
gasPrice,
|
|
1112
|
+
maxFeePerGas,
|
|
1113
|
+
maxPriorityFeePerGas,
|
|
1114
|
+
nonce,
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
// If writeContract is not available, use getContract to create a contract instance
|
|
1118
|
+
// This works with wagmi's connectorClient
|
|
1119
|
+
const contract = getContract({
|
|
1120
|
+
address,
|
|
1121
|
+
abi: abi,
|
|
1122
|
+
client: walletClientAny,
|
|
1123
|
+
});
|
|
1124
|
+
// Get the write function from the contract
|
|
1125
|
+
const writeFunction = contract.write?.[functionName];
|
|
1126
|
+
if (!writeFunction || typeof writeFunction !== 'function') {
|
|
1127
|
+
throw new Error(`Function "${functionName}" not found in contract ABI or is not a write function. ` +
|
|
1128
|
+
'Make sure the function exists in the ABI and is a state-changing function.');
|
|
1129
|
+
}
|
|
1130
|
+
// Prepare the call options (viem contract write methods accept options as the last parameter)
|
|
1131
|
+
const hasOptions = value !== undefined || account !== undefined || gas !== undefined ||
|
|
1132
|
+
gasPrice !== undefined || maxFeePerGas !== undefined ||
|
|
1133
|
+
maxPriorityFeePerGas !== undefined || nonce !== undefined;
|
|
1134
|
+
// Viem contract write methods expect args as an array, not spread
|
|
1135
|
+
const functionArgs = args || [];
|
|
1136
|
+
if (hasOptions) {
|
|
1137
|
+
const callOptions = {};
|
|
1138
|
+
if (value !== undefined)
|
|
1139
|
+
callOptions.value = value;
|
|
1140
|
+
if (account !== undefined)
|
|
1141
|
+
callOptions.account = account;
|
|
1142
|
+
if (gas !== undefined)
|
|
1143
|
+
callOptions.gas = gas;
|
|
1144
|
+
if (gasPrice !== undefined)
|
|
1145
|
+
callOptions.gasPrice = gasPrice;
|
|
1146
|
+
if (maxFeePerGas !== undefined)
|
|
1147
|
+
callOptions.maxFeePerGas = maxFeePerGas;
|
|
1148
|
+
if (maxPriorityFeePerGas !== undefined)
|
|
1149
|
+
callOptions.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
|
1150
|
+
if (nonce !== undefined)
|
|
1151
|
+
callOptions.nonce = nonce;
|
|
1152
|
+
// Call the write function with args array and options object
|
|
1153
|
+
return writeFunction(functionArgs, callOptions);
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
// Call the write function with args array only
|
|
1157
|
+
return writeFunction(functionArgs);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
catch (error) {
|
|
1161
|
+
if (error instanceof Error) {
|
|
1162
|
+
throw new Error(`Failed to write contract: ${error.message}`);
|
|
1163
|
+
}
|
|
1164
|
+
throw error;
|
|
1165
|
+
}
|
|
1166
|
+
}, [walletClient]);
|
|
1167
|
+
return { writeContract };
|
|
1168
|
+
}
|
|
394
1169
|
//# sourceMappingURL=hooks.js.map
|