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