@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/README.md +62 -7
- package/dist/index.d.ts +23 -4902
- package/dist/index.js +80 -1536
- package/package.json +2 -7
package/dist/index.js
CHANGED
|
@@ -1,1552 +1,96 @@
|
|
|
1
1
|
// src/provider.tsx
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
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("
|
|
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/
|
|
877
|
-
import { useState
|
|
878
|
-
function
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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/
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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
|
};
|