@naculus/connect-appkit-react 0.1.8 → 0.2.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.
Files changed (94) hide show
  1. package/README.md +4 -4
  2. package/dist/client.cjs +1 -1
  3. package/dist/client.cjs.map +1 -1
  4. package/dist/client.d.cts +144 -0
  5. package/dist/client.d.ts +144 -0
  6. package/dist/client.js +1 -1
  7. package/dist/client.js.map +1 -1
  8. package/dist/index.cjs +2 -1
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +2270 -0
  11. package/dist/index.d.ts +2270 -0
  12. package/dist/index.js +2 -1
  13. package/dist/index.js.map +1 -1
  14. package/package.json +30 -12
  15. package/api-extractor.json +0 -21
  16. package/src/chain-abstraction/index.ts +0 -8
  17. package/src/chain-abstraction/useCompareCosts.ts +0 -137
  18. package/src/chain-abstraction/useExecuteRoute.ts +0 -129
  19. package/src/chain-abstraction/useRouteQuote.ts +0 -168
  20. package/src/client.test.ts +0 -242
  21. package/src/client.ts +0 -265
  22. package/src/hooks/useAccount.test.ts +0 -67
  23. package/src/hooks/useAccount.ts +0 -37
  24. package/src/hooks/useBalance.test.ts +0 -459
  25. package/src/hooks/useBalance.ts +0 -170
  26. package/src/hooks/useChain.test.ts +0 -160
  27. package/src/hooks/useChain.ts +0 -53
  28. package/src/hooks/useConnect.test.ts +0 -112
  29. package/src/hooks/useConnect.ts +0 -29
  30. package/src/hooks/useDisconnect.test.ts +0 -75
  31. package/src/hooks/useDisconnect.ts +0 -32
  32. package/src/hooks/useERC20Allowance.ts +0 -93
  33. package/src/hooks/useERC20Approve.ts +0 -220
  34. package/src/hooks/useERC20Transfer.ts +0 -134
  35. package/src/hooks/useERC20TransferSimulation.ts +0 -147
  36. package/src/hooks/useEmbeddedWallet.test.ts +0 -266
  37. package/src/hooks/useEmbeddedWallet.ts +0 -181
  38. package/src/hooks/useLastTx.test.ts +0 -117
  39. package/src/hooks/useLastTx.ts +0 -73
  40. package/src/hooks/useLookupAddress.ts +0 -131
  41. package/src/hooks/useNotification.ts +0 -308
  42. package/src/hooks/useResolveName.ts +0 -128
  43. package/src/hooks/useSIWxLogin.test.ts +0 -307
  44. package/src/hooks/useSIWxLogin.ts +0 -163
  45. package/src/hooks/useSIWxSession.test.ts +0 -481
  46. package/src/hooks/useSIWxSession.ts +0 -290
  47. package/src/hooks/useSendCalls.ts +0 -103
  48. package/src/hooks/useSendTransaction.test.ts +0 -175
  49. package/src/hooks/useSendTransaction.ts +0 -80
  50. package/src/hooks/useSendUserOperation.ts +0 -313
  51. package/src/hooks/useSession.ts +0 -96
  52. package/src/hooks/useSessionKeys.ts +0 -356
  53. package/src/hooks/useSignInWithEthereum.test.ts +0 -224
  54. package/src/hooks/useSignInWithEthereum.ts +0 -46
  55. package/src/hooks/useSignInWithX.test.ts +0 -210
  56. package/src/hooks/useSignInWithX.ts +0 -141
  57. package/src/hooks/useSignMessage.test.ts +0 -151
  58. package/src/hooks/useSignMessage.ts +0 -65
  59. package/src/hooks/useSimulateTransfer.ts +0 -165
  60. package/src/hooks/useSiwxAuthSession.test.ts +0 -456
  61. package/src/hooks/useSiwxAuthSession.ts +0 -163
  62. package/src/hooks/useSmartAccount.ts +0 -194
  63. package/src/hooks/useSwitchChain.ts +0 -68
  64. package/src/hooks/useTokenBalance.test.ts +0 -337
  65. package/src/hooks/useTokenBalance.ts +0 -210
  66. package/src/hooks/useTransactionSimulation.ts +0 -401
  67. package/src/hooks/useTxHistory.test.ts +0 -108
  68. package/src/hooks/useTxHistory.ts +0 -83
  69. package/src/hooks/useTxMonitor.test.ts +0 -127
  70. package/src/hooks/useTxMonitor.ts +0 -178
  71. package/src/hooks/useUserOpStatus.ts +0 -245
  72. package/src/hooks/useValidateDestination.test.ts +0 -96
  73. package/src/hooks/useValidateDestination.ts +0 -72
  74. package/src/hooks/useViemClient.test.ts +0 -155
  75. package/src/hooks/useViemClient.ts +0 -65
  76. package/src/hooks/useWallet.test.ts +0 -133
  77. package/src/hooks/useWallet.ts +0 -39
  78. package/src/hooks/useWeb3ErrorHandler.ts +0 -102
  79. package/src/index.ts +0 -186
  80. package/src/provider/Web3ConnectProvider.test.tsx +0 -178
  81. package/src/provider/Web3ConnectProvider.tsx +0 -530
  82. package/src/token-list/TokenSelector.tsx +0 -351
  83. package/src/token-list/defaults.ts +0 -39
  84. package/src/token-list/index.ts +0 -14
  85. package/src/token-list/useTokenList.ts +0 -119
  86. package/src/token-list/useTokenSearch.ts +0 -99
  87. package/src/types/index.ts +0 -99
  88. package/src/utils/chains.ts +0 -115
  89. package/src/utils/errorMessages.ts +0 -142
  90. package/src/utils/i18n.ts +0 -90
  91. package/src/wc-generated/components.ts +0 -377
  92. package/test/setup.ts +0 -17
  93. package/test/test-utils.tsx +0 -78
  94. package/tsup.config.ts +0 -18
@@ -1,127 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
- import { renderHook, act, waitFor } from "@testing-library/react";
4
- import { useTxMonitor, TxMonitorContext } from "./useTxMonitor";
5
- import type { TxMonitorLike, TxStatusEntry } from "./useTxMonitor";
6
- import React from "react";
7
-
8
- class MockMonitor implements TxMonitorLike {
9
- private entries = new Map<string, TxStatusEntry>();
10
- private listeners = new Map<string, Set<Function>>();
11
-
12
- setEntry(hash: string, chainId: number, entry: TxStatusEntry): void {
13
- const key = `${chainId}:${hash}`;
14
- this.entries.set(key, entry);
15
- }
16
-
17
- async watchTx(hash: string, chainId: number): Promise<TxStatusEntry> {
18
- const key = `${chainId}:${hash}`;
19
- const entry = this.entries.get(key) ?? {
20
- hash,
21
- chainId,
22
- from: "",
23
- to: "",
24
- value: "0x0",
25
- status: "unknown" as const,
26
- createdAt: Date.now(),
27
- updatedAt: Date.now(),
28
- replacementCount: 0,
29
- };
30
- this.entries.set(key, entry);
31
- this.emit("statusChange", entry);
32
- return entry;
33
- }
34
-
35
- stopWatching(hash: string, chainId?: number): void {
36
- // no-op
37
- }
38
-
39
- getTxStatus(hash: string, chainId?: number): TxStatusEntry | null {
40
- if (chainId !== undefined) return this.entries.get(`${chainId}:${hash}`) ?? null;
41
- for (const [, entry] of this.entries) {
42
- if (entry.hash === hash) return entry;
43
- }
44
- return null;
45
- }
46
-
47
- async getTxHistory(): Promise<TxStatusEntry[]> {
48
- return Array.from(this.entries.values());
49
- }
50
-
51
- async refreshTx(): Promise<void> {
52
- // no-op
53
- }
54
-
55
- on(event: string, listener: Function): this {
56
- const set = this.listeners.get(event) ?? new Set();
57
- set.add(listener);
58
- this.listeners.set(event, set);
59
- return this;
60
- }
61
-
62
- off(event: string, listener: Function): this {
63
- const set = this.listeners.get(event);
64
- if (set) set.delete(listener);
65
- return this;
66
- }
67
-
68
- private emit(event: string, ...args: any[]): void {
69
- const set = this.listeners.get(event);
70
- if (set) set.forEach(fn => fn(...args));
71
- }
72
- }
73
-
74
- function Wrapper({ monitor, children }: { monitor: TxMonitorLike; children: React.ReactNode }) {
75
- return React.createElement(TxMonitorContext.Provider, { value: monitor }, children);
76
- }
77
-
78
- describe("useTxMonitor", () => {
79
- let monitor: MockMonitor;
80
-
81
- beforeEach(() => {
82
- monitor = new MockMonitor();
83
- });
84
-
85
- it("returns idle state when no hash provided", () => {
86
- const { result } = renderHook(() => useTxMonitor(undefined, undefined, monitor));
87
- expect(result.current.status).toBe("idle");
88
- expect(result.current.entry).toBeNull();
89
- expect(result.current.isWatching).toBe(false);
90
- });
91
-
92
- it("starts watching when hash is provided", async () => {
93
- const hash = "0x" + "a".repeat(64);
94
- const entry: TxStatusEntry = {
95
- hash, chainId: 1, from: "0x" + "b".repeat(40), to: "0x" + "c".repeat(40),
96
- value: "0x0", status: "pending", createdAt: Date.now(), updatedAt: Date.now(),
97
- replacementCount: 0,
98
- };
99
- monitor.setEntry(hash, 1, entry);
100
-
101
- const { result } = renderHook(() => useTxMonitor(hash, 1, monitor));
102
-
103
- await waitFor(() => {
104
- expect(result.current.isLoading).toBe(false);
105
- });
106
-
107
- expect(result.current.entry).not.toBeNull();
108
- expect(result.current.entry!.hash).toBe(hash);
109
- });
110
-
111
- it("provides refresh and stopWatching methods", () => {
112
- const hash = "0x" + "d".repeat(64);
113
- const { result } = renderHook(() => useTxMonitor(hash, 1, monitor));
114
-
115
- expect(typeof result.current.refresh).toBe("function");
116
- expect(typeof result.current.stopWatching).toBe("function");
117
- });
118
-
119
- it("works with TxMonitorContext", () => {
120
- const hash = "0x" + "e".repeat(64);
121
- const { result } = renderHook(() => useTxMonitor(hash, 1), {
122
- wrapper: ({ children }) => React.createElement(Wrapper, { monitor, children }, children),
123
- });
124
-
125
- expect(result.current.isWatching).toBe(true);
126
- });
127
- });
@@ -1,178 +0,0 @@
1
- import { useState, useEffect, useCallback, useRef } from "react";
2
-
3
- // ── Types (standalone, to avoid dependency on wallet-engine) ──────
4
-
5
- export type TxStatus =
6
- | "pending"
7
- | "mined"
8
- | "confirmed"
9
- | "failed"
10
- | "unknown";
11
-
12
- export interface TxStatusEntry {
13
- hash: string;
14
- chainId: number;
15
- from: string;
16
- to: string;
17
- value: string;
18
- data?: string;
19
- nonce?: number;
20
- status: TxStatus;
21
- blockNumber?: number;
22
- blockHash?: string;
23
- gasUsed?: string;
24
- effectiveGasPrice?: string;
25
- createdAt: number;
26
- confirmedAt?: number;
27
- updatedAt: number;
28
- error?: string;
29
- label?: string;
30
- memo?: string;
31
- replacementCount?: number;
32
- }
33
-
34
- /** Minimal TxMonitor interface for React hooks */
35
- export interface TxMonitorLike {
36
- watchTx(hash: string, chainId: number, options?: {
37
- requiredConfirmations?: number;
38
- pollInterval?: number;
39
- label?: string;
40
- memo?: string;
41
- initialEntry?: Partial<TxStatusEntry>;
42
- }): Promise<TxStatusEntry>;
43
-
44
- stopWatching(hash: string, chainId?: number): void;
45
- getTxStatus(hash: string, chainId?: number): TxStatusEntry | null;
46
- getTxHistory(address?: string, chainId?: number): Promise<TxStatusEntry[]>;
47
- refreshTx(hash: string, chainId?: number): Promise<void>;
48
-
49
- on(event: "statusChange", listener: (entry: TxStatusEntry) => void): this;
50
- on(event: "confirmed", listener: (entry: TxStatusEntry) => void): this;
51
- on(event: "failed", listener: (entry: TxStatusEntry) => void): this;
52
- off(event: string, listener: Function): this;
53
- }
54
-
55
- // ── Context for providing TxMonitor ───────────────────────────────
56
-
57
- import { createContext, useContext } from "react";
58
-
59
- export const TxMonitorContext = createContext<TxMonitorLike | null>(null);
60
-
61
- export function useTxMonitorProvider(): TxMonitorLike | null {
62
- return useContext(TxMonitorContext);
63
- }
64
-
65
- // ── Hook ──────────────────────────────────────────────────────────
66
-
67
- export interface UseTxMonitorResult {
68
- entry: TxStatusEntry | null;
69
- status: TxStatus | "idle";
70
- isLoading: boolean;
71
- isWatching: boolean;
72
- error: Error | null;
73
- refresh: () => Promise<void>;
74
- stopWatching: () => void;
75
- }
76
-
77
- /**
78
- * Monitor a single transaction's lifecycle.
79
- * When hash changes, automatically stops old watch and starts new one.
80
- */
81
- export function useTxMonitor(
82
- hash?: string,
83
- chainId?: number,
84
- monitor?: TxMonitorLike,
85
- ): UseTxMonitorResult {
86
- const ctxMonitor = useTxMonitorProvider();
87
- const effectiveMonitor = monitor ?? ctxMonitor;
88
-
89
- const [entry, setEntry] = useState<TxStatusEntry | null>(null);
90
- const [isLoading, setIsLoading] = useState(false);
91
- const [isWatching, setIsWatching] = useState(false);
92
- const [error, setError] = useState<Error | null>(null);
93
- const prevHashRef = useRef<string | undefined>(undefined);
94
-
95
- const refresh = useCallback(async () => {
96
- if (!effectiveMonitor || !hash) return;
97
- setIsLoading(true);
98
- setError(null);
99
- try {
100
- await effectiveMonitor.refreshTx(hash, chainId);
101
- const current = effectiveMonitor.getTxStatus(hash, chainId);
102
- if (current) setEntry(current);
103
- } catch (err) {
104
- setError(err instanceof Error ? err : new Error(String(err)));
105
- } finally {
106
- setIsLoading(false);
107
- }
108
- }, [effectiveMonitor, hash, chainId]);
109
-
110
- const stopWatching = useCallback(() => {
111
- if (!effectiveMonitor || !hash) return;
112
- effectiveMonitor.stopWatching(hash, chainId);
113
- setIsWatching(false);
114
- }, [effectiveMonitor, hash, chainId]);
115
-
116
- useEffect(() => {
117
- if (!effectiveMonitor || !hash || !chainId) {
118
- setEntry(null);
119
- setIsWatching(false);
120
- return;
121
- }
122
-
123
- // Stop previous watch if hash changed
124
- if (prevHashRef.current && prevHashRef.current !== hash) {
125
- effectiveMonitor.stopWatching(prevHashRef.current);
126
- }
127
- prevHashRef.current = hash;
128
-
129
- let cancelled = false;
130
- setIsWatching(true);
131
- setIsLoading(true);
132
- setError(null);
133
-
134
- // Check if we already have status
135
- const current = effectiveMonitor.getTxStatus(hash, chainId);
136
- if (current) {
137
- setEntry(current);
138
- setIsLoading(false);
139
- }
140
-
141
- // Start watching
142
- effectiveMonitor.watchTx(hash, chainId, {}).then((initial) => {
143
- if (!cancelled) {
144
- setEntry(initial);
145
- setIsLoading(false);
146
- }
147
- }).catch((err) => {
148
- if (!cancelled) {
149
- setError(err instanceof Error ? err : new Error(String(err)));
150
- setIsLoading(false);
151
- }
152
- });
153
-
154
- // Listen for status changes
155
- const onStatusChange = (updated: TxStatusEntry) => {
156
- if (!cancelled) setEntry({ ...updated });
157
- };
158
- effectiveMonitor.on("statusChange", onStatusChange);
159
-
160
- return () => {
161
- cancelled = true;
162
- setIsWatching(false);
163
- effectiveMonitor.off("statusChange", onStatusChange);
164
- };
165
- }, [effectiveMonitor, hash, chainId]);
166
-
167
- const status: TxStatus | "idle" = entry?.status ?? "idle";
168
-
169
- return {
170
- entry,
171
- status,
172
- isLoading,
173
- isWatching,
174
- error,
175
- refresh,
176
- stopWatching,
177
- };
178
- }
@@ -1,245 +0,0 @@
1
- /**
2
- * useUserOpStatus
3
- *
4
- * React hook for tracking ERC-4337 UserOperation lifecycle.
5
- * Polls the bundler for UserOperation receipt and reports status transitions.
6
- *
7
- * @example
8
- * ```tsx
9
- * const { userOpHash, status, receipt, error } = useUserOpStatus({
10
- * userOpHash: "0x...",
11
- * bundlerUrl: "https://api.pimlico.io/v2/1/rpc?apikey=...",
12
- * pollInterval: 2000,
13
- * });
14
- *
15
- * if (status === "confirmed") {
16
- * console.log("UserOp included in block!");
17
- * }
18
- * ```
19
- */
20
-
21
- import { useState, useEffect, useRef, useCallback } from "react";
22
- import type {
23
- UserOperationReceipt,
24
- Address,
25
- Hex,
26
- } from "@naculus/connect-core";
27
-
28
- export type UserOpStatus = "idle" | "pending" | "confirmed" | "failed" | "not_found";
29
-
30
- export interface UseUserOpStatusOptions {
31
- /** The UserOperation hash to track */
32
- userOpHash?: Hex | null;
33
- /** Bundler RPC URL */
34
- bundlerUrl?: string;
35
- /** Polling interval in milliseconds (default: 2000) */
36
- pollInterval?: number;
37
- /** Maximum number of retries (default: 30) */
38
- maxRetries?: number;
39
- /** Auto-start polling when userOpHash is provided */
40
- autoStart?: boolean;
41
- }
42
-
43
- export interface UseUserOpStatusReturn {
44
- /** Current status of the UserOperation */
45
- status: UserOpStatus;
46
- /** UserOperation receipt (available when confirmed or failed) */
47
- receipt: UserOperationReceipt | null;
48
- /** The userOpHash being tracked */
49
- userOpHash: Hex | null;
50
- /** Error state */
51
- error: Error | null;
52
- /** Whether polling is active */
53
- isPolling: boolean;
54
- /** Time elapsed since tracking started (ms) */
55
- elapsedMs: number;
56
- /** Number of poll attempts made */
57
- attempts: number;
58
- /** Start tracking a UserOperation */
59
- start: (hash: Hex) => void;
60
- /** Stop tracking */
61
- stop: () => void;
62
- /** Reset state */
63
- reset: () => void;
64
- }
65
-
66
- /**
67
- * Hook for tracking the lifecycle of an ERC-4337 UserOperation.
68
- *
69
- * @param options - Configuration for polling behavior
70
- * @returns UserOperation status, receipt, and lifecycle controls
71
- */
72
- export function useUserOpStatus(
73
- options?: UseUserOpStatusOptions,
74
- ): UseUserOpStatusReturn {
75
- const bundlerUrl = options?.bundlerUrl;
76
- const pollInterval = options?.pollInterval ?? 2000;
77
- const maxRetries = options?.maxRetries ?? 30;
78
-
79
- const [status, setStatus] = useState<UserOpStatus>("idle");
80
- const [receipt, setReceipt] = useState<UserOperationReceipt | null>(null);
81
- const [userOpHash, setUserOpHash] = useState<Hex | null>(options?.userOpHash ?? null);
82
- const [error, setError] = useState<Error | null>(null);
83
- const [isPolling, setIsPolling] = useState(false);
84
- const [attempts, setAttempts] = useState(0);
85
-
86
- const startTimeRef = useRef<number>(0);
87
- const [elapsedMs, setElapsedMs] = useState(0);
88
- const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
89
- const pollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
90
-
91
- // Clear all timers
92
- const clearTimers = useCallback(() => {
93
- if (intervalRef.current) {
94
- clearInterval(intervalRef.current);
95
- intervalRef.current = null;
96
- }
97
- if (pollTimerRef.current) {
98
- clearTimeout(pollTimerRef.current);
99
- pollTimerRef.current = null;
100
- }
101
- }, []);
102
-
103
- // Poll the bundler for receipt
104
- const poll = useCallback(async (hash: Hex) => {
105
- if (!bundlerUrl) return;
106
-
107
- try {
108
- const response = await fetch(bundlerUrl, {
109
- method: "POST",
110
- headers: { "Content-Type": "application/json" },
111
- body: JSON.stringify({
112
- jsonrpc: "2.0",
113
- id: 1,
114
- method: "eth_getUserOperationReceipt",
115
- params: [hash],
116
- }),
117
- });
118
-
119
- if (!response.ok) return;
120
-
121
- const json = await response.json() as {
122
- result?: {
123
- userOpHash: Hex;
124
- entryPoint: Address;
125
- sender: Address;
126
- nonce: string;
127
- paymaster?: Address;
128
- actualGasUsed: string;
129
- actualGasCost: string;
130
- success: boolean;
131
- transactionHash: Hex;
132
- logs: Array<{ address: Address; topics: Hex[]; data: Hex }>;
133
- };
134
- };
135
-
136
- if (json.result) {
137
- const receiptData: UserOperationReceipt = {
138
- userOpHash: json.result.userOpHash,
139
- entryPoint: json.result.entryPoint,
140
- sender: json.result.sender,
141
- nonce: BigInt(json.result.nonce),
142
- paymaster: json.result.paymaster,
143
- actualGasUsed: BigInt(json.result.actualGasUsed),
144
- actualGasCost: BigInt(json.result.actualGasCost),
145
- success: json.result.success,
146
- transactionHash: json.result.transactionHash,
147
- logs: json.result.logs,
148
- };
149
-
150
- setReceipt(receiptData);
151
- setStatus(json.result.success ? "confirmed" : "failed");
152
- setIsPolling(false);
153
- clearTimers();
154
-
155
- return;
156
- }
157
-
158
- // No receipt yet; schedule next poll
159
- setAttempts(prev => prev + 1);
160
- setStatus("pending");
161
-
162
- if (attempts < maxRetries) {
163
- pollTimerRef.current = setTimeout(() => poll(hash), pollInterval);
164
- } else {
165
- setStatus("not_found");
166
- setIsPolling(false);
167
- clearTimers();
168
- setError(new Error("UserOperation not included after maximum retries"));
169
- }
170
- } catch {
171
- // Network error; retry
172
- setAttempts(prev => prev + 1);
173
- if (attempts < maxRetries) {
174
- pollTimerRef.current = setTimeout(() => poll(hash), pollInterval);
175
- }
176
- }
177
- }, [bundlerUrl, pollInterval, maxRetries, attempts, clearTimers]);
178
-
179
- // Start tracking
180
- const start = useCallback((hash: Hex) => {
181
- clearTimers();
182
- setUserOpHash(hash);
183
- setStatus("pending");
184
- setReceipt(null);
185
- setError(null);
186
- setAttempts(0);
187
- setIsPolling(true);
188
- startTimeRef.current = Date.now();
189
-
190
- // Start elapsed timer
191
- intervalRef.current = setInterval(() => {
192
- setElapsedMs(Date.now() - startTimeRef.current);
193
- }, 1000);
194
-
195
- // Start polling
196
- poll(hash);
197
- }, [clearTimers, poll]);
198
-
199
- // Stop tracking
200
- const stop = useCallback(() => {
201
- clearTimers();
202
- setIsPolling(false);
203
- }, [clearTimers]);
204
-
205
- // Reset state
206
- const reset = useCallback(() => {
207
- clearTimers();
208
- setUserOpHash(null);
209
- setStatus("idle");
210
- setReceipt(null);
211
- setError(null);
212
- setIsPolling(false);
213
- setAttempts(0);
214
- setElapsedMs(0);
215
- }, [clearTimers]);
216
-
217
- // Auto-start if userOpHash provided on mount
218
- useEffect(() => {
219
- if (options?.userOpHash && options?.autoStart !== false) {
220
- start(options.userOpHash);
221
- }
222
- // Only run on mount
223
- // eslint-disable-next-line react-hooks/exhaustive-deps
224
- }, []);
225
-
226
- // Cleanup on unmount
227
- useEffect(() => {
228
- return () => {
229
- clearTimers();
230
- };
231
- }, [clearTimers]);
232
-
233
- return {
234
- status,
235
- receipt,
236
- userOpHash,
237
- error,
238
- isPolling,
239
- elapsedMs,
240
- attempts,
241
- start,
242
- stop,
243
- reset,
244
- };
245
- }
@@ -1,96 +0,0 @@
1
- /// <reference types="vitest" />
2
-
3
- // @vitest-environment jsdom
4
- import { describe, it, expect, vi } from "vitest";
5
- import { validateDestination, useValidateDestination } from "./useValidateDestination";
6
- import { renderHook } from "@testing-library/react";
7
-
8
- describe("validateDestination", () => {
9
- // Valid addresses
10
- it("accepts valid EVM addresses", () => {
11
- expect(validateDestination("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045").isValid).toBe(true);
12
- expect(validateDestination("0x1234567890abcdef1234567890abcdef12345678").isValid).toBe(true);
13
- });
14
-
15
- it("rejects empty address", () => {
16
- expect(validateDestination("").isValid).toBe(false);
17
- expect(validateDestination("").level).toBe("blocked");
18
- });
19
-
20
- it("rejects zero address", () => {
21
- expect(validateDestination("0x0000000000000000000000000000000000000000").isValid).toBe(false);
22
- expect(validateDestination("0x0000000000000000000000000000000000000000").level).toBe("blocked");
23
- });
24
-
25
- it("rejects burn addresses", () => {
26
- expect(validateDestination("0x000000000000000000000000000000000000dead").isValid).toBe(false);
27
- expect(validateDestination("0x0000000000000000000000000000000000000001").isValid).toBe(false);
28
- });
29
-
30
- it("rejects invalid format", () => {
31
- expect(validateDestination("not-an-address").isValid).toBe(false);
32
- expect(validateDestination("0x123").isValid).toBe(false);
33
- expect(validateDestination("0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG").isValid).toBe(false);
34
- });
35
-
36
- // Property-based: fuzz variant with many generated addresses
37
- it("correctly validates 50 random-like hex strings", () => {
38
- for (let i = 0; i < 50; i++) {
39
- // Generate random 40-char hex
40
- const hex = "0x" + Array.from({ length: 40 }, () =>
41
- Math.floor(Math.random() * 16).toString(16)
42
- ).join("");
43
-
44
- const result = validateDestination(hex);
45
-
46
- // All properly formatted EVM addresses should be valid unless they're zero/burn
47
- if (hex === "0x0000000000000000000000000000000000000000" ||
48
- hex.toLowerCase().includes("dead") && hex.length === 42) {
49
- expect(result.isValid).toBe(false);
50
- } else {
51
- expect(result.isValid).toBe(true);
52
- expect(result.level).toBe("safe");
53
- }
54
- }
55
- });
56
-
57
- // Edge cases
58
- it("handles uppercase addresses", () => {
59
- expect(validateDestination("0xABCDEF1234567890ABCDEF1234567890ABCDEF12").isValid).toBe(true);
60
- });
61
-
62
- it("handles mixed case checksum addresses", () => {
63
- expect(validateDestination("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045").isValid).toBe(true);
64
- });
65
- });
66
-
67
- describe("useValidateDestination", () => {
68
- it("returns safe validation for valid address", () => {
69
- const { result } = renderHook(() =>
70
- useValidateDestination({ address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" })
71
- );
72
- expect(result.current.validation.isValid).toBe(true);
73
- expect(result.current.validation.level).toBe("safe");
74
- expect(result.current.validation.warning).toBe(null);
75
- });
76
-
77
- it("returns blocked for zero address", () => {
78
- const { result } = renderHook(() =>
79
- useValidateDestination({ address: "0x0000000000000000000000000000000000000000" })
80
- );
81
- expect(result.current.validation.isValid).toBe(false);
82
- expect(result.current.validation.level).toBe("blocked");
83
- expect(result.current.validation.warning).toBeTruthy();
84
- });
85
-
86
- it("updates when address changes", () => {
87
- const { result, rerender } = renderHook(
88
- ({ address }) => useValidateDestination({ address }),
89
- { initialProps: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" } }
90
- );
91
- expect(result.current.validation.isValid).toBe(true);
92
-
93
- rerender({ address: "0x0000000000000000000000000000000000000000" });
94
- expect(result.current.validation.isValid).toBe(false);
95
- });
96
- });