@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.
- 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,459 +0,0 @@
|
|
|
1
|
-
/// <reference types="vitest" />
|
|
2
|
-
/// @vitest-environment jsdom
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect, vi, beforeEach, afterEach } 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 mockUseAccount = vi.fn();
|
|
14
|
-
vi.mock("./useAccount", () => ({
|
|
15
|
-
useAccount: () => mockUseAccount(),
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
const mockGetNativeTokenPriceUsd = vi.fn().mockResolvedValue(null);
|
|
19
|
-
vi.mock("@naculus/connect-core", () => ({
|
|
20
|
-
getNativeTokenPriceUsd: (...args: unknown[]) => mockGetNativeTokenPriceUsd(...args),
|
|
21
|
-
clearPriceCache: vi.fn(),
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
const mockGetBalance = vi.fn();
|
|
25
|
-
const mockCreatePublicClient = vi.fn();
|
|
26
|
-
vi.mock("viem", () => ({
|
|
27
|
-
createPublicClient: (...args: unknown[]) => mockCreatePublicClient(...args),
|
|
28
|
-
http: (url: string) => ({ transport: "http", url }),
|
|
29
|
-
formatEther: (value: bigint) => {
|
|
30
|
-
const str = value.toString();
|
|
31
|
-
if (str === "0") return "0";
|
|
32
|
-
const padded = str.padStart(19, "0");
|
|
33
|
-
const intPart = padded.slice(0, padded.length - 18) || "0";
|
|
34
|
-
const fracPart = padded.slice(padded.length - 18).replace(/0+$/, "");
|
|
35
|
-
return fracPart.length > 0 ? `${intPart}.${fracPart}` : intPart;
|
|
36
|
-
},
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
import { useBalance } from "./useBalance";
|
|
40
|
-
|
|
41
|
-
describe("useBalance", () => {
|
|
42
|
-
beforeEach(() => {
|
|
43
|
-
vi.clearAllMocks();
|
|
44
|
-
mockCreatePublicClient.mockImplementation(({ chain }: { chain: { id: number } }) => ({
|
|
45
|
-
chain,
|
|
46
|
-
getBalance: mockGetBalance,
|
|
47
|
-
}));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// M17/L11: Always restore real timers after each test to prevent timer leaks
|
|
51
|
-
afterEach(() => {
|
|
52
|
-
vi.useRealTimers();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("should return null balance when not connected", () => {
|
|
56
|
-
mockUseWeb3.mockReturnValue({
|
|
57
|
-
chainId: null,
|
|
58
|
-
chains: [],
|
|
59
|
-
});
|
|
60
|
-
mockUseAccount.mockReturnValue({
|
|
61
|
-
evmAccount: null,
|
|
62
|
-
isConnected: false,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const { result } = renderHook(() => useBalance());
|
|
66
|
-
|
|
67
|
-
expect(result.current.balance).toBeNull();
|
|
68
|
-
expect(result.current.formatted).toBeNull();
|
|
69
|
-
expect(result.current.isLoading).toBe(false);
|
|
70
|
-
expect(result.current.error).toBeNull();
|
|
71
|
-
expect(result.current.chain).toBeNull();
|
|
72
|
-
expect(result.current.symbol).toBe("ETH");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should create client and fetch balance when connected", async () => {
|
|
76
|
-
mockGetBalance.mockResolvedValue(BigInt("1000000000000000000"));
|
|
77
|
-
mockUseWeb3.mockReturnValue({
|
|
78
|
-
chainId: "eip155:1",
|
|
79
|
-
chains: [
|
|
80
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
81
|
-
],
|
|
82
|
-
});
|
|
83
|
-
mockUseAccount.mockReturnValue({
|
|
84
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
85
|
-
isConnected: true,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const { result } = renderHook(() => useBalance());
|
|
89
|
-
|
|
90
|
-
// Wait for the useEffect to trigger fetchBalance
|
|
91
|
-
await act(async () => {
|
|
92
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
expect(mockGetBalance).toHaveBeenCalledWith({
|
|
96
|
-
address: "0x1234567890123456789012345678901234567890",
|
|
97
|
-
});
|
|
98
|
-
expect(result.current.balance).toBe("1000000000000000000");
|
|
99
|
-
// 1 ETH formatted
|
|
100
|
-
expect(result.current.formatted).toBe("1");
|
|
101
|
-
expect(result.current.isLoading).toBe(false);
|
|
102
|
-
expect(result.current.chain?.name).toBe("Ethereum");
|
|
103
|
-
expect(result.current.symbol).toBe("ETH");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should handle plain 0x address format", async () => {
|
|
107
|
-
mockGetBalance.mockResolvedValue(BigInt("500000000000000000"));
|
|
108
|
-
mockUseWeb3.mockReturnValue({
|
|
109
|
-
chainId: "eip155:1",
|
|
110
|
-
chains: [
|
|
111
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
112
|
-
],
|
|
113
|
-
});
|
|
114
|
-
mockUseAccount.mockReturnValue({
|
|
115
|
-
evmAccount: "0xabcdef1234567890abcdef1234567890abcdef12",
|
|
116
|
-
isConnected: true,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const { result } = renderHook(() => useBalance());
|
|
120
|
-
|
|
121
|
-
await act(async () => {
|
|
122
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
expect(mockGetBalance).toHaveBeenCalledWith({
|
|
126
|
-
address: "0xabcdef1234567890abcdef1234567890abcdef12",
|
|
127
|
-
});
|
|
128
|
-
expect(result.current.balance).toBe("500000000000000000");
|
|
129
|
-
// 0.5 ETH formatted
|
|
130
|
-
expect(result.current.formatted).toBe("0.5");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("should not fetch balance when not connected", async () => {
|
|
134
|
-
mockUseWeb3.mockReturnValue({
|
|
135
|
-
chainId: "eip155:1",
|
|
136
|
-
chains: [
|
|
137
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
138
|
-
],
|
|
139
|
-
});
|
|
140
|
-
mockUseAccount.mockReturnValue({
|
|
141
|
-
evmAccount: null,
|
|
142
|
-
isConnected: false,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const { result } = renderHook(() => useBalance());
|
|
146
|
-
|
|
147
|
-
await act(async () => {
|
|
148
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
expect(mockGetBalance).not.toHaveBeenCalled();
|
|
152
|
-
expect(result.current.balance).toBeNull();
|
|
153
|
-
expect(result.current.formatted).toBeNull();
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("should handle fetch error gracefully", async () => {
|
|
157
|
-
mockGetBalance.mockRejectedValue(new Error("RPC error"));
|
|
158
|
-
mockUseWeb3.mockReturnValue({
|
|
159
|
-
chainId: "eip155:1",
|
|
160
|
-
chains: [
|
|
161
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
162
|
-
],
|
|
163
|
-
});
|
|
164
|
-
mockUseAccount.mockReturnValue({
|
|
165
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
166
|
-
isConnected: true,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const { result } = renderHook(() => useBalance());
|
|
170
|
-
|
|
171
|
-
await act(async () => {
|
|
172
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
expect(result.current.balance).toBeNull();
|
|
176
|
-
expect(result.current.formatted).toBeNull();
|
|
177
|
-
expect(result.current.error).not.toBeNull();
|
|
178
|
-
expect(result.current.error?.message).toBe("RPC error");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("should support refetch", async () => {
|
|
182
|
-
mockGetBalance.mockResolvedValue(BigInt("2000000000000000000"));
|
|
183
|
-
mockUseWeb3.mockReturnValue({
|
|
184
|
-
chainId: "eip155:1",
|
|
185
|
-
chains: [
|
|
186
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
187
|
-
],
|
|
188
|
-
});
|
|
189
|
-
mockUseAccount.mockReturnValue({
|
|
190
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
191
|
-
isConnected: true,
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const { result } = renderHook(() => useBalance());
|
|
195
|
-
|
|
196
|
-
// Initial fetch
|
|
197
|
-
await act(async () => {
|
|
198
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
expect(result.current.balance).toBe("2000000000000000000");
|
|
202
|
-
expect(result.current.formatted).toBe("2");
|
|
203
|
-
|
|
204
|
-
// Refetch with new value
|
|
205
|
-
mockGetBalance.mockResolvedValue(BigInt("3000000000000000000"));
|
|
206
|
-
await act(async () => {
|
|
207
|
-
await result.current.refetch();
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
expect(result.current.balance).toBe("3000000000000000000");
|
|
211
|
-
expect(result.current.formatted).toBe("3");
|
|
212
|
-
expect(mockGetBalance).toHaveBeenCalledTimes(2);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("should return null chain when unknown chainId", () => {
|
|
216
|
-
mockUseWeb3.mockReturnValue({
|
|
217
|
-
chainId: "eip155:9999",
|
|
218
|
-
chains: [
|
|
219
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
220
|
-
],
|
|
221
|
-
});
|
|
222
|
-
mockUseAccount.mockReturnValue({
|
|
223
|
-
evmAccount: null,
|
|
224
|
-
isConnected: false,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const { result } = renderHook(() => useBalance());
|
|
228
|
-
|
|
229
|
-
expect(result.current.chain).toBeNull();
|
|
230
|
-
expect(result.current.symbol).toBe("ETH");
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it("should auto-refresh at specified interval", async () => {
|
|
234
|
-
vi.useFakeTimers();
|
|
235
|
-
mockGetBalance.mockResolvedValue(BigInt("1000000000000000000"));
|
|
236
|
-
mockUseWeb3.mockReturnValue({
|
|
237
|
-
chainId: "eip155:1",
|
|
238
|
-
chains: [
|
|
239
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
240
|
-
],
|
|
241
|
-
});
|
|
242
|
-
mockUseAccount.mockReturnValue({
|
|
243
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
244
|
-
isConnected: true,
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
renderHook(() => useBalance({ refreshInterval: 10000 }));
|
|
248
|
-
|
|
249
|
-
// Initial fetch happens via useEffect — advance microtasks
|
|
250
|
-
await act(async () => {
|
|
251
|
-
vi.advanceTimersByTime(0);
|
|
252
|
-
});
|
|
253
|
-
expect(mockGetBalance).toHaveBeenCalledTimes(1);
|
|
254
|
-
|
|
255
|
-
// Advance time by the refresh interval
|
|
256
|
-
mockGetBalance.mockResolvedValue(BigInt("2000000000000000000"));
|
|
257
|
-
await act(async () => {
|
|
258
|
-
vi.advanceTimersByTime(10000);
|
|
259
|
-
});
|
|
260
|
-
expect(mockGetBalance).toHaveBeenCalledTimes(2);
|
|
261
|
-
|
|
262
|
-
// Advance again
|
|
263
|
-
mockGetBalance.mockResolvedValue(BigInt("3000000000000000000"));
|
|
264
|
-
await act(async () => {
|
|
265
|
-
vi.advanceTimersByTime(10000);
|
|
266
|
-
});
|
|
267
|
-
expect(mockGetBalance).toHaveBeenCalledTimes(3);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it("should clear interval on unmount", async () => {
|
|
271
|
-
vi.useFakeTimers();
|
|
272
|
-
mockGetBalance.mockResolvedValue(BigInt("1000000000000000000"));
|
|
273
|
-
mockUseWeb3.mockReturnValue({
|
|
274
|
-
chainId: "eip155:1",
|
|
275
|
-
chains: [
|
|
276
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
277
|
-
],
|
|
278
|
-
});
|
|
279
|
-
mockUseAccount.mockReturnValue({
|
|
280
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
281
|
-
isConnected: true,
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
const { unmount } = renderHook(() => useBalance({ refreshInterval: 5000 }));
|
|
285
|
-
|
|
286
|
-
// Advance microtasks for initial fetch
|
|
287
|
-
await act(async () => {
|
|
288
|
-
vi.advanceTimersByTime(0);
|
|
289
|
-
});
|
|
290
|
-
const callsAfterMount = mockGetBalance.mock.calls.length;
|
|
291
|
-
|
|
292
|
-
unmount();
|
|
293
|
-
|
|
294
|
-
// Advance time — should NOT trigger another fetch
|
|
295
|
-
vi.advanceTimersByTime(10000);
|
|
296
|
-
|
|
297
|
-
expect(mockGetBalance).toHaveBeenCalledTimes(callsAfterMount);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it("should not auto-refresh when refreshInterval is 0", async () => {
|
|
301
|
-
vi.useFakeTimers();
|
|
302
|
-
mockGetBalance.mockResolvedValue(BigInt("1000000000000000000"));
|
|
303
|
-
mockUseWeb3.mockReturnValue({
|
|
304
|
-
chainId: "eip155:1",
|
|
305
|
-
chains: [
|
|
306
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
307
|
-
],
|
|
308
|
-
});
|
|
309
|
-
mockUseAccount.mockReturnValue({
|
|
310
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
311
|
-
isConnected: true,
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
renderHook(() => useBalance({ refreshInterval: 0 }));
|
|
315
|
-
|
|
316
|
-
// Advance microtasks for initial fetch
|
|
317
|
-
await act(async () => {
|
|
318
|
-
vi.advanceTimersByTime(0);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const callsAfterInit = mockGetBalance.mock.calls.length;
|
|
322
|
-
|
|
323
|
-
// Advance time should not trigger more calls
|
|
324
|
-
vi.advanceTimersByTime(30000);
|
|
325
|
-
|
|
326
|
-
expect(mockGetBalance).toHaveBeenCalledTimes(callsAfterInit);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("should use chain token symbol when available", () => {
|
|
330
|
-
mockUseWeb3.mockReturnValue({
|
|
331
|
-
chainId: "eip155:137",
|
|
332
|
-
chains: [
|
|
333
|
-
{ id: 137, namespace: "eip155", name: "Polygon", rpcUrl: "https://polygon-rpc.com", token: "MATIC" },
|
|
334
|
-
],
|
|
335
|
-
});
|
|
336
|
-
mockUseAccount.mockReturnValue({
|
|
337
|
-
evmAccount: null,
|
|
338
|
-
isConnected: false,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
const { result } = renderHook(() => useBalance());
|
|
342
|
-
|
|
343
|
-
expect(result.current.symbol).toBe("MATIC");
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("should return usdPrice and usdValue when price oracle succeeds", async () => {
|
|
347
|
-
mockGetNativeTokenPriceUsd.mockResolvedValue(3500.50);
|
|
348
|
-
mockGetBalance.mockResolvedValue(BigInt("1000000000000000000")); // 1 ETH
|
|
349
|
-
mockUseWeb3.mockReturnValue({
|
|
350
|
-
chainId: "eip155:1",
|
|
351
|
-
chains: [
|
|
352
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
353
|
-
],
|
|
354
|
-
});
|
|
355
|
-
mockUseAccount.mockReturnValue({
|
|
356
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
357
|
-
isConnected: true,
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
const { result } = renderHook(() => useBalance());
|
|
361
|
-
|
|
362
|
-
await act(async () => {
|
|
363
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
expect(result.current.usdPrice).toBe(3500.50);
|
|
367
|
-
expect(result.current.usdValue).toBe("$3,500.50");
|
|
368
|
-
expect(mockGetNativeTokenPriceUsd).toHaveBeenCalledWith("eip155:1", "https://eth.llamarpc.com");
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it("should return null usd fields when price oracle fails", async () => {
|
|
372
|
-
mockGetNativeTokenPriceUsd.mockRejectedValue(new Error("Network error"));
|
|
373
|
-
mockGetBalance.mockResolvedValue(BigInt("1000000000000000000")); // 1 ETH
|
|
374
|
-
mockUseWeb3.mockReturnValue({
|
|
375
|
-
chainId: "eip155:1",
|
|
376
|
-
chains: [
|
|
377
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
378
|
-
],
|
|
379
|
-
});
|
|
380
|
-
mockUseAccount.mockReturnValue({
|
|
381
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
382
|
-
isConnected: true,
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
const { result } = renderHook(() => useBalance());
|
|
386
|
-
|
|
387
|
-
await act(async () => {
|
|
388
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// Balance should still be fetched, USD should be null
|
|
392
|
-
expect(result.current.balance).toBe("1000000000000000000");
|
|
393
|
-
expect(result.current.formatted).toBe("1");
|
|
394
|
-
expect(result.current.usdPrice).toBeNull();
|
|
395
|
-
expect(result.current.usdValue).toBeNull();
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it("should return null usd fields when not connected", () => {
|
|
399
|
-
mockUseWeb3.mockReturnValue({
|
|
400
|
-
chainId: null,
|
|
401
|
-
chains: [],
|
|
402
|
-
});
|
|
403
|
-
mockUseAccount.mockReturnValue({
|
|
404
|
-
evmAccount: null,
|
|
405
|
-
isConnected: false,
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
const { result } = renderHook(() => useBalance());
|
|
409
|
-
|
|
410
|
-
expect(result.current.usdPrice).toBeNull();
|
|
411
|
-
expect(result.current.usdValue).toBeNull();
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
it("should format small balances in USD correctly", async () => {
|
|
415
|
-
mockGetNativeTokenPriceUsd.mockResolvedValue(3500.50);
|
|
416
|
-
mockGetBalance.mockResolvedValue(BigInt("1000000000000000")); // 0.001 ETH
|
|
417
|
-
mockUseWeb3.mockReturnValue({
|
|
418
|
-
chainId: "eip155:1",
|
|
419
|
-
chains: [
|
|
420
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
421
|
-
],
|
|
422
|
-
});
|
|
423
|
-
mockUseAccount.mockReturnValue({
|
|
424
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
425
|
-
isConnected: true,
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
const { result } = renderHook(() => useBalance());
|
|
429
|
-
|
|
430
|
-
await act(async () => {
|
|
431
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
expect(result.current.formatted).toBe("0.001");
|
|
435
|
-
expect(result.current.usdValue).toBe("$3.50");
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
it("should update formatted balance when balance changes", async () => {
|
|
439
|
-
mockGetBalance.mockResolvedValue(BigInt("1500000000000000000")); // 1.5 ETH
|
|
440
|
-
mockUseWeb3.mockReturnValue({
|
|
441
|
-
chainId: "eip155:1",
|
|
442
|
-
chains: [
|
|
443
|
-
{ id: 1, namespace: "eip155", name: "Ethereum", rpcUrl: "https://eth.llamarpc.com", token: "ETH" },
|
|
444
|
-
],
|
|
445
|
-
});
|
|
446
|
-
mockUseAccount.mockReturnValue({
|
|
447
|
-
evmAccount: "eip155:1:0x1234567890123456789012345678901234567890",
|
|
448
|
-
isConnected: true,
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
const { result } = renderHook(() => useBalance());
|
|
452
|
-
|
|
453
|
-
await act(async () => {
|
|
454
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
expect(result.current.formatted).toBe("1.5");
|
|
458
|
-
});
|
|
459
|
-
});
|
package/src/hooks/useBalance.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
2
|
-
import { useWeb3 } from "../provider/Web3ConnectProvider";
|
|
3
|
-
import { useAccount } from "./useAccount";
|
|
4
|
-
import { createPublicClient, http, formatEther, type PublicClient, type Address } from "viem";
|
|
5
|
-
import { getNativeTokenPriceUsd } from "@naculus/connect-core";
|
|
6
|
-
|
|
7
|
-
export interface UseBalanceOptions {
|
|
8
|
-
/** Auto-refresh interval in milliseconds. Default: undefined (no auto-refresh). */
|
|
9
|
-
refreshInterval?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface UseBalanceResult {
|
|
13
|
-
/** Raw balance in wei (string), or null if not connected / error */
|
|
14
|
-
balance: string | null;
|
|
15
|
-
/** Human-readable formatted balance in ETH (or native token), or null */
|
|
16
|
-
formatted: string | null;
|
|
17
|
-
/** Native token symbol (e.g. "ETH", "MATIC") */
|
|
18
|
-
symbol: string;
|
|
19
|
-
/** USD price of one native token, or null if unavailable */
|
|
20
|
-
usdPrice: number | null;
|
|
21
|
-
/** Formatted USD value of the balance (e.g. "$1,234.56"), or null */
|
|
22
|
-
usdValue: string | null;
|
|
23
|
-
isLoading: boolean;
|
|
24
|
-
error: Error | null;
|
|
25
|
-
refetch: () => Promise<void>;
|
|
26
|
-
chain: { id: number; namespace: string; name: string; rpcUrl?: string; token?: string } | null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function useBalance(options?: UseBalanceOptions): UseBalanceResult {
|
|
30
|
-
const { evmAccount, isConnected } = useAccount();
|
|
31
|
-
const { chainId, chains } = useWeb3();
|
|
32
|
-
const [balance, setBalance] = useState<string | null>(null);
|
|
33
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
34
|
-
const [error, setError] = useState<Error | null>(null);
|
|
35
|
-
const [usdPrice, setUsdPrice] = useState<number | null>(null);
|
|
36
|
-
const refreshInterval = options?.refreshInterval;
|
|
37
|
-
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
38
|
-
|
|
39
|
-
const currentChain = useMemo(() => {
|
|
40
|
-
if (!chainId || !chains.length) return null;
|
|
41
|
-
|
|
42
|
-
const namespace = chainId.startsWith("eip155:") ? "eip155" : null;
|
|
43
|
-
if (!namespace) return null;
|
|
44
|
-
|
|
45
|
-
const chainPart = chainId.split(":")[1];
|
|
46
|
-
const chainNum = Number(chainPart);
|
|
47
|
-
if (!Number.isFinite(chainNum) || chainNum <= 0) return null; // H11: reject malformed chain IDs
|
|
48
|
-
return chains.find((c) => c.namespace === namespace && c.id === chainNum) ?? null;
|
|
49
|
-
}, [chainId, chains]);
|
|
50
|
-
|
|
51
|
-
const tokenSymbol = useMemo(() => {
|
|
52
|
-
return currentChain?.token ?? "ETH";
|
|
53
|
-
}, [currentChain]);
|
|
54
|
-
|
|
55
|
-
const [client, setClient] = useState<PublicClient | null>(null);
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (!currentChain?.rpcUrl) {
|
|
59
|
-
setClient(null);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const publicClient = createPublicClient({
|
|
64
|
-
transport: http(currentChain.rpcUrl),
|
|
65
|
-
chain: {
|
|
66
|
-
id: currentChain.id,
|
|
67
|
-
name: currentChain.name,
|
|
68
|
-
nativeCurrency: {
|
|
69
|
-
name: currentChain.token ?? "ETH",
|
|
70
|
-
symbol: currentChain.token ?? "ETH",
|
|
71
|
-
decimals: 18,
|
|
72
|
-
},
|
|
73
|
-
rpcUrls: {
|
|
74
|
-
default: { http: [currentChain.rpcUrl] },
|
|
75
|
-
public: { http: [currentChain.rpcUrl] },
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
setClient(publicClient);
|
|
81
|
-
|
|
82
|
-
// H10: Cleanup previous client on chain change
|
|
83
|
-
return () => {
|
|
84
|
-
// viem PublicClient doesn't have a close method, but we clear the reference
|
|
85
|
-
// to prevent stale state from being used in fetchBalance
|
|
86
|
-
};
|
|
87
|
-
}, [currentChain]);
|
|
88
|
-
|
|
89
|
-
const fetchBalance = useCallback(async () => {
|
|
90
|
-
if (!evmAccount || !client) {
|
|
91
|
-
setBalance(null);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
setIsLoading(true);
|
|
96
|
-
setError(null);
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const address = evmAccount.includes(":") ? evmAccount.split(":").pop()! : evmAccount;
|
|
100
|
-
const address_ = address as Address;
|
|
101
|
-
const [balanceResult, price] = await Promise.all([
|
|
102
|
-
client.getBalance({ address: address_ }),
|
|
103
|
-
chainId ? getNativeTokenPriceUsd(chainId, currentChain?.rpcUrl).catch(() => null) : Promise.resolve(null),
|
|
104
|
-
]);
|
|
105
|
-
setBalance(balanceResult.toString());
|
|
106
|
-
setUsdPrice(price);
|
|
107
|
-
} catch (err) {
|
|
108
|
-
setError(err instanceof Error ? err : new Error("Failed to fetch balance"));
|
|
109
|
-
setBalance(null);
|
|
110
|
-
setUsdPrice(null);
|
|
111
|
-
} finally {
|
|
112
|
-
setIsLoading(false);
|
|
113
|
-
}
|
|
114
|
-
}, [evmAccount, client, chainId, currentChain?.rpcUrl]);
|
|
115
|
-
|
|
116
|
-
// Initial fetch on connect / account change
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
if (isConnected && evmAccount) {
|
|
119
|
-
fetchBalance();
|
|
120
|
-
}
|
|
121
|
-
}, [isConnected, evmAccount, fetchBalance]);
|
|
122
|
-
|
|
123
|
-
// Auto-refresh interval
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
if (refreshInterval && refreshInterval > 0 && isConnected && evmAccount) {
|
|
126
|
-
intervalRef.current = setInterval(fetchBalance, refreshInterval);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return () => {
|
|
130
|
-
if (intervalRef.current) {
|
|
131
|
-
clearInterval(intervalRef.current);
|
|
132
|
-
intervalRef.current = null;
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
}, [refreshInterval, isConnected, evmAccount, fetchBalance]);
|
|
136
|
-
|
|
137
|
-
const formatted = useMemo(() => {
|
|
138
|
-
if (balance === null) return null;
|
|
139
|
-
try {
|
|
140
|
-
return formatEther(BigInt(balance));
|
|
141
|
-
} catch {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
}, [balance]);
|
|
145
|
-
|
|
146
|
-
const usdValue = useMemo(() => {
|
|
147
|
-
if (usdPrice === null || formatted === null) return null;
|
|
148
|
-
const numericBalance = parseFloat(formatted);
|
|
149
|
-
if (isNaN(numericBalance)) return null;
|
|
150
|
-
const totalUsd = numericBalance * usdPrice;
|
|
151
|
-
return new Intl.NumberFormat("en-US", {
|
|
152
|
-
style: "currency",
|
|
153
|
-
currency: "USD",
|
|
154
|
-
minimumFractionDigits: 2,
|
|
155
|
-
maximumFractionDigits: 2,
|
|
156
|
-
}).format(totalUsd);
|
|
157
|
-
}, [usdPrice, formatted]);
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
balance,
|
|
161
|
-
formatted,
|
|
162
|
-
symbol: tokenSymbol,
|
|
163
|
-
usdPrice,
|
|
164
|
-
usdValue,
|
|
165
|
-
isLoading,
|
|
166
|
-
error,
|
|
167
|
-
refetch: fetchBalance,
|
|
168
|
-
chain: currentChain,
|
|
169
|
-
};
|
|
170
|
-
}
|