@hardkas/react 0.7.13-alpha → 0.8.1-alpha

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 CHANGED
@@ -1,1552 +1,96 @@
1
1
  // src/provider.tsx
2
- import React, { createContext, useContext, useMemo } from "react";
3
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
- import { createPublicClient, http } from "viem";
5
- import { jsx, jsxs } from "react/jsx-runtime";
6
- var HardKasContext = createContext(void 0);
7
- function HardKasProvider({
8
- config,
9
- children,
10
- queryClient: externalQueryClient
11
- }) {
12
- const queryClient = useMemo(
13
- () => externalQueryClient ?? new QueryClient(),
14
- [externalQueryClient]
15
- );
16
- const [sseStatus, setSseStatus] = React.useState("disconnected");
17
- const [projectionStatus, setProjectionStatus] = React.useState("synced");
18
- const [generationId, setGenerationId] = React.useState(null);
19
- const [lastSyncedAt, setLastSyncedAt] = React.useState(null);
20
- const [lastEvent, setLastEvent] = React.useState(null);
21
- const listeners = React.useRef(/* @__PURE__ */ new Set());
22
- const eventSource = React.useRef(null);
23
- const reconnectTimer = React.useRef(null);
24
- const backoffMs = React.useRef(500);
25
- const [providers, setProviders] = React.useState([]);
26
- const [activeProvider, setActiveProvider] = React.useState(null);
27
- const [walletAddress, setWalletAddress] = React.useState(null);
28
- const [walletChainId, setWalletChainId] = React.useState(null);
29
- const devToken = React.useMemo(() => {
30
- if (typeof window !== "undefined") {
31
- if (window.__HARDKAS_DEV_TOKEN__) {
32
- return window.__HARDKAS_DEV_TOKEN__;
33
- }
34
- const params = new URLSearchParams(window.location.search);
35
- return params.get("token") || "";
36
- }
37
- return "";
38
- }, []);
39
- const [tokenMissing, setTokenMissing] = React.useState(false);
40
- React.useEffect(() => {
41
- if (typeof window !== "undefined") {
42
- const isTest = typeof globalThis.vi !== "undefined" || window.__MOCK_SSE__ || process.env.NODE_ENV === "test";
43
- const isDashboard = window.location.pathname.startsWith("/") || window.location.port === "7420" || window.location.port === "5173";
44
- if (isDashboard && !isTest && !devToken) {
45
- setTokenMissing(true);
46
- }
47
- }
48
- }, [devToken]);
49
- const subscribe = React.useCallback((callback) => {
50
- listeners.current.add(callback);
51
- return () => listeners.current.delete(callback);
52
- }, []);
53
- const apiFetch = React.useCallback(
54
- async (input, init) => {
55
- const headers = new Headers(init?.headers || {});
56
- if (devToken) {
57
- headers.set("Authorization", `Bearer ${devToken}`);
58
- }
59
- const method = init?.method?.toUpperCase() || "GET";
60
- if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
61
- headers.set("X-Hardkas-Request", "true");
62
- }
63
- const mergedInit = {
64
- ...init,
65
- headers
66
- };
67
- const response = await fetch(input, mergedInit);
68
- const genHeader = response.headers?.get?.("X-Hardkas-Generation");
69
- if (genHeader) {
70
- setGenerationId((prev) => {
71
- if (prev !== genHeader) {
72
- setTimeout(() => {
73
- if (queryClient) queryClient.invalidateQueries();
74
- }, 0);
75
- return genHeader;
76
- }
77
- return prev;
78
- });
79
- }
80
- return response;
81
- },
82
- [queryClient, devToken]
83
- );
84
- const connect = React.useCallback(() => {
85
- if (typeof window === "undefined") return;
86
- if (!config.devServerUrl) {
87
- setSseStatus("disconnected");
88
- return;
89
- }
90
- if (typeof EventSource === "undefined") {
91
- setSseStatus("failed");
92
- return;
93
- }
94
- if (eventSource.current) {
95
- eventSource.current.close();
96
- }
97
- setSseStatus("connecting");
98
- const baseUrl = config.devServerUrl;
99
- let url = baseUrl.endsWith("/") ? `${baseUrl}api/stream` : `${baseUrl}/api/stream`;
100
- if (devToken) {
101
- url += (url.includes("?") ? "&" : "?") + `token=${encodeURIComponent(devToken)}`;
102
- }
103
- const es = new EventSource(url);
104
- eventSource.current = es;
105
- if (typeof window !== "undefined") {
106
- window.__MOCK_SSE_CLOSE__ = () => {
107
- es.close();
108
- if (es.onerror) {
109
- es.onerror.call(es, new Event("error"));
110
- }
111
- };
112
- }
113
- es.onopen = () => {
114
- setSseStatus("connected");
115
- backoffMs.current = 500;
116
- };
117
- es.onerror = () => {
118
- es.close();
119
- eventSource.current = null;
120
- setSseStatus("reconnecting");
121
- const nextBackoff = Math.min(backoffMs.current * 2, 1e4);
122
- backoffMs.current = nextBackoff;
123
- reconnectTimer.current = setTimeout(() => {
124
- connect();
125
- }, nextBackoff);
126
- };
127
- es.onmessage = (e) => {
128
- const event = {
129
- type: "message",
130
- payload: e.data,
131
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
132
- };
133
- setLastEvent(event);
134
- listeners.current.forEach((l) => l(event));
135
- };
136
- const namedEvents = [
137
- "session-changed",
138
- "session-created",
139
- "session-deleted",
140
- "health-changed",
141
- "sandbox-session-created",
142
- "sandbox-session-paired",
143
- "sandbox-session-expired",
144
- "sandbox-session-disconnected",
145
- "projection-stale",
146
- "projection-synced",
147
- "query-synced",
148
- "ping",
149
- "heartbeat"
150
- ];
151
- namedEvents.forEach((type) => {
152
- es.addEventListener(type, (e) => {
153
- const event = {
154
- type,
155
- payload: e.data ? JSON.parse(e.data) : void 0,
156
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
157
- };
158
- setLastEvent(event);
159
- listeners.current.forEach((l) => l(event));
160
- if (type === "projection-stale") {
161
- setProjectionStatus("stale");
162
- }
163
- if (type === "projection-synced" || type === "query-synced") {
164
- setProjectionStatus("synced");
165
- setLastSyncedAt((/* @__PURE__ */ new Date()).toISOString());
166
- if (event.payload?.generationId) {
167
- setGenerationId(event.payload.generationId);
168
- }
169
- queryClient.invalidateQueries();
170
- }
171
- });
172
- });
173
- }, [config.devServerUrl, queryClient, devToken]);
174
- React.useEffect(() => {
175
- connect();
176
- return () => {
177
- if (eventSource.current) {
178
- eventSource.current.close();
179
- eventSource.current = null;
180
- }
181
- if (reconnectTimer.current) {
182
- clearTimeout(reconnectTimer.current);
183
- }
184
- };
185
- }, [connect]);
186
- React.useEffect(() => {
187
- if (typeof window === "undefined") return;
188
- const handleAnnounce = (event) => {
189
- const detail = event.detail;
190
- if (!detail || !detail.info || !detail.provider) return;
191
- setProviders((prev) => {
192
- if (prev.some((p) => p.info.rdns === detail.info.rdns)) return prev;
193
- return [...prev, detail];
194
- });
195
- };
196
- window.addEventListener("eip6963:announceProvider", handleAnnounce);
197
- window.dispatchEvent(new Event("eip6963:requestProvider"));
198
- return () => {
199
- window.removeEventListener("eip6963:announceProvider", handleAnnounce);
200
- };
201
- }, []);
202
- const connectWallet = React.useCallback(async (detail) => {
203
- try {
204
- const accounts = await detail.provider.request({ method: "eth_requestAccounts" });
205
- const chainIdHex = await detail.provider.request({ method: "eth_chainId" });
206
- const chainId = typeof chainIdHex === "string" ? parseInt(chainIdHex, 16) : Number(chainIdHex);
207
- setActiveProvider(detail);
208
- if (accounts && accounts[0]) {
209
- setWalletAddress(accounts[0]);
210
- }
211
- setWalletChainId(chainId);
212
- if (typeof window !== "undefined") {
213
- window.localStorage.setItem("hardkas:active-wallet", detail.info.rdns);
214
- }
215
- } catch (err) {
216
- console.error("Failed to connect wallet:", err);
217
- throw err;
218
- }
219
- }, []);
220
- const disconnectWallet = React.useCallback(() => {
221
- setActiveProvider(null);
222
- setWalletAddress(null);
223
- setWalletChainId(null);
224
- if (typeof window !== "undefined") {
225
- window.localStorage.removeItem("hardkas:active-wallet");
226
- }
227
- }, []);
228
- const switchChain = React.useCallback(
229
- async (targetChainId) => {
230
- if (!activeProvider) {
231
- throw new Error("No active wallet connected");
232
- }
233
- const hexChainId = `0x${targetChainId.toString(16)}`;
234
- try {
235
- await activeProvider.provider.request({
236
- method: "wallet_switchEthereumChain",
237
- params: [{ chainId: hexChainId }]
238
- });
239
- setWalletChainId(targetChainId);
240
- } catch (err) {
241
- if (err.code === 4902) {
242
- if (targetChainId === 19416) {
243
- await activeProvider.provider.request({
244
- method: "wallet_addEthereumChain",
245
- params: [
246
- {
247
- chainId: hexChainId,
248
- chainName: "Igra Local",
249
- nativeCurrency: { name: "Igra Kaspa", symbol: "iKAS", decimals: 18 },
250
- rpcUrls: [config.igraRpcUrl || "http://127.0.0.1:8545"]
251
- }
252
- ]
253
- });
254
- setWalletChainId(targetChainId);
255
- } else {
256
- throw err;
257
- }
258
- } else {
259
- throw err;
260
- }
261
- }
262
- },
263
- [activeProvider, config.igraRpcUrl]
264
- );
265
- React.useEffect(() => {
266
- if (typeof window === "undefined" || providers.length === 0 || activeProvider) return;
267
- const savedRdns = window.localStorage.getItem("hardkas:active-wallet");
268
- if (savedRdns) {
269
- const match = providers.find((p) => p.info.rdns === savedRdns);
270
- if (match) {
271
- connectWallet(match).catch(() => {
272
- });
273
- }
274
- }
275
- }, [providers, activeProvider, connectWallet]);
276
- React.useEffect(() => {
277
- if (!activeProvider) {
278
- setWalletAddress(null);
279
- setWalletChainId(null);
280
- return;
281
- }
282
- const provider = activeProvider.provider;
283
- const handleAccountsChanged = (accounts) => {
284
- if (accounts && accounts[0]) {
285
- setWalletAddress(accounts[0]);
286
- } else {
287
- setActiveProvider(null);
288
- setWalletAddress(null);
289
- setWalletChainId(null);
290
- if (typeof window !== "undefined") {
291
- window.localStorage.removeItem("hardkas:active-wallet");
292
- }
293
- }
294
- };
295
- const handleChainChanged = (chainIdHex) => {
296
- const chainId = typeof chainIdHex === "string" ? parseInt(chainIdHex, 16) : Number(chainIdHex);
297
- setWalletChainId(chainId);
298
- };
299
- const handleDisconnect = () => {
300
- setActiveProvider(null);
301
- setWalletAddress(null);
302
- setWalletChainId(null);
303
- if (typeof window !== "undefined") {
304
- window.localStorage.removeItem("hardkas:active-wallet");
305
- }
306
- };
307
- if (provider.on) {
308
- provider.on("accountsChanged", handleAccountsChanged);
309
- provider.on("chainChanged", handleChainChanged);
310
- provider.on("disconnect", handleDisconnect);
311
- }
312
- return () => {
313
- if (provider.removeListener) {
314
- provider.removeListener("accountsChanged", handleAccountsChanged);
315
- provider.removeListener("chainChanged", handleChainChanged);
316
- provider.removeListener("disconnect", handleDisconnect);
317
- }
318
- };
319
- }, [activeProvider]);
320
- const igraClient = useMemo(() => {
321
- return createPublicClient({
322
- chain: {
323
- id: 19416,
324
- // Igra Local Default
325
- name: "Igra Local",
326
- nativeCurrency: { name: "Igra Kaspa", symbol: "iKAS", decimals: 18 },
327
- rpcUrls: {
328
- default: { http: [config.igraRpcUrl || "http://127.0.0.1:8545"] }
329
- }
330
- },
331
- transport: http(config.igraRpcUrl || "http://127.0.0.1:8545", { retryCount: 0 })
332
- });
333
- }, [config.igraRpcUrl]);
334
- const value = useMemo(
335
- () => ({
336
- config: {
337
- ...config,
338
- localOnly: config.localOnly ?? true
339
- },
340
- igraClient,
341
- queryClient,
342
- sseStatus,
343
- projectionStatus,
344
- generationId,
345
- lastSyncedAt,
346
- apiFetch,
347
- lastEvent,
348
- subscribe,
349
- providers,
350
- activeProvider,
351
- walletAddress,
352
- walletChainId,
353
- connectWallet,
354
- disconnectWallet,
355
- switchChain
356
- }),
357
- [
358
- config,
359
- igraClient,
360
- queryClient,
361
- sseStatus,
362
- projectionStatus,
363
- generationId,
364
- lastSyncedAt,
365
- apiFetch,
366
- lastEvent,
367
- subscribe,
368
- providers,
369
- activeProvider,
370
- walletAddress,
371
- walletChainId,
372
- connectWallet,
373
- disconnectWallet,
374
- switchChain
375
- ]
376
- );
377
- return /* @__PURE__ */ jsx(HardKasContext.Provider, { value, children: /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: tokenMissing ? /* @__PURE__ */ jsx(
378
- "div",
379
- {
380
- style: {
381
- display: "flex",
382
- flexDirection: "column",
383
- alignItems: "center",
384
- justifyContent: "center",
385
- height: "100vh",
386
- width: "100vw",
387
- backgroundColor: "#1a1a1a",
388
- color: "#f87171",
389
- fontFamily: "sans-serif",
390
- textAlign: "center",
391
- padding: "20px"
392
- },
393
- children: /* @__PURE__ */ jsxs(
394
- "div",
395
- {
396
- style: {
397
- backgroundColor: "#2d2d2d",
398
- border: "2px solid #ef4444",
399
- borderRadius: "8px",
400
- padding: "30px",
401
- maxWidth: "450px",
402
- boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.5)"
403
- },
404
- children: [
405
- /* @__PURE__ */ jsx("h2", { style: { color: "#ef4444", marginTop: 0 }, children: "Dashboard authentication token missing." }),
406
- /* @__PURE__ */ jsx("p", { style: { color: "#d1d5db", lineHeight: "1.5" }, children: "The dev-server is secured against local workstation CSRF and DNS rebinding attacks." }),
407
- /* @__PURE__ */ jsx("p", { style: { color: "#9ca3af", fontWeight: "bold" }, children: "Restart hardkas dashboard." })
408
- ]
409
- }
410
- )
411
- }
412
- ) : children }) });
413
- }
414
- function useHardKas() {
415
- const context = useContext(HardKasContext);
2
+ import { createContext, useContext, useMemo } from "react";
3
+ import { createClient } from "@hardkas/client";
4
+ import { jsx } from "react/jsx-runtime";
5
+ var HardKASContext = createContext(null);
6
+ var HardKASProvider = ({ children, baseUrl, timeout }) => {
7
+ const client = useMemo(() => {
8
+ const config = {};
9
+ if (baseUrl !== void 0) config.baseUrl = baseUrl;
10
+ if (timeout !== void 0) config.timeout = timeout;
11
+ return createClient(config);
12
+ }, [baseUrl, timeout]);
13
+ return /* @__PURE__ */ jsx(HardKASContext.Provider, { value: client, children });
14
+ };
15
+ var useHardKAS = () => {
16
+ const context = useContext(HardKASContext);
416
17
  if (!context) {
417
- throw new Error("useHardKas must be used within a HardKasProvider");
18
+ throw new Error("useHardKAS must be used within a HardKASProvider");
418
19
  }
419
20
  return context;
420
- }
421
-
422
- // src/hooks/session.ts
423
- import { useQuery, useQueryClient } from "@tanstack/react-query";
424
- import { useEffect } from "react";
425
- function useHardKasSession(name) {
426
- const { config, subscribe, apiFetch } = useHardKas();
427
- const queryClient = useQueryClient();
428
- const sessionToResolve = name || config.sessionName;
429
- useEffect(() => {
430
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "session"] });
431
- return subscribe((event) => {
432
- if (["session-changed", "session-created", "session-deleted"].includes(event.type)) {
433
- sync();
434
- }
435
- });
436
- }, [queryClient, subscribe]);
437
- return useQuery({
438
- queryKey: ["hardkas", "session", sessionToResolve || "active"],
439
- queryFn: async () => {
440
- try {
441
- const baseUrl = config.devServerUrl || "";
442
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/session` : `${baseUrl}/api/session` : "/api/session";
443
- const response = await apiFetch(url);
444
- const json = await response.json();
445
- const active = json.active;
446
- if (!active) return null;
447
- return {
448
- name: active.name,
449
- l1: { wallet: active.l1?.wallet, address: active.l1?.address },
450
- l2: { account: active.l2?.account, address: active.l2?.address },
451
- bridge: { mode: active.bridge?.mode },
452
- health: active.health || { isHealthy: true, warnings: [] },
453
- diagnostics: json.diagnostics || []
454
- };
455
- } catch (e) {
456
- console.error("Failed to fetch session from dev server:", e);
457
- return null;
458
- }
459
- },
460
- staleTime: 3e4
461
- // Rely on SSE for updates
462
- });
463
- }
464
-
465
- // src/hooks/health.ts
466
- import { useQuery as useQuery2, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
467
- import { useEffect as useEffect2 } from "react";
468
- function useHardKasHealth() {
469
- const { config, subscribe, apiFetch } = useHardKas();
470
- const queryClient = useQueryClient2();
471
- useEffect2(() => {
472
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "health"] });
473
- return subscribe((event) => {
474
- if (["health-changed", "session-changed"].includes(event.type)) {
475
- sync();
476
- }
477
- });
478
- }, [queryClient, subscribe]);
479
- return useQuery2({
480
- queryKey: ["hardkas", "health"],
481
- queryFn: async () => {
482
- try {
483
- const baseUrl = config.devServerUrl || "";
484
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/health` : `${baseUrl}/api/health` : "/api/health";
485
- const res = await apiFetch(url);
486
- if (!res || !res.ok) {
487
- throw new Error("Failed to fetch health from dev server");
488
- }
489
- return await res.json();
490
- } catch (e) {
491
- console.error("Failed to fetch health from dev server:", e);
492
- throw e;
493
- }
494
- },
495
- refetchInterval: 3e4,
496
- // Background poll as fallback
497
- retry: 2
498
- });
499
- }
500
-
501
- // src/hooks/kaspa.ts
502
- import { useQuery as useQuery3, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
503
- import { useEffect as useEffect3 } from "react";
504
- function useKaspaWallet() {
505
- const { data: session } = useHardKasSession();
506
- return {
507
- name: session?.l1.wallet,
508
- address: session?.l1.address,
509
- isLoading: !session
510
- };
511
- }
512
- function useKaspaBalance(options = {}) {
513
- const { config, subscribe, apiFetch } = useHardKas();
514
- const queryClient = useQueryClient3();
515
- const { address, name } = useKaspaWallet();
516
- const { data: health } = useHardKasHealth();
517
- useEffect3(() => {
518
- const sync = () => queryClient.invalidateQueries({ queryKey: ["kaspa", "balance"] });
519
- return subscribe((event) => {
520
- if (["query-synced", "session-changed"].includes(event.type)) {
521
- sync();
522
- }
523
- });
524
- }, [queryClient, subscribe]);
525
- const l1Status = health?.kaspa?.status || health?.l1?.status;
526
- const isL1Online = l1Status === "healthy" || l1Status === "simulated-mode" || l1Status === "ok" || l1Status === "running" || l1Status === "online" || !!address && address.startsWith("kaspa:sim_");
527
- return useQuery3({
528
- queryKey: ["kaspa", "balance", address, config.kaspaRpcUrl, name],
529
- queryFn: async () => {
530
- if (!address) return 0n;
531
- if (address.startsWith("kaspa:sim_") || l1Status === "simulated-mode") {
532
- try {
533
- const baseUrl = config.devServerUrl || "";
534
- const fetchUrl = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/accounts` : `${baseUrl}/api/accounts` : "/api/accounts";
535
- const res = await apiFetch(fetchUrl);
536
- if (res.ok) {
537
- const data = await res.json();
538
- const match = (data.accounts || []).find((a) => a.address === address);
539
- if (match) {
540
- return BigInt(match.balanceSompi || match.balance || "0");
541
- }
542
- }
543
- } catch (e) {
544
- console.warn(
545
- "Failed to fetch derived simulated balance, falling back to 0:",
546
- e
547
- );
548
- }
549
- return 0n;
550
- }
551
- let url = config.kaspaRpcUrl || "http://127.0.0.1:16110";
552
- if (url.includes("127.0.0.1:16110") || url.includes("localhost:16110")) {
553
- url = "ws://127.0.0.1:18210";
554
- } else if (url.includes("127.0.0.1:18210") || url.includes("localhost:18210")) {
555
- url = "ws://127.0.0.1:18210";
556
- }
557
- if (url.startsWith("ws://") || url.startsWith("wss://")) {
558
- try {
559
- return await new Promise((resolve, reject) => {
560
- const ws = new WebSocket(url);
561
- const timer = setTimeout(() => {
562
- ws.close();
563
- reject(new Error("WebSocket timeout"));
564
- }, 3e3);
565
- ws.onopen = () => {
566
- ws.send(
567
- JSON.stringify({
568
- id: 1,
569
- method: "getBalanceByAddressRequest",
570
- params: { address }
571
- })
572
- );
573
- };
574
- ws.onmessage = (event) => {
575
- clearTimeout(timer);
576
- try {
577
- const response = JSON.parse(event.data);
578
- ws.close();
579
- if (response.error) {
580
- reject(new Error(response.error.message));
581
- } else {
582
- const data = response.result !== void 0 ? response.result : response.params;
583
- resolve(BigInt(data?.balance || 0));
584
- }
585
- } catch (err) {
586
- ws.close();
587
- reject(err);
588
- }
589
- };
590
- ws.onerror = (err) => {
591
- clearTimeout(timer);
592
- ws.close();
593
- reject(err);
594
- };
595
- });
596
- } catch (e) {
597
- console.warn(
598
- "Failed to fetch Kaspa balance via WebSocket, falling back to 0:",
599
- e
600
- );
601
- return 0n;
602
- }
603
- }
604
- try {
605
- const response = await apiFetch(url, {
606
- method: "POST",
607
- headers: { "Content-Type": "application/json" },
608
- body: JSON.stringify({
609
- jsonrpc: "2.0",
610
- id: 1,
611
- method: "getBalanceByAddressRequest",
612
- params: { address }
613
- })
614
- });
615
- const json = await response.json();
616
- return BigInt(json.result?.balance || 0);
617
- } catch (e) {
618
- return 0n;
619
- }
620
- },
621
- enabled: !!address && isL1Online,
622
- refetchInterval: options.refetchInterval ?? false
623
- });
624
- }
625
-
626
- // src/hooks/igra.ts
627
- import { useQuery as useQuery4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
628
- import { useEffect as useEffect4 } from "react";
629
- function useIgraAccount() {
630
- const { data: session } = useHardKasSession();
631
- const { walletAddress, apiFetch } = useHardKas();
632
- const address = walletAddress || session?.l2.address;
633
- return {
634
- name: walletAddress ? "Browser Wallet" : session?.l2.account,
635
- address,
636
- isWallet: !!walletAddress,
637
- isLoading: !session && !walletAddress
638
- };
639
- }
640
- function useIgraWallet() {
641
- const {
642
- providers,
643
- activeProvider,
644
- walletAddress,
645
- walletChainId,
646
- connectWallet,
647
- disconnectWallet,
648
- switchChain,
649
- apiFetch
650
- } = useHardKas();
651
- return {
652
- providers,
653
- activeProvider,
654
- walletAddress,
655
- walletChainId,
656
- connectWallet,
657
- disconnectWallet,
658
- switchChain,
659
- isConnected: !!walletAddress
660
- };
661
- }
662
- function useIgraBalance(options = {}) {
663
- const { igraClient, config, subscribe, apiFetch } = useHardKas();
664
- const queryClient = useQueryClient4();
665
- const { address } = useIgraAccount();
666
- useEffect4(() => {
667
- const sync = () => queryClient.invalidateQueries({ queryKey: ["igra", "balance"] });
668
- return subscribe((event) => {
669
- if (["query-synced", "session-changed"].includes(event.type)) {
670
- sync();
671
- }
672
- });
673
- }, [queryClient, subscribe]);
674
- const { data: session } = useHardKasSession();
675
- const { data: health } = useHardKasHealth();
676
- const l2Status = health?.igra?.status || health?.l2?.status;
677
- const isL2Online = l2Status === "healthy" || l2Status === "ok" || l2Status === "running" || l2Status === "online" || !!address && (address.startsWith("0xsim_") || address.startsWith("kaspa:sim_") || !address.startsWith("0x"));
678
- return useQuery4({
679
- queryKey: ["igra", "balance", address, config.igraRpcUrl, session?.name],
680
- queryFn: async () => {
681
- if (!address) return 0n;
682
- if (address.startsWith("0xsim_") || !address.startsWith("0x") || l2Status === "simulated-mode") {
683
- try {
684
- const baseUrl = config.devServerUrl || "";
685
- const fetchUrl = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/accounts` : `${baseUrl}/api/accounts` : "/api/accounts";
686
- const res = await apiFetch(fetchUrl);
687
- if (res.ok) {
688
- const data = await res.json();
689
- const match = (data.accounts || []).find(
690
- (a) => a.address.toLowerCase() === address.toLowerCase() || a.name.toLowerCase() === address.toLowerCase()
691
- );
692
- if (match) {
693
- return BigInt(match.balanceSompi || match.balance || "0");
694
- }
695
- }
696
- } catch (e) {
697
- console.warn(
698
- "Failed to fetch derived simulated L2 balance, falling back to 0:",
699
- e
700
- );
701
- }
702
- return 0n;
703
- }
704
- try {
705
- return await igraClient.getBalance({ address });
706
- } catch (e) {
707
- return 0n;
708
- }
709
- },
710
- enabled: !!address && isL2Online,
711
- refetchInterval: options.refetchInterval ?? false
712
- });
713
- }
714
-
715
- // src/hooks/events.ts
716
- import { useQuery as useQuery5 } from "@tanstack/react-query";
717
- import { useEffect as useEffect5 } from "react";
718
- import { useQueryClient as useQueryClient5 } from "@tanstack/react-query";
719
- function useEvents(kind, txId) {
720
- const { config, subscribe, apiFetch } = useHardKas();
721
- const queryClient = useQueryClient5();
722
- const queryKey = ["hardkas", "events", kind, txId];
723
- useEffect5(() => {
724
- return subscribe((event) => {
725
- queryClient.invalidateQueries({ queryKey: ["hardkas", "events"] });
726
- });
727
- }, [queryClient, subscribe]);
728
- return useQuery5({
729
- queryKey,
730
- queryFn: async () => {
731
- try {
732
- const baseUrl = config.devServerUrl || "";
733
- const queryParams = new URLSearchParams();
734
- if (kind) queryParams.append("kind", kind);
735
- if (txId) queryParams.append("txId", txId);
736
- const queryStr = queryParams.toString() ? `?${queryParams.toString()}` : "";
737
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/events${queryStr}` : `${baseUrl}/api/events${queryStr}` : `/api/events${queryStr}`;
738
- const response = await apiFetch(url);
739
- if (!response.ok) return { events: [] };
740
- const data = await response.json();
741
- return {
742
- events: data.events || [],
743
- observabilityDrift: data.observabilityDrift,
744
- reason: data.reason
745
- };
746
- } catch (e) {
747
- console.error("Failed to fetch events from dev server:", e);
748
- return { events: [] };
749
- }
750
- },
751
- staleTime: 5e3
752
- });
753
- }
754
-
755
- // src/hooks/metamask.ts
756
- import { useState, useEffect as useEffect6, useCallback } from "react";
757
- import { createWalletClient, custom } from "viem";
758
- var getMetaMaskProvider = () => {
759
- if (typeof window === "undefined" || !window.ethereum) return null;
760
- if (window.ethereum.providers) {
761
- return window.ethereum.providers.find((p) => p.isMetaMask) || window.ethereum;
762
- }
763
- return window.ethereum;
764
21
  };
765
- function useMetaMaskLocal() {
766
- const [state, setState] = useState({
767
- installed: false,
768
- connected: false,
769
- supported: false,
770
- localIgraDetected: false,
771
- errors: []
772
- });
773
- const checkStatus = useCallback(async () => {
774
- const provider = getMetaMaskProvider();
775
- if (!provider) {
776
- setState((s) => ({ ...s, installed: false }));
777
- return;
778
- }
779
- try {
780
- const chainIdHex = await provider.request({ method: "eth_chainId" });
781
- const chainId = parseInt(chainIdHex, 16);
782
- const accounts = await provider.request({
783
- method: "eth_accounts"
784
- });
785
- setState({
786
- installed: true,
787
- connected: accounts.length > 0,
788
- supported: true,
789
- account: accounts[0],
790
- chainId,
791
- localIgraDetected: chainId === 19416,
792
- errors: []
793
- });
794
- } catch (e) {
795
- setState((s) => ({ ...s, errors: [e.message] }));
796
- }
797
- }, []);
798
- const connect = useCallback(async () => {
799
- const provider = getMetaMaskProvider();
800
- if (!provider) return;
801
- try {
802
- await provider.request({ method: "eth_requestAccounts" });
803
- await checkStatus();
804
- } catch (e) {
805
- setState((s) => ({ ...s, errors: [...s.errors, e.message] }));
806
- }
807
- }, [checkStatus]);
808
- useEffect6(() => {
809
- const provider = getMetaMaskProvider();
810
- if (!provider) return;
811
- checkStatus();
812
- const handleChange = () => checkStatus();
813
- provider.on("accountsChanged", handleChange);
814
- provider.on("chainChanged", handleChange);
815
- provider.on("disconnect", handleChange);
816
- return () => {
817
- if (provider.removeListener) {
818
- provider.removeListener("accountsChanged", handleChange);
819
- provider.removeListener("chainChanged", handleChange);
820
- provider.removeListener("disconnect", handleChange);
821
- }
822
- };
823
- }, [checkStatus]);
824
- return { state, refresh: checkStatus, connect };
825
- }
826
- function useSwitchToLocalIgra() {
827
- const switchChain = async () => {
828
- const provider = getMetaMaskProvider();
829
- if (!provider) return;
830
- try {
831
- await provider.request({
832
- method: "wallet_switchEthereumChain",
833
- params: [{ chainId: "0x4bd8" }]
834
- // 19416
835
- });
836
- } catch (e) {
837
- if (e.code === 4902) {
838
- await provider.request({
839
- method: "wallet_addEthereumChain",
840
- params: [
841
- {
842
- chainId: "0x4bd8",
843
- chainName: "HardKas Igra Local",
844
- rpcUrls: ["http://127.0.0.1:8545"],
845
- nativeCurrency: { name: "iKAS", symbol: "iKAS", decimals: 18 }
846
- }
847
- ]
848
- });
849
- }
850
- }
851
- };
852
- return { switchChain };
853
- }
854
- function useIgraInjectedAccount(sessionL2Address) {
855
- const { state } = useMetaMaskLocal();
856
- const matches = !!state.account && !!sessionL2Address && state.account.toLowerCase() === sessionL2Address.toLowerCase();
857
- return {
858
- injectedAddress: state.account,
859
- sessionAddress: sessionL2Address,
860
- matches
861
- };
862
- }
863
- function useLocalIgraWalletClient() {
864
- const { state } = useMetaMaskLocal();
865
- const getClient = useCallback(() => {
866
- const provider = getMetaMaskProvider();
867
- if (!state.connected || !provider) return null;
868
- return createWalletClient({
869
- account: state.account,
870
- transport: custom(provider)
871
- });
872
- }, [state.connected, state.account]);
873
- return { getClient };
874
- }
875
22
 
876
- // src/hooks/kasware.ts
877
- import { useState as useState2, useEffect as useEffect7, useCallback as useCallback2 } from "react";
878
- function useKasWareLocal() {
879
- const [state, setState] = useState2({
880
- installed: false,
881
- connected: false,
882
- supported: false,
883
- localNetworkDetected: false,
884
- errors: []
885
- });
886
- const checkStatus = useCallback2(async () => {
887
- if (typeof window === "undefined" || !window.kasware) {
888
- setState((s) => ({ ...s, installed: false }));
889
- return;
890
- }
23
+ // src/hooks/useQuery.ts
24
+ import { useState, useEffect, useCallback } from "react";
25
+ function useQuery(queryFn, deps = [], options = { enabled: true }) {
26
+ const client = useHardKAS();
27
+ const [data, setData] = useState(null);
28
+ const [error, setError] = useState(null);
29
+ const [loading, setLoading] = useState(false);
30
+ const refetch = useCallback(async () => {
31
+ setLoading(true);
32
+ setError(null);
891
33
  try {
892
- const provider = window.kasware;
893
- const accounts = await provider.getAccounts();
894
- const network = await provider.getNetwork();
895
- setState({
896
- installed: true,
897
- connected: accounts.length > 0,
898
- supported: true,
899
- address: accounts[0],
900
- network,
901
- // Common local/dev network strings for KasWare
902
- localNetworkDetected: ["kasparegtest", "localnet", "simnet"].includes(network),
903
- errors: []
904
- });
905
- } catch (e) {
906
- setState((s) => ({ ...s, errors: [e.message] }));
907
- }
908
- }, []);
909
- useEffect7(() => {
910
- if (typeof window === "undefined" || !window.kasware) return;
911
- const provider = window.kasware;
912
- checkStatus();
913
- const handleChange = () => checkStatus();
914
- provider.on("accountsChanged", handleChange);
915
- provider.on("networkChanged", handleChange);
916
- provider.on("disconnect", handleChange);
917
- return () => {
918
- if (provider.removeListener) {
919
- provider.removeListener("accountsChanged", handleChange);
920
- provider.removeListener("networkChanged", handleChange);
921
- provider.removeListener("disconnect", handleChange);
922
- }
923
- };
924
- }, [checkStatus]);
925
- return { state, refresh: checkStatus };
926
- }
927
- function useConnectKasWareLocal() {
928
- const { refresh } = useKasWareLocal();
929
- const connect = async () => {
930
- if (typeof window === "undefined" || !window.kasware) return null;
931
- try {
932
- const accounts = await window.kasware.requestAccounts();
933
- await refresh();
934
- return accounts[0];
935
- } catch (e) {
936
- console.error("KasWare connection failed:", e);
937
- return null;
938
- }
939
- };
940
- return { connect };
941
- }
942
- function useKasWareSessionMatch(sessionL1Address) {
943
- const { state } = useKasWareLocal();
944
- let matches = false;
945
- let reason;
946
- if (!state.installed) {
947
- reason = "not-installed";
948
- } else if (!state.connected) {
949
- reason = "not-connected";
950
- } else if (!sessionL1Address) {
951
- reason = "no-session";
952
- } else if (!state.localNetworkDetected) {
953
- reason = "network-mismatch";
954
- } else {
955
- const normalizedWallet = state.address?.toLowerCase().trim();
956
- const normalizedSession = sessionL1Address?.toLowerCase().trim();
957
- matches = normalizedWallet === normalizedSession;
958
- if (!matches) reason = "address-mismatch";
959
- }
960
- return {
961
- walletAddress: state.address,
962
- sessionAddress: sessionL1Address || void 0,
963
- matches,
964
- reason
965
- };
966
- }
967
-
968
- // src/hooks/sandbox.ts
969
- import { useQuery as useQuery6, useMutation, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
970
- import { useEffect as useEffect8 } from "react";
971
- function useSandboxSessions() {
972
- const { config, subscribe, apiFetch } = useHardKas();
973
- const queryClient = useQueryClient6();
974
- useEffect8(() => {
975
- const sync = () => queryClient.invalidateQueries({ queryKey: ["sandbox", "sessions"] });
976
- return subscribe((event) => {
977
- if ([
978
- "sandbox-session-created",
979
- "sandbox-session-paired",
980
- "sandbox-session-expired",
981
- "sandbox-session-disconnected"
982
- ].includes(event.type)) {
983
- sync();
34
+ const response = await queryFn(client);
35
+ if (response.ok) {
36
+ setData(response.data);
37
+ } else {
38
+ setError(response);
984
39
  }
985
- });
986
- }, [queryClient, subscribe]);
987
- return useQuery6({
988
- queryKey: ["sandbox", "sessions"],
989
- queryFn: async () => {
990
- const baseUrl = config.devServerUrl || "";
991
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/walletconnect/sandbox/sessions` : `${baseUrl}/api/walletconnect/sandbox/sessions` : "/api/walletconnect/sandbox/sessions";
992
- const res = await apiFetch(url);
993
- const json = await res.json();
994
- return json.sessions;
995
- }
996
- });
997
- }
998
- function useCreateSandboxSession() {
999
- const { config, apiFetch } = useHardKas();
1000
- const queryClient = useQueryClient6();
1001
- return useMutation({
1002
- mutationFn: async () => {
1003
- const baseUrl = config.devServerUrl || "";
1004
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/walletconnect/sandbox/create` : `${baseUrl}/api/walletconnect/sandbox/create` : "/api/walletconnect/sandbox/create";
1005
- const res = await apiFetch(url, { method: "POST" });
1006
- return await res.json();
1007
- },
1008
- onSuccess: () => {
1009
- queryClient.invalidateQueries({ queryKey: ["sandbox", "sessions"] });
1010
- }
1011
- });
1012
- }
1013
- function usePairSandboxSession() {
1014
- const { config, apiFetch } = useHardKas();
1015
- const queryClient = useQueryClient6();
1016
- return useMutation({
1017
- mutationFn: async (id) => {
1018
- const baseUrl = config.devServerUrl || "";
1019
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/walletconnect/sandbox/pair` : `${baseUrl}/api/walletconnect/sandbox/pair` : "/api/walletconnect/sandbox/pair";
1020
- const res = await apiFetch(url, {
1021
- method: "POST",
1022
- headers: { "Content-Type": "application/json" },
1023
- body: JSON.stringify({ id })
1024
- });
1025
- return await res.json();
1026
- },
1027
- onSuccess: () => {
1028
- queryClient.invalidateQueries({ queryKey: ["sandbox", "sessions"] });
1029
- }
1030
- });
1031
- }
1032
- function useDisconnectSandboxSession() {
1033
- const { config, apiFetch } = useHardKas();
1034
- const queryClient = useQueryClient6();
1035
- return useMutation({
1036
- mutationFn: async (id) => {
1037
- const baseUrl = config.devServerUrl || "";
1038
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/walletconnect/sandbox/disconnect` : `${baseUrl}/api/walletconnect/sandbox/disconnect` : "/api/walletconnect/sandbox/disconnect";
1039
- const res = await apiFetch(url, {
1040
- method: "POST",
1041
- headers: { "Content-Type": "application/json" },
1042
- body: JSON.stringify({ id })
1043
- });
1044
- return await res.json();
1045
- },
1046
- onSuccess: () => {
1047
- queryClient.invalidateQueries({ queryKey: ["sandbox", "sessions"] });
40
+ } catch (err) {
41
+ setError(err);
42
+ } finally {
43
+ setLoading(false);
1048
44
  }
1049
- });
1050
- }
1051
-
1052
- // src/hooks/contracts.ts
1053
- import { useQuery as useQuery7, useMutation as useMutation2 } from "@tanstack/react-query";
1054
- import { createWalletClient as createWalletClient2, custom as custom2 } from "viem";
1055
- function useIgraReadContract(options) {
1056
- const { igraClient, config } = useHardKas();
1057
- const { data: session } = useHardKasSession();
1058
- return useQuery7({
1059
- queryKey: [
1060
- "igra",
1061
- "read",
1062
- options.address,
1063
- options.functionName,
1064
- options.args,
1065
- config.igraRpcUrl,
1066
- session?.name
1067
- ],
1068
- queryFn: async () => {
1069
- return await igraClient.readContract({
1070
- address: options.address,
1071
- abi: options.abi,
1072
- functionName: options.functionName,
1073
- args: options.args
1074
- });
1075
- },
1076
- enabled: options.enabled ?? true,
1077
- refetchInterval: options.refetchInterval ?? false
1078
- });
1079
- }
1080
- function useIgraWriteContract() {
1081
- const { activeProvider, walletAddress, igraClient } = useHardKas();
1082
- return useMutation2({
1083
- mutationFn: async (params) => {
1084
- let client = params.walletClient;
1085
- if (!client) {
1086
- if (!activeProvider) {
1087
- throw new Error(
1088
- "No active browser wallet connected and no walletClient was provided."
1089
- );
1090
- }
1091
- if (!walletAddress) {
1092
- throw new Error("No active account address available on connected wallet.");
1093
- }
1094
- client = createWalletClient2({
1095
- account: walletAddress,
1096
- chain: igraClient.chain,
1097
- transport: custom2(activeProvider.provider)
1098
- });
45
+ }, [client, ...deps]);
46
+ useEffect(() => {
47
+ if (options.enabled) {
48
+ refetch();
49
+ }
50
+ }, [refetch, options.enabled]);
51
+ return { data, error, loading, refetch };
52
+ }
53
+
54
+ // src/hooks/useMutation.ts
55
+ import { useState as useState2, useCallback as useCallback2 } from "react";
56
+ function useMutation(mutationFn) {
57
+ const client = useHardKAS();
58
+ const [data, setData] = useState2(null);
59
+ const [error, setError] = useState2(null);
60
+ const [loading, setLoading] = useState2(false);
61
+ const execute = useCallback2(async (variables) => {
62
+ setLoading(true);
63
+ setError(null);
64
+ try {
65
+ const response = await mutationFn(client, variables);
66
+ if (response.ok) {
67
+ setData(response.data);
68
+ } else {
69
+ setError(response);
1099
70
  }
1100
- return await client.writeContract({
1101
- address: params.address,
1102
- abi: params.abi,
1103
- functionName: params.functionName,
1104
- args: params.args
1105
- });
1106
- }
1107
- });
1108
- }
1109
- function useIgraWaitForReceipt() {
1110
- const { igraClient } = useHardKas();
1111
- return useMutation2({
1112
- mutationFn: async (hash) => {
1113
- return await igraClient.waitForTransactionReceipt({ hash });
71
+ return response;
72
+ } catch (err) {
73
+ setError(err);
74
+ return { ok: false, code: "UNEXPECTED_ERROR", message: String(err) };
75
+ } finally {
76
+ setLoading(false);
1114
77
  }
1115
- });
1116
- }
1117
-
1118
- // src/hooks/overview.ts
1119
- import { useQuery as useQuery8, useQueryClient as useQueryClient7 } from "@tanstack/react-query";
1120
- import { useEffect as useEffect9 } from "react";
1121
- function useOverview() {
1122
- const { config, subscribe, apiFetch } = useHardKas();
1123
- const queryClient = useQueryClient7();
1124
- useEffect9(() => {
1125
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "overview"] });
1126
- return subscribe((event) => {
1127
- if (["query-synced", "session-changed"].includes(event.type)) {
1128
- sync();
1129
- }
1130
- });
1131
- }, [queryClient, subscribe]);
1132
- return useQuery8({
1133
- queryKey: ["hardkas", "overview"],
1134
- queryFn: async () => {
1135
- try {
1136
- const baseUrl = config.devServerUrl || "";
1137
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/overview` : `${baseUrl}/api/overview` : "/api/overview";
1138
- const response = await apiFetch(url);
1139
- if (!response.ok) return null;
1140
- return await response.json();
1141
- } catch (e) {
1142
- console.error("Failed to fetch overview stats from dev server:", e);
1143
- return null;
1144
- }
1145
- },
1146
- staleTime: 6e4
1147
- });
78
+ }, [client, mutationFn]);
79
+ return { data, error, loading, execute };
1148
80
  }
1149
81
 
1150
- // src/hooks/accounts.ts
1151
- import { useQuery as useQuery9, useQueryClient as useQueryClient8 } from "@tanstack/react-query";
1152
- import { useEffect as useEffect10 } from "react";
1153
- function useAccounts() {
1154
- const { config, subscribe, apiFetch } = useHardKas();
1155
- const queryClient = useQueryClient8();
1156
- useEffect10(() => {
1157
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "accounts"] });
1158
- return subscribe((event) => {
1159
- if (["query-synced", "session-changed"].includes(event.type)) {
1160
- sync();
1161
- }
1162
- });
1163
- }, [queryClient, subscribe]);
1164
- return useQuery9({
1165
- queryKey: ["hardkas", "accounts"],
1166
- queryFn: async () => {
1167
- try {
1168
- const baseUrl = config.devServerUrl || "";
1169
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/accounts` : `${baseUrl}/api/accounts` : "/api/accounts";
1170
- const response = await apiFetch(url);
1171
- if (!response.ok) return { accounts: [] };
1172
- const data = await response.json();
1173
- return data || { accounts: [] };
1174
- } catch (e) {
1175
- console.error("Failed to fetch accounts from dev server:", e);
1176
- return { accounts: [] };
1177
- }
1178
- },
1179
- staleTime: 3e4
1180
- });
1181
- }
1182
-
1183
- // src/hooks/transactions.ts
1184
- import { useQuery as useQuery10, useQueryClient as useQueryClient9 } from "@tanstack/react-query";
1185
- import { useEffect as useEffect11 } from "react";
1186
- function useTransactions() {
1187
- const { config, subscribe, apiFetch } = useHardKas();
1188
- const queryClient = useQueryClient9();
1189
- useEffect11(() => {
1190
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "transactions"] });
1191
- return subscribe((event) => {
1192
- if (["query-synced", "session-changed"].includes(event.type)) {
1193
- sync();
1194
- }
1195
- });
1196
- }, [queryClient, subscribe]);
1197
- return useQuery10({
1198
- queryKey: ["hardkas", "transactions"],
1199
- queryFn: async () => {
1200
- try {
1201
- const baseUrl = config.devServerUrl || "";
1202
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/transactions` : `${baseUrl}/api/transactions` : "/api/transactions";
1203
- const response = await apiFetch(url);
1204
- if (!response.ok) return [];
1205
- const data = await response.json();
1206
- return data.transactions || [];
1207
- } catch (e) {
1208
- console.error("Failed to fetch transactions from dev server:", e);
1209
- return [];
1210
- }
1211
- },
1212
- staleTime: 1e4
1213
- });
1214
- }
1215
- function useTransaction(id) {
1216
- const { config, subscribe, apiFetch } = useHardKas();
1217
- const queryClient = useQueryClient9();
1218
- useEffect11(() => {
1219
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "transaction", id] });
1220
- return subscribe((event) => {
1221
- if (["query-synced", "session-changed"].includes(event.type)) {
1222
- sync();
1223
- }
1224
- });
1225
- }, [id, queryClient, subscribe]);
1226
- return useQuery10({
1227
- queryKey: ["hardkas", "transaction", id],
1228
- queryFn: async () => {
1229
- if (!id) return null;
1230
- try {
1231
- const baseUrl = config.devServerUrl || "";
1232
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/transactions/${id}` : `${baseUrl}/api/transactions/${id}` : `/api/transactions/${id}`;
1233
- const response = await apiFetch(url);
1234
- if (!response.ok) return null;
1235
- return await response.json();
1236
- } catch (e) {
1237
- console.error(`Failed to fetch transaction detail for '${id}':`, e);
1238
- return null;
1239
- }
1240
- },
1241
- enabled: !!id,
1242
- staleTime: 1e4
1243
- });
1244
- }
1245
-
1246
- // src/hooks/artifacts.ts
1247
- import { useQuery as useQuery11, useQueryClient as useQueryClient10 } from "@tanstack/react-query";
1248
- import { useEffect as useEffect12 } from "react";
1249
- function useArtifacts(schemaFilter) {
1250
- const { config, subscribe, apiFetch } = useHardKas();
1251
- const queryClient = useQueryClient10();
1252
- useEffect12(() => {
1253
- const sync = () => queryClient.invalidateQueries({
1254
- queryKey: ["hardkas", "artifacts", schemaFilter || "all"]
1255
- });
1256
- return subscribe((event) => {
1257
- if (["query-synced", "session-changed"].includes(event.type)) {
1258
- sync();
1259
- }
1260
- });
1261
- }, [schemaFilter, queryClient, subscribe]);
1262
- return useQuery11({
1263
- queryKey: ["hardkas", "artifacts", schemaFilter || "all"],
1264
- queryFn: async () => {
1265
- try {
1266
- const baseUrl = config.devServerUrl || "";
1267
- const queryParams = schemaFilter ? `?schema=${schemaFilter}` : "";
1268
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/artifacts${queryParams}` : `${baseUrl}/api/artifacts${queryParams}` : `/api/artifacts${queryParams}`;
1269
- const response = await apiFetch(url);
1270
- if (!response.ok) return [];
1271
- const data = await response.json();
1272
- return data.artifacts || [];
1273
- } catch (e) {
1274
- console.error("Failed to fetch artifacts from dev server:", e);
1275
- return [];
1276
- }
1277
- },
1278
- staleTime: 3e4
1279
- });
1280
- }
1281
- function useArtifact(id) {
1282
- const { config, subscribe, apiFetch } = useHardKas();
1283
- const queryClient = useQueryClient10();
1284
- useEffect12(() => {
1285
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "artifact", id] });
1286
- return subscribe((event) => {
1287
- if (["query-synced", "session-changed"].includes(event.type)) {
1288
- sync();
1289
- }
1290
- });
1291
- }, [id, queryClient, subscribe]);
1292
- return useQuery11({
1293
- queryKey: ["hardkas", "artifact", id],
1294
- queryFn: async () => {
1295
- if (!id) return null;
1296
- try {
1297
- const baseUrl = config.devServerUrl || "";
1298
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/artifacts/${id}` : `${baseUrl}/api/artifacts/${id}` : `/api/artifacts/${id}`;
1299
- const response = await apiFetch(url);
1300
- if (!response.ok) return null;
1301
- const data = await response.json();
1302
- return data.artifact || null;
1303
- } catch (e) {
1304
- console.error(`Failed to fetch artifact detail for '${id}':`, e);
1305
- return null;
1306
- }
1307
- },
1308
- enabled: !!id,
1309
- staleTime: 3e4
1310
- });
1311
- }
1312
- function useExplain(id) {
1313
- const { config, apiFetch } = useHardKas();
1314
- return useQuery11({
1315
- queryKey: ["hardkas", "explain", id],
1316
- queryFn: async () => {
1317
- if (!id) return null;
1318
- try {
1319
- const baseUrl = config.devServerUrl || "";
1320
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/artifacts/${id}/explain` : `${baseUrl}/api/artifacts/${id}/explain` : `/api/artifacts/${id}/explain`;
1321
- const response = await apiFetch(url);
1322
- if (!response.ok) return null;
1323
- const data = await response.json();
1324
- return data.data || null;
1325
- } catch (e) {
1326
- console.error(`Failed to explain artifact '${id}':`, e);
1327
- return null;
1328
- }
1329
- },
1330
- enabled: !!id,
1331
- staleTime: Infinity
1332
- });
1333
- }
1334
- function useWorkflow(txId) {
1335
- return useArtifacts("all");
1336
- }
1337
-
1338
- // src/hooks/replay.ts
1339
- import { useQuery as useQuery12, useQueryClient as useQueryClient11 } from "@tanstack/react-query";
1340
- import { useEffect as useEffect13 } from "react";
1341
- function useReplayStatus() {
1342
- const { config, subscribe, apiFetch } = useHardKas();
1343
- const queryClient = useQueryClient11();
1344
- useEffect13(() => {
1345
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "replay"] });
1346
- return subscribe((event) => {
1347
- if (["query-synced", "session-changed"].includes(event.type)) {
1348
- sync();
1349
- }
1350
- });
1351
- }, [queryClient, subscribe]);
1352
- return useQuery12({
1353
- queryKey: ["hardkas", "replay"],
1354
- queryFn: async () => {
1355
- try {
1356
- const baseUrl = config.devServerUrl || "";
1357
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/replay` : `${baseUrl}/api/replay` : "/api/replay";
1358
- const response = await apiFetch(url);
1359
- if (!response.ok)
1360
- return { replays: [], pendingReplays: [], pendingReplay: false };
1361
- const data = await response.json();
1362
- const formatReplay = (r) => {
1363
- return {
1364
- artifactId: r.artifactId,
1365
- txId: r.payload?.txId || r.txId,
1366
- planOk: r.payload?.planOk ?? false,
1367
- receiptOk: r.payload?.receiptOk ?? false,
1368
- invariantsOk: r.payload?.invariantsOk ?? false,
1369
- ok: r.payload?.planOk && r.payload?.receiptOk && r.payload?.invariantsOk ? true : false,
1370
- checks: r.payload?.checks || {
1371
- workflowDeterministic: "unknown",
1372
- consensusValidation: "unknown",
1373
- l2BridgeCorrectness: "unknown"
1374
- },
1375
- errors: r.payload?.errors || [],
1376
- divergencesCount: r.payload?.divergencesCount || 0,
1377
- createdAt: r.createdAt || r.timestamp || (/* @__PURE__ */ new Date()).toISOString()
1378
- };
1379
- };
1380
- return {
1381
- replays: (data.replays || []).map(formatReplay),
1382
- pendingReplays: data.pendingReplays || [],
1383
- pendingReplay: data.pendingReplay || false,
1384
- reason: data.reason
1385
- };
1386
- } catch (e) {
1387
- console.error("Failed to fetch replay status from dev server:", e);
1388
- return { replays: [], pendingReplays: [], pendingReplay: false };
1389
- }
1390
- },
1391
- staleTime: 3e4
1392
- });
1393
- }
1394
- function useReplayDetail(txId) {
1395
- const { config, subscribe, apiFetch } = useHardKas();
1396
- const queryClient = useQueryClient11();
1397
- useEffect13(() => {
1398
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "replay", txId] });
1399
- return subscribe((event) => {
1400
- if (["query-synced", "session-changed"].includes(event.type)) {
1401
- sync();
1402
- }
1403
- });
1404
- }, [txId, queryClient, subscribe]);
1405
- return useQuery12({
1406
- queryKey: ["hardkas", "replay", txId],
1407
- queryFn: async () => {
1408
- if (!txId) return null;
1409
- try {
1410
- const baseUrl = config.devServerUrl || "";
1411
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/replay/${txId}` : `${baseUrl}/api/replay/${txId}` : `/api/replay/${txId}`;
1412
- const response = await apiFetch(url);
1413
- if (!response.ok) return null;
1414
- const data = await response.json();
1415
- return data.replay || null;
1416
- } catch (e) {
1417
- console.error(`Failed to fetch replay details for transaction '${txId}':`, e);
1418
- return null;
1419
- }
1420
- },
1421
- enabled: !!txId,
1422
- staleTime: 3e4
1423
- });
1424
- }
1425
- function useReplay(id) {
1426
- const { config, apiFetch } = useHardKas();
1427
- return useQuery12({
1428
- queryKey: ["hardkas", "replay-artifact", id],
1429
- queryFn: async () => {
1430
- if (!id) return null;
1431
- try {
1432
- const baseUrl = config.devServerUrl || "";
1433
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/artifacts/${id}/replay` : `${baseUrl}/api/artifacts/${id}/replay` : `/api/artifacts/${id}/replay`;
1434
- const response = await apiFetch(url, { method: "POST" });
1435
- if (!response.ok) return null;
1436
- const data = await response.json();
1437
- return data.data || null;
1438
- } catch (e) {
1439
- console.error(`Failed to replay artifact '${id}':`, e);
1440
- return null;
1441
- }
1442
- },
1443
- enabled: !!id,
1444
- staleTime: Infinity
1445
- });
1446
- }
1447
-
1448
- // src/hooks/deployments.ts
1449
- import { useQuery as useQuery13, useQueryClient as useQueryClient12 } from "@tanstack/react-query";
1450
- import { useEffect as useEffect14 } from "react";
1451
- function useDeployments() {
1452
- const { config, subscribe, apiFetch } = useHardKas();
1453
- const queryClient = useQueryClient12();
1454
- useEffect14(() => {
1455
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "deployments"] });
1456
- return subscribe((event) => {
1457
- if (["query-synced", "session-changed"].includes(event.type)) {
1458
- sync();
1459
- }
1460
- });
1461
- }, [queryClient, subscribe]);
1462
- return useQuery13({
1463
- queryKey: ["hardkas", "deployments"],
1464
- queryFn: async () => {
1465
- try {
1466
- const baseUrl = config.devServerUrl || "";
1467
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/deployments` : `${baseUrl}/api/deployments` : "/api/deployments";
1468
- const response = await apiFetch(url);
1469
- if (!response.ok) return [];
1470
- const data = await response.json();
1471
- return data.deployments || [];
1472
- } catch (e) {
1473
- console.error("Failed to fetch deployments from dev server:", e);
1474
- return [];
1475
- }
1476
- },
1477
- staleTime: 3e4
1478
- });
1479
- }
1480
-
1481
- // src/hooks/activity.ts
1482
- import { useQuery as useQuery14, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
1483
- import { useEffect as useEffect15 } from "react";
1484
- function useActivity() {
1485
- const { config, subscribe, apiFetch } = useHardKas();
1486
- const queryClient = useQueryClient13();
1487
- useEffect15(() => {
1488
- const sync = () => queryClient.invalidateQueries({ queryKey: ["hardkas", "activity"] });
1489
- return subscribe((event) => {
1490
- if (["query-synced", "session-changed"].includes(event.type)) {
1491
- sync();
1492
- }
1493
- });
1494
- }, [queryClient, subscribe]);
1495
- return useQuery14({
1496
- queryKey: ["hardkas", "activity"],
1497
- queryFn: async () => {
1498
- try {
1499
- const baseUrl = config.devServerUrl || "";
1500
- const url = baseUrl ? baseUrl.endsWith("/") ? `${baseUrl}api/activity` : `${baseUrl}/api/activity` : "/api/activity";
1501
- const response = await apiFetch(url);
1502
- if (!response.ok) return [];
1503
- const data = await response.json();
1504
- return data.activity || [];
1505
- } catch (e) {
1506
- console.error("Failed to fetch activity from dev server:", e);
1507
- return [];
1508
- }
1509
- },
1510
- staleTime: 5e3
1511
- // Frequent updates safe because it's local dev
1512
- });
82
+ // src/hooks/useWallet.ts
83
+ function useWallet(address) {
84
+ return useQuery(
85
+ (client) => client.getWallet(address),
86
+ [address],
87
+ { enabled: !!address }
88
+ );
1513
89
  }
1514
90
  export {
1515
- HardKasProvider,
1516
- useAccounts,
1517
- useActivity,
1518
- useArtifact,
1519
- useArtifacts,
1520
- useConnectKasWareLocal,
1521
- useCreateSandboxSession,
1522
- useDeployments,
1523
- useDisconnectSandboxSession,
1524
- useEvents,
1525
- useExplain,
1526
- useHardKas,
1527
- useHardKasHealth,
1528
- useHardKasSession,
1529
- useIgraAccount,
1530
- useIgraBalance,
1531
- useIgraInjectedAccount,
1532
- useIgraReadContract,
1533
- useIgraWaitForReceipt,
1534
- useIgraWallet,
1535
- useIgraWriteContract,
1536
- useKasWareLocal,
1537
- useKasWareSessionMatch,
1538
- useKaspaBalance,
1539
- useKaspaWallet,
1540
- useLocalIgraWalletClient,
1541
- useMetaMaskLocal,
1542
- useOverview,
1543
- usePairSandboxSession,
1544
- useReplay,
1545
- useReplayDetail,
1546
- useReplayStatus,
1547
- useSandboxSessions,
1548
- useSwitchToLocalIgra,
1549
- useTransaction,
1550
- useTransactions,
1551
- useWorkflow
91
+ HardKASProvider,
92
+ useHardKAS,
93
+ useMutation,
94
+ useQuery,
95
+ useWallet
1552
96
  };