@naculus/connect-appkit-react 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +144 -0
- package/dist/client.d.ts +144 -0
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2270 -0
- package/dist/index.d.ts +2270 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +30 -12
- package/api-extractor.json +0 -21
- package/src/chain-abstraction/index.ts +0 -8
- package/src/chain-abstraction/useCompareCosts.ts +0 -137
- package/src/chain-abstraction/useExecuteRoute.ts +0 -129
- package/src/chain-abstraction/useRouteQuote.ts +0 -168
- package/src/client.test.ts +0 -242
- package/src/client.ts +0 -265
- package/src/hooks/useAccount.test.ts +0 -67
- package/src/hooks/useAccount.ts +0 -37
- package/src/hooks/useBalance.test.ts +0 -459
- package/src/hooks/useBalance.ts +0 -170
- package/src/hooks/useChain.test.ts +0 -160
- package/src/hooks/useChain.ts +0 -53
- package/src/hooks/useConnect.test.ts +0 -112
- package/src/hooks/useConnect.ts +0 -29
- package/src/hooks/useDisconnect.test.ts +0 -75
- package/src/hooks/useDisconnect.ts +0 -32
- package/src/hooks/useERC20Allowance.ts +0 -93
- package/src/hooks/useERC20Approve.ts +0 -220
- package/src/hooks/useERC20Transfer.ts +0 -134
- package/src/hooks/useERC20TransferSimulation.ts +0 -147
- package/src/hooks/useEmbeddedWallet.test.ts +0 -266
- package/src/hooks/useEmbeddedWallet.ts +0 -181
- package/src/hooks/useLastTx.test.ts +0 -117
- package/src/hooks/useLastTx.ts +0 -73
- package/src/hooks/useLookupAddress.ts +0 -131
- package/src/hooks/useNotification.ts +0 -308
- package/src/hooks/useResolveName.ts +0 -128
- package/src/hooks/useSIWxLogin.test.ts +0 -307
- package/src/hooks/useSIWxLogin.ts +0 -163
- package/src/hooks/useSIWxSession.test.ts +0 -481
- package/src/hooks/useSIWxSession.ts +0 -290
- package/src/hooks/useSendCalls.ts +0 -103
- package/src/hooks/useSendTransaction.test.ts +0 -175
- package/src/hooks/useSendTransaction.ts +0 -80
- package/src/hooks/useSendUserOperation.ts +0 -313
- package/src/hooks/useSession.ts +0 -96
- package/src/hooks/useSessionKeys.ts +0 -356
- package/src/hooks/useSignInWithEthereum.test.ts +0 -224
- package/src/hooks/useSignInWithEthereum.ts +0 -46
- package/src/hooks/useSignInWithX.test.ts +0 -210
- package/src/hooks/useSignInWithX.ts +0 -141
- package/src/hooks/useSignMessage.test.ts +0 -151
- package/src/hooks/useSignMessage.ts +0 -65
- package/src/hooks/useSimulateTransfer.ts +0 -165
- package/src/hooks/useSiwxAuthSession.test.ts +0 -456
- package/src/hooks/useSiwxAuthSession.ts +0 -163
- package/src/hooks/useSmartAccount.ts +0 -194
- package/src/hooks/useSwitchChain.ts +0 -68
- package/src/hooks/useTokenBalance.test.ts +0 -337
- package/src/hooks/useTokenBalance.ts +0 -210
- package/src/hooks/useTransactionSimulation.ts +0 -401
- package/src/hooks/useTxHistory.test.ts +0 -108
- package/src/hooks/useTxHistory.ts +0 -83
- package/src/hooks/useTxMonitor.test.ts +0 -127
- package/src/hooks/useTxMonitor.ts +0 -178
- package/src/hooks/useUserOpStatus.ts +0 -245
- package/src/hooks/useValidateDestination.test.ts +0 -96
- package/src/hooks/useValidateDestination.ts +0 -72
- package/src/hooks/useViemClient.test.ts +0 -155
- package/src/hooks/useViemClient.ts +0 -65
- package/src/hooks/useWallet.test.ts +0 -133
- package/src/hooks/useWallet.ts +0 -39
- package/src/hooks/useWeb3ErrorHandler.ts +0 -102
- package/src/index.ts +0 -186
- package/src/provider/Web3ConnectProvider.test.tsx +0 -178
- package/src/provider/Web3ConnectProvider.tsx +0 -530
- package/src/token-list/TokenSelector.tsx +0 -351
- package/src/token-list/defaults.ts +0 -39
- package/src/token-list/index.ts +0 -14
- package/src/token-list/useTokenList.ts +0 -119
- package/src/token-list/useTokenSearch.ts +0 -99
- package/src/types/index.ts +0 -99
- package/src/utils/chains.ts +0 -115
- package/src/utils/errorMessages.ts +0 -142
- package/src/utils/i18n.ts +0 -90
- package/src/wc-generated/components.ts +0 -377
- package/test/setup.ts +0 -17
- package/test/test-utils.tsx +0 -78
- package/tsup.config.ts +0 -18
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSIWxSession — Unified SIWx session management hook.
|
|
3
|
-
*
|
|
4
|
-
* Combines the sign-in flow with session persistence, expiry tracking,
|
|
5
|
-
* and automatic restoration. Replaces the older useSiwxAuthSession.
|
|
6
|
-
*
|
|
7
|
-
* Provides:
|
|
8
|
-
* - signIn / signOut / refresh lifecycle
|
|
9
|
-
* - Auto-restore from storage on mount
|
|
10
|
-
* - isExpired / timeUntilExpiry query
|
|
11
|
-
* - Loading/error state
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { useState, useCallback, useEffect, useRef } from "react";
|
|
15
|
-
import { useSIWxLogin } from "./useSIWxLogin";
|
|
16
|
-
import type { UseSIWxLoginOptions } from "./useSIWxLogin";
|
|
17
|
-
import { LocalStorageAdapter, logger } from "@naculus/connect-core";
|
|
18
|
-
import type {
|
|
19
|
-
SiwxResult,
|
|
20
|
-
SiwxMessage,
|
|
21
|
-
} from "@naculus/siwx";
|
|
22
|
-
import { checkSessionExpired, createLocalStorageSiwxSessionStorage, SiwxSessionManager } from "@naculus/siwx";
|
|
23
|
-
import type { SiwxSession } from "@naculus/siwx";
|
|
24
|
-
import { useWeb3 } from "../provider/Web3ConnectProvider";
|
|
25
|
-
import { getClient } from "../client";
|
|
26
|
-
|
|
27
|
-
// ── Constants ────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
const SIWX_SESSION_KEY = "naculus_siwx_session";
|
|
30
|
-
|
|
31
|
-
// ── Types ────────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
export interface UseSIWxSessionOptions {
|
|
34
|
-
/** Storage key prefix (default: "naculus_") */
|
|
35
|
-
storagePrefix?: string;
|
|
36
|
-
/** Auto-restore session from storage on mount (default: true) */
|
|
37
|
-
autoRestore?: boolean;
|
|
38
|
-
/** Default chain ID when signing in (default: current chain from context) */
|
|
39
|
-
defaultChainId?: string;
|
|
40
|
-
/** Default session lifetime in seconds (default: 86400 = 24h) */
|
|
41
|
-
defaultExpirySeconds?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface UseSIWxSessionReturn {
|
|
45
|
-
/** The current SIWx session, or null */
|
|
46
|
-
session: SiwxSession | null;
|
|
47
|
-
/** Whether a valid session exists */
|
|
48
|
-
isAuthenticated: boolean;
|
|
49
|
-
/** Whether we're still checking persisted auth on mount */
|
|
50
|
-
isRestoring: boolean;
|
|
51
|
-
/** Whether a sign-in is in progress */
|
|
52
|
-
isSigningIn: boolean;
|
|
53
|
-
/** Whether the current session has expired */
|
|
54
|
-
isExpired: boolean;
|
|
55
|
-
/** Milliseconds until session expires, or null */
|
|
56
|
-
timeUntilExpiry: number | null;
|
|
57
|
-
|
|
58
|
-
/** Sign in with optional overrides */
|
|
59
|
-
signIn: (options?: UseSIWxLoginOptions) => Promise<SiwxSession>;
|
|
60
|
-
/** Sign out: clears session from memory and storage */
|
|
61
|
-
signOut: () => Promise<void>;
|
|
62
|
-
/** Refresh the current session */
|
|
63
|
-
refresh: () => Promise<SiwxSession>;
|
|
64
|
-
|
|
65
|
-
/** Last error */
|
|
66
|
-
error: Error | null;
|
|
67
|
-
/** Clear the last error */
|
|
68
|
-
clearError: () => void;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ── Helpers ──────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
function siwxResultToSession(result: SiwxResult): SiwxSession {
|
|
74
|
-
return {
|
|
75
|
-
id: `siwx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
76
|
-
chainId: result.message.chainId,
|
|
77
|
-
address: result.message.address,
|
|
78
|
-
domain: result.message.domain,
|
|
79
|
-
message: result.message,
|
|
80
|
-
signature: result.signature,
|
|
81
|
-
issuedAt: result.message.issuedAt ?? new Date().toISOString(),
|
|
82
|
-
expiresAt: result.message.expirationTime,
|
|
83
|
-
refreshedAt: null,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ── Hook ─────────────────────────────────────────────────────────
|
|
88
|
-
|
|
89
|
-
export function useSIWxSession(
|
|
90
|
-
options?: UseSIWxSessionOptions
|
|
91
|
-
): UseSIWxSessionReturn {
|
|
92
|
-
const {
|
|
93
|
-
storagePrefix = "naculus_",
|
|
94
|
-
autoRestore = true,
|
|
95
|
-
defaultExpirySeconds = 86_400,
|
|
96
|
-
} = options ?? {};
|
|
97
|
-
|
|
98
|
-
const [session, setSession] = useState<SiwxSession | null>(null);
|
|
99
|
-
const [isRestoring, setIsRestoring] = useState(autoRestore);
|
|
100
|
-
const [isSigningIn, setIsSigningIn] = useState(false);
|
|
101
|
-
const [error, setError] = useState<Error | null>(null);
|
|
102
|
-
|
|
103
|
-
const { session: web3Session, chainId: currentChainId } = useWeb3();
|
|
104
|
-
const login = useSIWxLogin();
|
|
105
|
-
const storageRef = useRef(
|
|
106
|
-
createLocalStorageSiwxSessionStorage(`${storagePrefix}siwx_session`)
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
const clearError = useCallback(() => setError(null), []);
|
|
110
|
-
|
|
111
|
-
// ── Restore from storage on mount ──────────────────────────────
|
|
112
|
-
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
if (!autoRestore) {
|
|
115
|
-
setIsRestoring(false);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let cancelled = false;
|
|
120
|
-
|
|
121
|
-
(async () => {
|
|
122
|
-
try {
|
|
123
|
-
const storage = storageRef.current;
|
|
124
|
-
const saved = await storage.get();
|
|
125
|
-
|
|
126
|
-
if (cancelled) return;
|
|
127
|
-
|
|
128
|
-
if (saved) {
|
|
129
|
-
logger.info("react/useSIWxSession", "Restored persisted SIWx session");
|
|
130
|
-
setSession(saved);
|
|
131
|
-
setError(null);
|
|
132
|
-
} else {
|
|
133
|
-
// No valid session in storage
|
|
134
|
-
setSession(null);
|
|
135
|
-
}
|
|
136
|
-
} catch (err) {
|
|
137
|
-
logger.warn("react/useSIWxSession", "Failed to restore SIWx session:", err);
|
|
138
|
-
setSession(null);
|
|
139
|
-
} finally {
|
|
140
|
-
if (!cancelled) setIsRestoring(false);
|
|
141
|
-
}
|
|
142
|
-
})();
|
|
143
|
-
|
|
144
|
-
return () => {
|
|
145
|
-
cancelled = true;
|
|
146
|
-
};
|
|
147
|
-
}, [autoRestore]);
|
|
148
|
-
|
|
149
|
-
// ── Expiry refresh timer ───────────────────────────────────────
|
|
150
|
-
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
if (!session?.expiresAt) return;
|
|
153
|
-
|
|
154
|
-
const timeUntilExpiry = new Date(session.expiresAt).getTime() - Date.now();
|
|
155
|
-
if (timeUntilExpiry <= 0) {
|
|
156
|
-
// Already expired — clear session
|
|
157
|
-
setSession(null);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Schedule clearing when expired
|
|
162
|
-
const timer = setTimeout(() => {
|
|
163
|
-
setSession((prev) => {
|
|
164
|
-
if (prev && prev.id === session.id) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
return prev;
|
|
168
|
-
});
|
|
169
|
-
}, timeUntilExpiry);
|
|
170
|
-
|
|
171
|
-
return () => clearTimeout(timer);
|
|
172
|
-
}, [session?.id, session?.expiresAt]);
|
|
173
|
-
|
|
174
|
-
// ── Sign In ────────────────────────────────────────────────────
|
|
175
|
-
|
|
176
|
-
const signIn = useCallback(
|
|
177
|
-
async (signOptions?: UseSIWxLoginOptions): Promise<SiwxSession> => {
|
|
178
|
-
setIsSigningIn(true);
|
|
179
|
-
setError(null);
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
// Merge default options with provided overrides
|
|
183
|
-
const effectiveOptions: UseSIWxLoginOptions = {
|
|
184
|
-
expirySeconds: defaultExpirySeconds,
|
|
185
|
-
chainId: currentChainId ?? undefined,
|
|
186
|
-
...signOptions,
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const result = await login.signIn(effectiveOptions);
|
|
190
|
-
|
|
191
|
-
// Convert SiwxResult to SiwxSession
|
|
192
|
-
const siwxSession = siwxResultToSession(result);
|
|
193
|
-
|
|
194
|
-
// Persist to storage
|
|
195
|
-
const storage = storageRef.current;
|
|
196
|
-
await storage.set(siwxSession);
|
|
197
|
-
|
|
198
|
-
setSession(siwxSession);
|
|
199
|
-
setIsSigningIn(false);
|
|
200
|
-
return siwxSession;
|
|
201
|
-
} catch (err) {
|
|
202
|
-
const wrapped = err instanceof Error ? err : new Error("SIWx sign-in failed");
|
|
203
|
-
setError(wrapped);
|
|
204
|
-
setIsSigningIn(false);
|
|
205
|
-
throw wrapped;
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
[login, currentChainId, defaultExpirySeconds]
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// ── Sign Out ───────────────────────────────────────────────────
|
|
212
|
-
|
|
213
|
-
const signOut = useCallback(async () => {
|
|
214
|
-
try {
|
|
215
|
-
const storage = storageRef.current;
|
|
216
|
-
await storage.remove();
|
|
217
|
-
} catch (err) {
|
|
218
|
-
logger.warn("react/useSIWxSession", "Failed to clear session:", err);
|
|
219
|
-
}
|
|
220
|
-
setSession(null);
|
|
221
|
-
setError(null);
|
|
222
|
-
}, []);
|
|
223
|
-
|
|
224
|
-
// ── Refresh ────────────────────────────────────────────────────
|
|
225
|
-
|
|
226
|
-
const refresh = useCallback(async (): Promise<SiwxSession> => {
|
|
227
|
-
if (!session) {
|
|
228
|
-
throw new Error("No active session to refresh");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
setIsSigningIn(true);
|
|
232
|
-
setError(null);
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
// Re-sign with updated timestamps
|
|
236
|
-
const result = await login.signIn({
|
|
237
|
-
domain: session.domain,
|
|
238
|
-
statement: session.message.statement ?? undefined,
|
|
239
|
-
uri: session.message.uri,
|
|
240
|
-
chainId: session.chainId,
|
|
241
|
-
expirySeconds: defaultExpirySeconds,
|
|
242
|
-
resources: session.message.resources.length > 0 ? session.message.resources : undefined,
|
|
243
|
-
requestId: session.message.requestId ?? undefined,
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const refreshedSession: SiwxSession = {
|
|
247
|
-
...siwxResultToSession(result),
|
|
248
|
-
id: session.id, // preserve the original session ID
|
|
249
|
-
refreshedAt: new Date().toISOString(),
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
// Persist
|
|
253
|
-
const storage = storageRef.current;
|
|
254
|
-
await storage.set(refreshedSession);
|
|
255
|
-
|
|
256
|
-
setSession(refreshedSession);
|
|
257
|
-
setIsSigningIn(false);
|
|
258
|
-
return refreshedSession;
|
|
259
|
-
} catch (err) {
|
|
260
|
-
const wrapped = err instanceof Error ? err : new Error("Session refresh failed");
|
|
261
|
-
setError(wrapped);
|
|
262
|
-
setIsSigningIn(false);
|
|
263
|
-
throw wrapped;
|
|
264
|
-
}
|
|
265
|
-
}, [session, login, defaultExpirySeconds]);
|
|
266
|
-
|
|
267
|
-
// ── Computed state ─────────────────────────────────────────────
|
|
268
|
-
|
|
269
|
-
const isAuthenticated = session !== null && !checkSessionExpired(session);
|
|
270
|
-
const expired = checkSessionExpired(session);
|
|
271
|
-
const timeUntilExpiry = session?.expiresAt
|
|
272
|
-
? Math.max(0, new Date(session.expiresAt).getTime() - Date.now())
|
|
273
|
-
: null;
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
session: isAuthenticated ? session : null,
|
|
277
|
-
isAuthenticated,
|
|
278
|
-
isRestoring,
|
|
279
|
-
isSigningIn,
|
|
280
|
-
isExpired: expired,
|
|
281
|
-
timeUntilExpiry,
|
|
282
|
-
|
|
283
|
-
signIn,
|
|
284
|
-
signOut,
|
|
285
|
-
refresh,
|
|
286
|
-
|
|
287
|
-
error,
|
|
288
|
-
clearError,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from "react";
|
|
2
|
-
import { useWeb3 } from "../provider/Web3ConnectProvider";
|
|
3
|
-
import { getClient } from "../client";
|
|
4
|
-
import { WalletError } from "@naculus/connect-core";
|
|
5
|
-
import type { BatchCall, CallsStatus } from "@naculus/connect-core";
|
|
6
|
-
|
|
7
|
-
export type SendCallsStatus = "idle" | "awaiting_approval" | "confirmed" | "failed";
|
|
8
|
-
|
|
9
|
-
export function useSendCalls() {
|
|
10
|
-
const { session, chainId } = useWeb3();
|
|
11
|
-
const [status, setStatus] = useState<SendCallsStatus>("idle");
|
|
12
|
-
const [error, setError] = useState<Error | null>(null);
|
|
13
|
-
const [batchHash, setBatchHash] = useState<string | null>(null);
|
|
14
|
-
const [callsStatus, setCallsStatus] = useState<CallsStatus | null>(null);
|
|
15
|
-
|
|
16
|
-
const sendCalls = useCallback(
|
|
17
|
-
async (calls: BatchCall[], options?: { chainId?: string }): Promise<string> => {
|
|
18
|
-
if (!session) {
|
|
19
|
-
setStatus("failed");
|
|
20
|
-
const err = new WalletError("wallet_unavailable", "No active session");
|
|
21
|
-
setError(err);
|
|
22
|
-
throw err;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const client = getClient();
|
|
26
|
-
if (!client) {
|
|
27
|
-
setStatus("failed");
|
|
28
|
-
const err = new WalletError("wallet_unavailable", "Client not initialized");
|
|
29
|
-
setError(err);
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (calls.length === 0) {
|
|
34
|
-
setStatus("failed");
|
|
35
|
-
const err = new WalletError("invalid_input", "At least one call is required");
|
|
36
|
-
setError(err);
|
|
37
|
-
throw err;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
setStatus("awaiting_approval");
|
|
41
|
-
setError(null);
|
|
42
|
-
setBatchHash(null);
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const result = await client.sendCalls(
|
|
46
|
-
session,
|
|
47
|
-
calls,
|
|
48
|
-
options?.chainId ?? chainId ?? undefined,
|
|
49
|
-
);
|
|
50
|
-
setStatus("confirmed");
|
|
51
|
-
setBatchHash(result);
|
|
52
|
-
return result;
|
|
53
|
-
} catch (err) {
|
|
54
|
-
setStatus("failed");
|
|
55
|
-
const errorMessage = err instanceof Error ? err.message : "sendCalls failed";
|
|
56
|
-
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
57
|
-
throw err;
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
[session, chainId],
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const getCallsStatus = useCallback(
|
|
64
|
-
async (hash?: string): Promise<CallsStatus> => {
|
|
65
|
-
if (!session) {
|
|
66
|
-
throw new WalletError("wallet_unavailable", "No active session");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const client = getClient();
|
|
70
|
-
if (!client) {
|
|
71
|
-
throw new WalletError("wallet_unavailable", "Client not initialized");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const bundleHash = hash ?? batchHash;
|
|
75
|
-
if (!bundleHash) {
|
|
76
|
-
throw new WalletError("invalid_input", "No bundle hash available");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const result = await client.getCallsStatus(session, bundleHash);
|
|
80
|
-
setCallsStatus(result);
|
|
81
|
-
return result;
|
|
82
|
-
},
|
|
83
|
-
[session, batchHash],
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const reset = useCallback(() => {
|
|
87
|
-
setStatus("idle");
|
|
88
|
-
setError(null);
|
|
89
|
-
setBatchHash(null);
|
|
90
|
-
setCallsStatus(null);
|
|
91
|
-
}, []);
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
sendCalls,
|
|
95
|
-
getCallsStatus,
|
|
96
|
-
status,
|
|
97
|
-
error,
|
|
98
|
-
batchHash,
|
|
99
|
-
callsStatus,
|
|
100
|
-
reset,
|
|
101
|
-
isSending: status === "awaiting_approval",
|
|
102
|
-
};
|
|
103
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/// <reference types="vitest" />
|
|
2
|
-
/// @vitest-environment jsdom
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
5
|
-
import { renderHook, act } from "@testing-library/react";
|
|
6
|
-
|
|
7
|
-
const mockUseWeb3 = vi.fn();
|
|
8
|
-
vi.mock("../provider/Web3ConnectProvider", () => ({
|
|
9
|
-
useWeb3: () => mockUseWeb3(),
|
|
10
|
-
Web3ConnectProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
const mockGetClient = vi.fn();
|
|
14
|
-
vi.mock("../client", () => ({
|
|
15
|
-
getClient: () => mockGetClient(),
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
import { useSendTransaction } from "./useSendTransaction";
|
|
19
|
-
|
|
20
|
-
function createEvmSession() {
|
|
21
|
-
return {
|
|
22
|
-
topic: "test-topic",
|
|
23
|
-
namespaces: {
|
|
24
|
-
eip155: {
|
|
25
|
-
chains: ["eip155:1"],
|
|
26
|
-
accounts: ["eip155:1:0x1234567890123456789012345678901234567890"],
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe("useSendTransaction", () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
vi.clearAllMocks();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("should have initial idle state", () => {
|
|
38
|
-
mockUseWeb3.mockReturnValue({ session: null, chainId: null });
|
|
39
|
-
mockGetClient.mockReturnValue(null);
|
|
40
|
-
|
|
41
|
-
const { result } = renderHook(() => useSendTransaction());
|
|
42
|
-
|
|
43
|
-
expect(result.current.isSending).toBe(false);
|
|
44
|
-
expect(result.current.error).toBeNull();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("should throw when no session", async () => {
|
|
48
|
-
mockUseWeb3.mockReturnValue({ session: null, chainId: null });
|
|
49
|
-
mockGetClient.mockReturnValue({
|
|
50
|
-
sendTransaction: vi.fn(),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const { result } = renderHook(() => useSendTransaction());
|
|
54
|
-
|
|
55
|
-
await expect(async () => {
|
|
56
|
-
await act(async () => {
|
|
57
|
-
await result.current.sendTransaction({ to: "0xabcd" });
|
|
58
|
-
});
|
|
59
|
-
}).rejects.toThrow("No active session");
|
|
60
|
-
|
|
61
|
-
// Pre-condition checks throw directly without setting state
|
|
62
|
-
expect(result.current.error).toBeNull();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("should throw when client not initialized", async () => {
|
|
66
|
-
mockUseWeb3.mockReturnValue({ session: createEvmSession(), chainId: "eip155:1" });
|
|
67
|
-
mockGetClient.mockReturnValue(null);
|
|
68
|
-
|
|
69
|
-
const { result } = renderHook(() => useSendTransaction());
|
|
70
|
-
|
|
71
|
-
await expect(async () => {
|
|
72
|
-
await act(async () => {
|
|
73
|
-
await result.current.sendTransaction({ to: "0xabcd" });
|
|
74
|
-
});
|
|
75
|
-
}).rejects.toThrow("Client not initialized");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("should send transaction successfully", async () => {
|
|
79
|
-
const mockSendTransaction = vi.fn().mockResolvedValue("0xtxhash123");
|
|
80
|
-
mockUseWeb3.mockReturnValue({ session: createEvmSession(), chainId: "eip155:1" });
|
|
81
|
-
mockGetClient.mockReturnValue({
|
|
82
|
-
sendTransaction: mockSendTransaction,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const { result } = renderHook(() => useSendTransaction());
|
|
86
|
-
|
|
87
|
-
let txHash: string;
|
|
88
|
-
await act(async () => {
|
|
89
|
-
txHash = await result.current.sendTransaction({
|
|
90
|
-
to: "0xabcd",
|
|
91
|
-
value: "0xde0b6b3a7640000",
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
expect(txHash!).toBe("0xtxhash123");
|
|
96
|
-
expect(mockSendTransaction).toHaveBeenCalledTimes(1);
|
|
97
|
-
expect(mockSendTransaction).toHaveBeenCalledWith(createEvmSession(), {
|
|
98
|
-
transaction: {
|
|
99
|
-
to: "0xabcd",
|
|
100
|
-
value: "0xde0b6b3a7640000",
|
|
101
|
-
from: "0x1234567890123456789012345678901234567890",
|
|
102
|
-
},
|
|
103
|
-
chainId: "eip155:1",
|
|
104
|
-
});
|
|
105
|
-
expect(result.current.isSending).toBe(false);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("should throw when no EVM account found", async () => {
|
|
109
|
-
const sessionWithoutEvm = {
|
|
110
|
-
topic: "test",
|
|
111
|
-
namespaces: {},
|
|
112
|
-
};
|
|
113
|
-
mockUseWeb3.mockReturnValue({ session: sessionWithoutEvm, chainId: null });
|
|
114
|
-
mockGetClient.mockReturnValue({
|
|
115
|
-
sendTransaction: vi.fn(),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const { result } = renderHook(() => useSendTransaction());
|
|
119
|
-
|
|
120
|
-
await expect(async () => {
|
|
121
|
-
await act(async () => {
|
|
122
|
-
await result.current.sendTransaction({ to: "0xabcd" });
|
|
123
|
-
});
|
|
124
|
-
}).rejects.toThrow("No EVM account found");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("should set loading state during sending", async () => {
|
|
128
|
-
let resolvePromise: (v: string) => void;
|
|
129
|
-
const sendPromise = new Promise<string>((resolve) => {
|
|
130
|
-
resolvePromise = resolve;
|
|
131
|
-
});
|
|
132
|
-
const mockSendTransaction = vi.fn().mockReturnValue(sendPromise);
|
|
133
|
-
mockUseWeb3.mockReturnValue({ session: createEvmSession(), chainId: "eip155:1" });
|
|
134
|
-
mockGetClient.mockReturnValue({
|
|
135
|
-
sendTransaction: mockSendTransaction,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const { result } = renderHook(() => useSendTransaction());
|
|
139
|
-
|
|
140
|
-
let callPromise: Promise<string>;
|
|
141
|
-
act(() => {
|
|
142
|
-
callPromise = result.current.sendTransaction({ to: "0xabcd" });
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
expect(result.current.isSending).toBe(true);
|
|
146
|
-
|
|
147
|
-
await act(async () => {
|
|
148
|
-
resolvePromise!("0xtx");
|
|
149
|
-
await callPromise!;
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
expect(result.current.isSending).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("should capture error on failed send", async () => {
|
|
156
|
-
const mockSendTransaction = vi.fn().mockRejectedValue(new Error("Transaction rejected"));
|
|
157
|
-
mockUseWeb3.mockReturnValue({ session: createEvmSession(), chainId: "eip155:1" });
|
|
158
|
-
mockGetClient.mockReturnValue({
|
|
159
|
-
sendTransaction: mockSendTransaction,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const { result } = renderHook(() => useSendTransaction());
|
|
163
|
-
|
|
164
|
-
await act(async () => {
|
|
165
|
-
try {
|
|
166
|
-
await result.current.sendTransaction({ to: "0xabcd" });
|
|
167
|
-
} catch {
|
|
168
|
-
// Expected
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
expect(result.current.error).not.toBeNull();
|
|
173
|
-
expect(result.current.error?.message).toBe("Transaction rejected");
|
|
174
|
-
});
|
|
175
|
-
});
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback } from "react";
|
|
2
|
-
import { useWeb3 } from "../provider/Web3ConnectProvider";
|
|
3
|
-
import { getClient } from "../client";
|
|
4
|
-
import { WalletError } from "@naculus/connect-core";
|
|
5
|
-
import type { EvmTransaction } from "../types";
|
|
6
|
-
|
|
7
|
-
export type SendTransactionStatus = "idle" | "awaiting_approval" | "confirmed" | "failed";
|
|
8
|
-
|
|
9
|
-
export function useSendTransaction() {
|
|
10
|
-
const { session, chainId } = useWeb3();
|
|
11
|
-
const [status, setStatus] = useState<SendTransactionStatus>("idle");
|
|
12
|
-
const [error, setError] = useState<Error | null>(null);
|
|
13
|
-
|
|
14
|
-
const sendTransaction = useCallback(
|
|
15
|
-
async (transaction: EvmTransaction): Promise<string> => {
|
|
16
|
-
if (!session) {
|
|
17
|
-
setStatus("failed");
|
|
18
|
-
const err = new WalletError("wallet_unavailable", "No active session");
|
|
19
|
-
setError(err);
|
|
20
|
-
throw err;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const client = getClient();
|
|
24
|
-
if (!client) {
|
|
25
|
-
setStatus("failed");
|
|
26
|
-
const err = new WalletError("wallet_unavailable", "Client not initialized");
|
|
27
|
-
setError(err);
|
|
28
|
-
throw err;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const evmAccount = Object.values(session.namespaces)
|
|
32
|
-
.flatMap((ns) => ns.accounts)
|
|
33
|
-
.find((acc) => acc.includes("0x"));
|
|
34
|
-
|
|
35
|
-
if (!evmAccount) {
|
|
36
|
-
setStatus("failed");
|
|
37
|
-
const err = new WalletError("wallet_unavailable", "No EVM account found");
|
|
38
|
-
setError(err);
|
|
39
|
-
throw err;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
setStatus("awaiting_approval");
|
|
43
|
-
setError(null);
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const txWithFrom = {
|
|
47
|
-
...transaction,
|
|
48
|
-
from: transaction.from ?? evmAccount.split(":").pop()
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const result = (await client.sendTransaction(session, {
|
|
52
|
-
transaction: txWithFrom,
|
|
53
|
-
chainId: chainId ?? undefined
|
|
54
|
-
})) as string;
|
|
55
|
-
|
|
56
|
-
setStatus("confirmed");
|
|
57
|
-
return result;
|
|
58
|
-
} catch (err) {
|
|
59
|
-
setStatus("failed");
|
|
60
|
-
const errorMessage = err instanceof Error ? err.message : "Transaction failed";
|
|
61
|
-
setError(err instanceof Error ? err : new Error(errorMessage));
|
|
62
|
-
throw err;
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
[session, chainId]
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const reset = useCallback(() => {
|
|
69
|
-
setStatus("idle");
|
|
70
|
-
setError(null);
|
|
71
|
-
}, []);
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
sendTransaction,
|
|
75
|
-
status,
|
|
76
|
-
error,
|
|
77
|
-
reset,
|
|
78
|
-
isSending: status === "awaiting_approval"
|
|
79
|
-
};
|
|
80
|
-
}
|