@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,401 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useTransactionSimulation — React hook for simulating transactions
|
|
3
|
-
* before signing/submitting.
|
|
4
|
-
*
|
|
5
|
-
* Provides a reactive SimulationResult that updates when tx parameters change.
|
|
6
|
-
* Uses the simulation module from @naculus/connect-core.
|
|
7
|
-
*
|
|
8
|
-
* @see /docs/features/transaction-simulation.md
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { useState, useCallback, useEffect, useRef } from "react";
|
|
12
|
-
import { useWeb3 } from "../provider/Web3ConnectProvider";
|
|
13
|
-
import { useAccount } from "./useAccount";
|
|
14
|
-
import { useChain } from "./useChain";
|
|
15
|
-
import { useViemClient } from "./useViemClient";
|
|
16
|
-
|
|
17
|
-
import type { EvmTransaction } from "../types";
|
|
18
|
-
|
|
19
|
-
// ── Types (mirrored from @naculus/connect-core simulation module) ──
|
|
20
|
-
|
|
21
|
-
export type SimulationStatus = "success" | "reverted" | "unavailable";
|
|
22
|
-
|
|
23
|
-
export type RiskLevel = "safe" | "warning" | "malicious" | "unknown";
|
|
24
|
-
|
|
25
|
-
export interface SimulationResult {
|
|
26
|
-
status: SimulationStatus;
|
|
27
|
-
revertReason?: string;
|
|
28
|
-
balanceChanges: BalanceChange[];
|
|
29
|
-
approvalChanges: ApprovalChange[];
|
|
30
|
-
riskAssessment: RiskAssessment;
|
|
31
|
-
gasInfo?: GasInfo;
|
|
32
|
-
provider: string;
|
|
33
|
-
summary?: string;
|
|
34
|
-
changesDetected: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface BalanceChange {
|
|
38
|
-
tokenAddress: string;
|
|
39
|
-
tokenSymbol: string;
|
|
40
|
-
tokenDecimals: number;
|
|
41
|
-
amount: string;
|
|
42
|
-
direction: "in" | "out";
|
|
43
|
-
from: string;
|
|
44
|
-
to: string;
|
|
45
|
-
humanReadable: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface ApprovalChange {
|
|
49
|
-
tokenAddress: string;
|
|
50
|
-
tokenSymbol: string;
|
|
51
|
-
owner: string;
|
|
52
|
-
spender: string;
|
|
53
|
-
amount: string;
|
|
54
|
-
isUnlimited: boolean;
|
|
55
|
-
humanReadable: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface RiskAssessment {
|
|
59
|
-
level: RiskLevel;
|
|
60
|
-
score: number;
|
|
61
|
-
warnings: RiskWarning[];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface RiskWarning {
|
|
65
|
-
category: string;
|
|
66
|
-
severity: string;
|
|
67
|
-
message: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface GasInfo {
|
|
71
|
-
gasLimit: bigint;
|
|
72
|
-
gasPrice?: bigint;
|
|
73
|
-
estimatedFeeEth?: string;
|
|
74
|
-
estimatedFeeUsd?: string;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── Hook return type ──────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
export interface UseTransactionSimulationReturn {
|
|
80
|
-
/** Current simulation result (undefined until first run) */
|
|
81
|
-
result: SimulationResult | undefined;
|
|
82
|
-
/** Whether a simulation is in progress */
|
|
83
|
-
isSimulating: boolean;
|
|
84
|
-
/** Error from the last simulation attempt */
|
|
85
|
-
error: Error | null;
|
|
86
|
-
/** Manually trigger a simulation */
|
|
87
|
-
simulate: () => Promise<SimulationResult>;
|
|
88
|
-
/** Reset the simulation state */
|
|
89
|
-
reset: () => void;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ── Default owner address for eth_call ────────────────────────────
|
|
93
|
-
|
|
94
|
-
function getNativeSymbol(chainId: number): string {
|
|
95
|
-
const ethChains = [1, 5, 11155111, 10, 42161, 421614, 8453, 84532];
|
|
96
|
-
const maticChains = [137, 80002];
|
|
97
|
-
const bnbChains = [56, 97];
|
|
98
|
-
|
|
99
|
-
if (ethChains.includes(chainId)) return "ETH";
|
|
100
|
-
if (maticChains.includes(chainId)) return "MATIC";
|
|
101
|
-
if (bnbChains.includes(chainId)) return "BNB";
|
|
102
|
-
return "ETH";
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Hook ──────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
export function useTransactionSimulation(
|
|
108
|
-
tx: EvmTransaction | undefined,
|
|
109
|
-
chainId?: number,
|
|
110
|
-
): UseTransactionSimulationReturn {
|
|
111
|
-
const { session } = useWeb3();
|
|
112
|
-
const { evmAccount, isConnected } = useAccount();
|
|
113
|
-
const { currentChain } = useChain();
|
|
114
|
-
const { publicClient } = useViemClient();
|
|
115
|
-
|
|
116
|
-
const [result, setResult] = useState<SimulationResult | undefined>(undefined);
|
|
117
|
-
const [isSimulating, setIsSimulating] = useState(false);
|
|
118
|
-
const [error, setError] = useState<Error | null>(null);
|
|
119
|
-
|
|
120
|
-
// Debounce ref to avoid rapid re-simulations
|
|
121
|
-
const debounceRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
122
|
-
|
|
123
|
-
// ── Core simulation logic ───────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
const simulate = useCallback(async (): Promise<SimulationResult> => {
|
|
126
|
-
if (!tx) {
|
|
127
|
-
const noTxResult: SimulationResult = {
|
|
128
|
-
status: "unavailable" as SimulationStatus,
|
|
129
|
-
balanceChanges: [],
|
|
130
|
-
approvalChanges: [],
|
|
131
|
-
riskAssessment: { level: "unknown" as RiskLevel, score: 0, warnings: [] },
|
|
132
|
-
provider: "eth_call",
|
|
133
|
-
summary: "No transaction to simulate",
|
|
134
|
-
changesDetected: false,
|
|
135
|
-
};
|
|
136
|
-
setResult(noTxResult);
|
|
137
|
-
return noTxResult;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
setIsSimulating(true);
|
|
141
|
-
setError(null);
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
// Determine the target chain ID
|
|
145
|
-
const targetChainId = chainId ?? currentChain?.id ?? 1;
|
|
146
|
-
|
|
147
|
-
// Determine RPC URL from the current chain context
|
|
148
|
-
const rpcUrl = currentChain?.rpcUrl;
|
|
149
|
-
|
|
150
|
-
// If we have a viem publicClient, use it for eth_call simulation
|
|
151
|
-
if (publicClient) {
|
|
152
|
-
try {
|
|
153
|
-
const from = evmAccount?.includes(":")
|
|
154
|
-
? (evmAccount.split(":").pop() as `0x${string}`)
|
|
155
|
-
: evmAccount;
|
|
156
|
-
|
|
157
|
-
const callParams: any = {
|
|
158
|
-
to: tx.to as `0x${string}`,
|
|
159
|
-
data: (tx.data ?? "0x") as `0x${string}`,
|
|
160
|
-
value: tx.value ? BigInt(tx.value) : 0n,
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
if (from) {
|
|
164
|
-
callParams.from = from as `0x${string}`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
await publicClient.call(callParams);
|
|
168
|
-
|
|
169
|
-
// eth_call succeeded — transaction won't revert
|
|
170
|
-
const simResult: SimulationResult = {
|
|
171
|
-
status: "success",
|
|
172
|
-
balanceChanges: [],
|
|
173
|
-
approvalChanges: [],
|
|
174
|
-
riskAssessment: { level: "unknown", score: 0, warnings: [] },
|
|
175
|
-
gasInfo: tx.gas
|
|
176
|
-
? { gasLimit: BigInt(tx.gas), estimatedFeeEth: "0" }
|
|
177
|
-
: undefined,
|
|
178
|
-
provider: "eth_call",
|
|
179
|
-
summary: "Transaction simulation succeeded (basic revert check only)",
|
|
180
|
-
changesDetected: true,
|
|
181
|
-
};
|
|
182
|
-
setResult(simResult);
|
|
183
|
-
return simResult;
|
|
184
|
-
} catch (callErr: any) {
|
|
185
|
-
// eth_call reverted — extract reason
|
|
186
|
-
let revertReason: string | undefined;
|
|
187
|
-
const errMsg = callErr?.message ?? String(callErr);
|
|
188
|
-
const errData = callErr?.data ?? callErr?.cause?.data;
|
|
189
|
-
|
|
190
|
-
// Try to extract human-readable revert reason
|
|
191
|
-
if (errData && typeof errData === "string") {
|
|
192
|
-
const clean = errData.startsWith("0x") ? errData.slice(2) : errData;
|
|
193
|
-
if (clean.startsWith("08c379a0") && clean.length > 138) {
|
|
194
|
-
// Error(string) — ABI decode
|
|
195
|
-
const length = parseInt(clean.slice(72, 136), 16);
|
|
196
|
-
if (length > 0) {
|
|
197
|
-
const msgHex = clean.slice(136, 136 + length * 2);
|
|
198
|
-
const bytes = new Uint8Array(length);
|
|
199
|
-
for (let i = 0; i < length; i++) {
|
|
200
|
-
bytes[i] = parseInt(msgHex.slice(i * 2, i * 2 + 2), 16);
|
|
201
|
-
}
|
|
202
|
-
revertReason = new TextDecoder().decode(bytes);
|
|
203
|
-
}
|
|
204
|
-
} else if (clean.startsWith("4e487b71")) {
|
|
205
|
-
revertReason = `Panic: Built-in failure`;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (!revertReason) {
|
|
210
|
-
revertReason = errMsg.includes("revert")
|
|
211
|
-
? errMsg
|
|
212
|
-
: "Transaction reverted";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const simResult: SimulationResult = {
|
|
216
|
-
status: "reverted",
|
|
217
|
-
revertReason,
|
|
218
|
-
balanceChanges: [],
|
|
219
|
-
approvalChanges: [],
|
|
220
|
-
riskAssessment: {
|
|
221
|
-
level: "unknown",
|
|
222
|
-
score: 0,
|
|
223
|
-
warnings: [
|
|
224
|
-
{
|
|
225
|
-
category: "simulation_failed",
|
|
226
|
-
severity: "high",
|
|
227
|
-
message: revertReason
|
|
228
|
-
? `Transaction would revert: ${revertReason}`
|
|
229
|
-
: "Transaction would revert",
|
|
230
|
-
},
|
|
231
|
-
],
|
|
232
|
-
},
|
|
233
|
-
provider: "eth_call",
|
|
234
|
-
summary: revertReason
|
|
235
|
-
? `Transaction reverted: ${revertReason}`
|
|
236
|
-
: "Transaction reverted",
|
|
237
|
-
changesDetected: false,
|
|
238
|
-
};
|
|
239
|
-
setResult(simResult);
|
|
240
|
-
return simResult;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Fallback: raw fetch eth_call
|
|
245
|
-
if (rpcUrl) {
|
|
246
|
-
try {
|
|
247
|
-
const response = await fetch(rpcUrl, {
|
|
248
|
-
method: "POST",
|
|
249
|
-
headers: { "Content-Type": "application/json" },
|
|
250
|
-
body: JSON.stringify({
|
|
251
|
-
jsonrpc: "2.0",
|
|
252
|
-
id: Date.now(),
|
|
253
|
-
method: "eth_call",
|
|
254
|
-
params: [
|
|
255
|
-
{
|
|
256
|
-
to: tx.to,
|
|
257
|
-
from: evmAccount?.includes(":")
|
|
258
|
-
? evmAccount.split(":").pop()
|
|
259
|
-
: evmAccount,
|
|
260
|
-
data: tx.data ?? "0x",
|
|
261
|
-
value: tx.value ?? "0x0",
|
|
262
|
-
},
|
|
263
|
-
"latest",
|
|
264
|
-
],
|
|
265
|
-
}),
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const json: any = await response.json();
|
|
269
|
-
|
|
270
|
-
if (json.error) {
|
|
271
|
-
const simResult: SimulationResult = {
|
|
272
|
-
status: "reverted",
|
|
273
|
-
balanceChanges: [],
|
|
274
|
-
approvalChanges: [],
|
|
275
|
-
riskAssessment: {
|
|
276
|
-
level: "unknown",
|
|
277
|
-
score: 0,
|
|
278
|
-
warnings: [
|
|
279
|
-
{
|
|
280
|
-
category: "simulation_failed",
|
|
281
|
-
severity: "high",
|
|
282
|
-
message: `Transaction would revert: ${json.error.message}`,
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
},
|
|
286
|
-
provider: "eth_call",
|
|
287
|
-
summary: "Transaction reverted",
|
|
288
|
-
changesDetected: false,
|
|
289
|
-
};
|
|
290
|
-
setResult(simResult);
|
|
291
|
-
return simResult;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const simResult: SimulationResult = {
|
|
295
|
-
status: "success",
|
|
296
|
-
balanceChanges: [],
|
|
297
|
-
approvalChanges: [],
|
|
298
|
-
riskAssessment: { level: "unknown", score: 0, warnings: [] },
|
|
299
|
-
provider: "eth_call",
|
|
300
|
-
summary: "Transaction simulation succeeded",
|
|
301
|
-
changesDetected: true,
|
|
302
|
-
};
|
|
303
|
-
setResult(simResult);
|
|
304
|
-
return simResult;
|
|
305
|
-
} catch (fetchErr: any) {
|
|
306
|
-
// Network error
|
|
307
|
-
const simResult: SimulationResult = {
|
|
308
|
-
status: "unavailable",
|
|
309
|
-
balanceChanges: [],
|
|
310
|
-
approvalChanges: [],
|
|
311
|
-
riskAssessment: {
|
|
312
|
-
level: "unknown",
|
|
313
|
-
score: 0,
|
|
314
|
-
warnings: [
|
|
315
|
-
{
|
|
316
|
-
category: "simulation_failed",
|
|
317
|
-
severity: "medium",
|
|
318
|
-
message: `Network error: ${fetchErr?.message ?? "Unknown"}`,
|
|
319
|
-
},
|
|
320
|
-
],
|
|
321
|
-
},
|
|
322
|
-
provider: "eth_call",
|
|
323
|
-
summary: "Simulation unavailable due to network error",
|
|
324
|
-
changesDetected: false,
|
|
325
|
-
};
|
|
326
|
-
setResult(simResult);
|
|
327
|
-
return simResult;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// No RPC URL available
|
|
332
|
-
const simResult: SimulationResult = {
|
|
333
|
-
status: "unavailable",
|
|
334
|
-
balanceChanges: [],
|
|
335
|
-
approvalChanges: [],
|
|
336
|
-
riskAssessment: {
|
|
337
|
-
level: "unknown",
|
|
338
|
-
score: 0,
|
|
339
|
-
warnings: [
|
|
340
|
-
{
|
|
341
|
-
category: "simulation_failed",
|
|
342
|
-
severity: "low",
|
|
343
|
-
message: "No RPC URL or public client available for simulation",
|
|
344
|
-
},
|
|
345
|
-
],
|
|
346
|
-
},
|
|
347
|
-
provider: "eth_call",
|
|
348
|
-
summary: "Simulation unavailable: no RPC URL",
|
|
349
|
-
changesDetected: false,
|
|
350
|
-
};
|
|
351
|
-
setResult(simResult);
|
|
352
|
-
return simResult;
|
|
353
|
-
} catch (err) {
|
|
354
|
-
const error = err instanceof Error ? err : new Error("Simulation failed");
|
|
355
|
-
setError(error);
|
|
356
|
-
throw error;
|
|
357
|
-
} finally {
|
|
358
|
-
setIsSimulating(false);
|
|
359
|
-
}
|
|
360
|
-
}, [tx, chainId, currentChain, publicClient, evmAccount]);
|
|
361
|
-
|
|
362
|
-
// ── Auto-simulate on tx change (debounced) ──────────────────────
|
|
363
|
-
|
|
364
|
-
useEffect(() => {
|
|
365
|
-
if (!tx) return;
|
|
366
|
-
|
|
367
|
-
// Clear previous debounce
|
|
368
|
-
if (debounceRef.current) {
|
|
369
|
-
clearTimeout(debounceRef.current);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Debounce: wait 300ms after last tx change before simulating
|
|
373
|
-
debounceRef.current = setTimeout(() => {
|
|
374
|
-
simulate().catch(() => {
|
|
375
|
-
// Silently handle; errors are captured in state
|
|
376
|
-
});
|
|
377
|
-
}, 300);
|
|
378
|
-
|
|
379
|
-
return () => {
|
|
380
|
-
if (debounceRef.current) {
|
|
381
|
-
clearTimeout(debounceRef.current);
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
}, [tx, simulate]);
|
|
385
|
-
|
|
386
|
-
// ── Reset ───────────────────────────────────────────────────────
|
|
387
|
-
|
|
388
|
-
const reset = useCallback(() => {
|
|
389
|
-
setResult(undefined);
|
|
390
|
-
setError(null);
|
|
391
|
-
setIsSimulating(false);
|
|
392
|
-
}, []);
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
result,
|
|
396
|
-
isSimulating,
|
|
397
|
-
error,
|
|
398
|
-
simulate,
|
|
399
|
-
reset,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
3
|
-
import { renderHook, waitFor } from "@testing-library/react";
|
|
4
|
-
import { useTxHistory } from "./useTxHistory";
|
|
5
|
-
import type { TxMonitorLike, TxStatusEntry } from "./useTxMonitor";
|
|
6
|
-
|
|
7
|
-
class MockMonitor implements TxMonitorLike {
|
|
8
|
-
private entries: TxStatusEntry[] = [];
|
|
9
|
-
private listeners = new Map<string, Set<Function>>();
|
|
10
|
-
|
|
11
|
-
setEntries(entries: TxStatusEntry[]): void {
|
|
12
|
-
this.entries = entries;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async watchTx(): Promise<TxStatusEntry> {
|
|
16
|
-
throw new Error("not implemented");
|
|
17
|
-
}
|
|
18
|
-
stopWatching(): void {}
|
|
19
|
-
getTxStatus(): TxStatusEntry | null { return null; }
|
|
20
|
-
|
|
21
|
-
async getTxHistory(): Promise<TxStatusEntry[]> {
|
|
22
|
-
return this.entries;
|
|
23
|
-
}
|
|
24
|
-
async refreshTx(): Promise<void> {}
|
|
25
|
-
|
|
26
|
-
on(event: string, listener: Function): this {
|
|
27
|
-
const set = this.listeners.get(event) ?? new Set();
|
|
28
|
-
set.add(listener);
|
|
29
|
-
this.listeners.set(event, set);
|
|
30
|
-
return this;
|
|
31
|
-
}
|
|
32
|
-
off(event: string, listener: Function): this {
|
|
33
|
-
const set = this.listeners.get(event);
|
|
34
|
-
if (set) set.delete(listener);
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
emit(event: string, ...args: any[]): void {
|
|
39
|
-
const set = this.listeners.get(event);
|
|
40
|
-
if (set) set.forEach(fn => fn(...args));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe("useTxHistory", () => {
|
|
45
|
-
let monitor: MockMonitor;
|
|
46
|
-
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
monitor = new MockMonitor();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("returns empty array when no history exists", async () => {
|
|
52
|
-
const { result } = renderHook(() => useTxHistory(undefined, undefined, monitor));
|
|
53
|
-
|
|
54
|
-
await waitFor(() => {
|
|
55
|
-
expect(result.current.isLoading).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(result.current.entries).toEqual([]);
|
|
59
|
-
expect(result.current.summary.pending).toBe(0);
|
|
60
|
-
expect(result.current.summary.confirmed).toBe(0);
|
|
61
|
-
expect(result.current.summary.failed).toBe(0);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("returns history entries", async () => {
|
|
65
|
-
const entry: TxStatusEntry = {
|
|
66
|
-
hash: "0x" + "a".repeat(64), chainId: 1,
|
|
67
|
-
from: "", to: "", value: "0x0",
|
|
68
|
-
status: "confirmed", createdAt: Date.now(), updatedAt: Date.now(),
|
|
69
|
-
replacementCount: 0,
|
|
70
|
-
};
|
|
71
|
-
monitor.setEntries([entry]);
|
|
72
|
-
|
|
73
|
-
const { result } = renderHook(() => useTxHistory(undefined, undefined, monitor));
|
|
74
|
-
|
|
75
|
-
await waitFor(() => {
|
|
76
|
-
expect(result.current.entries.length).toBe(1);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
expect(result.current.entries[0].hash).toBe(entry.hash);
|
|
80
|
-
expect(result.current.summary.confirmed).toBe(1);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("provides status summary counts", async () => {
|
|
84
|
-
const now = Date.now();
|
|
85
|
-
monitor.setEntries([
|
|
86
|
-
{ hash: "0x1" + "a".repeat(63), chainId: 1, from: "", to: "", value: "0x0",
|
|
87
|
-
status: "pending", createdAt: now, updatedAt: now, replacementCount: 0 },
|
|
88
|
-
{ hash: "0x2" + "b".repeat(63), chainId: 1, from: "", to: "", value: "0x0",
|
|
89
|
-
status: "confirmed", createdAt: now, updatedAt: now, replacementCount: 0, confirmedAt: now },
|
|
90
|
-
{ hash: "0x3" + "c".repeat(63), chainId: 1, from: "", to: "", value: "0x0",
|
|
91
|
-
status: "failed", createdAt: now, updatedAt: now, replacementCount: 0, error: "reverted" },
|
|
92
|
-
{ hash: "0x4" + "d".repeat(63), chainId: 1, from: "", to: "", value: "0x0",
|
|
93
|
-
status: "confirmed", createdAt: now, updatedAt: now, replacementCount: 0, confirmedAt: now },
|
|
94
|
-
]);
|
|
95
|
-
|
|
96
|
-
const { result } = renderHook(() => useTxHistory(undefined, undefined, monitor));
|
|
97
|
-
|
|
98
|
-
await waitFor(() => {
|
|
99
|
-
expect(result.current.entries.length).toBe(4);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(result.current.summary).toEqual({
|
|
103
|
-
pending: 1,
|
|
104
|
-
confirmed: 2,
|
|
105
|
-
failed: 1,
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
-
import type { TxMonitorLike, TxStatusEntry, TxStatus } from "./useTxMonitor";
|
|
3
|
-
import { useTxMonitorProvider } from "./useTxMonitor";
|
|
4
|
-
|
|
5
|
-
// ── Hook ──────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
export interface UseTxHistoryResult {
|
|
8
|
-
entries: TxStatusEntry[];
|
|
9
|
-
isLoading: boolean;
|
|
10
|
-
error: Error | null;
|
|
11
|
-
refresh: () => Promise<void>;
|
|
12
|
-
summary: {
|
|
13
|
-
pending: number;
|
|
14
|
-
confirmed: number;
|
|
15
|
-
failed: number;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Query transaction history.
|
|
21
|
-
* address defaults to current wallet address; chainId defaults to current chain.
|
|
22
|
-
*/
|
|
23
|
-
export function useTxHistory(
|
|
24
|
-
address?: string,
|
|
25
|
-
chainId?: number,
|
|
26
|
-
monitor?: TxMonitorLike,
|
|
27
|
-
): UseTxHistoryResult {
|
|
28
|
-
const ctxMonitor = useTxMonitorProvider();
|
|
29
|
-
const effectiveMonitor = monitor ?? ctxMonitor;
|
|
30
|
-
|
|
31
|
-
const [entries, setEntries] = useState<TxStatusEntry[]>([]);
|
|
32
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
33
|
-
const [error, setError] = useState<Error | null>(null);
|
|
34
|
-
const refreshCounter = useRef(0);
|
|
35
|
-
|
|
36
|
-
const refresh = useCallback(async () => {
|
|
37
|
-
if (!effectiveMonitor) return;
|
|
38
|
-
setIsLoading(true);
|
|
39
|
-
setError(null);
|
|
40
|
-
refreshCounter.current++;
|
|
41
|
-
try {
|
|
42
|
-
const results = await effectiveMonitor.getTxHistory(address, chainId);
|
|
43
|
-
setEntries(results);
|
|
44
|
-
} catch (err) {
|
|
45
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
46
|
-
} finally {
|
|
47
|
-
setIsLoading(false);
|
|
48
|
-
}
|
|
49
|
-
}, [effectiveMonitor, address, chainId]);
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
refresh().catch(() => {});
|
|
53
|
-
}, [refresh]);
|
|
54
|
-
|
|
55
|
-
// Listen for status changes to update entries reactively
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (!effectiveMonitor) return;
|
|
58
|
-
|
|
59
|
-
const onStatusChange = () => {
|
|
60
|
-
// Re-fetch on any status change
|
|
61
|
-
refresh().catch(() => {});
|
|
62
|
-
};
|
|
63
|
-
effectiveMonitor.on("statusChange", onStatusChange);
|
|
64
|
-
|
|
65
|
-
return () => {
|
|
66
|
-
effectiveMonitor.off("statusChange", onStatusChange as any);
|
|
67
|
-
};
|
|
68
|
-
}, [effectiveMonitor, refresh]);
|
|
69
|
-
|
|
70
|
-
const summary = {
|
|
71
|
-
pending: entries.filter(e => e.status === "pending").length,
|
|
72
|
-
confirmed: entries.filter(e => e.status === "confirmed").length,
|
|
73
|
-
failed: entries.filter(e => e.status === "failed").length,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
entries,
|
|
78
|
-
isLoading,
|
|
79
|
-
error,
|
|
80
|
-
refresh,
|
|
81
|
-
summary,
|
|
82
|
-
};
|
|
83
|
-
}
|