@thehoneyjar/sigil-dev-toolbar 0.1.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/dist/index.js ADDED
@@ -0,0 +1,2368 @@
1
+ import { createContext, useMemo, useEffect, useContext, useState, useRef, useCallback } from 'react';
2
+ import { create } from 'zustand';
3
+ import { persist } from 'zustand/middleware';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { useAccount } from 'wagmi';
6
+ import { isAddress } from 'viem';
7
+
8
+ var __defProp = Object.defineProperty;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __publicField = (obj, key, value) => {
11
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
12
+ return value;
13
+ };
14
+ var initialState = {
15
+ visible: true,
16
+ collapsed: true,
17
+ activeTab: "lens",
18
+ userLens: {
19
+ enabled: false,
20
+ impersonatedAddress: null,
21
+ savedAddresses: []
22
+ },
23
+ simulation: {
24
+ enabled: false,
25
+ scenario: null
26
+ },
27
+ comparison: {
28
+ enabled: false,
29
+ beforeState: null,
30
+ afterState: null
31
+ },
32
+ diagnostics: {
33
+ enabled: false,
34
+ violations: [],
35
+ tasteSignals: []
36
+ }
37
+ };
38
+ var createDevToolbarStore = (config) => create()(
39
+ persist(
40
+ (set, get) => ({
41
+ ...initialState,
42
+ collapsed: config.defaultCollapsed ?? true,
43
+ // Visibility
44
+ show: () => set({ visible: true }),
45
+ hide: () => set({ visible: false }),
46
+ toggle: () => set((state) => ({ visible: !state.visible })),
47
+ collapse: () => set({ collapsed: true }),
48
+ expand: () => set({ collapsed: false }),
49
+ setActiveTab: (tab) => set({ activeTab: tab, collapsed: false }),
50
+ // User Lens
51
+ enableLens: (address) => set({
52
+ userLens: {
53
+ ...get().userLens,
54
+ enabled: true,
55
+ impersonatedAddress: address
56
+ }
57
+ }),
58
+ disableLens: () => set({
59
+ userLens: {
60
+ ...get().userLens,
61
+ enabled: false
62
+ }
63
+ }),
64
+ setImpersonatedAddress: (address) => set({
65
+ userLens: {
66
+ ...get().userLens,
67
+ impersonatedAddress: address,
68
+ enabled: address !== null
69
+ }
70
+ }),
71
+ saveAddress: (entry) => set({
72
+ userLens: {
73
+ ...get().userLens,
74
+ savedAddresses: [
75
+ ...get().userLens.savedAddresses.filter((a) => a.address !== entry.address),
76
+ entry
77
+ ]
78
+ }
79
+ }),
80
+ removeAddress: (address) => set({
81
+ userLens: {
82
+ ...get().userLens,
83
+ savedAddresses: get().userLens.savedAddresses.filter((a) => a.address !== address)
84
+ }
85
+ }),
86
+ // Simulation
87
+ enableSimulation: (scenario) => set({
88
+ simulation: {
89
+ enabled: true,
90
+ scenario
91
+ }
92
+ }),
93
+ disableSimulation: () => set({
94
+ simulation: {
95
+ enabled: false,
96
+ scenario: null
97
+ }
98
+ }),
99
+ // Comparison
100
+ captureBeforeState: (state) => set({
101
+ comparison: {
102
+ ...get().comparison,
103
+ enabled: true,
104
+ beforeState: state
105
+ }
106
+ }),
107
+ captureAfterState: (state) => set({
108
+ comparison: {
109
+ ...get().comparison,
110
+ afterState: state
111
+ }
112
+ }),
113
+ clearComparison: () => set({
114
+ comparison: {
115
+ enabled: false,
116
+ beforeState: null,
117
+ afterState: null
118
+ }
119
+ }),
120
+ // Diagnostics
121
+ addViolation: (violation) => set({
122
+ diagnostics: {
123
+ ...get().diagnostics,
124
+ violations: [...get().diagnostics.violations.slice(-49), violation]
125
+ }
126
+ }),
127
+ clearViolations: () => set({
128
+ diagnostics: {
129
+ ...get().diagnostics,
130
+ violations: []
131
+ }
132
+ }),
133
+ addTasteSignal: (signal) => {
134
+ set({
135
+ diagnostics: {
136
+ ...get().diagnostics,
137
+ tasteSignals: [...get().diagnostics.tasteSignals.slice(-49), signal]
138
+ }
139
+ });
140
+ config.onTasteSignal?.(signal);
141
+ },
142
+ clearTasteSignals: () => set({
143
+ diagnostics: {
144
+ ...get().diagnostics,
145
+ tasteSignals: []
146
+ }
147
+ }),
148
+ // Reset
149
+ reset: () => set(initialState)
150
+ }),
151
+ {
152
+ name: "sigil-dev-toolbar",
153
+ partialize: (state) => ({
154
+ collapsed: state.collapsed,
155
+ activeTab: state.activeTab,
156
+ userLens: {
157
+ savedAddresses: state.userLens.savedAddresses
158
+ }
159
+ })
160
+ }
161
+ )
162
+ );
163
+ var DevToolbarContext = createContext(null);
164
+ var DevToolbarConfigContext = createContext(null);
165
+ var defaultConfig = {
166
+ position: "bottom-right",
167
+ defaultCollapsed: true,
168
+ enableUserLens: true,
169
+ enableSimulation: true,
170
+ enableComparison: true,
171
+ enableDiagnostics: true,
172
+ toggleShortcut: "ctrl+shift+d"
173
+ };
174
+ function DevToolbarProvider({ children, config: userConfig }) {
175
+ const config = useMemo(() => ({ ...defaultConfig, ...userConfig }), [userConfig]);
176
+ const store = useMemo(() => createDevToolbarStore(config), [config]);
177
+ useEffect(() => {
178
+ const handleKeyDown = (e) => {
179
+ const keys = config.toggleShortcut.toLowerCase().split("+");
180
+ const ctrlMatch = keys.includes("ctrl") === (e.ctrlKey || e.metaKey);
181
+ const shiftMatch = keys.includes("shift") === e.shiftKey;
182
+ const altMatch = keys.includes("alt") === e.altKey;
183
+ const keyMatch = keys.filter((k) => !["ctrl", "shift", "alt"].includes(k))[0] === e.key.toLowerCase();
184
+ if (ctrlMatch && shiftMatch && altMatch && keyMatch) {
185
+ e.preventDefault();
186
+ store.getState().toggle();
187
+ }
188
+ };
189
+ window.addEventListener("keydown", handleKeyDown);
190
+ return () => window.removeEventListener("keydown", handleKeyDown);
191
+ }, [config.toggleShortcut, store]);
192
+ return /* @__PURE__ */ jsx(DevToolbarConfigContext.Provider, { value: config, children: /* @__PURE__ */ jsx(DevToolbarContext.Provider, { value: store, children }) });
193
+ }
194
+ function useDevToolbar() {
195
+ const store = useContext(DevToolbarContext);
196
+ if (!store) {
197
+ throw new Error("useDevToolbar must be used within a DevToolbarProvider");
198
+ }
199
+ return store();
200
+ }
201
+ function useDevToolbarSelector(selector) {
202
+ const store = useContext(DevToolbarContext);
203
+ if (!store) {
204
+ throw new Error("useDevToolbarSelector must be used within a DevToolbarProvider");
205
+ }
206
+ return store(selector);
207
+ }
208
+ function useDevToolbarConfig() {
209
+ const config = useContext(DevToolbarConfigContext);
210
+ if (!config) {
211
+ throw new Error("useDevToolbarConfig must be used within a DevToolbarProvider");
212
+ }
213
+ return config;
214
+ }
215
+ function useLensAwareAccount() {
216
+ const { address: realAddress, isConnected } = useAccount();
217
+ const userLens = useDevToolbarSelector((state) => state.userLens);
218
+ const isImpersonating = userLens.enabled && userLens.impersonatedAddress !== null;
219
+ const address = isImpersonating ? userLens.impersonatedAddress : realAddress;
220
+ return {
221
+ address,
222
+ realAddress,
223
+ isImpersonating,
224
+ impersonatedAddress: userLens.impersonatedAddress,
225
+ isConnected
226
+ };
227
+ }
228
+ function useIsImpersonating() {
229
+ return useDevToolbarSelector(
230
+ (state) => state.userLens.enabled && state.userLens.impersonatedAddress !== null
231
+ );
232
+ }
233
+ function useImpersonatedAddress() {
234
+ return useDevToolbarSelector(
235
+ (state) => state.userLens.enabled ? state.userLens.impersonatedAddress : null
236
+ );
237
+ }
238
+ function useSavedAddresses() {
239
+ const savedAddresses = useDevToolbarSelector((state) => state.userLens.savedAddresses);
240
+ const { saveAddress, removeAddress, setImpersonatedAddress } = useDevToolbarSelector((state) => ({
241
+ saveAddress: state.saveAddress,
242
+ removeAddress: state.removeAddress,
243
+ setImpersonatedAddress: state.setImpersonatedAddress
244
+ }));
245
+ return {
246
+ savedAddresses,
247
+ saveAddress,
248
+ removeAddress,
249
+ selectAddress: setImpersonatedAddress
250
+ };
251
+ }
252
+
253
+ // src/ipc/types.ts
254
+ var DEFAULT_IPC_CONFIG = {
255
+ basePath: "grimoires/pub",
256
+ timeout: 3e4,
257
+ pollInterval: 100
258
+ };
259
+
260
+ // src/ipc/client.ts
261
+ function generateUUID() {
262
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
263
+ return crypto.randomUUID();
264
+ }
265
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
266
+ const r = Math.random() * 16 | 0;
267
+ const v = c === "x" ? r : r & 3 | 8;
268
+ return v.toString(16);
269
+ });
270
+ }
271
+ var LocalStorageTransport = class {
272
+ constructor(prefix = "sigil-ipc") {
273
+ __publicField(this, "prefix");
274
+ this.prefix = prefix;
275
+ }
276
+ async writeRequest(request) {
277
+ const key = `${this.prefix}:request:${request.id}`;
278
+ localStorage.setItem(key, JSON.stringify(request));
279
+ }
280
+ async readResponse(requestId, cliType) {
281
+ const key = `${this.prefix}:response:${requestId}:${cliType}`;
282
+ const data = localStorage.getItem(key);
283
+ if (!data)
284
+ return null;
285
+ try {
286
+ return JSON.parse(data);
287
+ } catch {
288
+ return null;
289
+ }
290
+ }
291
+ async cleanup(requestId) {
292
+ const keysToRemove = [];
293
+ for (let i = 0; i < localStorage.length; i++) {
294
+ const key = localStorage.key(i);
295
+ if (key?.includes(requestId)) {
296
+ keysToRemove.push(key);
297
+ }
298
+ }
299
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
300
+ }
301
+ };
302
+ var MockTransport = class {
303
+ constructor() {
304
+ __publicField(this, "mockResponses", /* @__PURE__ */ new Map());
305
+ }
306
+ setMockResponse(requestId, cliType, response) {
307
+ this.mockResponses.set(`${requestId}:${cliType}`, response);
308
+ }
309
+ async writeRequest(_request) {
310
+ }
311
+ async readResponse(requestId, cliType) {
312
+ return this.mockResponses.get(`${requestId}:${cliType}`) ?? null;
313
+ }
314
+ async cleanup(_requestId) {
315
+ }
316
+ };
317
+ var IPCClient = class {
318
+ constructor(transport, config) {
319
+ __publicField(this, "config");
320
+ __publicField(this, "transport");
321
+ this.config = { ...DEFAULT_IPC_CONFIG, ...config };
322
+ this.transport = transport ?? new LocalStorageTransport();
323
+ }
324
+ /**
325
+ * Send a request and wait for response
326
+ */
327
+ async send(type, payload, cliType = "anchor") {
328
+ const request = {
329
+ id: generateUUID(),
330
+ type,
331
+ timestamp: Date.now(),
332
+ payload
333
+ };
334
+ await this.transport.writeRequest(request);
335
+ const response = await this.waitForResponse(request.id, cliType);
336
+ await this.transport.cleanup(request.id);
337
+ if (response.status === "error") {
338
+ throw new Error(response.error ?? "Unknown IPC error");
339
+ }
340
+ if (response.status === "timeout") {
341
+ throw new Error("IPC request timed out");
342
+ }
343
+ return response.data;
344
+ }
345
+ /**
346
+ * Poll for response until timeout
347
+ */
348
+ async waitForResponse(requestId, cliType) {
349
+ const startTime = Date.now();
350
+ while (Date.now() - startTime < this.config.timeout) {
351
+ const response = await this.transport.readResponse(requestId, cliType);
352
+ if (response) {
353
+ return response;
354
+ }
355
+ await new Promise((resolve) => setTimeout(resolve, this.config.pollInterval));
356
+ }
357
+ return {
358
+ requestId,
359
+ status: "timeout",
360
+ timestamp: Date.now(),
361
+ error: "Request timed out"
362
+ };
363
+ }
364
+ /**
365
+ * Validate lens context via Anchor CLI
366
+ */
367
+ async validateLensContext(context, zone) {
368
+ const payload = { context, zone };
369
+ return this.send("lens-validate", payload, "anchor");
370
+ }
371
+ /**
372
+ * Run full Anchor validation with optional lens context
373
+ */
374
+ async validateAnchor(statement, lensContext, zone) {
375
+ const payload = { statement, lensContext, zone };
376
+ return this.send("anchor-validate", payload, "anchor");
377
+ }
378
+ /**
379
+ * Get the current transport
380
+ */
381
+ getTransport() {
382
+ return this.transport;
383
+ }
384
+ /**
385
+ * Set a new transport
386
+ */
387
+ setTransport(transport) {
388
+ this.transport = transport;
389
+ }
390
+ };
391
+ var defaultClient = null;
392
+ function getIPCClient() {
393
+ if (!defaultClient) {
394
+ defaultClient = new IPCClient();
395
+ }
396
+ return defaultClient;
397
+ }
398
+ function resetIPCClient() {
399
+ defaultClient = null;
400
+ }
401
+
402
+ // src/hooks/useIPCClient.ts
403
+ function useIPCClient() {
404
+ const [isLoading, setIsLoading] = useState(false);
405
+ const [error, setError] = useState(null);
406
+ const clientRef = useRef(null);
407
+ const getClient = useCallback(() => {
408
+ if (!clientRef.current) {
409
+ clientRef.current = getIPCClient();
410
+ }
411
+ return clientRef.current;
412
+ }, []);
413
+ const validateLens = useCallback(
414
+ async (context, zone) => {
415
+ setIsLoading(true);
416
+ setError(null);
417
+ try {
418
+ const client = getClient();
419
+ const result = await client.validateLensContext(context, zone);
420
+ return result;
421
+ } catch (err) {
422
+ const message = err instanceof Error ? err.message : "Unknown error";
423
+ setError(message);
424
+ throw err;
425
+ } finally {
426
+ setIsLoading(false);
427
+ }
428
+ },
429
+ [getClient]
430
+ );
431
+ const validateAnchor = useCallback(
432
+ async (statement, lensContext, zone) => {
433
+ setIsLoading(true);
434
+ setError(null);
435
+ try {
436
+ const client = getClient();
437
+ const result = await client.validateAnchor(statement, lensContext, zone);
438
+ return result;
439
+ } catch (err) {
440
+ const message = err instanceof Error ? err.message : "Unknown error";
441
+ setError(message);
442
+ throw err;
443
+ } finally {
444
+ setIsLoading(false);
445
+ }
446
+ },
447
+ [getClient]
448
+ );
449
+ const clearError = useCallback(() => {
450
+ setError(null);
451
+ }, []);
452
+ return {
453
+ validateLens,
454
+ validateAnchor,
455
+ isLoading,
456
+ error,
457
+ clearError
458
+ };
459
+ }
460
+
461
+ // src/services/fork.ts
462
+ function createAnvilForkService() {
463
+ let state = {
464
+ active: false,
465
+ rpcUrl: null,
466
+ blockNumber: null,
467
+ chainId: null,
468
+ createdAt: null,
469
+ snapshotCount: 0,
470
+ currentSnapshotId: null
471
+ };
472
+ const snapshots = /* @__PURE__ */ new Map();
473
+ async function jsonRpc(method, params = []) {
474
+ if (!state.rpcUrl) {
475
+ throw new Error("Fork not active");
476
+ }
477
+ const response = await fetch(state.rpcUrl, {
478
+ method: "POST",
479
+ headers: { "Content-Type": "application/json" },
480
+ body: JSON.stringify({
481
+ jsonrpc: "2.0",
482
+ id: Date.now(),
483
+ method,
484
+ params
485
+ })
486
+ });
487
+ const data = await response.json();
488
+ if (data.error) {
489
+ throw new Error(data.error.message || "RPC error");
490
+ }
491
+ return data.result;
492
+ }
493
+ return {
494
+ async createFork(config) {
495
+ const rpcUrl = config.customForkRpc || `http://127.0.0.1:${config.anvilPort || 8545}`;
496
+ try {
497
+ const chainIdHex = await fetch(rpcUrl, {
498
+ method: "POST",
499
+ headers: { "Content-Type": "application/json" },
500
+ body: JSON.stringify({
501
+ jsonrpc: "2.0",
502
+ id: 1,
503
+ method: "eth_chainId",
504
+ params: []
505
+ })
506
+ }).then((r) => r.json()).then((d) => d.result);
507
+ const blockNumberHex = await fetch(rpcUrl, {
508
+ method: "POST",
509
+ headers: { "Content-Type": "application/json" },
510
+ body: JSON.stringify({
511
+ jsonrpc: "2.0",
512
+ id: 2,
513
+ method: "eth_blockNumber",
514
+ params: []
515
+ })
516
+ }).then((r) => r.json()).then((d) => d.result);
517
+ state = {
518
+ active: true,
519
+ rpcUrl,
520
+ blockNumber: BigInt(blockNumberHex),
521
+ chainId: parseInt(chainIdHex, 16),
522
+ createdAt: Date.now(),
523
+ snapshotCount: 0,
524
+ currentSnapshotId: null
525
+ };
526
+ return state;
527
+ } catch (error) {
528
+ throw new Error(`Failed to connect to Anvil at ${rpcUrl}: ${error instanceof Error ? error.message : "Unknown error"}`);
529
+ }
530
+ },
531
+ getState() {
532
+ return { ...state };
533
+ },
534
+ async snapshot(description) {
535
+ const result = await jsonRpc("evm_snapshot");
536
+ const id = result;
537
+ const blockNumberHex = await jsonRpc("eth_blockNumber");
538
+ const snapshot = {
539
+ id,
540
+ blockNumber: BigInt(blockNumberHex),
541
+ timestamp: Date.now(),
542
+ description
543
+ };
544
+ snapshots.set(id, snapshot);
545
+ state.snapshotCount++;
546
+ state.currentSnapshotId = id;
547
+ return snapshot;
548
+ },
549
+ async revert(snapshotId) {
550
+ const result = await jsonRpc("evm_revert", [snapshotId]);
551
+ if (result) {
552
+ state.currentSnapshotId = snapshotId;
553
+ }
554
+ return result;
555
+ },
556
+ async reset() {
557
+ await jsonRpc("anvil_reset");
558
+ snapshots.clear();
559
+ state.snapshotCount = 0;
560
+ state.currentSnapshotId = null;
561
+ },
562
+ async destroy() {
563
+ state = {
564
+ active: false,
565
+ rpcUrl: null,
566
+ blockNumber: null,
567
+ chainId: null,
568
+ createdAt: null,
569
+ snapshotCount: 0,
570
+ currentSnapshotId: null
571
+ };
572
+ snapshots.clear();
573
+ },
574
+ async setBalance(address, balance) {
575
+ await jsonRpc("anvil_setBalance", [address, `0x${balance.toString(16)}`]);
576
+ },
577
+ async impersonateAccount(address) {
578
+ await jsonRpc("anvil_impersonateAccount", [address]);
579
+ },
580
+ async stopImpersonating(address) {
581
+ await jsonRpc("anvil_stopImpersonatingAccount", [address]);
582
+ },
583
+ async mineBlock(blocks = 1) {
584
+ await jsonRpc("anvil_mine", [blocks]);
585
+ },
586
+ getRpcUrl() {
587
+ return state.rpcUrl;
588
+ }
589
+ };
590
+ }
591
+ function createTenderlyForkService() {
592
+ let state = {
593
+ active: false,
594
+ rpcUrl: null,
595
+ blockNumber: null,
596
+ chainId: null,
597
+ createdAt: null,
598
+ snapshotCount: 0,
599
+ currentSnapshotId: null
600
+ };
601
+ let forkId = null;
602
+ let config = null;
603
+ async function tenderlyApi(endpoint, method = "GET", body) {
604
+ if (!config?.tenderlyApiKey || !config?.tenderlyProject) {
605
+ throw new Error("Tenderly API key and project required");
606
+ }
607
+ const response = await fetch(`https://api.tenderly.co/api/v1/account/${config.tenderlyProject}/project/${config.tenderlyProject}${endpoint}`, {
608
+ method,
609
+ headers: {
610
+ "Content-Type": "application/json",
611
+ "X-Access-Key": config.tenderlyApiKey
612
+ },
613
+ body: body ? JSON.stringify(body) : void 0
614
+ });
615
+ if (!response.ok) {
616
+ throw new Error(`Tenderly API error: ${response.statusText}`);
617
+ }
618
+ return response.json();
619
+ }
620
+ return {
621
+ async createFork(cfg) {
622
+ config = cfg;
623
+ const forkResponse = await tenderlyApi("/fork", "POST", {
624
+ network_id: cfg.chainId.toString(),
625
+ block_number: cfg.forkBlockNumber ? Number(cfg.forkBlockNumber) : void 0
626
+ });
627
+ forkId = forkResponse.simulation_fork.id;
628
+ state = {
629
+ active: true,
630
+ rpcUrl: forkResponse.simulation_fork.rpc_url,
631
+ blockNumber: BigInt(forkResponse.simulation_fork.block_number),
632
+ chainId: cfg.chainId,
633
+ createdAt: Date.now(),
634
+ snapshotCount: 0,
635
+ currentSnapshotId: null
636
+ };
637
+ return state;
638
+ },
639
+ getState() {
640
+ return { ...state };
641
+ },
642
+ async snapshot(description) {
643
+ if (!forkId)
644
+ throw new Error("Fork not active");
645
+ const response = await tenderlyApi(`/fork/${forkId}/snapshot`, "POST");
646
+ const snapshot = {
647
+ id: response.snapshot.id,
648
+ blockNumber: BigInt(response.snapshot.block_number),
649
+ timestamp: Date.now(),
650
+ description
651
+ };
652
+ state.snapshotCount++;
653
+ state.currentSnapshotId = snapshot.id;
654
+ return snapshot;
655
+ },
656
+ async revert(snapshotId) {
657
+ if (!forkId)
658
+ throw new Error("Fork not active");
659
+ try {
660
+ await tenderlyApi(`/fork/${forkId}/snapshot/${snapshotId}`, "PUT");
661
+ state.currentSnapshotId = snapshotId;
662
+ return true;
663
+ } catch {
664
+ return false;
665
+ }
666
+ },
667
+ async reset() {
668
+ if (!forkId || !config)
669
+ throw new Error("Fork not active");
670
+ await tenderlyApi(`/fork/${forkId}`, "DELETE");
671
+ await this.createFork(config);
672
+ },
673
+ async destroy() {
674
+ if (forkId) {
675
+ try {
676
+ await tenderlyApi(`/fork/${forkId}`, "DELETE");
677
+ } catch {
678
+ }
679
+ }
680
+ state = {
681
+ active: false,
682
+ rpcUrl: null,
683
+ blockNumber: null,
684
+ chainId: null,
685
+ createdAt: null,
686
+ snapshotCount: 0,
687
+ currentSnapshotId: null
688
+ };
689
+ forkId = null;
690
+ config = null;
691
+ },
692
+ async setBalance(address, balance) {
693
+ if (!state.rpcUrl)
694
+ throw new Error("Fork not active");
695
+ await fetch(state.rpcUrl, {
696
+ method: "POST",
697
+ headers: { "Content-Type": "application/json" },
698
+ body: JSON.stringify({
699
+ jsonrpc: "2.0",
700
+ id: Date.now(),
701
+ method: "tenderly_setBalance",
702
+ params: [address, `0x${balance.toString(16)}`]
703
+ })
704
+ });
705
+ },
706
+ async impersonateAccount(_address) {
707
+ },
708
+ async stopImpersonating(_address) {
709
+ },
710
+ async mineBlock(blocks = 1) {
711
+ if (!state.rpcUrl)
712
+ throw new Error("Fork not active");
713
+ await fetch(state.rpcUrl, {
714
+ method: "POST",
715
+ headers: { "Content-Type": "application/json" },
716
+ body: JSON.stringify({
717
+ jsonrpc: "2.0",
718
+ id: Date.now(),
719
+ method: "evm_increaseTime",
720
+ params: [blocks * 12]
721
+ // 12 seconds per block
722
+ })
723
+ });
724
+ },
725
+ getRpcUrl() {
726
+ return state.rpcUrl;
727
+ }
728
+ };
729
+ }
730
+ function createForkService(provider) {
731
+ switch (provider) {
732
+ case "anvil":
733
+ return createAnvilForkService();
734
+ case "tenderly":
735
+ return createTenderlyForkService();
736
+ case "custom":
737
+ return createAnvilForkService();
738
+ default:
739
+ throw new Error(`Unknown fork provider: ${provider}`);
740
+ }
741
+ }
742
+ var defaultForkService = null;
743
+ function getForkService(provider = "anvil") {
744
+ if (!defaultForkService) {
745
+ defaultForkService = createForkService(provider);
746
+ }
747
+ return defaultForkService;
748
+ }
749
+ function resetForkService() {
750
+ if (defaultForkService) {
751
+ defaultForkService.destroy();
752
+ defaultForkService = null;
753
+ }
754
+ }
755
+
756
+ // src/hooks/useForkState.ts
757
+ function useForkState(defaultProvider = "anvil") {
758
+ const [state, setState] = useState({
759
+ active: false,
760
+ rpcUrl: null,
761
+ blockNumber: null,
762
+ chainId: null,
763
+ createdAt: null,
764
+ snapshotCount: 0,
765
+ currentSnapshotId: null
766
+ });
767
+ const [isLoading, setIsLoading] = useState(false);
768
+ const [error, setError] = useState(null);
769
+ const [snapshots, setSnapshots] = useState([]);
770
+ const serviceRef = useRef(null);
771
+ useEffect(() => {
772
+ serviceRef.current = createForkService(defaultProvider);
773
+ return () => {
774
+ if (serviceRef.current) {
775
+ serviceRef.current.destroy();
776
+ serviceRef.current = null;
777
+ }
778
+ };
779
+ }, [defaultProvider]);
780
+ const createFork = useCallback(async (config) => {
781
+ if (!serviceRef.current) {
782
+ serviceRef.current = createForkService(config.provider);
783
+ }
784
+ setIsLoading(true);
785
+ setError(null);
786
+ try {
787
+ const newState = await serviceRef.current.createFork(config);
788
+ setState(newState);
789
+ setSnapshots([]);
790
+ } catch (err) {
791
+ const message = err instanceof Error ? err.message : "Failed to create fork";
792
+ setError(message);
793
+ throw err;
794
+ } finally {
795
+ setIsLoading(false);
796
+ }
797
+ }, []);
798
+ const snapshot = useCallback(async (description) => {
799
+ if (!serviceRef.current) {
800
+ setError("Fork service not initialized");
801
+ return null;
802
+ }
803
+ setIsLoading(true);
804
+ setError(null);
805
+ try {
806
+ const snap = await serviceRef.current.snapshot(description);
807
+ setSnapshots((prev) => [...prev, snap]);
808
+ setState(serviceRef.current.getState());
809
+ return snap;
810
+ } catch (err) {
811
+ const message = err instanceof Error ? err.message : "Failed to take snapshot";
812
+ setError(message);
813
+ return null;
814
+ } finally {
815
+ setIsLoading(false);
816
+ }
817
+ }, []);
818
+ const revert = useCallback(async (snapshotId) => {
819
+ if (!serviceRef.current) {
820
+ setError("Fork service not initialized");
821
+ return false;
822
+ }
823
+ setIsLoading(true);
824
+ setError(null);
825
+ try {
826
+ const success = await serviceRef.current.revert(snapshotId);
827
+ if (success) {
828
+ setState(serviceRef.current.getState());
829
+ }
830
+ return success;
831
+ } catch (err) {
832
+ const message = err instanceof Error ? err.message : "Failed to revert";
833
+ setError(message);
834
+ return false;
835
+ } finally {
836
+ setIsLoading(false);
837
+ }
838
+ }, []);
839
+ const reset = useCallback(async () => {
840
+ if (!serviceRef.current) {
841
+ setError("Fork service not initialized");
842
+ return;
843
+ }
844
+ setIsLoading(true);
845
+ setError(null);
846
+ try {
847
+ await serviceRef.current.reset();
848
+ setState(serviceRef.current.getState());
849
+ setSnapshots([]);
850
+ } catch (err) {
851
+ const message = err instanceof Error ? err.message : "Failed to reset";
852
+ setError(message);
853
+ } finally {
854
+ setIsLoading(false);
855
+ }
856
+ }, []);
857
+ const destroy = useCallback(async () => {
858
+ if (!serviceRef.current)
859
+ return;
860
+ setIsLoading(true);
861
+ setError(null);
862
+ try {
863
+ await serviceRef.current.destroy();
864
+ setState({
865
+ active: false,
866
+ rpcUrl: null,
867
+ blockNumber: null,
868
+ chainId: null,
869
+ createdAt: null,
870
+ snapshotCount: 0,
871
+ currentSnapshotId: null
872
+ });
873
+ setSnapshots([]);
874
+ } catch (err) {
875
+ const message = err instanceof Error ? err.message : "Failed to destroy fork";
876
+ setError(message);
877
+ } finally {
878
+ setIsLoading(false);
879
+ }
880
+ }, []);
881
+ const setBalance = useCallback(async (address, balance) => {
882
+ if (!serviceRef.current) {
883
+ setError("Fork service not initialized");
884
+ return;
885
+ }
886
+ setIsLoading(true);
887
+ setError(null);
888
+ try {
889
+ await serviceRef.current.setBalance(address, balance);
890
+ } catch (err) {
891
+ const message = err instanceof Error ? err.message : "Failed to set balance";
892
+ setError(message);
893
+ } finally {
894
+ setIsLoading(false);
895
+ }
896
+ }, []);
897
+ const impersonateAccount = useCallback(async (address) => {
898
+ if (!serviceRef.current) {
899
+ setError("Fork service not initialized");
900
+ return;
901
+ }
902
+ setIsLoading(true);
903
+ setError(null);
904
+ try {
905
+ await serviceRef.current.impersonateAccount(address);
906
+ } catch (err) {
907
+ const message = err instanceof Error ? err.message : "Failed to impersonate account";
908
+ setError(message);
909
+ } finally {
910
+ setIsLoading(false);
911
+ }
912
+ }, []);
913
+ const stopImpersonating = useCallback(async (address) => {
914
+ if (!serviceRef.current) {
915
+ setError("Fork service not initialized");
916
+ return;
917
+ }
918
+ setIsLoading(true);
919
+ setError(null);
920
+ try {
921
+ await serviceRef.current.stopImpersonating(address);
922
+ } catch (err) {
923
+ const message = err instanceof Error ? err.message : "Failed to stop impersonating";
924
+ setError(message);
925
+ } finally {
926
+ setIsLoading(false);
927
+ }
928
+ }, []);
929
+ const mineBlock = useCallback(async (blocks = 1) => {
930
+ if (!serviceRef.current) {
931
+ setError("Fork service not initialized");
932
+ return;
933
+ }
934
+ setIsLoading(true);
935
+ setError(null);
936
+ try {
937
+ await serviceRef.current.mineBlock(blocks);
938
+ setState(serviceRef.current.getState());
939
+ } catch (err) {
940
+ const message = err instanceof Error ? err.message : "Failed to mine block";
941
+ setError(message);
942
+ } finally {
943
+ setIsLoading(false);
944
+ }
945
+ }, []);
946
+ return {
947
+ state,
948
+ isLoading,
949
+ error,
950
+ createFork,
951
+ snapshot,
952
+ revert,
953
+ reset,
954
+ destroy,
955
+ setBalance,
956
+ impersonateAccount,
957
+ stopImpersonating,
958
+ mineBlock,
959
+ snapshots
960
+ };
961
+ }
962
+
963
+ // src/services/simulation.ts
964
+ function parseRevertReason(data) {
965
+ if (data.startsWith("0x08c379a0") && data.length >= 138) {
966
+ try {
967
+ const lengthHex = data.slice(74, 138);
968
+ const length = parseInt(lengthHex, 16);
969
+ if (length > 0 && length < 1e3) {
970
+ const messageHex = data.slice(138, 138 + length * 2);
971
+ const message = Buffer.from(messageHex, "hex").toString("utf8");
972
+ return message;
973
+ }
974
+ } catch {
975
+ }
976
+ }
977
+ if (data.startsWith("0x4e487b71") && data.length >= 74) {
978
+ const panicCode = parseInt(data.slice(10, 74), 16);
979
+ const panicMessages = {
980
+ 0: "Generic compiler panic",
981
+ 1: "Assert failed",
982
+ 17: "Arithmetic overflow/underflow",
983
+ 18: "Division by zero",
984
+ 33: "Invalid enum value",
985
+ 34: "Invalid storage byte array",
986
+ 49: "Pop on empty array",
987
+ 50: "Array index out of bounds",
988
+ 65: "Too much memory allocated",
989
+ 81: "Internal function called"
990
+ };
991
+ return panicMessages[panicCode] || `Panic(0x${panicCode.toString(16)})`;
992
+ }
993
+ if (data.length > 10) {
994
+ const selector = data.slice(0, 10);
995
+ return `Custom error: ${selector}`;
996
+ }
997
+ return null;
998
+ }
999
+ function createSimulationService(forkService) {
1000
+ async function jsonRpc(method, params = []) {
1001
+ const rpcUrl = forkService.getRpcUrl();
1002
+ if (!rpcUrl) {
1003
+ throw new Error("Fork not active");
1004
+ }
1005
+ const response = await fetch(rpcUrl, {
1006
+ method: "POST",
1007
+ headers: { "Content-Type": "application/json" },
1008
+ body: JSON.stringify({
1009
+ jsonrpc: "2.0",
1010
+ id: Date.now(),
1011
+ method,
1012
+ params
1013
+ })
1014
+ });
1015
+ const data = await response.json();
1016
+ if (data.error) {
1017
+ throw new Error(data.error.message || "RPC error");
1018
+ }
1019
+ return data.result;
1020
+ }
1021
+ async function getBalance(address) {
1022
+ const result = await jsonRpc("eth_getBalance", [address, "latest"]);
1023
+ return BigInt(result);
1024
+ }
1025
+ return {
1026
+ async simulate(tx) {
1027
+ const timestamp = Date.now();
1028
+ const blockNumberHex = await jsonRpc("eth_blockNumber");
1029
+ const blockNumber = BigInt(blockNumberHex);
1030
+ const fromBalanceBefore = await getBalance(tx.from);
1031
+ const toBalanceBefore = await getBalance(tx.to);
1032
+ const gasLimit = tx.gas ?? await this.estimateGas(tx);
1033
+ const gasPrice = tx.gasPrice ?? tx.maxFeePerGas ?? await this.getGasPrice();
1034
+ const snapshot = await forkService.snapshot("pre-simulation");
1035
+ const txParams = {
1036
+ from: tx.from,
1037
+ to: tx.to,
1038
+ value: tx.value ? `0x${tx.value.toString(16)}` : "0x0",
1039
+ data: tx.data ?? "0x",
1040
+ gas: `0x${gasLimit.toString(16)}`,
1041
+ gasPrice: `0x${gasPrice.toString(16)}`,
1042
+ nonce: tx.nonce !== void 0 ? `0x${tx.nonce.toString(16)}` : void 0
1043
+ };
1044
+ let success = true;
1045
+ let hash;
1046
+ let gasUsed = 0n;
1047
+ let returnValue;
1048
+ let revertReason;
1049
+ const logs = [];
1050
+ try {
1051
+ await forkService.impersonateAccount(tx.from);
1052
+ hash = await jsonRpc("eth_sendTransaction", [txParams]);
1053
+ let receipt = null;
1054
+ let attempts = 0;
1055
+ while (!receipt && attempts < 50) {
1056
+ await new Promise((resolve) => setTimeout(resolve, 100));
1057
+ receipt = await jsonRpc("eth_getTransactionReceipt", [hash]);
1058
+ attempts++;
1059
+ }
1060
+ if (receipt) {
1061
+ const r = receipt;
1062
+ success = r.status === "0x1";
1063
+ gasUsed = BigInt(r.gasUsed);
1064
+ for (const log of r.logs) {
1065
+ logs.push({
1066
+ address: log.address,
1067
+ topics: log.topics,
1068
+ data: log.data
1069
+ });
1070
+ }
1071
+ if (!success) {
1072
+ try {
1073
+ await jsonRpc("eth_call", [txParams, "latest"]);
1074
+ } catch (callError) {
1075
+ if (callError instanceof Error && callError.message) {
1076
+ revertReason = callError.message;
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1081
+ } catch (error) {
1082
+ success = false;
1083
+ if (error instanceof Error) {
1084
+ const errorData = error.data;
1085
+ if (errorData) {
1086
+ revertReason = parseRevertReason(errorData) ?? error.message;
1087
+ } else {
1088
+ revertReason = error.message;
1089
+ }
1090
+ }
1091
+ } finally {
1092
+ try {
1093
+ await forkService.stopImpersonating(tx.from);
1094
+ } catch {
1095
+ }
1096
+ }
1097
+ const fromBalanceAfter = await getBalance(tx.from);
1098
+ const toBalanceAfter = await getBalance(tx.to);
1099
+ const balanceChanges = [];
1100
+ if (fromBalanceBefore !== fromBalanceAfter) {
1101
+ balanceChanges.push({
1102
+ address: tx.from,
1103
+ token: null,
1104
+ symbol: "ETH",
1105
+ before: fromBalanceBefore,
1106
+ after: fromBalanceAfter,
1107
+ delta: fromBalanceAfter - fromBalanceBefore
1108
+ });
1109
+ }
1110
+ if (tx.from !== tx.to && toBalanceBefore !== toBalanceAfter) {
1111
+ balanceChanges.push({
1112
+ address: tx.to,
1113
+ token: null,
1114
+ symbol: "ETH",
1115
+ before: toBalanceBefore,
1116
+ after: toBalanceAfter,
1117
+ delta: toBalanceAfter - toBalanceBefore
1118
+ });
1119
+ }
1120
+ await forkService.revert(snapshot.id);
1121
+ const effectiveGasPrice = gasPrice;
1122
+ const totalCost = gasUsed * effectiveGasPrice + (tx.value ?? 0n);
1123
+ return {
1124
+ success,
1125
+ hash,
1126
+ gasUsed,
1127
+ gasLimit,
1128
+ effectiveGasPrice,
1129
+ totalCost,
1130
+ returnValue,
1131
+ revertReason,
1132
+ balanceChanges,
1133
+ stateChanges: [],
1134
+ // State changes require trace API
1135
+ logs,
1136
+ blockNumber,
1137
+ timestamp
1138
+ };
1139
+ },
1140
+ async estimateGas(tx) {
1141
+ const txParams = {
1142
+ from: tx.from,
1143
+ to: tx.to,
1144
+ value: tx.value ? `0x${tx.value.toString(16)}` : "0x0",
1145
+ data: tx.data ?? "0x"
1146
+ };
1147
+ try {
1148
+ const result = await jsonRpc("eth_estimateGas", [txParams]);
1149
+ const estimate = BigInt(result);
1150
+ return estimate * 120n / 100n;
1151
+ } catch (error) {
1152
+ return 21000n;
1153
+ }
1154
+ },
1155
+ async getGasPrice() {
1156
+ const result = await jsonRpc("eth_gasPrice");
1157
+ return BigInt(result);
1158
+ },
1159
+ decodeRevertReason(data) {
1160
+ return parseRevertReason(data);
1161
+ }
1162
+ };
1163
+ }
1164
+ var defaultSimulationService = null;
1165
+ function getSimulationService(forkService) {
1166
+ if (!defaultSimulationService) {
1167
+ defaultSimulationService = createSimulationService(forkService);
1168
+ }
1169
+ return defaultSimulationService;
1170
+ }
1171
+ function resetSimulationService() {
1172
+ defaultSimulationService = null;
1173
+ }
1174
+
1175
+ // src/hooks/useTransactionSimulation.ts
1176
+ function useTransactionSimulation(config = {}) {
1177
+ const {
1178
+ provider = "anvil",
1179
+ forkUrl,
1180
+ chainId = 1,
1181
+ anvilPort = 8545,
1182
+ autoConnect = false,
1183
+ from
1184
+ } = config;
1185
+ const [result, setResult] = useState(null);
1186
+ const [isSimulating, setIsSimulating] = useState(false);
1187
+ const [isConnected, setIsConnected] = useState(false);
1188
+ const [isConnecting, setIsConnecting] = useState(false);
1189
+ const [error, setError] = useState(null);
1190
+ const forkServiceRef = useRef(null);
1191
+ const simulationServiceRef = useRef(null);
1192
+ const fromAddressRef = useRef(from);
1193
+ useEffect(() => {
1194
+ fromAddressRef.current = from;
1195
+ }, [from]);
1196
+ const connect = useCallback(async (overrideConfig) => {
1197
+ if (isConnected || isConnecting)
1198
+ return;
1199
+ setIsConnecting(true);
1200
+ setError(null);
1201
+ try {
1202
+ const forkConfig = {
1203
+ provider: overrideConfig?.provider ?? provider,
1204
+ forkUrl: overrideConfig?.forkUrl ?? forkUrl ?? "",
1205
+ chainId: overrideConfig?.chainId ?? chainId,
1206
+ anvilPort: overrideConfig?.anvilPort ?? anvilPort,
1207
+ customForkRpc: overrideConfig?.customForkRpc
1208
+ };
1209
+ forkServiceRef.current = createForkService(forkConfig.provider);
1210
+ await forkServiceRef.current.createFork(forkConfig);
1211
+ simulationServiceRef.current = createSimulationService(forkServiceRef.current);
1212
+ setIsConnected(true);
1213
+ } catch (err) {
1214
+ const message = err instanceof Error ? err.message : "Failed to connect to fork";
1215
+ setError(message);
1216
+ forkServiceRef.current = null;
1217
+ simulationServiceRef.current = null;
1218
+ } finally {
1219
+ setIsConnecting(false);
1220
+ }
1221
+ }, [isConnected, isConnecting, provider, forkUrl, chainId, anvilPort]);
1222
+ const disconnect = useCallback(async () => {
1223
+ if (forkServiceRef.current) {
1224
+ await forkServiceRef.current.destroy();
1225
+ forkServiceRef.current = null;
1226
+ simulationServiceRef.current = null;
1227
+ }
1228
+ setIsConnected(false);
1229
+ setResult(null);
1230
+ setError(null);
1231
+ }, []);
1232
+ useEffect(() => {
1233
+ if (autoConnect) {
1234
+ connect();
1235
+ }
1236
+ return () => {
1237
+ if (forkServiceRef.current) {
1238
+ forkServiceRef.current.destroy();
1239
+ }
1240
+ };
1241
+ }, []);
1242
+ const simulate = useCallback(async (tx) => {
1243
+ if (!simulationServiceRef.current || !forkServiceRef.current) {
1244
+ setError("Not connected to fork. Call connect() first.");
1245
+ return null;
1246
+ }
1247
+ const senderAddress = fromAddressRef.current;
1248
+ if (!senderAddress) {
1249
+ setError('No sender address provided. Set "from" in config.');
1250
+ return null;
1251
+ }
1252
+ setIsSimulating(true);
1253
+ setError(null);
1254
+ try {
1255
+ const request = {
1256
+ from: senderAddress,
1257
+ to: tx.to,
1258
+ data: tx.data,
1259
+ value: tx.value,
1260
+ gas: tx.gas
1261
+ };
1262
+ const simResult = await simulationServiceRef.current.simulate(request);
1263
+ setResult(simResult);
1264
+ return simResult;
1265
+ } catch (err) {
1266
+ const message = err instanceof Error ? err.message : "Simulation failed";
1267
+ setError(message);
1268
+ return null;
1269
+ } finally {
1270
+ setIsSimulating(false);
1271
+ }
1272
+ }, []);
1273
+ const clearResult = useCallback(() => {
1274
+ setResult(null);
1275
+ setError(null);
1276
+ }, []);
1277
+ const reset = useCallback(async () => {
1278
+ if (forkServiceRef.current) {
1279
+ await forkServiceRef.current.reset();
1280
+ setResult(null);
1281
+ setError(null);
1282
+ }
1283
+ }, []);
1284
+ return {
1285
+ result,
1286
+ isSimulating,
1287
+ isConnected,
1288
+ isConnecting,
1289
+ error,
1290
+ simulate,
1291
+ connect,
1292
+ disconnect,
1293
+ clearResult,
1294
+ reset
1295
+ };
1296
+ }
1297
+ function useSimulation(from, options = {}) {
1298
+ const hook = useTransactionSimulation({
1299
+ ...options,
1300
+ from,
1301
+ autoConnect: true
1302
+ });
1303
+ const { connect: _connect, disconnect: _disconnect, ...rest } = hook;
1304
+ return rest;
1305
+ }
1306
+ function isValidAddressInput(input) {
1307
+ if (isAddress(input))
1308
+ return true;
1309
+ if (input.endsWith(".eth") && input.length > 4)
1310
+ return true;
1311
+ return false;
1312
+ }
1313
+ function truncateAddress(address) {
1314
+ if (address.length <= 10)
1315
+ return address;
1316
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
1317
+ }
1318
+ function UserLens() {
1319
+ const [input, setInput] = useState("");
1320
+ const [error, setError] = useState(null);
1321
+ const [isResolving, setIsResolving] = useState(false);
1322
+ const { setImpersonatedAddress, disableLens, saveAddress, userLens } = useDevToolbar();
1323
+ const { savedAddresses, selectAddress, removeAddress } = useSavedAddresses();
1324
+ const isImpersonating = useIsImpersonating();
1325
+ const handleSubmit = useCallback(
1326
+ async (e) => {
1327
+ e.preventDefault();
1328
+ setError(null);
1329
+ const trimmed = input.trim();
1330
+ if (!trimmed)
1331
+ return;
1332
+ if (!isValidAddressInput(trimmed)) {
1333
+ setError("Invalid address or ENS name");
1334
+ return;
1335
+ }
1336
+ if (isAddress(trimmed)) {
1337
+ setImpersonatedAddress(trimmed);
1338
+ setInput("");
1339
+ } else if (trimmed.endsWith(".eth")) {
1340
+ setIsResolving(true);
1341
+ setError("ENS resolution not yet implemented. Please use address.");
1342
+ setIsResolving(false);
1343
+ }
1344
+ },
1345
+ [input, setImpersonatedAddress]
1346
+ );
1347
+ const handleClear = useCallback(() => {
1348
+ disableLens();
1349
+ setInput("");
1350
+ setError(null);
1351
+ }, [disableLens]);
1352
+ const handleSaveCurrentAddress = useCallback(() => {
1353
+ const trimmed = input.trim();
1354
+ if (isAddress(trimmed)) {
1355
+ saveAddress({
1356
+ address: trimmed,
1357
+ label: `Address ${savedAddresses.length + 1}`
1358
+ });
1359
+ }
1360
+ }, [input, saveAddress, savedAddresses.length]);
1361
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-user-lens", children: [
1362
+ /* @__PURE__ */ jsxs("div", { className: "sigil-user-lens__header", children: [
1363
+ /* @__PURE__ */ jsx("h3", { children: "User Lens" }),
1364
+ isImpersonating && /* @__PURE__ */ jsx("span", { className: "sigil-user-lens__badge", children: "Active" })
1365
+ ] }),
1366
+ /* @__PURE__ */ jsx("p", { className: "sigil-user-lens__description", children: "View the app as another address. Reads use the impersonated address, transactions still sign with your real wallet." }),
1367
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "sigil-user-lens__form", children: [
1368
+ /* @__PURE__ */ jsxs("div", { className: "sigil-user-lens__input-group", children: [
1369
+ /* @__PURE__ */ jsx(
1370
+ "input",
1371
+ {
1372
+ type: "text",
1373
+ value: input,
1374
+ onChange: (e) => setInput(e.target.value),
1375
+ placeholder: "0x... or name.eth",
1376
+ className: "sigil-user-lens__input",
1377
+ disabled: isResolving
1378
+ }
1379
+ ),
1380
+ /* @__PURE__ */ jsx(
1381
+ "button",
1382
+ {
1383
+ type: "submit",
1384
+ className: "sigil-user-lens__button sigil-user-lens__button--primary",
1385
+ disabled: isResolving || !input.trim(),
1386
+ children: isResolving ? "Resolving..." : "Impersonate"
1387
+ }
1388
+ )
1389
+ ] }),
1390
+ error && /* @__PURE__ */ jsx("p", { className: "sigil-user-lens__error", children: error })
1391
+ ] }),
1392
+ isImpersonating && /* @__PURE__ */ jsxs("div", { className: "sigil-user-lens__active", children: [
1393
+ /* @__PURE__ */ jsxs("div", { className: "sigil-user-lens__active-header", children: [
1394
+ /* @__PURE__ */ jsx("span", { children: "Currently viewing as:" }),
1395
+ /* @__PURE__ */ jsx(
1396
+ "button",
1397
+ {
1398
+ onClick: handleClear,
1399
+ className: "sigil-user-lens__button sigil-user-lens__button--danger",
1400
+ children: "Clear"
1401
+ }
1402
+ )
1403
+ ] }),
1404
+ /* @__PURE__ */ jsx("code", { className: "sigil-user-lens__address", children: userLens.impersonatedAddress })
1405
+ ] }),
1406
+ savedAddresses.length > 0 && /* @__PURE__ */ jsxs("div", { className: "sigil-user-lens__saved", children: [
1407
+ /* @__PURE__ */ jsx("h4", { children: "Saved Addresses" }),
1408
+ /* @__PURE__ */ jsx("ul", { className: "sigil-user-lens__saved-list", children: savedAddresses.map((entry) => /* @__PURE__ */ jsxs("li", { className: "sigil-user-lens__saved-item", children: [
1409
+ /* @__PURE__ */ jsxs(
1410
+ "button",
1411
+ {
1412
+ onClick: () => selectAddress(entry.address),
1413
+ className: "sigil-user-lens__saved-button",
1414
+ children: [
1415
+ /* @__PURE__ */ jsx("span", { className: "sigil-user-lens__saved-label", children: entry.label }),
1416
+ /* @__PURE__ */ jsx("span", { className: "sigil-user-lens__saved-address", children: truncateAddress(entry.address) })
1417
+ ]
1418
+ }
1419
+ ),
1420
+ /* @__PURE__ */ jsx(
1421
+ "button",
1422
+ {
1423
+ onClick: () => removeAddress(entry.address),
1424
+ className: "sigil-user-lens__remove",
1425
+ "aria-label": "Remove",
1426
+ children: "\xD7"
1427
+ }
1428
+ )
1429
+ ] }, entry.address)) })
1430
+ ] }),
1431
+ input && isAddress(input) && !savedAddresses.find((a) => a.address === input) && /* @__PURE__ */ jsx(
1432
+ "button",
1433
+ {
1434
+ onClick: handleSaveCurrentAddress,
1435
+ className: "sigil-user-lens__button sigil-user-lens__button--secondary",
1436
+ children: "Save Address"
1437
+ }
1438
+ )
1439
+ ] });
1440
+ }
1441
+ function LensActiveBadge() {
1442
+ const isImpersonating = useIsImpersonating();
1443
+ if (!isImpersonating)
1444
+ return null;
1445
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-lens-badge", children: [
1446
+ /* @__PURE__ */ jsx("span", { className: "sigil-lens-badge__icon", children: "\u{1F441}" }),
1447
+ /* @__PURE__ */ jsx("span", { className: "sigil-lens-badge__text", children: "Lens Active" })
1448
+ ] });
1449
+ }
1450
+ var TABS = [
1451
+ { id: "lens", label: "Lens", icon: "\u{1F441}", configKey: "enableUserLens" },
1452
+ { id: "simulate", label: "Simulate", icon: "\u{1F3AD}", configKey: "enableSimulation" },
1453
+ { id: "compare", label: "Compare", icon: "\u2696\uFE0F", configKey: "enableComparison" },
1454
+ { id: "diagnose", label: "Diagnose", icon: "\u{1F50D}", configKey: "enableDiagnostics" }
1455
+ ];
1456
+ function PlaceholderPanel({ title }) {
1457
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-toolbar__placeholder", children: [
1458
+ /* @__PURE__ */ jsx("h3", { children: title }),
1459
+ /* @__PURE__ */ jsx("p", { children: "Coming in Sprint 4+" })
1460
+ ] });
1461
+ }
1462
+ function TabContent({ tab }) {
1463
+ switch (tab) {
1464
+ case "lens":
1465
+ return /* @__PURE__ */ jsx(UserLens, {});
1466
+ case "simulate":
1467
+ return /* @__PURE__ */ jsx(PlaceholderPanel, { title: "Simulation" });
1468
+ case "compare":
1469
+ return /* @__PURE__ */ jsx(PlaceholderPanel, { title: "State Comparison" });
1470
+ case "diagnose":
1471
+ return /* @__PURE__ */ jsx(PlaceholderPanel, { title: "Diagnostics" });
1472
+ default:
1473
+ return null;
1474
+ }
1475
+ }
1476
+ function DevToolbar() {
1477
+ const config = useDevToolbarConfig();
1478
+ const {
1479
+ visible,
1480
+ collapsed,
1481
+ activeTab,
1482
+ setActiveTab,
1483
+ collapse,
1484
+ expand,
1485
+ toggle,
1486
+ userLens
1487
+ } = useDevToolbar();
1488
+ const handleTabClick = useCallback(
1489
+ (tab) => {
1490
+ if (collapsed) {
1491
+ expand();
1492
+ }
1493
+ setActiveTab(tab);
1494
+ },
1495
+ [collapsed, expand, setActiveTab]
1496
+ );
1497
+ const handleToggleCollapse = useCallback(() => {
1498
+ if (collapsed) {
1499
+ expand();
1500
+ } else {
1501
+ collapse();
1502
+ }
1503
+ }, [collapsed, collapse, expand]);
1504
+ if (process.env.NODE_ENV === "production") {
1505
+ return null;
1506
+ }
1507
+ if (!visible) {
1508
+ return null;
1509
+ }
1510
+ const enabledTabs = TABS.filter((tab) => config[tab.configKey]);
1511
+ const positionClass = `sigil-toolbar--${config.position}`;
1512
+ return /* @__PURE__ */ jsxs("div", { className: `sigil-toolbar ${positionClass} ${collapsed ? "sigil-toolbar--collapsed" : ""}`, children: [
1513
+ /* @__PURE__ */ jsxs("div", { className: "sigil-toolbar__header", children: [
1514
+ /* @__PURE__ */ jsx("div", { className: "sigil-toolbar__tabs", children: enabledTabs.map((tab) => /* @__PURE__ */ jsxs(
1515
+ "button",
1516
+ {
1517
+ onClick: () => handleTabClick(tab.id),
1518
+ className: `sigil-toolbar__tab ${activeTab === tab.id ? "sigil-toolbar__tab--active" : ""}`,
1519
+ title: tab.label,
1520
+ children: [
1521
+ /* @__PURE__ */ jsx("span", { className: "sigil-toolbar__tab-icon", children: tab.icon }),
1522
+ !collapsed && /* @__PURE__ */ jsx("span", { className: "sigil-toolbar__tab-label", children: tab.label })
1523
+ ]
1524
+ },
1525
+ tab.id
1526
+ )) }),
1527
+ /* @__PURE__ */ jsxs("div", { className: "sigil-toolbar__controls", children: [
1528
+ userLens.enabled && collapsed && /* @__PURE__ */ jsx("span", { className: "sigil-toolbar__lens-indicator", title: "Lens Active", children: "\u{1F441}" }),
1529
+ /* @__PURE__ */ jsx(
1530
+ "button",
1531
+ {
1532
+ onClick: handleToggleCollapse,
1533
+ className: "sigil-toolbar__collapse-btn",
1534
+ "aria-label": collapsed ? "Expand toolbar" : "Collapse toolbar",
1535
+ children: collapsed ? "\u25C0" : "\u25B6"
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ jsx(
1539
+ "button",
1540
+ {
1541
+ onClick: toggle,
1542
+ className: "sigil-toolbar__close-btn",
1543
+ "aria-label": "Close toolbar",
1544
+ children: "\xD7"
1545
+ }
1546
+ )
1547
+ ] })
1548
+ ] }),
1549
+ !collapsed && /* @__PURE__ */ jsx("div", { className: "sigil-toolbar__content", children: /* @__PURE__ */ jsx(TabContent, { tab: activeTab }) }),
1550
+ collapsed && /* @__PURE__ */ jsx("div", { className: "sigil-toolbar__shortcut-hint", children: config.toggleShortcut })
1551
+ ] });
1552
+ }
1553
+ function DevToolbarTrigger() {
1554
+ const { visible, show } = useDevToolbar();
1555
+ const config = useDevToolbarConfig();
1556
+ if (process.env.NODE_ENV === "production") {
1557
+ return null;
1558
+ }
1559
+ if (visible) {
1560
+ return null;
1561
+ }
1562
+ const positionClass = `sigil-toolbar-trigger--${config.position}`;
1563
+ return /* @__PURE__ */ jsx(
1564
+ "button",
1565
+ {
1566
+ onClick: show,
1567
+ className: `sigil-toolbar-trigger ${positionClass}`,
1568
+ "aria-label": "Open Sigil Dev Toolbar",
1569
+ title: `Sigil Dev Toolbar (${config.toggleShortcut})`,
1570
+ children: /* @__PURE__ */ jsx("span", { children: "\u26A1" })
1571
+ }
1572
+ );
1573
+ }
1574
+ function DevToolbarWithTrigger() {
1575
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1576
+ /* @__PURE__ */ jsx(DevToolbar, {}),
1577
+ /* @__PURE__ */ jsx(DevToolbarTrigger, {}),
1578
+ /* @__PURE__ */ jsx(LensActiveBadge, {})
1579
+ ] });
1580
+ }
1581
+ function generateQuestions(issues) {
1582
+ return issues.map((issue, index) => {
1583
+ let question;
1584
+ let severity;
1585
+ switch (issue.type) {
1586
+ case "data_source_mismatch":
1587
+ question = `The ${issue.component} shows "${issue.actual}" but on-chain value is "${issue.expected}". Which is correct?`;
1588
+ severity = issue.severity === "error" ? "critical" : "important";
1589
+ break;
1590
+ case "stale_indexed_data":
1591
+ question = `The indexer shows stale data for ${issue.component}. Expected: "${issue.expected}", Got: "${issue.actual}". Has this been recently updated?`;
1592
+ severity = issue.severity === "error" ? "critical" : "info";
1593
+ break;
1594
+ case "lens_financial_check":
1595
+ question = `${issue.component} uses indexed data for financial operations. Should this use on-chain data instead?`;
1596
+ severity = "critical";
1597
+ break;
1598
+ case "impersonation_leak":
1599
+ question = `${issue.component} appears to show the real address instead of the impersonated one. Is this intentional?`;
1600
+ severity = "critical";
1601
+ break;
1602
+ default:
1603
+ question = issue.message;
1604
+ severity = "info";
1605
+ }
1606
+ return {
1607
+ id: `q-${index}`,
1608
+ question,
1609
+ context: issue.suggestion ?? "",
1610
+ severity
1611
+ };
1612
+ });
1613
+ }
1614
+ function DiagnosticPanel({
1615
+ items: initialItems = [],
1616
+ autoQuery = false,
1617
+ onDiagnosisComplete
1618
+ }) {
1619
+ const [items, setItems] = useState(initialItems);
1620
+ const [questions, setQuestions] = useState([]);
1621
+ const [isQuerying, setIsQuerying] = useState(false);
1622
+ const { validateLens, isLoading, error } = useIPCClient();
1623
+ const { address, isImpersonating, realAddress, impersonatedAddress } = useLensAwareAccount();
1624
+ const diagnosticsState = useDevToolbarSelector((state) => state.diagnostics);
1625
+ const { addViolation } = useDevToolbar();
1626
+ const runDiagnostics = useCallback(async () => {
1627
+ if (!address)
1628
+ return;
1629
+ setIsQuerying(true);
1630
+ const newIssues = [];
1631
+ const updatedItems = [...items];
1632
+ for (let i = 0; i < updatedItems.length; i++) {
1633
+ const item = updatedItems[i];
1634
+ const context = {
1635
+ impersonatedAddress: isImpersonating ? impersonatedAddress : address,
1636
+ realAddress,
1637
+ component: item.component,
1638
+ observedValue: item.observedValue,
1639
+ onChainValue: item.onChainValue,
1640
+ indexedValue: item.indexedValue,
1641
+ dataSource: item.dataSource
1642
+ };
1643
+ try {
1644
+ const result = await validateLens(context, item.zone);
1645
+ if (result.valid) {
1646
+ updatedItems[i] = { ...item, status: "verified" };
1647
+ } else {
1648
+ updatedItems[i] = { ...item, status: "mismatch" };
1649
+ newIssues.push(...result.issues);
1650
+ result.issues.forEach((issue) => {
1651
+ addViolation({
1652
+ id: `${item.id}-${issue.type}`,
1653
+ timestamp: Date.now(),
1654
+ type: "behavioral",
1655
+ severity: issue.severity,
1656
+ message: issue.message,
1657
+ element: issue.component,
1658
+ suggestion: issue.suggestion
1659
+ });
1660
+ });
1661
+ }
1662
+ } catch {
1663
+ updatedItems[i] = { ...item, status: "unknown" };
1664
+ }
1665
+ }
1666
+ setItems(updatedItems);
1667
+ setQuestions(generateQuestions(newIssues));
1668
+ setIsQuerying(false);
1669
+ if (onDiagnosisComplete) {
1670
+ onDiagnosisComplete({
1671
+ verified: updatedItems.filter((i) => i.status === "verified"),
1672
+ issues: newIssues
1673
+ });
1674
+ }
1675
+ }, [address, items, isImpersonating, impersonatedAddress, realAddress, validateLens, addViolation, onDiagnosisComplete]);
1676
+ useEffect(() => {
1677
+ if (autoQuery && address && items.length > 0) {
1678
+ runDiagnostics();
1679
+ }
1680
+ }, [autoQuery, address]);
1681
+ const verifiedItems = items.filter((i) => i.status === "verified");
1682
+ const attentionItems = items.filter((i) => i.status === "mismatch" || i.status === "unknown");
1683
+ const pendingItems = items.filter((i) => i.status === "pending");
1684
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-panel", children: [
1685
+ /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-header", children: [
1686
+ /* @__PURE__ */ jsx("h3", { children: "Diagnostics" }),
1687
+ isImpersonating && /* @__PURE__ */ jsxs("span", { className: "sigil-diagnostic-lens-badge", children: [
1688
+ "Lens Active: ",
1689
+ impersonatedAddress?.slice(0, 6),
1690
+ "...",
1691
+ impersonatedAddress?.slice(-4)
1692
+ ] })
1693
+ ] }),
1694
+ error && /* @__PURE__ */ jsx("div", { className: "sigil-diagnostic-error", children: error }),
1695
+ /* @__PURE__ */ jsx("div", { className: "sigil-diagnostic-actions", children: /* @__PURE__ */ jsx(
1696
+ "button",
1697
+ {
1698
+ onClick: runDiagnostics,
1699
+ disabled: isLoading || isQuerying || items.length === 0,
1700
+ className: "sigil-diagnostic-query-btn",
1701
+ children: isQuerying ? "Querying..." : "Run Diagnostics"
1702
+ }
1703
+ ) }),
1704
+ verifiedItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-section sigil-diagnostic-verified", children: [
1705
+ /* @__PURE__ */ jsxs("h4", { children: [
1706
+ "\u2713 Verified (",
1707
+ verifiedItems.length,
1708
+ ")"
1709
+ ] }),
1710
+ /* @__PURE__ */ jsx("ul", { children: verifiedItems.map((item) => /* @__PURE__ */ jsxs("li", { className: "sigil-diagnostic-item verified", children: [
1711
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-label", children: item.label }),
1712
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-value", children: item.observedValue }),
1713
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-source", children: item.dataSource })
1714
+ ] }, item.id)) })
1715
+ ] }),
1716
+ attentionItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-section sigil-diagnostic-attention", children: [
1717
+ /* @__PURE__ */ jsxs("h4", { children: [
1718
+ "\u26A0 Needs Attention (",
1719
+ attentionItems.length,
1720
+ ")"
1721
+ ] }),
1722
+ /* @__PURE__ */ jsx("ul", { children: attentionItems.map((item) => /* @__PURE__ */ jsxs("li", { className: "sigil-diagnostic-item attention", children: [
1723
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-label", children: item.label }),
1724
+ /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-comparison", children: [
1725
+ /* @__PURE__ */ jsxs("span", { className: "sigil-diagnostic-observed", children: [
1726
+ "UI: ",
1727
+ item.observedValue
1728
+ ] }),
1729
+ item.onChainValue && /* @__PURE__ */ jsxs("span", { className: "sigil-diagnostic-onchain", children: [
1730
+ "On-chain: ",
1731
+ item.onChainValue
1732
+ ] }),
1733
+ item.indexedValue && /* @__PURE__ */ jsxs("span", { className: "sigil-diagnostic-indexed", children: [
1734
+ "Indexed: ",
1735
+ item.indexedValue
1736
+ ] })
1737
+ ] })
1738
+ ] }, item.id)) })
1739
+ ] }),
1740
+ pendingItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-section sigil-diagnostic-pending", children: [
1741
+ /* @__PURE__ */ jsxs("h4", { children: [
1742
+ "\u25CB Pending (",
1743
+ pendingItems.length,
1744
+ ")"
1745
+ ] }),
1746
+ /* @__PURE__ */ jsx("ul", { children: pendingItems.map((item) => /* @__PURE__ */ jsxs("li", { className: "sigil-diagnostic-item pending", children: [
1747
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-label", children: item.label }),
1748
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-value", children: item.observedValue })
1749
+ ] }, item.id)) })
1750
+ ] }),
1751
+ questions.length > 0 && /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-section sigil-diagnostic-questions", children: [
1752
+ /* @__PURE__ */ jsx("h4", { children: "Questions for User" }),
1753
+ /* @__PURE__ */ jsx("ul", { children: questions.map((q) => /* @__PURE__ */ jsxs("li", { className: `sigil-diagnostic-question ${q.severity}`, children: [
1754
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-question-text", children: q.question }),
1755
+ q.context && /* @__PURE__ */ jsxs("span", { className: "sigil-diagnostic-question-context", children: [
1756
+ "Suggestion: ",
1757
+ q.context
1758
+ ] })
1759
+ ] }, q.id)) })
1760
+ ] }),
1761
+ diagnosticsState.violations.length > 0 && /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-section sigil-diagnostic-violations", children: [
1762
+ /* @__PURE__ */ jsxs("h4", { children: [
1763
+ "Recent Violations (",
1764
+ diagnosticsState.violations.length,
1765
+ ")"
1766
+ ] }),
1767
+ /* @__PURE__ */ jsx("ul", { children: diagnosticsState.violations.slice(0, 5).map((v) => /* @__PURE__ */ jsxs("li", { className: `sigil-diagnostic-violation ${v.severity}`, children: [
1768
+ /* @__PURE__ */ jsx("span", { className: "sigil-diagnostic-violation-msg", children: v.message }),
1769
+ v.suggestion && /* @__PURE__ */ jsxs("span", { className: "sigil-diagnostic-violation-suggestion", children: [
1770
+ "\u2192 ",
1771
+ v.suggestion
1772
+ ] })
1773
+ ] }, v.id)) })
1774
+ ] }),
1775
+ items.length === 0 && /* @__PURE__ */ jsxs("div", { className: "sigil-diagnostic-empty", children: [
1776
+ /* @__PURE__ */ jsx("p", { children: "No items to diagnose." }),
1777
+ /* @__PURE__ */ jsx("p", { children: "Add diagnostic items to validate on-chain state." })
1778
+ ] })
1779
+ ] });
1780
+ }
1781
+ function useDiagnosticItems() {
1782
+ const [items, setItems] = useState([]);
1783
+ const addItem = useCallback((item) => {
1784
+ setItems((prev) => [
1785
+ ...prev,
1786
+ {
1787
+ ...item,
1788
+ id: `diag-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
1789
+ status: "pending"
1790
+ }
1791
+ ]);
1792
+ }, []);
1793
+ const removeItem = useCallback((id) => {
1794
+ setItems((prev) => prev.filter((i) => i.id !== id));
1795
+ }, []);
1796
+ const clearItems = useCallback(() => {
1797
+ setItems([]);
1798
+ }, []);
1799
+ const updateItem = useCallback((id, updates) => {
1800
+ setItems(
1801
+ (prev) => prev.map((i) => i.id === id ? { ...i, ...updates } : i)
1802
+ );
1803
+ }, []);
1804
+ return {
1805
+ items,
1806
+ addItem,
1807
+ removeItem,
1808
+ clearItems,
1809
+ updateItem
1810
+ };
1811
+ }
1812
+ function formatEther(wei, decimals = 4) {
1813
+ const ethString = (Number(wei) / 1e18).toFixed(decimals);
1814
+ return `${ethString} ETH`;
1815
+ }
1816
+ function formatGas(gas) {
1817
+ if (gas >= 1000000n) {
1818
+ return `${(Number(gas) / 1e6).toFixed(2)}M`;
1819
+ }
1820
+ if (gas >= 1000n) {
1821
+ return `${(Number(gas) / 1e3).toFixed(1)}K`;
1822
+ }
1823
+ return gas.toString();
1824
+ }
1825
+ function truncateAddress2(address) {
1826
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
1827
+ }
1828
+ function formatBalanceChange(change) {
1829
+ const sign = change.delta >= 0n ? "+" : "";
1830
+ return `${sign}${formatEther(change.delta)}`;
1831
+ }
1832
+ function estimateUSD(wei, ethPrice = 2e3) {
1833
+ const eth = Number(wei) / 1e18;
1834
+ const usd = eth * ethPrice;
1835
+ return `~$${usd.toFixed(2)}`;
1836
+ }
1837
+ function BalanceChangesSection({ changes }) {
1838
+ if (changes.length === 0)
1839
+ return null;
1840
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-section sigil-simulation-balances", children: [
1841
+ /* @__PURE__ */ jsx("h4", { children: "Balance Changes" }),
1842
+ /* @__PURE__ */ jsx("ul", { children: changes.map((change, i) => /* @__PURE__ */ jsxs("li", { className: "sigil-simulation-balance-item", children: [
1843
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-address", title: change.address, children: truncateAddress2(change.address) }),
1844
+ /* @__PURE__ */ jsx("span", { className: `sigil-simulation-delta ${change.delta >= 0n ? "positive" : "negative"}`, children: formatBalanceChange(change) }),
1845
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-token", children: change.symbol ?? (change.token ? truncateAddress2(change.token) : "ETH") })
1846
+ ] }, i)) })
1847
+ ] });
1848
+ }
1849
+ function EventLogsSection({ logs, showDecoded }) {
1850
+ if (logs.length === 0)
1851
+ return null;
1852
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-section sigil-simulation-logs", children: [
1853
+ /* @__PURE__ */ jsxs("h4", { children: [
1854
+ "Event Logs (",
1855
+ logs.length,
1856
+ ")"
1857
+ ] }),
1858
+ /* @__PURE__ */ jsxs("ul", { children: [
1859
+ logs.slice(0, 10).map((log, i) => /* @__PURE__ */ jsxs("li", { className: "sigil-simulation-log-item", children: [
1860
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-address", title: log.address, children: truncateAddress2(log.address) }),
1861
+ showDecoded && log.eventName ? /* @__PURE__ */ jsx("span", { className: "sigil-simulation-event-name", children: log.eventName }) : /* @__PURE__ */ jsxs("span", { className: "sigil-simulation-topic", title: log.topics[0], children: [
1862
+ log.topics[0]?.slice(0, 10),
1863
+ "..."
1864
+ ] })
1865
+ ] }, i)),
1866
+ logs.length > 10 && /* @__PURE__ */ jsxs("li", { className: "sigil-simulation-more", children: [
1867
+ "+",
1868
+ logs.length - 10,
1869
+ " more events"
1870
+ ] })
1871
+ ] })
1872
+ ] });
1873
+ }
1874
+ function GasSummarySection({
1875
+ result,
1876
+ ethPrice
1877
+ }) {
1878
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-section sigil-simulation-gas", children: [
1879
+ /* @__PURE__ */ jsx("h4", { children: "Gas" }),
1880
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-gas-grid", children: [
1881
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-gas-item", children: [
1882
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-label", children: "Used" }),
1883
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-value", children: formatGas(result.gasUsed) })
1884
+ ] }),
1885
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-gas-item", children: [
1886
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-label", children: "Limit" }),
1887
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-value", children: formatGas(result.gasLimit) })
1888
+ ] }),
1889
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-gas-item", children: [
1890
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-label", children: "Price" }),
1891
+ /* @__PURE__ */ jsxs("span", { className: "sigil-simulation-value", children: [
1892
+ (Number(result.effectiveGasPrice) / 1e9).toFixed(2),
1893
+ " gwei"
1894
+ ] })
1895
+ ] }),
1896
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-gas-item sigil-simulation-gas-total", children: [
1897
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-label", children: "Total Cost" }),
1898
+ /* @__PURE__ */ jsxs("span", { className: "sigil-simulation-value", children: [
1899
+ formatEther(result.totalCost),
1900
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-usd", children: estimateUSD(result.totalCost, ethPrice) })
1901
+ ] })
1902
+ ] })
1903
+ ] })
1904
+ ] });
1905
+ }
1906
+ function SimulationResultDisplay({
1907
+ result,
1908
+ ethPrice,
1909
+ showDecodedLogs
1910
+ }) {
1911
+ return /* @__PURE__ */ jsxs("div", { className: `sigil-simulation-result ${result.success ? "success" : "failure"}`, children: [
1912
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-status", children: [
1913
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-status-icon", children: result.success ? "\u2713" : "\u2717" }),
1914
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-status-text", children: result.success ? "Transaction Successful" : "Transaction Failed" })
1915
+ ] }),
1916
+ !result.success && result.revertReason && /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-revert", children: [
1917
+ /* @__PURE__ */ jsx("h4", { children: "Revert Reason" }),
1918
+ /* @__PURE__ */ jsx("pre", { className: "sigil-simulation-revert-message", children: result.revertReason })
1919
+ ] }),
1920
+ /* @__PURE__ */ jsx(GasSummarySection, { result, ethPrice }),
1921
+ /* @__PURE__ */ jsx(BalanceChangesSection, { changes: result.balanceChanges }),
1922
+ /* @__PURE__ */ jsx(EventLogsSection, { logs: result.logs, showDecoded: showDecodedLogs }),
1923
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-section sigil-simulation-meta", children: [
1924
+ /* @__PURE__ */ jsxs("span", { className: "sigil-simulation-block", children: [
1925
+ "Block: ",
1926
+ result.blockNumber.toString()
1927
+ ] }),
1928
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-time", children: new Date(result.timestamp).toLocaleTimeString() })
1929
+ ] })
1930
+ ] });
1931
+ }
1932
+ function TransactionInputForm({
1933
+ onSimulate,
1934
+ isSimulating
1935
+ }) {
1936
+ const [from, setFrom] = useState("");
1937
+ const [to, setTo] = useState("");
1938
+ const [value, setValue] = useState("");
1939
+ const [data, setData] = useState("");
1940
+ const handleSubmit = useCallback(
1941
+ (e) => {
1942
+ e.preventDefault();
1943
+ if (!from || !to)
1944
+ return;
1945
+ const tx = {
1946
+ from,
1947
+ to,
1948
+ value: value ? BigInt(value) : void 0,
1949
+ data: data ? data : void 0
1950
+ };
1951
+ onSimulate(tx);
1952
+ },
1953
+ [from, to, value, data, onSimulate]
1954
+ );
1955
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "sigil-simulation-form", children: [
1956
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-field", children: [
1957
+ /* @__PURE__ */ jsx("label", { htmlFor: "sim-from", children: "From" }),
1958
+ /* @__PURE__ */ jsx(
1959
+ "input",
1960
+ {
1961
+ id: "sim-from",
1962
+ type: "text",
1963
+ value: from,
1964
+ onChange: (e) => setFrom(e.target.value),
1965
+ placeholder: "0x...",
1966
+ disabled: isSimulating
1967
+ }
1968
+ )
1969
+ ] }),
1970
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-field", children: [
1971
+ /* @__PURE__ */ jsx("label", { htmlFor: "sim-to", children: "To" }),
1972
+ /* @__PURE__ */ jsx(
1973
+ "input",
1974
+ {
1975
+ id: "sim-to",
1976
+ type: "text",
1977
+ value: to,
1978
+ onChange: (e) => setTo(e.target.value),
1979
+ placeholder: "0x...",
1980
+ disabled: isSimulating
1981
+ }
1982
+ )
1983
+ ] }),
1984
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-field", children: [
1985
+ /* @__PURE__ */ jsx("label", { htmlFor: "sim-value", children: "Value (wei)" }),
1986
+ /* @__PURE__ */ jsx(
1987
+ "input",
1988
+ {
1989
+ id: "sim-value",
1990
+ type: "text",
1991
+ value,
1992
+ onChange: (e) => setValue(e.target.value),
1993
+ placeholder: "0",
1994
+ disabled: isSimulating
1995
+ }
1996
+ )
1997
+ ] }),
1998
+ /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-field", children: [
1999
+ /* @__PURE__ */ jsx("label", { htmlFor: "sim-data", children: "Data" }),
2000
+ /* @__PURE__ */ jsx(
2001
+ "input",
2002
+ {
2003
+ id: "sim-data",
2004
+ type: "text",
2005
+ value: data,
2006
+ onChange: (e) => setData(e.target.value),
2007
+ placeholder: "0x...",
2008
+ disabled: isSimulating
2009
+ }
2010
+ )
2011
+ ] }),
2012
+ /* @__PURE__ */ jsx(
2013
+ "button",
2014
+ {
2015
+ type: "submit",
2016
+ disabled: isSimulating || !from || !to,
2017
+ className: "sigil-simulation-submit",
2018
+ children: isSimulating ? "Simulating..." : "Simulate"
2019
+ }
2020
+ )
2021
+ ] });
2022
+ }
2023
+ function SimulationPanel({
2024
+ onSimulate,
2025
+ result,
2026
+ isSimulating = false,
2027
+ error,
2028
+ ethPrice = 2e3,
2029
+ showDecodedLogs = true
2030
+ }) {
2031
+ const handleSimulate = useCallback(
2032
+ async (tx) => {
2033
+ if (onSimulate) {
2034
+ await onSimulate(tx);
2035
+ }
2036
+ },
2037
+ [onSimulate]
2038
+ );
2039
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-panel", children: [
2040
+ /* @__PURE__ */ jsx("div", { className: "sigil-simulation-header", children: /* @__PURE__ */ jsx("h3", { children: "Transaction Simulation" }) }),
2041
+ onSimulate && /* @__PURE__ */ jsx(
2042
+ TransactionInputForm,
2043
+ {
2044
+ onSimulate: handleSimulate,
2045
+ isSimulating
2046
+ }
2047
+ ),
2048
+ error && /* @__PURE__ */ jsx("div", { className: "sigil-simulation-error", children: error }),
2049
+ isSimulating && !result && /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-loading", children: [
2050
+ /* @__PURE__ */ jsx("span", { className: "sigil-simulation-spinner" }),
2051
+ /* @__PURE__ */ jsx("span", { children: "Running simulation..." })
2052
+ ] }),
2053
+ result && /* @__PURE__ */ jsx(
2054
+ SimulationResultDisplay,
2055
+ {
2056
+ result,
2057
+ ethPrice,
2058
+ showDecodedLogs
2059
+ }
2060
+ ),
2061
+ !result && !isSimulating && !error && /* @__PURE__ */ jsxs("div", { className: "sigil-simulation-empty", children: [
2062
+ /* @__PURE__ */ jsx("p", { children: "No simulation results yet." }),
2063
+ /* @__PURE__ */ jsx("p", { children: "Enter transaction details above to simulate." })
2064
+ ] })
2065
+ ] });
2066
+ }
2067
+ function formatValue(value) {
2068
+ if (value === void 0)
2069
+ return "undefined";
2070
+ if (value === null)
2071
+ return "null";
2072
+ if (typeof value === "bigint")
2073
+ return `${value.toString()}n`;
2074
+ if (typeof value === "object") {
2075
+ try {
2076
+ return JSON.stringify(value, null, 2);
2077
+ } catch {
2078
+ return "[Object]";
2079
+ }
2080
+ }
2081
+ return String(value);
2082
+ }
2083
+ function valuesEqual(a, b) {
2084
+ if (a === b)
2085
+ return true;
2086
+ if (typeof a === "bigint" && typeof b === "bigint")
2087
+ return a === b;
2088
+ if (typeof a === "object" && typeof b === "object") {
2089
+ try {
2090
+ return JSON.stringify(a) === JSON.stringify(b);
2091
+ } catch {
2092
+ return false;
2093
+ }
2094
+ }
2095
+ return false;
2096
+ }
2097
+ function getSourceColor(source) {
2098
+ switch (source) {
2099
+ case "on-chain":
2100
+ return "sigil-source-onchain";
2101
+ case "indexed":
2102
+ return "sigil-source-indexed";
2103
+ case "cache":
2104
+ return "sigil-source-cache";
2105
+ case "local":
2106
+ return "sigil-source-local";
2107
+ default:
2108
+ return "sigil-source-unknown";
2109
+ }
2110
+ }
2111
+ function getChangeColor(type) {
2112
+ switch (type) {
2113
+ case "added":
2114
+ return "sigil-change-added";
2115
+ case "removed":
2116
+ return "sigil-change-removed";
2117
+ case "modified":
2118
+ return "sigil-change-modified";
2119
+ default:
2120
+ return "sigil-change-unchanged";
2121
+ }
2122
+ }
2123
+ function ComparisonRow({ item }) {
2124
+ const [expanded, setExpanded] = useState(false);
2125
+ const leftValue = item.left ? formatValue(item.left.value) : "\u2014";
2126
+ const rightValue = item.right ? formatValue(item.right.value) : "\u2014";
2127
+ const isLongValue = leftValue.length > 50 || rightValue.length > 50;
2128
+ return /* @__PURE__ */ jsxs("div", { className: `sigil-comparison-row ${item.isDifferent ? "different" : ""}`, children: [
2129
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-path", children: [
2130
+ /* @__PURE__ */ jsx("code", { children: item.path }),
2131
+ /* @__PURE__ */ jsx("span", { className: `sigil-comparison-badge ${getChangeColor(item.changeType)}`, children: item.changeType })
2132
+ ] }),
2133
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-values", children: [
2134
+ /* @__PURE__ */ jsx("div", { className: "sigil-comparison-value sigil-comparison-left", children: item.left ? /* @__PURE__ */ jsxs(Fragment, { children: [
2135
+ /* @__PURE__ */ jsx("span", { className: `sigil-comparison-source ${getSourceColor(item.left.source)}`, children: item.left.source }),
2136
+ isLongValue ? /* @__PURE__ */ jsx(
2137
+ "button",
2138
+ {
2139
+ className: "sigil-comparison-expand",
2140
+ onClick: () => setExpanded(!expanded),
2141
+ children: expanded ? "Collapse" : "Expand"
2142
+ }
2143
+ ) : /* @__PURE__ */ jsx("code", { className: "sigil-comparison-value-text", children: leftValue })
2144
+ ] }) : /* @__PURE__ */ jsx("span", { className: "sigil-comparison-empty", children: "\u2014" }) }),
2145
+ /* @__PURE__ */ jsx("div", { className: "sigil-comparison-value sigil-comparison-right", children: item.right ? /* @__PURE__ */ jsxs(Fragment, { children: [
2146
+ /* @__PURE__ */ jsx("span", { className: `sigil-comparison-source ${getSourceColor(item.right.source)}`, children: item.right.source }),
2147
+ isLongValue ? /* @__PURE__ */ jsx(
2148
+ "button",
2149
+ {
2150
+ className: "sigil-comparison-expand",
2151
+ onClick: () => setExpanded(!expanded),
2152
+ children: expanded ? "Collapse" : "Expand"
2153
+ }
2154
+ ) : /* @__PURE__ */ jsx("code", { className: "sigil-comparison-value-text", children: rightValue })
2155
+ ] }) : /* @__PURE__ */ jsx("span", { className: "sigil-comparison-empty", children: "\u2014" }) })
2156
+ ] }),
2157
+ expanded && isLongValue && /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-expanded", children: [
2158
+ /* @__PURE__ */ jsx("div", { className: "sigil-comparison-expanded-left", children: /* @__PURE__ */ jsx("pre", { children: leftValue }) }),
2159
+ /* @__PURE__ */ jsx("div", { className: "sigil-comparison-expanded-right", children: /* @__PURE__ */ jsx("pre", { children: rightValue }) })
2160
+ ] })
2161
+ ] });
2162
+ }
2163
+ function StateComparison({
2164
+ leftSnapshot,
2165
+ rightSnapshot,
2166
+ leftLabel = "Before",
2167
+ rightLabel = "After",
2168
+ showOnlyDifferences = false,
2169
+ filterSource = null,
2170
+ onExport
2171
+ }) {
2172
+ const [filter, setFilter] = useState("");
2173
+ const [onlyDiff, setOnlyDiff] = useState(showOnlyDifferences);
2174
+ const [sourceFilter, setSourceFilter] = useState(filterSource);
2175
+ const comparisonItems = useMemo(() => {
2176
+ const items = [];
2177
+ const allPaths = /* @__PURE__ */ new Set();
2178
+ if (leftSnapshot) {
2179
+ Object.keys(leftSnapshot.values).forEach((path) => allPaths.add(path));
2180
+ }
2181
+ if (rightSnapshot) {
2182
+ Object.keys(rightSnapshot.values).forEach((path) => allPaths.add(path));
2183
+ }
2184
+ for (const path of allPaths) {
2185
+ const left = leftSnapshot?.values[path];
2186
+ const right = rightSnapshot?.values[path];
2187
+ let changeType = "unchanged";
2188
+ let isDifferent = false;
2189
+ if (left && !right) {
2190
+ changeType = "removed";
2191
+ isDifferent = true;
2192
+ } else if (!left && right) {
2193
+ changeType = "added";
2194
+ isDifferent = true;
2195
+ } else if (left && right && !valuesEqual(left.value, right.value)) {
2196
+ changeType = "modified";
2197
+ isDifferent = true;
2198
+ }
2199
+ items.push({
2200
+ path,
2201
+ left,
2202
+ right,
2203
+ isDifferent,
2204
+ changeType
2205
+ });
2206
+ }
2207
+ items.sort((a, b) => a.path.localeCompare(b.path));
2208
+ return items;
2209
+ }, [leftSnapshot, rightSnapshot]);
2210
+ const filteredItems = useMemo(() => {
2211
+ return comparisonItems.filter((item) => {
2212
+ if (filter && !item.path.toLowerCase().includes(filter.toLowerCase())) {
2213
+ return false;
2214
+ }
2215
+ if (onlyDiff && !item.isDifferent) {
2216
+ return false;
2217
+ }
2218
+ if (sourceFilter) {
2219
+ const leftMatch = item.left?.source === sourceFilter;
2220
+ const rightMatch = item.right?.source === sourceFilter;
2221
+ if (!leftMatch && !rightMatch) {
2222
+ return false;
2223
+ }
2224
+ }
2225
+ return true;
2226
+ });
2227
+ }, [comparisonItems, filter, onlyDiff, sourceFilter]);
2228
+ const stats = useMemo(() => {
2229
+ const added = comparisonItems.filter((i) => i.changeType === "added").length;
2230
+ const removed = comparisonItems.filter((i) => i.changeType === "removed").length;
2231
+ const modified = comparisonItems.filter((i) => i.changeType === "modified").length;
2232
+ const unchanged = comparisonItems.filter((i) => i.changeType === "unchanged").length;
2233
+ return { added, removed, modified, unchanged, total: comparisonItems.length };
2234
+ }, [comparisonItems]);
2235
+ const handleExport = useCallback(() => {
2236
+ if (onExport) {
2237
+ onExport(filteredItems);
2238
+ } else {
2239
+ const blob = new Blob([JSON.stringify(filteredItems, null, 2)], {
2240
+ type: "application/json"
2241
+ });
2242
+ const url = URL.createObjectURL(blob);
2243
+ const a = document.createElement("a");
2244
+ a.href = url;
2245
+ a.download = `state-comparison-${Date.now()}.json`;
2246
+ a.click();
2247
+ URL.revokeObjectURL(url);
2248
+ }
2249
+ }, [filteredItems, onExport]);
2250
+ return /* @__PURE__ */ jsxs("div", { className: "sigil-state-comparison", children: [
2251
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-header", children: [
2252
+ /* @__PURE__ */ jsx("h3", { children: "State Comparison" }),
2253
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-stats", children: [
2254
+ /* @__PURE__ */ jsxs("span", { className: "sigil-stat sigil-stat-added", children: [
2255
+ "+",
2256
+ stats.added
2257
+ ] }),
2258
+ /* @__PURE__ */ jsxs("span", { className: "sigil-stat sigil-stat-removed", children: [
2259
+ "-",
2260
+ stats.removed
2261
+ ] }),
2262
+ /* @__PURE__ */ jsxs("span", { className: "sigil-stat sigil-stat-modified", children: [
2263
+ "~",
2264
+ stats.modified
2265
+ ] }),
2266
+ /* @__PURE__ */ jsxs("span", { className: "sigil-stat sigil-stat-unchanged", children: [
2267
+ "=",
2268
+ stats.unchanged
2269
+ ] })
2270
+ ] })
2271
+ ] }),
2272
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-snapshots", children: [
2273
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-snapshot-left", children: [
2274
+ /* @__PURE__ */ jsx("span", { className: "sigil-comparison-label", children: leftLabel }),
2275
+ leftSnapshot && /* @__PURE__ */ jsx("span", { className: "sigil-comparison-time", children: new Date(leftSnapshot.timestamp).toLocaleTimeString() })
2276
+ ] }),
2277
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-snapshot-right", children: [
2278
+ /* @__PURE__ */ jsx("span", { className: "sigil-comparison-label", children: rightLabel }),
2279
+ rightSnapshot && /* @__PURE__ */ jsx("span", { className: "sigil-comparison-time", children: new Date(rightSnapshot.timestamp).toLocaleTimeString() })
2280
+ ] })
2281
+ ] }),
2282
+ /* @__PURE__ */ jsxs("div", { className: "sigil-comparison-filters", children: [
2283
+ /* @__PURE__ */ jsx(
2284
+ "input",
2285
+ {
2286
+ type: "text",
2287
+ value: filter,
2288
+ onChange: (e) => setFilter(e.target.value),
2289
+ placeholder: "Filter by path...",
2290
+ className: "sigil-comparison-filter-input"
2291
+ }
2292
+ ),
2293
+ /* @__PURE__ */ jsxs("label", { className: "sigil-comparison-filter-checkbox", children: [
2294
+ /* @__PURE__ */ jsx(
2295
+ "input",
2296
+ {
2297
+ type: "checkbox",
2298
+ checked: onlyDiff,
2299
+ onChange: (e) => setOnlyDiff(e.target.checked)
2300
+ }
2301
+ ),
2302
+ "Only differences"
2303
+ ] }),
2304
+ /* @__PURE__ */ jsxs(
2305
+ "select",
2306
+ {
2307
+ value: sourceFilter ?? "",
2308
+ onChange: (e) => setSourceFilter(e.target.value || null),
2309
+ className: "sigil-comparison-filter-select",
2310
+ children: [
2311
+ /* @__PURE__ */ jsx("option", { value: "", children: "All sources" }),
2312
+ /* @__PURE__ */ jsx("option", { value: "on-chain", children: "On-chain" }),
2313
+ /* @__PURE__ */ jsx("option", { value: "indexed", children: "Indexed" }),
2314
+ /* @__PURE__ */ jsx("option", { value: "cache", children: "Cache" }),
2315
+ /* @__PURE__ */ jsx("option", { value: "local", children: "Local" })
2316
+ ]
2317
+ }
2318
+ ),
2319
+ /* @__PURE__ */ jsx("button", { onClick: handleExport, className: "sigil-comparison-export", children: "Export JSON" })
2320
+ ] }),
2321
+ /* @__PURE__ */ jsx("div", { className: "sigil-comparison-items", children: filteredItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "sigil-comparison-empty-state", children: comparisonItems.length === 0 ? "No state to compare. Capture snapshots first." : "No items match the current filters." }) : filteredItems.map((item) => /* @__PURE__ */ jsx(ComparisonRow, { item }, item.path)) })
2322
+ ] });
2323
+ }
2324
+ function useStateSnapshots() {
2325
+ const [snapshots, setSnapshots] = useState([]);
2326
+ const [leftId, setLeftId] = useState(null);
2327
+ const [rightId, setRightId] = useState(null);
2328
+ const captureSnapshot = useCallback((label, values) => {
2329
+ const snapshot = {
2330
+ id: `snap-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
2331
+ label,
2332
+ timestamp: Date.now(),
2333
+ values
2334
+ };
2335
+ setSnapshots((prev) => [...prev, snapshot]);
2336
+ return snapshot;
2337
+ }, []);
2338
+ const deleteSnapshot = useCallback((id) => {
2339
+ setSnapshots((prev) => prev.filter((s) => s.id !== id));
2340
+ if (leftId === id)
2341
+ setLeftId(null);
2342
+ if (rightId === id)
2343
+ setRightId(null);
2344
+ }, [leftId, rightId]);
2345
+ const clearSnapshots = useCallback(() => {
2346
+ setSnapshots([]);
2347
+ setLeftId(null);
2348
+ setRightId(null);
2349
+ }, []);
2350
+ const leftSnapshot = snapshots.find((s) => s.id === leftId) ?? null;
2351
+ const rightSnapshot = snapshots.find((s) => s.id === rightId) ?? null;
2352
+ return {
2353
+ snapshots,
2354
+ leftSnapshot,
2355
+ rightSnapshot,
2356
+ leftId,
2357
+ rightId,
2358
+ setLeftId,
2359
+ setRightId,
2360
+ captureSnapshot,
2361
+ deleteSnapshot,
2362
+ clearSnapshots
2363
+ };
2364
+ }
2365
+
2366
+ export { DevToolbar, DevToolbarProvider, DevToolbarTrigger, DevToolbarWithTrigger, DiagnosticPanel, IPCClient, LensActiveBadge, LocalStorageTransport, MockTransport, SimulationPanel, StateComparison, UserLens, createAnvilForkService, createForkService, createSimulationService, createTenderlyForkService, getForkService, getIPCClient, getSimulationService, resetForkService, resetIPCClient, resetSimulationService, useDevToolbar, useDevToolbarConfig, useDevToolbarSelector, useDiagnosticItems, useForkState, useIPCClient, useImpersonatedAddress, useIsImpersonating, useLensAwareAccount, useSavedAddresses, useSimulation, useStateSnapshots, useTransactionSimulation };
2367
+ //# sourceMappingURL=out.js.map
2368
+ //# sourceMappingURL=index.js.map