@abstraxn/signer-react 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/src/AbstraxnProvider.d.ts +9 -0
- package/dist/src/AbstraxnProvider.js +21 -0
- package/dist/src/AbstraxnProvider.js.map +1 -0
- package/dist/src/ConnectButton.css +217 -0
- package/dist/src/ConnectButton.d.ts +71 -0
- package/dist/src/ConnectButton.js +102 -0
- package/dist/src/ConnectButton.js.map +1 -0
- package/dist/src/ExternalWalletButtons.css +319 -0
- package/dist/src/ExternalWalletButtons.d.ts +56 -0
- package/dist/src/ExternalWalletButtons.js +272 -0
- package/dist/src/ExternalWalletButtons.js.map +1 -0
- package/dist/src/OnboardingUI.d.ts +63 -0
- package/dist/src/OnboardingUI.js +66 -0
- package/dist/src/OnboardingUI.js.map +1 -0
- package/dist/src/WalletModal.css +2319 -0
- package/dist/src/WalletModal.d.ts +7 -0
- package/dist/src/WalletModal.js +322 -0
- package/dist/src/WalletModal.js.map +1 -0
- package/dist/src/chains.d.ts +56 -0
- package/dist/src/chains.js +291 -0
- package/dist/src/chains.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProvider.d.ts +12 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProvider.js +146 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProvider.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.d.ts +25 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js +3086 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithWagmi.d.ts +8 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithWagmi.js +46 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithWagmi.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithoutWagmi.d.ts +8 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithoutWagmi.js +12 -0
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderWithoutWagmi.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/context.d.ts +2 -0
- package/dist/src/components/AbstraxnProvider/context.js +6 -0
- package/dist/src/components/AbstraxnProvider/context.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/index.d.ts +6 -0
- package/dist/src/components/AbstraxnProvider/index.js +7 -0
- package/dist/src/components/AbstraxnProvider/index.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.d.ts +30 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.js +49 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnWallet.d.ts +2 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnWallet.js +13 -0
- package/dist/src/components/AbstraxnProvider/useAbstraxnWallet.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.d.ts +22 -0
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js +242 -0
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.d.ts +25 -0
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js +539 -0
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js.map +1 -0
- package/dist/src/components/AbstraxnProvider/utils.d.ts +41 -0
- package/dist/src/components/AbstraxnProvider/utils.js +139 -0
- package/dist/src/components/AbstraxnProvider/utils.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUI.css +1062 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +15 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js +318 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +265 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +3782 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +16 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.js +27 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/Modal.d.ts +14 -0
- package/dist/src/components/OnboardingUI/components/Modal.js +61 -0
- package/dist/src/components/OnboardingUI/components/Modal.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +20 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.js +72 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +14 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js +22 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +15 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.js +20 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/index.d.ts +13 -0
- package/dist/src/components/OnboardingUI/components/index.js +9 -0
- package/dist/src/components/OnboardingUI/components/index.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/index.d.ts +7 -0
- package/dist/src/components/OnboardingUI/hooks/index.js +6 -0
- package/dist/src/components/OnboardingUI/hooks/index.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.d.ts +11 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +243 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +21 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +153 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -0
- package/dist/src/components/OnboardingUI/index.d.ts +12 -0
- package/dist/src/components/OnboardingUI/index.js +15 -0
- package/dist/src/components/OnboardingUI/index.js.map +1 -0
- package/dist/src/components/QRCode.d.ts +13 -0
- package/dist/src/components/QRCode.js +6 -0
- package/dist/src/components/QRCode.js.map +1 -0
- package/dist/src/components/WalletModal/components/ChainSelector.css +327 -0
- package/dist/src/components/WalletModal/components/ChainSelector.d.ts +11 -0
- package/dist/src/components/WalletModal/components/ChainSelector.js +75 -0
- package/dist/src/components/WalletModal/components/ChainSelector.js.map +1 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.css +134 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.d.ts +14 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.js +26 -0
- package/dist/src/components/WalletModal/components/ExportKeyModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.css +107 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.d.ts +17 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.js +20 -0
- package/dist/src/components/WalletModal/components/ExportWarningModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.css +246 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.d.ts +12 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.js +36 -0
- package/dist/src/components/WalletModal/components/ManageWalletModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/PreviewTransactionModal.css +127 -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 +136 -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 +277 -0
- package/dist/src/components/WalletModal/components/SendModal.d.ts +16 -0
- package/dist/src/components/WalletModal/components/SendModal.js +219 -0
- package/dist/src/components/WalletModal/components/SendModal.js.map +1 -0
- package/dist/src/components/WalletModal/components/SuccessModal.css +85 -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/TokenSelectorModal.css +240 -0
- package/dist/src/components/WalletModal/components/TokenSelectorModal.d.ts +21 -0
- package/dist/src/components/WalletModal/components/TokenSelectorModal.js +44 -0
- package/dist/src/components/WalletModal/components/TokenSelectorModal.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 +23 -0
- package/dist/src/components/WalletModal/components/index.js +14 -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/connectors.d.ts +27 -0
- package/dist/src/connectors.js +70 -0
- package/dist/src/connectors.js.map +1 -0
- package/dist/src/hooks.d.ts +13136 -0
- package/dist/src/hooks.js +1358 -0
- package/dist/src/hooks.js.map +1 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.js +14 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +224 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/wagmiConfig.d.ts +16 -0
- package/dist/src/wagmiConfig.js +103 -0
- package/dist/src/wagmiConfig.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,1358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Hooks for Abstraxn Wallet SDK
|
|
3
|
+
*/
|
|
4
|
+
import { useState, useCallback, useEffect, useMemo } from 'react';
|
|
5
|
+
import { useAbstraxnWallet } from './AbstraxnProvider';
|
|
6
|
+
import { createPublicClient, createWalletClient, http, getContract, serializeTransaction, parseEther, encodeFunctionData } from 'viem';
|
|
7
|
+
import { useWalletClient as useWagmiWalletClient, useAccount, useConfig, useChainId as useWagmiChainId, useSwitchChain as useWagmiSwitchChain, useSignMessage as useWagmiSignMessage } from 'wagmi';
|
|
8
|
+
import { getWalletClient, switchChain } from '@wagmi/core';
|
|
9
|
+
import { getConnectorMeta } from './connectors';
|
|
10
|
+
import { getChainById } from './chains';
|
|
11
|
+
/**
|
|
12
|
+
* Hook to check if wallet is connected
|
|
13
|
+
*/
|
|
14
|
+
export function useIsConnected() {
|
|
15
|
+
const { isConnected } = useAbstraxnWallet();
|
|
16
|
+
return isConnected;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Hook to get wallet address
|
|
20
|
+
*/
|
|
21
|
+
export function useAddress() {
|
|
22
|
+
const { address } = useAbstraxnWallet();
|
|
23
|
+
return address;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Hook to get current user and whoami information
|
|
27
|
+
* Returns an object with both user and whoami data
|
|
28
|
+
*/
|
|
29
|
+
export function useAuthContext() {
|
|
30
|
+
const { user, whoami } = useAbstraxnWallet();
|
|
31
|
+
return { user, whoami };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hook to get current chain ID
|
|
35
|
+
*/
|
|
36
|
+
export function useChainId() {
|
|
37
|
+
const { chainId } = useAbstraxnWallet();
|
|
38
|
+
return chainId;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Hook to get error state
|
|
42
|
+
*/
|
|
43
|
+
export function useError() {
|
|
44
|
+
const { error } = useAbstraxnWallet();
|
|
45
|
+
return error;
|
|
46
|
+
}
|
|
47
|
+
// Re-export core hook for modules that import from './hooks'
|
|
48
|
+
export { useAbstraxnWallet } from './AbstraxnProvider';
|
|
49
|
+
/**
|
|
50
|
+
* Hook to get wallet instance (for advanced usage)
|
|
51
|
+
*/
|
|
52
|
+
export function useWallet() {
|
|
53
|
+
const { wallet } = useAbstraxnWallet();
|
|
54
|
+
return wallet;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Hook to export wallet private key
|
|
58
|
+
* Returns the export bundle containing the private key
|
|
59
|
+
* Automatically picks the connected wallet address (EVM or Solana) from whoami
|
|
60
|
+
*/
|
|
61
|
+
export function useExportWallet() {
|
|
62
|
+
const { wallet, isConnected, address } = useAbstraxnWallet();
|
|
63
|
+
const exportWallet = async (targetPublicKey, privateKey, blockchain) => {
|
|
64
|
+
if (!isConnected || !wallet) {
|
|
65
|
+
throw new Error('Wallet is not connected');
|
|
66
|
+
}
|
|
67
|
+
// Pass blockchain type to wallet.exportWallet, which will load address from whoami
|
|
68
|
+
return await wallet.exportWallet(targetPublicKey, privateKey, blockchain);
|
|
69
|
+
};
|
|
70
|
+
return { exportWallet, isConnected, address };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Hook to call whoami API
|
|
74
|
+
* Returns whoami data, loading state, error, and a refresh function
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* const { whoami, loading, error, refreshWhoami } = useWhoami();
|
|
79
|
+
*
|
|
80
|
+
* useEffect(() => {
|
|
81
|
+
* refreshWhoami();
|
|
82
|
+
* }, []);
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function useWhoami() {
|
|
86
|
+
const { isConnected, whoami: cachedWhoami, refreshWhoami: refreshWhoamiFromContext, loading: contextLoading, error: contextError } = useAbstraxnWallet();
|
|
87
|
+
const [whoami, setWhoami] = useState(cachedWhoami);
|
|
88
|
+
const [loading, setLoading] = useState(false);
|
|
89
|
+
const [error, setError] = useState(null);
|
|
90
|
+
const refreshWhoami = useCallback(async () => {
|
|
91
|
+
if (!isConnected) {
|
|
92
|
+
setError(new Error('Wallet is not connected'));
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
setLoading(true);
|
|
96
|
+
setError(null);
|
|
97
|
+
try {
|
|
98
|
+
// Use refreshWhoami from context which updates both API and context state
|
|
99
|
+
// This will handle the check for external wallets and connection status internally
|
|
100
|
+
const whoamiData = await refreshWhoamiFromContext();
|
|
101
|
+
setWhoami(whoamiData);
|
|
102
|
+
return whoamiData;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
// Don't throw error if wallet is not connected (might be during disconnect)
|
|
106
|
+
const error = err instanceof Error ? err : new Error('Failed to refresh whoami information');
|
|
107
|
+
// Only set error if it's not a connection error
|
|
108
|
+
if (!error.message.includes('not connected') && !error.message.includes('Wallet is not connected')) {
|
|
109
|
+
setError(error);
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
setLoading(false);
|
|
115
|
+
}
|
|
116
|
+
}, [isConnected, refreshWhoamiFromContext]);
|
|
117
|
+
// Update whoami when cached value changes
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (cachedWhoami) {
|
|
120
|
+
setWhoami(cachedWhoami);
|
|
121
|
+
}
|
|
122
|
+
}, [cachedWhoami]);
|
|
123
|
+
// Use context loading/error if hook is not in loading state
|
|
124
|
+
const displayLoading = loading || contextLoading;
|
|
125
|
+
const displayError = error || contextError;
|
|
126
|
+
return {
|
|
127
|
+
whoami,
|
|
128
|
+
loading: displayLoading,
|
|
129
|
+
error: displayError,
|
|
130
|
+
refreshWhoami,
|
|
131
|
+
isConnected,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Hook to access external wallet functionality
|
|
136
|
+
* Returns external wallet connection state and methods
|
|
137
|
+
*/
|
|
138
|
+
export function useExternalWallet() {
|
|
139
|
+
const { connectExternalWallet, disconnectExternalWallet, isExternalWalletConnected, externalWalletAddress, externalWalletChainId, externalWalletBalance, externalWalletNetwork, availableConnectors, switchExternalWalletChain, } = useAbstraxnWallet();
|
|
140
|
+
return {
|
|
141
|
+
connectExternalWallet: connectExternalWallet || (async () => {
|
|
142
|
+
throw new Error('External wallets are not enabled');
|
|
143
|
+
}),
|
|
144
|
+
disconnectExternalWallet: disconnectExternalWallet || (async () => {
|
|
145
|
+
throw new Error('External wallets are not enabled');
|
|
146
|
+
}),
|
|
147
|
+
isExternalWalletConnected: isExternalWalletConnected ?? false,
|
|
148
|
+
externalWalletAddress: externalWalletAddress ?? null,
|
|
149
|
+
externalWalletChainId: externalWalletChainId ?? null,
|
|
150
|
+
externalWalletBalance: externalWalletBalance ?? null,
|
|
151
|
+
externalWalletNetwork: externalWalletNetwork ?? null,
|
|
152
|
+
availableConnectors: availableConnectors ?? [],
|
|
153
|
+
switchExternalWalletChain: switchExternalWalletChain || (async () => {
|
|
154
|
+
throw new Error('External wallets are not enabled');
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Hook to get external wallet balance
|
|
160
|
+
* Returns balance in wei (BigInt) and formatted balance
|
|
161
|
+
*/
|
|
162
|
+
export function useExternalWalletBalance() {
|
|
163
|
+
const { externalWalletBalance, externalWalletAddress, isExternalWalletConnected } = useExternalWallet();
|
|
164
|
+
const [formattedBalance, setFormattedBalance] = useState('0');
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (externalWalletBalance && externalWalletBalance > 0n) {
|
|
167
|
+
// Convert from wei to ETH (18 decimals)
|
|
168
|
+
const balanceInEth = Number(externalWalletBalance) / 1e18;
|
|
169
|
+
setFormattedBalance(balanceInEth.toFixed(6));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
setFormattedBalance('0');
|
|
173
|
+
}
|
|
174
|
+
}, [externalWalletBalance]);
|
|
175
|
+
return {
|
|
176
|
+
balance: externalWalletBalance,
|
|
177
|
+
formattedBalance,
|
|
178
|
+
address: externalWalletAddress,
|
|
179
|
+
isConnected: isExternalWalletConnected,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Hook to get external wallet chain information
|
|
184
|
+
* Returns chainId, network name, and switch chain function
|
|
185
|
+
*/
|
|
186
|
+
export function useExternalWalletChain() {
|
|
187
|
+
const { externalWalletChainId, externalWalletNetwork, switchExternalWalletChain, isExternalWalletConnected } = useExternalWallet();
|
|
188
|
+
return {
|
|
189
|
+
chainId: externalWalletChainId,
|
|
190
|
+
network: externalWalletNetwork,
|
|
191
|
+
switchChain: switchExternalWalletChain,
|
|
192
|
+
isConnected: isExternalWalletConnected,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Comprehensive hook for external wallet information
|
|
197
|
+
* Returns chainId, network, balance, switchChain function, connection status, and walletClient
|
|
198
|
+
* This is the main hook to use for external wallet operations
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```tsx
|
|
202
|
+
* const { chainId, network, balance, formattedBalance, switchChain, isConnected, address, walletClient } = useExternalWalletInfo();
|
|
203
|
+
*
|
|
204
|
+
* // Switch chain
|
|
205
|
+
* await switchChain(137); // Switch to Polygon
|
|
206
|
+
*
|
|
207
|
+
* // Send transaction using walletClient
|
|
208
|
+
* const txHash = await walletClient.sendTransaction({
|
|
209
|
+
* to: '0x...',
|
|
210
|
+
* value: '0.1',
|
|
211
|
+
* });
|
|
212
|
+
*
|
|
213
|
+
* // Sign message using walletClient
|
|
214
|
+
* const signature = await walletClient.signMessage('Hello World');
|
|
215
|
+
*
|
|
216
|
+
* // Display info
|
|
217
|
+
* <div>
|
|
218
|
+
* <p>Network: {network}</p>
|
|
219
|
+
* <p>Balance: {formattedBalance} ETH</p>
|
|
220
|
+
* </div>
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
export function useExternalWalletInfo() {
|
|
224
|
+
const { externalWalletChainId, externalWalletNetwork, switchExternalWalletChain, isExternalWalletConnected, externalWalletAddress, externalWalletBalance, } = useExternalWallet();
|
|
225
|
+
const { sendTransaction, signMessage, signTransaction, switchChain, } = useAbstraxnWallet();
|
|
226
|
+
const [formattedBalance, setFormattedBalance] = useState('0');
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (externalWalletBalance && externalWalletBalance > 0n) {
|
|
229
|
+
// Convert from wei to ETH (18 decimals)
|
|
230
|
+
const balanceInEth = Number(externalWalletBalance) / 1e18;
|
|
231
|
+
setFormattedBalance(balanceInEth.toFixed(6));
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
setFormattedBalance('0');
|
|
235
|
+
}
|
|
236
|
+
}, [externalWalletBalance]);
|
|
237
|
+
// Create walletClient object for external wallet transactions
|
|
238
|
+
const walletClient = {
|
|
239
|
+
/**
|
|
240
|
+
* Send a transaction using external wallet
|
|
241
|
+
* @param tx - Transaction request object
|
|
242
|
+
* @returns Promise resolving to transaction response with hash
|
|
243
|
+
*/
|
|
244
|
+
sendTransaction: async (tx) => {
|
|
245
|
+
if (!isExternalWalletConnected) {
|
|
246
|
+
throw new Error('External wallet is not connected');
|
|
247
|
+
}
|
|
248
|
+
return await sendTransaction(tx);
|
|
249
|
+
},
|
|
250
|
+
/**
|
|
251
|
+
* Sign a message using external wallet
|
|
252
|
+
* @param message - Message string to sign
|
|
253
|
+
* @returns Promise resolving to signature string
|
|
254
|
+
*/
|
|
255
|
+
signMessage: async (message) => {
|
|
256
|
+
if (!isExternalWalletConnected) {
|
|
257
|
+
throw new Error('External wallet is not connected');
|
|
258
|
+
}
|
|
259
|
+
return await signMessage(message);
|
|
260
|
+
},
|
|
261
|
+
/**
|
|
262
|
+
* Sign a transaction using external wallet
|
|
263
|
+
* @param tx - Transaction request object
|
|
264
|
+
* @returns Promise resolving to signed transaction string
|
|
265
|
+
*/
|
|
266
|
+
signTransaction: async (tx) => {
|
|
267
|
+
if (!isExternalWalletConnected) {
|
|
268
|
+
throw new Error('External wallet is not connected');
|
|
269
|
+
}
|
|
270
|
+
return await signTransaction(tx);
|
|
271
|
+
},
|
|
272
|
+
/**
|
|
273
|
+
* Switch chain on external wallet
|
|
274
|
+
* @param chainId - Chain ID to switch to
|
|
275
|
+
* @returns Promise that resolves when chain is switched
|
|
276
|
+
*/
|
|
277
|
+
switchChain: async (chainId) => {
|
|
278
|
+
if (!isExternalWalletConnected) {
|
|
279
|
+
throw new Error('External wallet is not connected');
|
|
280
|
+
}
|
|
281
|
+
return await switchChain(chainId);
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
return {
|
|
285
|
+
// Chain information
|
|
286
|
+
chainId: externalWalletChainId,
|
|
287
|
+
network: externalWalletNetwork,
|
|
288
|
+
// Balance information
|
|
289
|
+
balance: externalWalletBalance, // BigInt in wei
|
|
290
|
+
formattedBalance, // String formatted as ETH (e.g., "0.123456")
|
|
291
|
+
// Chain switching
|
|
292
|
+
switchChain: switchExternalWalletChain,
|
|
293
|
+
// Connection status
|
|
294
|
+
isConnected: isExternalWalletConnected,
|
|
295
|
+
address: externalWalletAddress,
|
|
296
|
+
// Wallet client for executing transactions
|
|
297
|
+
walletClient,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Hook to create a publicClient using viem
|
|
302
|
+
* Works for all wallet types: external wallet, Google, email OTP, Discord, X, passkey
|
|
303
|
+
*
|
|
304
|
+
* @param chain - Viem chain object (can use from 'viem/chains' or create custom)
|
|
305
|
+
* @param rpcUrl - RPC URL for the chain
|
|
306
|
+
* @returns Object with publicClient instance from viem
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```tsx
|
|
310
|
+
* import { usePublicClient } from '@abstraxn/signer-react';
|
|
311
|
+
* import { polygonAmoy } from 'viem/chains';
|
|
312
|
+
*
|
|
313
|
+
* function MyComponent() {
|
|
314
|
+
* const { publicClient } = usePublicClient(
|
|
315
|
+
* polygonAmoy,
|
|
316
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
317
|
+
* );
|
|
318
|
+
*
|
|
319
|
+
* // Read operations
|
|
320
|
+
* const balance = await publicClient.getBalance({ address: '0x...' });
|
|
321
|
+
* const tokenBalance = await publicClient.readContract({
|
|
322
|
+
* address: '0x...',
|
|
323
|
+
* abi: erc20Abi,
|
|
324
|
+
* functionName: 'balanceOf',
|
|
325
|
+
* args: ['0x...'],
|
|
326
|
+
* });
|
|
327
|
+
* }
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
export function usePublicClient(chain, rpcUrl) {
|
|
331
|
+
const publicClient = useMemo(() => {
|
|
332
|
+
if (!chain || !rpcUrl) {
|
|
333
|
+
throw new Error('Chain and RPC URL are required');
|
|
334
|
+
}
|
|
335
|
+
return createPublicClient({
|
|
336
|
+
chain,
|
|
337
|
+
transport: http(rpcUrl),
|
|
338
|
+
});
|
|
339
|
+
}, [chain, rpcUrl]);
|
|
340
|
+
return { publicClient };
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Hook to create a walletClient using viem
|
|
344
|
+
* Used ONLY to broadcast signed transactions. No private key involved.
|
|
345
|
+
* Works for all wallet types: external wallet, Google, email OTP, Discord, X, passkey
|
|
346
|
+
*
|
|
347
|
+
* @param chain - Viem chain object (can use from 'viem/chains' or create custom)
|
|
348
|
+
* @param rpcUrl - RPC URL for the chain (optional, will use chain's default RPC if not provided)
|
|
349
|
+
* @returns Object with walletClient instance from viem
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```tsx
|
|
353
|
+
* import { useWalletClient } from '@abstraxn/signer-react';
|
|
354
|
+
* import { polygonAmoy } from 'viem/chains';
|
|
355
|
+
*
|
|
356
|
+
* function MyComponent() {
|
|
357
|
+
* const { walletClient } = useWalletClient(
|
|
358
|
+
* polygonAmoy,
|
|
359
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
360
|
+
* );
|
|
361
|
+
*
|
|
362
|
+
* // Broadcast signed transaction
|
|
363
|
+
* const hash = await walletClient.sendRawTransaction({
|
|
364
|
+
* serializedTransaction: signedTx,
|
|
365
|
+
* });
|
|
366
|
+
* }
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
export function useWalletClient(chain, rpcUrl) {
|
|
370
|
+
const walletClient = useMemo(() => {
|
|
371
|
+
if (!chain) {
|
|
372
|
+
throw new Error('Chain is required');
|
|
373
|
+
}
|
|
374
|
+
// Extract RPC URL from chain if not provided
|
|
375
|
+
let finalRpcUrl = rpcUrl;
|
|
376
|
+
if (!finalRpcUrl) {
|
|
377
|
+
if (chain.rpcUrls?.default?.http) {
|
|
378
|
+
finalRpcUrl = Array.isArray(chain.rpcUrls.default.http)
|
|
379
|
+
? chain.rpcUrls.default.http[0]
|
|
380
|
+
: chain.rpcUrls.default.http;
|
|
381
|
+
}
|
|
382
|
+
else if (chain.rpcUrls?.public?.http) {
|
|
383
|
+
finalRpcUrl = Array.isArray(chain.rpcUrls.public.http)
|
|
384
|
+
? chain.rpcUrls.public.http[0]
|
|
385
|
+
: chain.rpcUrls.public.http;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (!finalRpcUrl) {
|
|
389
|
+
throw new Error('RPC URL is required. Either provide it as a parameter or ensure the chain has an RPC URL configured.');
|
|
390
|
+
}
|
|
391
|
+
return createWalletClient({
|
|
392
|
+
chain: chain,
|
|
393
|
+
transport: http(finalRpcUrl),
|
|
394
|
+
});
|
|
395
|
+
}, [chain, rpcUrl]);
|
|
396
|
+
return { walletClient };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Hook to create a contract instance using viem
|
|
400
|
+
* Works for all wallet types: external wallet, Google, email OTP, Discord, X, passkey
|
|
401
|
+
*
|
|
402
|
+
* @param address - Contract address
|
|
403
|
+
* @param abi - Contract ABI
|
|
404
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
405
|
+
* @returns Object with contract instance from viem
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```tsx
|
|
409
|
+
* import { useContract, usePublicClient } from '@abstraxn/signer-react';
|
|
410
|
+
* import { polygonAmoy } from 'viem/chains';
|
|
411
|
+
* import erc20Abi from './erc20Abi.json';
|
|
412
|
+
*
|
|
413
|
+
* function MyComponent() {
|
|
414
|
+
* const { publicClient } = usePublicClient(
|
|
415
|
+
* polygonAmoy,
|
|
416
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
417
|
+
* );
|
|
418
|
+
*
|
|
419
|
+
* const { contract } = useContract({
|
|
420
|
+
* address: '0x...',
|
|
421
|
+
* abi: erc20Abi,
|
|
422
|
+
* provider: publicClient,
|
|
423
|
+
* });
|
|
424
|
+
*
|
|
425
|
+
* // Read from contract
|
|
426
|
+
* const balance = await contract.read.balanceOf(['0x...']);
|
|
427
|
+
*
|
|
428
|
+
* // Write to contract (requires walletClient)
|
|
429
|
+
* const hash = await contract.write.transfer(['0x...', '1000000000000000000']);
|
|
430
|
+
* }
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
export function useContract({ address, abi, provider, }) {
|
|
434
|
+
const contract = useMemo(() => {
|
|
435
|
+
if (!address) {
|
|
436
|
+
throw new Error('Contract address is required');
|
|
437
|
+
}
|
|
438
|
+
if (!abi) {
|
|
439
|
+
throw new Error('Contract ABI is required');
|
|
440
|
+
}
|
|
441
|
+
if (!provider) {
|
|
442
|
+
throw new Error('Provider (publicClient) is required');
|
|
443
|
+
}
|
|
444
|
+
return getContract({
|
|
445
|
+
address,
|
|
446
|
+
abi,
|
|
447
|
+
client: provider,
|
|
448
|
+
});
|
|
449
|
+
}, [address, abi, provider]);
|
|
450
|
+
return { contract };
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Hook to prepare raw transaction data with encoding support
|
|
454
|
+
* Returns { to, value, data } for both native transfers and contract calls
|
|
455
|
+
* Can encode function data internally when ABI, functionName, and args are provided
|
|
456
|
+
*
|
|
457
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
458
|
+
* @returns Object with prepareRawTxn function
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```tsx
|
|
462
|
+
* // Native transfer
|
|
463
|
+
* const { prepareRawTxn } = usePrepareRawTxn(publicClient);
|
|
464
|
+
* const nativeTx = await prepareRawTxn({
|
|
465
|
+
* from: address!,
|
|
466
|
+
* to: '0x...',
|
|
467
|
+
* value: '0.001', // ETH amount
|
|
468
|
+
* });
|
|
469
|
+
* // Returns: { to: '0x...', value: '0x...', data: '0x' }
|
|
470
|
+
*
|
|
471
|
+
* // Contract call with encoding (recommended)
|
|
472
|
+
* const contractTx = await prepareRawTxn({
|
|
473
|
+
* from: address!,
|
|
474
|
+
* to: '0x...', // Contract address
|
|
475
|
+
* abi: erc20Abi,
|
|
476
|
+
* functionName: 'transfer',
|
|
477
|
+
* args: ['0x...', '0.001'], // Automatically converts ETH to wei
|
|
478
|
+
* value: '0', // Optional ETH value
|
|
479
|
+
* });
|
|
480
|
+
* // Returns: { to: '0x...', value: '0x', data: '0xa9059cbb...' }
|
|
481
|
+
*
|
|
482
|
+
* // Contract call with pre-encoded data
|
|
483
|
+
* const contractTx2 = await prepareRawTxn({
|
|
484
|
+
* from: address!,
|
|
485
|
+
* to: '0x...',
|
|
486
|
+
* data: '0xa9059cbb...', // Already encoded
|
|
487
|
+
* });
|
|
488
|
+
* // Returns: { to: '0x...', value: '0x', data: '0xa9059cbb...' }
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
export function usePrepareRawTxn(provider) {
|
|
492
|
+
const prepareRawTxn = useCallback(async ({ from, to, value, data, abi, functionName, args, }) => {
|
|
493
|
+
if (!provider) {
|
|
494
|
+
throw new Error('Provider (publicClient) is required');
|
|
495
|
+
}
|
|
496
|
+
if (!from) {
|
|
497
|
+
throw new Error('From address is required');
|
|
498
|
+
}
|
|
499
|
+
if (!to) {
|
|
500
|
+
throw new Error('To address is required');
|
|
501
|
+
}
|
|
502
|
+
let finalValue;
|
|
503
|
+
let finalData;
|
|
504
|
+
// Check if we need to encode function data
|
|
505
|
+
const needsEncoding = abi && functionName;
|
|
506
|
+
const hasPreEncodedData = data && data !== '0x';
|
|
507
|
+
if (needsEncoding && hasPreEncodedData) {
|
|
508
|
+
throw new Error('Cannot provide both encoded data and encoding parameters (abi/functionName). Use one or the other.');
|
|
509
|
+
}
|
|
510
|
+
// Determine if this is a native transfer
|
|
511
|
+
const isNativeTransfer = !needsEncoding && !hasPreEncodedData;
|
|
512
|
+
if (isNativeTransfer) {
|
|
513
|
+
// Native transfer: convert value to wei and set data to '0x'
|
|
514
|
+
if (!value || value === '0' || value === 0) {
|
|
515
|
+
throw new Error('Value is required for native transfer');
|
|
516
|
+
}
|
|
517
|
+
// Convert value to wei
|
|
518
|
+
const valueStr = typeof value === 'string' ? value.trim() : String(value);
|
|
519
|
+
let valueInWei;
|
|
520
|
+
try {
|
|
521
|
+
valueInWei = parseEther(valueStr);
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
throw new Error(`Invalid value format: "${valueStr}". Value must be a valid number string (e.g., "0.001").`);
|
|
525
|
+
}
|
|
526
|
+
// Convert to hex string
|
|
527
|
+
finalValue = `0x${valueInWei.toString(16)}`;
|
|
528
|
+
finalData = '0x';
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
// Contract call: encode data if needed, or use provided data
|
|
532
|
+
if (needsEncoding) {
|
|
533
|
+
// Encode function data
|
|
534
|
+
if (!abi) {
|
|
535
|
+
throw new Error('ABI is required for encoding function data');
|
|
536
|
+
}
|
|
537
|
+
if (!functionName) {
|
|
538
|
+
throw new Error('Function name is required for encoding function data');
|
|
539
|
+
}
|
|
540
|
+
// Process args to convert ETH amounts to wei for transfer functions
|
|
541
|
+
const processedArgs = (args || []).map((arg, index) => {
|
|
542
|
+
// Check if this is likely an amount parameter for transfer/transferFrom functions
|
|
543
|
+
const isAmountParam = (functionName === 'transfer' || functionName === 'transferFrom') && index === 1;
|
|
544
|
+
if (isAmountParam) {
|
|
545
|
+
// If arg is a string or number that looks like ETH (has decimal point)
|
|
546
|
+
if (typeof arg === 'string' && arg.includes('.')) {
|
|
547
|
+
try {
|
|
548
|
+
// Convert ETH to wei
|
|
549
|
+
const weiAmount = parseEther(arg);
|
|
550
|
+
return weiAmount.toString();
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
throw new Error(`Failed to convert amount "${arg}" to wei. Make sure it's a valid number string (e.g., "0.001").`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else if (typeof arg === 'number' && arg % 1 !== 0) {
|
|
557
|
+
// Number with decimal places - convert to wei
|
|
558
|
+
try {
|
|
559
|
+
const weiAmount = parseEther(arg.toString());
|
|
560
|
+
return weiAmount.toString();
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
throw new Error(`Failed to convert amount ${arg} to wei. Make sure it's a valid number.`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// For other args, just return as-is
|
|
568
|
+
return arg;
|
|
569
|
+
});
|
|
570
|
+
try {
|
|
571
|
+
finalData = encodeFunctionData({
|
|
572
|
+
abi: abi,
|
|
573
|
+
functionName: functionName,
|
|
574
|
+
args: processedArgs,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
if (error instanceof Error && error.message.includes('BigInt')) {
|
|
579
|
+
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).`);
|
|
580
|
+
}
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else if (hasPreEncodedData) {
|
|
585
|
+
// Use provided encoded data
|
|
586
|
+
finalData = data;
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
throw new Error('Either provide encoded data, or provide abi/functionName/args for encoding.');
|
|
590
|
+
}
|
|
591
|
+
// Handle value for contract calls
|
|
592
|
+
if (value && value !== '0' && value !== 0) {
|
|
593
|
+
// Convert value to wei if provided
|
|
594
|
+
const valueStr = typeof value === 'string' ? value.trim() : String(value);
|
|
595
|
+
let valueInWei;
|
|
596
|
+
try {
|
|
597
|
+
valueInWei = parseEther(valueStr);
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
throw new Error(`Invalid value format: "${valueStr}". Value must be a valid number string (e.g., "0.001").`);
|
|
601
|
+
}
|
|
602
|
+
finalValue = `0x${valueInWei.toString(16)}`;
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
finalValue = '0x0';
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
to,
|
|
610
|
+
value: finalValue,
|
|
611
|
+
data: finalData,
|
|
612
|
+
};
|
|
613
|
+
}, [provider]);
|
|
614
|
+
return { prepareRawTxn };
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Hook to sign transaction using Turnkey API
|
|
618
|
+
* Accepts raw transaction data from usePrepareRawTxn, serializes it, and signs via API
|
|
619
|
+
*
|
|
620
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
621
|
+
* @returns Object with signTxn function
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* ```tsx
|
|
625
|
+
* const { prepareRawTxn } = usePrepareRawTxn(publicClient);
|
|
626
|
+
* const { signTxn } = useSignTxn(publicClient);
|
|
627
|
+
*
|
|
628
|
+
* // Prepare transaction
|
|
629
|
+
* const rawTx = await prepareRawTxn({
|
|
630
|
+
* from: address!,
|
|
631
|
+
* to: '0x...',
|
|
632
|
+
* value: '0.001',
|
|
633
|
+
* });
|
|
634
|
+
*
|
|
635
|
+
* // Sign transaction
|
|
636
|
+
* const signedTx = await signTxn({
|
|
637
|
+
* from: address!,
|
|
638
|
+
* ...rawTx, // Spread to, value, data from prepareRawTxn
|
|
639
|
+
* });
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
642
|
+
export function useSignTxn(provider) {
|
|
643
|
+
const { wallet, isConnected, address, signTransactionViaAPI } = useAbstraxnWallet();
|
|
644
|
+
const signTxn = useCallback(async ({ from, to, value, data, chainId, }) => {
|
|
645
|
+
if (!isConnected || !wallet) {
|
|
646
|
+
throw new Error('Wallet is not connected');
|
|
647
|
+
}
|
|
648
|
+
if (!provider) {
|
|
649
|
+
throw new Error('Provider (publicClient) is required');
|
|
650
|
+
}
|
|
651
|
+
if (!from) {
|
|
652
|
+
throw new Error('From address is required');
|
|
653
|
+
}
|
|
654
|
+
if (!to) {
|
|
655
|
+
throw new Error('To address is required');
|
|
656
|
+
}
|
|
657
|
+
// Get chain ID from provider or use provided one
|
|
658
|
+
const targetChainId = chainId || provider.chain?.id;
|
|
659
|
+
if (!targetChainId) {
|
|
660
|
+
throw new Error('Chain ID is required. Either provide it or ensure provider has a chain configured.');
|
|
661
|
+
}
|
|
662
|
+
// Get nonce
|
|
663
|
+
const nonce = await provider.getTransactionCount({ address: from });
|
|
664
|
+
// Convert hex value to bigint
|
|
665
|
+
// Handle empty hex string "0x" by converting to "0x0"
|
|
666
|
+
const normalizedValue = value === '0x' ? '0x0' : value;
|
|
667
|
+
const valueInWei = BigInt(normalizedValue);
|
|
668
|
+
// Estimate gas
|
|
669
|
+
const gas = await provider.estimateGas({
|
|
670
|
+
account: from,
|
|
671
|
+
to,
|
|
672
|
+
data,
|
|
673
|
+
value: valueInWei,
|
|
674
|
+
});
|
|
675
|
+
// Estimate fees
|
|
676
|
+
const fee = await provider.estimateFeesPerGas();
|
|
677
|
+
// Create unsigned transaction
|
|
678
|
+
const unsignedTx = {
|
|
679
|
+
chainId: targetChainId,
|
|
680
|
+
from,
|
|
681
|
+
to,
|
|
682
|
+
data,
|
|
683
|
+
value: valueInWei,
|
|
684
|
+
nonce,
|
|
685
|
+
gas,
|
|
686
|
+
maxFeePerGas: fee.maxFeePerGas,
|
|
687
|
+
maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
|
|
688
|
+
type: 'eip1559',
|
|
689
|
+
};
|
|
690
|
+
// Serialize transaction
|
|
691
|
+
const serializedTx = serializeTransaction(unsignedTx);
|
|
692
|
+
// Sign transaction via Turnkey API
|
|
693
|
+
const signResult = await signTransactionViaAPI(serializedTx, from);
|
|
694
|
+
return {
|
|
695
|
+
unsignedTransaction: serializedTx,
|
|
696
|
+
signedTransaction: signResult.signedTransaction,
|
|
697
|
+
};
|
|
698
|
+
}, [provider, isConnected, wallet, address, signTransactionViaAPI]);
|
|
699
|
+
return { signTxn, isConnected, address };
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Hook to sign and send transaction using Turnkey API
|
|
703
|
+
* Same as useSignTxn but also sends the transaction on blockchain using publicClient
|
|
704
|
+
*
|
|
705
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
706
|
+
* @returns Object with signAndSendTxn function
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* ```tsx
|
|
710
|
+
* const { prepareRawTxn } = usePrepareRawTxn(publicClient);
|
|
711
|
+
* const { signAndSendTxn } = useSignAndSendTxn(publicClient);
|
|
712
|
+
*
|
|
713
|
+
* // Prepare transaction
|
|
714
|
+
* const rawTx = await prepareRawTxn({
|
|
715
|
+
* from: address!,
|
|
716
|
+
* to: '0x...',
|
|
717
|
+
* value: '0.001',
|
|
718
|
+
* });
|
|
719
|
+
*
|
|
720
|
+
* // Sign and send transaction
|
|
721
|
+
* const result = await signAndSendTxn({
|
|
722
|
+
* from: address!,
|
|
723
|
+
* ...rawTx, // Spread to, value, data from prepareRawTxn
|
|
724
|
+
* });
|
|
725
|
+
*
|
|
726
|
+
* console.log('Transaction hash:', result.hash);
|
|
727
|
+
* ```
|
|
728
|
+
*/
|
|
729
|
+
export function useSignAndSendTxn(provider) {
|
|
730
|
+
const { wallet, isConnected, address, signTransactionViaAPI } = useAbstraxnWallet();
|
|
731
|
+
const signAndSendTxn = useCallback(async ({ from, to, value, data, chainId, }) => {
|
|
732
|
+
if (!isConnected || !wallet) {
|
|
733
|
+
throw new Error('Wallet is not connected');
|
|
734
|
+
}
|
|
735
|
+
if (!provider) {
|
|
736
|
+
throw new Error('Provider (publicClient) is required');
|
|
737
|
+
}
|
|
738
|
+
if (!from) {
|
|
739
|
+
throw new Error('From address is required');
|
|
740
|
+
}
|
|
741
|
+
if (!to) {
|
|
742
|
+
throw new Error('To address is required');
|
|
743
|
+
}
|
|
744
|
+
// Get chain ID from provider or use provided one
|
|
745
|
+
const targetChainId = chainId || provider.chain?.id;
|
|
746
|
+
if (!targetChainId) {
|
|
747
|
+
throw new Error('Chain ID is required. Either provide it or ensure provider has a chain configured.');
|
|
748
|
+
}
|
|
749
|
+
// Get nonce
|
|
750
|
+
const nonce = await provider.getTransactionCount({ address: from });
|
|
751
|
+
// Convert hex value to bigint
|
|
752
|
+
// Handle empty hex string "0x" by converting to "0x0"
|
|
753
|
+
const normalizedValue = value === '0x' ? '0x0' : value;
|
|
754
|
+
const valueInWei = BigInt(normalizedValue);
|
|
755
|
+
// Estimate gas
|
|
756
|
+
const gas = await provider.estimateGas({
|
|
757
|
+
account: from,
|
|
758
|
+
to,
|
|
759
|
+
data,
|
|
760
|
+
value: valueInWei,
|
|
761
|
+
});
|
|
762
|
+
// Estimate fees
|
|
763
|
+
const fee = await provider.estimateFeesPerGas();
|
|
764
|
+
// Create unsigned transaction
|
|
765
|
+
const unsignedTx = {
|
|
766
|
+
chainId: targetChainId,
|
|
767
|
+
from,
|
|
768
|
+
to,
|
|
769
|
+
data,
|
|
770
|
+
value: valueInWei,
|
|
771
|
+
nonce,
|
|
772
|
+
gas,
|
|
773
|
+
maxFeePerGas: fee.maxFeePerGas,
|
|
774
|
+
maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
|
|
775
|
+
type: 'eip1559',
|
|
776
|
+
};
|
|
777
|
+
// Serialize transaction
|
|
778
|
+
const serializedTx = serializeTransaction(unsignedTx);
|
|
779
|
+
// Sign transaction via Turnkey API
|
|
780
|
+
const signResult = await signTransactionViaAPI(serializedTx, from);
|
|
781
|
+
// Send the signed transaction
|
|
782
|
+
const hash = await provider.sendRawTransaction({
|
|
783
|
+
serializedTransaction: signResult.signedTransaction,
|
|
784
|
+
});
|
|
785
|
+
return {
|
|
786
|
+
hash,
|
|
787
|
+
unsignedTransaction: serializedTx,
|
|
788
|
+
signedTransaction: signResult.signedTransaction,
|
|
789
|
+
};
|
|
790
|
+
}, [provider, isConnected, wallet, address, signTransactionViaAPI]);
|
|
791
|
+
return { signAndSendTxn, isConnected, address };
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Hook to wait for transaction receipt
|
|
795
|
+
* Waits for a transaction to be mined and returns the receipt
|
|
796
|
+
*
|
|
797
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
798
|
+
* @returns Object with waitForTxnReceipt function
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```tsx
|
|
802
|
+
* const { publicClient } = usePublicClient(
|
|
803
|
+
* polygonAmoy,
|
|
804
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
805
|
+
* );
|
|
806
|
+
* const { waitForTxnReceipt } = useWaitForTxnReceipt(publicClient);
|
|
807
|
+
*
|
|
808
|
+
* // After sending a transaction
|
|
809
|
+
* const hash = '0x...'; // Transaction hash
|
|
810
|
+
*
|
|
811
|
+
* // Wait for receipt
|
|
812
|
+
* const receipt = await waitForTxnReceipt({
|
|
813
|
+
* hash,
|
|
814
|
+
* confirmations: 1, // Optional: number of confirmations to wait for
|
|
815
|
+
* });
|
|
816
|
+
*
|
|
817
|
+
* console.log('Transaction confirmed:', receipt);
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
820
|
+
export function useWaitForTxnReceipt(provider) {
|
|
821
|
+
const waitForTxnReceipt = useCallback(async ({ hash, confirmations, timeout, }) => {
|
|
822
|
+
if (!provider) {
|
|
823
|
+
throw new Error('Provider (publicClient) is required');
|
|
824
|
+
}
|
|
825
|
+
if (!hash) {
|
|
826
|
+
throw new Error('Transaction hash is required');
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
// waitForTransactionReceipt is a method on PublicClient
|
|
830
|
+
const receipt = await provider.waitForTransactionReceipt({
|
|
831
|
+
hash,
|
|
832
|
+
confirmations,
|
|
833
|
+
timeout,
|
|
834
|
+
});
|
|
835
|
+
return receipt;
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
if (error instanceof Error) {
|
|
839
|
+
throw new Error(`Failed to wait for transaction receipt: ${error.message}`);
|
|
840
|
+
}
|
|
841
|
+
throw error;
|
|
842
|
+
}
|
|
843
|
+
}, [provider]);
|
|
844
|
+
return { waitForTxnReceipt };
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Hook to estimate gas price
|
|
848
|
+
* Estimates gas price and fees for transactions
|
|
849
|
+
*
|
|
850
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
851
|
+
* @returns Object with estimateGas function
|
|
852
|
+
*
|
|
853
|
+
* @example
|
|
854
|
+
* ```tsx
|
|
855
|
+
* const { publicClient } = usePublicClient(
|
|
856
|
+
* polygonAmoy,
|
|
857
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
858
|
+
* );
|
|
859
|
+
* const { estimateGas } = useEstimateGas(publicClient);
|
|
860
|
+
*
|
|
861
|
+
* // Estimate gas price
|
|
862
|
+
* const gasPrice = await estimateGas();
|
|
863
|
+
*
|
|
864
|
+
* // Returns:
|
|
865
|
+
* // {
|
|
866
|
+
* // gasPrice?: bigint, // Legacy gas price (for non-EIP-1559 chains)
|
|
867
|
+
* // maxFeePerGas?: bigint, // EIP-1559 max fee per gas
|
|
868
|
+
* // maxPriorityFeePerGas?: bigint // EIP-1559 max priority fee per gas
|
|
869
|
+
* // }
|
|
870
|
+
* ```
|
|
871
|
+
*/
|
|
872
|
+
export function useEstimateGas(provider) {
|
|
873
|
+
const estimateGas = useCallback(async () => {
|
|
874
|
+
if (!provider) {
|
|
875
|
+
throw new Error('Provider (publicClient) is required');
|
|
876
|
+
}
|
|
877
|
+
try {
|
|
878
|
+
// Try to get EIP-1559 fees first (for modern chains)
|
|
879
|
+
const fees = await provider.estimateFeesPerGas();
|
|
880
|
+
return {
|
|
881
|
+
maxFeePerGas: fees.maxFeePerGas,
|
|
882
|
+
maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
|
|
883
|
+
// Also get legacy gas price as fallback
|
|
884
|
+
gasPrice: fees.gasPrice,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
catch (error) {
|
|
888
|
+
// If EIP-1559 fails, try legacy gas price
|
|
889
|
+
try {
|
|
890
|
+
const gasPrice = await provider.getGasPrice();
|
|
891
|
+
return {
|
|
892
|
+
gasPrice,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
catch (legacyError) {
|
|
896
|
+
if (error instanceof Error) {
|
|
897
|
+
throw new Error(`Failed to estimate gas price: ${error.message}`);
|
|
898
|
+
}
|
|
899
|
+
throw error;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}, [provider]);
|
|
903
|
+
return { estimateGas };
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Hook to read from contract
|
|
907
|
+
* Reads contract data using publicClient.readContract
|
|
908
|
+
*
|
|
909
|
+
* @param provider - PublicClient instance (can be created using usePublicClient hook)
|
|
910
|
+
* @returns Object with readContract function
|
|
911
|
+
*
|
|
912
|
+
* @example
|
|
913
|
+
* ```tsx
|
|
914
|
+
* const { publicClient } = usePublicClient(
|
|
915
|
+
* polygonAmoy,
|
|
916
|
+
* 'https://rpc-amoy.polygon.technology'
|
|
917
|
+
* );
|
|
918
|
+
* const { readContract } = useReadContract(publicClient);
|
|
919
|
+
*
|
|
920
|
+
* // Read contract data
|
|
921
|
+
* const balance = await readContract({
|
|
922
|
+
* address: '0x...',
|
|
923
|
+
* abi: erc20Abi,
|
|
924
|
+
* functionName: 'balanceOf',
|
|
925
|
+
* args: ['0x...'],
|
|
926
|
+
* });
|
|
927
|
+
*
|
|
928
|
+
* const name = await readContract({
|
|
929
|
+
* address: '0x...',
|
|
930
|
+
* abi: erc20Abi,
|
|
931
|
+
* functionName: 'name',
|
|
932
|
+
* });
|
|
933
|
+
* ```
|
|
934
|
+
*/
|
|
935
|
+
export function useReadContract(provider) {
|
|
936
|
+
const readContract = useCallback(({ address, abi, functionName, args, }) => {
|
|
937
|
+
if (!provider) {
|
|
938
|
+
throw new Error('Provider (publicClient) is required');
|
|
939
|
+
}
|
|
940
|
+
if (!address) {
|
|
941
|
+
throw new Error('Contract address is required');
|
|
942
|
+
}
|
|
943
|
+
if (!abi) {
|
|
944
|
+
throw new Error('Contract ABI is required');
|
|
945
|
+
}
|
|
946
|
+
if (!functionName) {
|
|
947
|
+
throw new Error('Function name is required');
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
return provider.readContract({
|
|
951
|
+
address,
|
|
952
|
+
abi: abi,
|
|
953
|
+
functionName: functionName,
|
|
954
|
+
args: args,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
if (error instanceof Error) {
|
|
959
|
+
throw new Error(`Failed to read contract: ${error.message}`);
|
|
960
|
+
}
|
|
961
|
+
throw error;
|
|
962
|
+
}
|
|
963
|
+
}, [provider]);
|
|
964
|
+
return { readContract };
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Hook to get walletClient for external wallets (MetaMask, WalletConnect, etc.)
|
|
968
|
+
* Uses wagmi's useConnectorClient to get the walletClient
|
|
969
|
+
*
|
|
970
|
+
* @returns Object with walletClient instance (null if not connected)
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* ```tsx
|
|
974
|
+
* import { useExternalWalletClient } from '@abstraxn/signer-react';
|
|
975
|
+
*
|
|
976
|
+
* function MyComponent() {
|
|
977
|
+
* const { walletClient, isConnected } = useExternalWalletClient();
|
|
978
|
+
*
|
|
979
|
+
* if (!walletClient || !isConnected) {
|
|
980
|
+
* return <div>Please connect your wallet</div>;
|
|
981
|
+
* }
|
|
982
|
+
*
|
|
983
|
+
* // Use walletClient for transactions
|
|
984
|
+
* }
|
|
985
|
+
* ```
|
|
986
|
+
*/
|
|
987
|
+
export function useExternalWalletClient() {
|
|
988
|
+
const { isExternalWalletConnected } = useExternalWallet();
|
|
989
|
+
const { data: walletClient, isPending, isError, error } = useWagmiWalletClient();
|
|
990
|
+
const { isConnected: wagmiIsConnected, connector } = useAccount();
|
|
991
|
+
// Check both our internal state and wagmi's state
|
|
992
|
+
// useConnectorClient might return undefined even when connected, so we check both
|
|
993
|
+
// Edge case: connectorClient can be undefined/pending even when wallet appears connected
|
|
994
|
+
// This happens during:
|
|
995
|
+
// 1. Initial connection phase (connector is still initializing)
|
|
996
|
+
// 2. Chain switching (connector needs to re-initialize for new chain)
|
|
997
|
+
// 3. Connector re-initialization after page refresh/reconnect
|
|
998
|
+
const isActuallyConnected = isExternalWalletConnected && wagmiIsConnected;
|
|
999
|
+
// Return walletClient if available, even if isPending (might be refetching)
|
|
1000
|
+
// Only return null if walletClient is actually undefined
|
|
1001
|
+
const finalClient = (isActuallyConnected && walletClient) ? walletClient : null;
|
|
1002
|
+
return {
|
|
1003
|
+
walletClient: finalClient,
|
|
1004
|
+
// isConnected should be true only when both states agree AND walletClient is available
|
|
1005
|
+
// Note: isPending might be true even when walletClient exists (during refetch)
|
|
1006
|
+
isConnected: isActuallyConnected && !!walletClient,
|
|
1007
|
+
isPending,
|
|
1008
|
+
isError,
|
|
1009
|
+
error,
|
|
1010
|
+
// Additional info for debugging edge cases
|
|
1011
|
+
connector,
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Hook to write to contract (for external wallets like MetaMask, WalletConnect, etc.)
|
|
1016
|
+
* Uses walletClient from useExternalWalletClient to send transactions
|
|
1017
|
+
*
|
|
1018
|
+
* @returns Object with writeContract function
|
|
1019
|
+
*
|
|
1020
|
+
* @example
|
|
1021
|
+
* ```tsx
|
|
1022
|
+
* import { useWriteContract } from '@abstraxn/signer-react';
|
|
1023
|
+
* import erc20Abi from './erc20Abi.json';
|
|
1024
|
+
*
|
|
1025
|
+
* function MyComponent() {
|
|
1026
|
+
* const { writeContract } = useWriteContract();
|
|
1027
|
+
*
|
|
1028
|
+
* const handleTransfer = async () => {
|
|
1029
|
+
* try {
|
|
1030
|
+
* // Write to contract
|
|
1031
|
+
* const hash = await writeContract({
|
|
1032
|
+
* address: '0x...', // Contract address
|
|
1033
|
+
* abi: erc20Abi,
|
|
1034
|
+
* functionName: 'transfer',
|
|
1035
|
+
* args: ['0x...', '1000000000000000000'], // to, amount in wei
|
|
1036
|
+
* });
|
|
1037
|
+
*
|
|
1038
|
+
* console.log('Transaction hash:', hash);
|
|
1039
|
+
* } catch (error) {
|
|
1040
|
+
* console.error('Transaction failed:', error);
|
|
1041
|
+
* }
|
|
1042
|
+
* };
|
|
1043
|
+
*
|
|
1044
|
+
* // With ETH value
|
|
1045
|
+
* const handleDeposit = async () => {
|
|
1046
|
+
* const hash = await writeContract({
|
|
1047
|
+
* address: '0x...',
|
|
1048
|
+
* abi: contractAbi,
|
|
1049
|
+
* functionName: 'deposit',
|
|
1050
|
+
* value: parseEther('0.1'), // Send 0.1 ETH with the transaction
|
|
1051
|
+
* });
|
|
1052
|
+
* };
|
|
1053
|
+
*
|
|
1054
|
+
* return <button onClick={handleTransfer}>Transfer Tokens</button>;
|
|
1055
|
+
* }
|
|
1056
|
+
* ```
|
|
1057
|
+
*/
|
|
1058
|
+
// ... (keep existing code)
|
|
1059
|
+
// ... (keep existing code)
|
|
1060
|
+
export function useWriteContract() {
|
|
1061
|
+
const { walletClient: hookClient, isPending, isError, error, isConnected } = useExternalWalletClient();
|
|
1062
|
+
const config = useConfig();
|
|
1063
|
+
const currentChainId = useWagmiChainId();
|
|
1064
|
+
const writeContract = useCallback(({ address, abi, functionName, args, value, account, gas, gasLimit, gasPrice, maxFeePerGas, maxPriorityFeePerGas, nonce, }) => {
|
|
1065
|
+
return (async () => {
|
|
1066
|
+
let activeClient = hookClient;
|
|
1067
|
+
// If hook client is missing or in error, try to recover
|
|
1068
|
+
if (!activeClient || isError) {
|
|
1069
|
+
// Check if the initial error was a chain mismatch
|
|
1070
|
+
const isMismatch = isError && (error?.name === 'ConnectorChainMismatchError' || error?.message?.includes('ChainMismatch'));
|
|
1071
|
+
if (isMismatch) {
|
|
1072
|
+
try {
|
|
1073
|
+
if (!currentChainId)
|
|
1074
|
+
throw new Error('Current chain ID is not available');
|
|
1075
|
+
await switchChain(config, { chainId: currentChainId });
|
|
1076
|
+
// Retry getting client after switch
|
|
1077
|
+
activeClient = await getWalletClient(config);
|
|
1078
|
+
}
|
|
1079
|
+
catch (switchErr) {
|
|
1080
|
+
console.error("Failed to auto-switch chain:", switchErr);
|
|
1081
|
+
// Fall through to try getWalletClient directly or throw
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
// If we still don't have a client (or switch failed), try to get it directly
|
|
1085
|
+
if (!activeClient) {
|
|
1086
|
+
try {
|
|
1087
|
+
// Try to get client directly
|
|
1088
|
+
activeClient = await getWalletClient(config);
|
|
1089
|
+
}
|
|
1090
|
+
catch (err) {
|
|
1091
|
+
// If mismatch error (and we haven't tried switching yet or it failed differently), try to switch chain
|
|
1092
|
+
// This catches cases where getWalletClient throws mismatch but the hook error wasn't mismatch (unlikely but possible)
|
|
1093
|
+
if (err?.name === 'ConnectorChainMismatchError' || err?.message?.includes('ChainMismatch')) {
|
|
1094
|
+
try {
|
|
1095
|
+
if (!currentChainId)
|
|
1096
|
+
throw new Error('Current chain ID is not available');
|
|
1097
|
+
await switchChain(config, { chainId: currentChainId });
|
|
1098
|
+
// Retry getting client
|
|
1099
|
+
activeClient = await getWalletClient(config);
|
|
1100
|
+
}
|
|
1101
|
+
catch (switchErr) {
|
|
1102
|
+
throw new Error(`Failed to switch chain: ${switchErr instanceof Error ? switchErr.message : 'Unknown error'}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
// Re-throw if it's not a mismatch we can handle
|
|
1107
|
+
if (isError && error) {
|
|
1108
|
+
console.error("WalletClient Initialization Error:", error);
|
|
1109
|
+
throw new Error(`Failed to initialize WalletClient: ${error.message}. Please try reconnecting your wallet.`);
|
|
1110
|
+
}
|
|
1111
|
+
throw err;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (!activeClient) {
|
|
1117
|
+
throw new Error('WalletClient is not available. Please connect an external wallet.');
|
|
1118
|
+
}
|
|
1119
|
+
// Cast to any to avoid type issues with different viem versions
|
|
1120
|
+
const walletClientAny = activeClient;
|
|
1121
|
+
// Try using writeContract method directly if available
|
|
1122
|
+
if (walletClientAny && typeof walletClientAny.writeContract === 'function') {
|
|
1123
|
+
return walletClientAny.writeContract({
|
|
1124
|
+
address,
|
|
1125
|
+
abi: abi,
|
|
1126
|
+
functionName: functionName,
|
|
1127
|
+
args: args,
|
|
1128
|
+
value,
|
|
1129
|
+
account,
|
|
1130
|
+
gas,
|
|
1131
|
+
gasPrice,
|
|
1132
|
+
maxFeePerGas,
|
|
1133
|
+
maxPriorityFeePerGas,
|
|
1134
|
+
nonce,
|
|
1135
|
+
chain: null, // Let the client determine the chain (or use current)
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
// If writeContract is not available, use getContract to create a contract instance
|
|
1139
|
+
// This works with wagmi's connectorClient
|
|
1140
|
+
const contract = getContract({
|
|
1141
|
+
address,
|
|
1142
|
+
abi: abi,
|
|
1143
|
+
client: walletClientAny,
|
|
1144
|
+
});
|
|
1145
|
+
// Get the write function from the contract
|
|
1146
|
+
const writeFunction = contract.write?.[functionName];
|
|
1147
|
+
if (!writeFunction || typeof writeFunction !== 'function') {
|
|
1148
|
+
throw new Error(`Function "${functionName}" not found in contract ABI or is not a write function. ` +
|
|
1149
|
+
'Make sure the function exists in the ABI and is a state-changing function.');
|
|
1150
|
+
}
|
|
1151
|
+
// Prepare the call options (viem contract write methods accept options as the last parameter)
|
|
1152
|
+
const hasOptions = value !== undefined || account !== undefined || gas !== undefined || gasLimit !== undefined ||
|
|
1153
|
+
gasPrice !== undefined || maxFeePerGas !== undefined ||
|
|
1154
|
+
maxPriorityFeePerGas !== undefined || nonce !== undefined;
|
|
1155
|
+
// Viem contract write methods expect args as an array, not spread
|
|
1156
|
+
const functionArgs = args || [];
|
|
1157
|
+
if (hasOptions) {
|
|
1158
|
+
const callOptions = {};
|
|
1159
|
+
if (value !== undefined)
|
|
1160
|
+
callOptions.value = value;
|
|
1161
|
+
if (account !== undefined)
|
|
1162
|
+
callOptions.account = account;
|
|
1163
|
+
// `gas` in viem is the gas *limit*. Allow either `gas` or `gasLimit` from the hook caller.
|
|
1164
|
+
if (gas !== undefined) {
|
|
1165
|
+
callOptions.gas = gas;
|
|
1166
|
+
}
|
|
1167
|
+
else if (gasLimit !== undefined) {
|
|
1168
|
+
callOptions.gas = gasLimit;
|
|
1169
|
+
}
|
|
1170
|
+
if (gasPrice !== undefined)
|
|
1171
|
+
callOptions.gasPrice = gasPrice;
|
|
1172
|
+
if (maxFeePerGas !== undefined)
|
|
1173
|
+
callOptions.maxFeePerGas = maxFeePerGas;
|
|
1174
|
+
if (maxPriorityFeePerGas !== undefined)
|
|
1175
|
+
callOptions.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
|
1176
|
+
if (nonce !== undefined)
|
|
1177
|
+
callOptions.nonce = nonce;
|
|
1178
|
+
// Call the write function with args array and options object
|
|
1179
|
+
return writeFunction(functionArgs, callOptions);
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
// Call the write function with args array only
|
|
1183
|
+
return writeFunction(functionArgs);
|
|
1184
|
+
}
|
|
1185
|
+
})();
|
|
1186
|
+
}, [hookClient, isPending, isError, error, isConnected, config, currentChainId]);
|
|
1187
|
+
return { writeContract };
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Hook to get the connection type (how the user connected)
|
|
1191
|
+
* Returns the method used to connect: 'google', 'x', 'discord', 'metamask', 'walletconnect',
|
|
1192
|
+
* 'email', 'passkey', or null if not connected
|
|
1193
|
+
*
|
|
1194
|
+
* @returns Object with connectionType and connector metadata
|
|
1195
|
+
*
|
|
1196
|
+
* @example
|
|
1197
|
+
* ```tsx
|
|
1198
|
+
* import { useConnectionType } from '@abstraxn/signer-react';
|
|
1199
|
+
*
|
|
1200
|
+
* function MyComponent() {
|
|
1201
|
+
* const { connectionType, connectorMeta } = useConnectionType();
|
|
1202
|
+
*
|
|
1203
|
+
* if (!connectionType) {
|
|
1204
|
+
* return <div>Not connected</div>;
|
|
1205
|
+
* }
|
|
1206
|
+
*
|
|
1207
|
+
* return (
|
|
1208
|
+
* <div>
|
|
1209
|
+
* Connected via: {connectorMeta?.name || connectionType}
|
|
1210
|
+
* {connectionType === 'google' && <div>Welcome Google user!</div>}
|
|
1211
|
+
* {connectionType === 'metamask' && <div>MetaMask wallet connected</div>}
|
|
1212
|
+
* </div>
|
|
1213
|
+
* );
|
|
1214
|
+
* }
|
|
1215
|
+
* ```
|
|
1216
|
+
*/
|
|
1217
|
+
export function useConnectionType() {
|
|
1218
|
+
const { connectionType } = useAbstraxnWallet();
|
|
1219
|
+
// Normalize connection type to match ConnectorType
|
|
1220
|
+
const normalizedType = connectionType ? normalizeConnectionType(connectionType) : null;
|
|
1221
|
+
const connectorMeta = getConnectorMeta(normalizedType);
|
|
1222
|
+
return {
|
|
1223
|
+
connectionType: normalizedType,
|
|
1224
|
+
connectorMeta,
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Normalize connection type string to match ConnectorType
|
|
1229
|
+
*/
|
|
1230
|
+
function normalizeConnectionType(type) {
|
|
1231
|
+
const lower = type.toLowerCase();
|
|
1232
|
+
// Map to standard connector types
|
|
1233
|
+
if (lower.includes('google'))
|
|
1234
|
+
return 'google';
|
|
1235
|
+
if (lower.includes('twitter') || lower === 'x')
|
|
1236
|
+
return 'x';
|
|
1237
|
+
if (lower.includes('discord'))
|
|
1238
|
+
return 'discord';
|
|
1239
|
+
if (lower.includes('metamask') || lower.includes('io.metamask'))
|
|
1240
|
+
return 'metamask';
|
|
1241
|
+
if (lower.includes('walletconnect') || lower.includes('wallet_connect'))
|
|
1242
|
+
return 'walletconnect';
|
|
1243
|
+
if (lower.includes('email') || lower.includes('otp'))
|
|
1244
|
+
return 'email';
|
|
1245
|
+
if (lower.includes('passkey'))
|
|
1246
|
+
return 'passkey';
|
|
1247
|
+
// Return original if no match (for other wallet types like coinbase, phantom, etc.)
|
|
1248
|
+
return type;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Hook to switch chain
|
|
1252
|
+
* Handles both internal (social) and external wallets
|
|
1253
|
+
*/
|
|
1254
|
+
export function useSwitchChain() {
|
|
1255
|
+
const { wallet, connectionType } = useAbstraxnWallet();
|
|
1256
|
+
const { switchChainAsync } = useWagmiSwitchChain();
|
|
1257
|
+
const { isConnected: isWagmiConnected } = useAccount();
|
|
1258
|
+
const config = useConfig();
|
|
1259
|
+
const switchChain = async (chainId) => {
|
|
1260
|
+
// Normalize connection type
|
|
1261
|
+
const type = connectionType ? connectionType.toLowerCase() : '';
|
|
1262
|
+
// Check if it's a social login
|
|
1263
|
+
const isSocial = ['google', 'email', 'otp', 'x', 'discord', 'passkey'].some(t => type.includes(t));
|
|
1264
|
+
if (isSocial) {
|
|
1265
|
+
if (!wallet)
|
|
1266
|
+
throw new Error('Wallet not initialized');
|
|
1267
|
+
// Use internal wallet switch
|
|
1268
|
+
await wallet.switchChain(chainId);
|
|
1269
|
+
}
|
|
1270
|
+
else if (isWagmiConnected) {
|
|
1271
|
+
// Use wagmi for external wallets
|
|
1272
|
+
try {
|
|
1273
|
+
await switchChainAsync({ chainId });
|
|
1274
|
+
}
|
|
1275
|
+
catch (error) {
|
|
1276
|
+
// Check if error is related to chain not being added or RPC URL missing
|
|
1277
|
+
// "UserRejectedRequestError" with "chain.rpcUrls is undefined" often means the chain isn't known to the wallet/viem
|
|
1278
|
+
// Try to add the chain if we have its details
|
|
1279
|
+
let chainParams = null;
|
|
1280
|
+
// 1. Try to get from internal SDK chains
|
|
1281
|
+
const chainData = getChainById(chainId);
|
|
1282
|
+
if (chainData && chainData.type === 'evm') {
|
|
1283
|
+
chainParams = {
|
|
1284
|
+
chainName: chainData.displayName,
|
|
1285
|
+
nativeCurrency: chainData.nativeCurrency,
|
|
1286
|
+
rpcUrls: [chainData.rpcUrl],
|
|
1287
|
+
blockExplorerUrls: chainData.blockExplorer ? [chainData.blockExplorer.url] : undefined,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
// 2. If not found, try to get from wagmi config (includes custom chains passed to provider)
|
|
1291
|
+
if (!chainParams) {
|
|
1292
|
+
const wagmiChain = config.chains.find(c => c.id === chainId);
|
|
1293
|
+
if (wagmiChain) {
|
|
1294
|
+
// Handle both viem Chain (rpcUrls) and CoreChain (rpcUrl) formats
|
|
1295
|
+
// The chain object in config might be a CoreChain if it wasn't converted correctly
|
|
1296
|
+
const rpcUrls = wagmiChain.rpcUrls?.default?.http || (wagmiChain.rpcUrl ? [wagmiChain.rpcUrl] : []);
|
|
1297
|
+
const blockExplorerUrls = wagmiChain.blockExplorers?.default?.url
|
|
1298
|
+
? [wagmiChain.blockExplorers.default.url]
|
|
1299
|
+
: (wagmiChain.explorerUrl ? [wagmiChain.explorerUrl] : undefined);
|
|
1300
|
+
chainParams = {
|
|
1301
|
+
chainName: wagmiChain.name,
|
|
1302
|
+
nativeCurrency: wagmiChain.nativeCurrency,
|
|
1303
|
+
rpcUrls: rpcUrls,
|
|
1304
|
+
blockExplorerUrls: blockExplorerUrls,
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (chainParams) {
|
|
1309
|
+
try {
|
|
1310
|
+
await switchChainAsync({
|
|
1311
|
+
chainId,
|
|
1312
|
+
addEthereumChainParameter: chainParams
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
catch (retryError) {
|
|
1316
|
+
console.error('Failed to add and switch chain:', retryError);
|
|
1317
|
+
throw retryError;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
// If we don't have chain data, rethrow the original error
|
|
1322
|
+
throw error;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
throw new Error('No active connection to switch chain');
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
return { switchChain };
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Hook to sign a message
|
|
1334
|
+
* Handles both internal (social) and external wallets
|
|
1335
|
+
*/
|
|
1336
|
+
export function useSignMessage() {
|
|
1337
|
+
const { wallet, connectionType } = useAbstraxnWallet();
|
|
1338
|
+
const { signMessageAsync } = useWagmiSignMessage();
|
|
1339
|
+
const { isConnected: isWagmiConnected } = useAccount();
|
|
1340
|
+
const signMessage = async (message) => {
|
|
1341
|
+
// Normalize connection type
|
|
1342
|
+
const type = connectionType ? connectionType.toLowerCase() : '';
|
|
1343
|
+
// Check if it's a social login
|
|
1344
|
+
const isSocial = ['google', 'email', 'otp', 'x', 'discord', 'passkey'].some(t => type.includes(t));
|
|
1345
|
+
if (isSocial) {
|
|
1346
|
+
throw new Error('Signing messages is not supported for embedded wallets');
|
|
1347
|
+
}
|
|
1348
|
+
else if (isWagmiConnected) {
|
|
1349
|
+
// Use wagmi for external wallets
|
|
1350
|
+
return await signMessageAsync({ message });
|
|
1351
|
+
}
|
|
1352
|
+
else {
|
|
1353
|
+
throw new Error('No active connection to sign message');
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
return { signMessage };
|
|
1357
|
+
}
|
|
1358
|
+
//# sourceMappingURL=hooks.js.map
|