@horus-wallet/sdk-react 0.1.0-beta.2 → 0.3.0-beta.2
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 +133 -174
- package/dist/connect.cjs +218 -0
- package/dist/connect.d.cts +155 -0
- package/dist/connect.d.ts +155 -0
- package/dist/connect.js +188 -0
- package/dist/index.cjs +811 -64
- package/dist/index.d.cts +605 -31
- package/dist/index.d.ts +605 -31
- package/dist/index.js +798 -64
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -89,19 +89,21 @@ function makeHttp(deps) {
|
|
|
89
89
|
return path.startsWith("/") ? `${base}${path}` : `${base}/${path}`;
|
|
90
90
|
};
|
|
91
91
|
async function rawRequest(method, path, body, options = {}) {
|
|
92
|
-
const headers = {
|
|
92
|
+
const headers = {
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
"x-horus-key": deps.appId
|
|
95
|
+
};
|
|
93
96
|
if (options.auth !== false) {
|
|
94
97
|
const tokens = deps.getTokens();
|
|
95
98
|
if (tokens?.idToken) {
|
|
96
|
-
headers["
|
|
99
|
+
headers["Authorization"] = `Bearer ${tokens.idToken}`;
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
const res = await fetch(url(path), {
|
|
100
103
|
method,
|
|
101
104
|
headers,
|
|
102
105
|
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
103
|
-
signal: options.signal
|
|
104
|
-
credentials: "same-origin"
|
|
106
|
+
signal: options.signal
|
|
105
107
|
});
|
|
106
108
|
const text = await res.text();
|
|
107
109
|
let parsed = void 0;
|
|
@@ -142,9 +144,15 @@ function makeHttp(deps) {
|
|
|
142
144
|
|
|
143
145
|
// src/HorusProvider.tsx
|
|
144
146
|
import { jsx } from "react/jsx-runtime";
|
|
145
|
-
var DEFAULT_API_BASE = "
|
|
147
|
+
var DEFAULT_API_BASE = "https://api.horuswallet.com";
|
|
146
148
|
var DEFAULT_AUTH_PAGE = "https://auth.horuswallet.com";
|
|
147
149
|
var REFRESH_LEAD_SECONDS = 60;
|
|
150
|
+
var DEFAULT_AUTO_PROVISION = {
|
|
151
|
+
network: "ETHEREUM",
|
|
152
|
+
networkType: "MAINNET"
|
|
153
|
+
};
|
|
154
|
+
var DEFAULT_CHAIN = { network: "ETHEREUM", networkType: "MAINNET" };
|
|
155
|
+
var AUTO_PROVISION_STORAGE_KEY = "horus.autoProvisioned.localIds";
|
|
148
156
|
function HorusProvider(props) {
|
|
149
157
|
const {
|
|
150
158
|
appId,
|
|
@@ -153,11 +161,25 @@ function HorusProvider(props) {
|
|
|
153
161
|
branding,
|
|
154
162
|
tokenStorage = "localStorage",
|
|
155
163
|
autoRefresh = true,
|
|
164
|
+
autoProvisionWallet = DEFAULT_AUTO_PROVISION,
|
|
165
|
+
defaultChain = DEFAULT_CHAIN,
|
|
156
166
|
children
|
|
157
167
|
} = props;
|
|
158
168
|
const tokenStoreRef = useRef(createTokenStore(tokenStorage));
|
|
159
169
|
const tokensRef = useRef(null);
|
|
160
170
|
const [state, setState] = useState({ status: "loading" });
|
|
171
|
+
const [walletsVersion, setWalletsVersion] = useState(0);
|
|
172
|
+
const revalidateWallets = useCallback(() => {
|
|
173
|
+
setWalletsVersion((v) => v + 1);
|
|
174
|
+
}, []);
|
|
175
|
+
const [currentChain, setCurrentChain] = useState(defaultChain);
|
|
176
|
+
const setChain = useCallback((chain) => {
|
|
177
|
+
setCurrentChain(chain);
|
|
178
|
+
}, []);
|
|
179
|
+
const [mcpKeysVersion, setMcpKeysVersion] = useState(0);
|
|
180
|
+
const revalidateMcpKeys = useCallback(() => {
|
|
181
|
+
setMcpKeysVersion((v) => v + 1);
|
|
182
|
+
}, []);
|
|
161
183
|
const setTokens = useCallback((tokens) => {
|
|
162
184
|
tokensRef.current = tokens;
|
|
163
185
|
if (tokens) {
|
|
@@ -178,9 +200,11 @@ function HorusProvider(props) {
|
|
|
178
200
|
if (!cur?.refreshToken) throw new Error("no refresh token cached");
|
|
179
201
|
const fresh = await fetch(joinUrl(apiBase, "/auth/refresh"), {
|
|
180
202
|
method: "POST",
|
|
181
|
-
headers: {
|
|
182
|
-
|
|
183
|
-
|
|
203
|
+
headers: {
|
|
204
|
+
"Content-Type": "application/json",
|
|
205
|
+
"x-horus-key": appId
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify({ refreshToken: cur.refreshToken })
|
|
184
208
|
});
|
|
185
209
|
if (!fresh.ok) throw new Error(`refresh failed: ${fresh.status}`);
|
|
186
210
|
const raw = await fresh.json();
|
|
@@ -190,11 +214,12 @@ function HorusProvider(props) {
|
|
|
190
214
|
};
|
|
191
215
|
return makeHttp({
|
|
192
216
|
apiBase,
|
|
217
|
+
appId,
|
|
193
218
|
getTokens: () => tokensRef.current,
|
|
194
219
|
refreshTokens: refreshOnce,
|
|
195
220
|
autoRefresh
|
|
196
221
|
});
|
|
197
|
-
}, [apiBase, autoRefresh, setTokens]);
|
|
222
|
+
}, [apiBase, appId, autoRefresh, setTokens]);
|
|
198
223
|
useEffect(() => {
|
|
199
224
|
const stored = tokenStoreRef.current.read();
|
|
200
225
|
if (stored) {
|
|
@@ -208,6 +233,58 @@ function HorusProvider(props) {
|
|
|
208
233
|
setState({ status: "unauthenticated" });
|
|
209
234
|
}
|
|
210
235
|
}, []);
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
if (typeof window === "undefined") return;
|
|
238
|
+
const params = new URLSearchParams(window.location.search);
|
|
239
|
+
const mode = params.get("mode");
|
|
240
|
+
const oobCode = params.get("oobCode");
|
|
241
|
+
if (mode !== "signIn" || !oobCode) return;
|
|
242
|
+
if (consumedMagicLinkCodes.has(oobCode)) return;
|
|
243
|
+
consumedMagicLinkCodes.add(oobCode);
|
|
244
|
+
const email = params.get("email") ?? (typeof window.localStorage !== "undefined" ? window.localStorage.getItem("horus.signinEmail") : null) ?? "";
|
|
245
|
+
if (!email) {
|
|
246
|
+
console.warn(
|
|
247
|
+
"@horus-wallet/sdk-react: detected a magic-link return but no email is available. Pass `email` in the continueUrl or set localStorage.horus.signinEmail at send time."
|
|
248
|
+
);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
void (async () => {
|
|
252
|
+
try {
|
|
253
|
+
const stripUrlParams = () => {
|
|
254
|
+
const u = new URL(window.location.href);
|
|
255
|
+
["mode", "oobCode", "apiKey", "continueUrl", "lang", "email"].forEach(
|
|
256
|
+
(k) => u.searchParams.delete(k)
|
|
257
|
+
);
|
|
258
|
+
window.history.replaceState({}, "", u.toString());
|
|
259
|
+
if (window.localStorage) {
|
|
260
|
+
window.localStorage.removeItem("horus.signinEmail");
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const raw = await fetch(joinUrl(apiBase, "/auth/email-link/verify"), {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/json",
|
|
267
|
+
"x-horus-key": appId
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify({ email, oobCode })
|
|
270
|
+
});
|
|
271
|
+
if (!raw.ok) {
|
|
272
|
+
const body = await raw.text();
|
|
273
|
+
console.warn(
|
|
274
|
+
`@horus-wallet/sdk-react: magic-link verify failed (HTTP ${raw.status}): ${body}`
|
|
275
|
+
);
|
|
276
|
+
stripUrlParams();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const json = await raw.json();
|
|
280
|
+
const stamped = stampExpiry(json);
|
|
281
|
+
setTokens(stamped);
|
|
282
|
+
stripUrlParams();
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.warn("@horus-wallet/sdk-react: magic-link verify threw", err);
|
|
285
|
+
}
|
|
286
|
+
})();
|
|
287
|
+
}, [apiBase, appId, setTokens]);
|
|
211
288
|
useEffect(() => {
|
|
212
289
|
if (typeof window === "undefined") return;
|
|
213
290
|
const onStorage = (ev) => {
|
|
@@ -233,6 +310,27 @@ function HorusProvider(props) {
|
|
|
233
310
|
}, refreshIn);
|
|
234
311
|
return () => clearTimeout(handle);
|
|
235
312
|
}, [state, autoRefresh, http, setTokens]);
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
if (state.status !== "authenticated") return;
|
|
315
|
+
if (!autoProvisionWallet) return;
|
|
316
|
+
const localId = state.tokens.localId;
|
|
317
|
+
if (!localId) return;
|
|
318
|
+
if (alreadyAutoProvisioned(localId)) return;
|
|
319
|
+
let cancelled = false;
|
|
320
|
+
(async () => {
|
|
321
|
+
try {
|
|
322
|
+
await http.post("/createWallet", autoProvisionWallet);
|
|
323
|
+
if (cancelled) return;
|
|
324
|
+
markAutoProvisioned(localId);
|
|
325
|
+
revalidateWallets();
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.warn("@horus-wallet/sdk-react: auto-provision wallet failed", err);
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
return () => {
|
|
331
|
+
cancelled = true;
|
|
332
|
+
};
|
|
333
|
+
}, [state, autoProvisionWallet, http, revalidateWallets]);
|
|
236
334
|
const ctx = useMemo(
|
|
237
335
|
() => ({
|
|
238
336
|
state,
|
|
@@ -253,9 +351,28 @@ function HorusProvider(props) {
|
|
|
253
351
|
const stamped = stampExpiry(raw);
|
|
254
352
|
setTokens(stamped);
|
|
255
353
|
return stamped;
|
|
256
|
-
}
|
|
354
|
+
},
|
|
355
|
+
walletsVersion,
|
|
356
|
+
revalidateWallets,
|
|
357
|
+
currentChain,
|
|
358
|
+
setChain,
|
|
359
|
+
mcpKeysVersion,
|
|
360
|
+
revalidateMcpKeys
|
|
257
361
|
}),
|
|
258
|
-
[
|
|
362
|
+
[
|
|
363
|
+
state,
|
|
364
|
+
http,
|
|
365
|
+
authPageUrl,
|
|
366
|
+
appId,
|
|
367
|
+
branding,
|
|
368
|
+
setTokens,
|
|
369
|
+
walletsVersion,
|
|
370
|
+
revalidateWallets,
|
|
371
|
+
currentChain,
|
|
372
|
+
setChain,
|
|
373
|
+
mcpKeysVersion,
|
|
374
|
+
revalidateMcpKeys
|
|
375
|
+
]
|
|
259
376
|
);
|
|
260
377
|
return /* @__PURE__ */ jsx(HorusContext.Provider, { value: ctx, children });
|
|
261
378
|
}
|
|
@@ -269,6 +386,37 @@ function userFromTokens(t) {
|
|
|
269
386
|
providerId: t.providerId
|
|
270
387
|
};
|
|
271
388
|
}
|
|
389
|
+
var consumedMagicLinkCodes = /* @__PURE__ */ new Set();
|
|
390
|
+
var memoryAutoProvisioned = /* @__PURE__ */ new Set();
|
|
391
|
+
function readAutoProvisionedSet() {
|
|
392
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
393
|
+
return memoryAutoProvisioned;
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const raw = window.localStorage.getItem(AUTO_PROVISION_STORAGE_KEY);
|
|
397
|
+
if (!raw) return /* @__PURE__ */ new Set();
|
|
398
|
+
const parsed = JSON.parse(raw);
|
|
399
|
+
return new Set(Array.isArray(parsed) ? parsed : []);
|
|
400
|
+
} catch {
|
|
401
|
+
return /* @__PURE__ */ new Set();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function alreadyAutoProvisioned(localId) {
|
|
405
|
+
return readAutoProvisionedSet().has(localId);
|
|
406
|
+
}
|
|
407
|
+
function markAutoProvisioned(localId) {
|
|
408
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
409
|
+
memoryAutoProvisioned.add(localId);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
const set = readAutoProvisionedSet();
|
|
414
|
+
set.add(localId);
|
|
415
|
+
window.localStorage.setItem(AUTO_PROVISION_STORAGE_KEY, JSON.stringify([...set]));
|
|
416
|
+
} catch {
|
|
417
|
+
memoryAutoProvisioned.add(localId);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
272
420
|
function joinUrl(base, path) {
|
|
273
421
|
const b = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
274
422
|
const p = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -277,6 +425,39 @@ function joinUrl(base, path) {
|
|
|
277
425
|
|
|
278
426
|
// src/hooks/useHorusAuth.ts
|
|
279
427
|
import { useCallback as useCallback2 } from "react";
|
|
428
|
+
|
|
429
|
+
// src/internal/authUrl.ts
|
|
430
|
+
function buildAuthUrl(p, state) {
|
|
431
|
+
const url = new URL(p.baseUrl);
|
|
432
|
+
url.searchParams.set("flow", p.flow);
|
|
433
|
+
url.searchParams.set("origin", window.location.origin);
|
|
434
|
+
url.searchParams.set("state", state);
|
|
435
|
+
url.searchParams.set("appKey", p.appId);
|
|
436
|
+
url.searchParams.set("mode", p.mode);
|
|
437
|
+
if (p.phone) url.searchParams.set("phone", p.phone);
|
|
438
|
+
if (p.flow === "unified" && p.enabledMethods && p.enabledMethods.length > 0) {
|
|
439
|
+
url.searchParams.set("methods", p.enabledMethods.join(","));
|
|
440
|
+
}
|
|
441
|
+
const b = p.branding;
|
|
442
|
+
if (b) {
|
|
443
|
+
if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
|
|
444
|
+
if (b.brandName) url.searchParams.set("b_name", b.brandName);
|
|
445
|
+
if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
|
|
446
|
+
if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
|
|
447
|
+
if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
|
|
448
|
+
if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
|
|
449
|
+
if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
|
|
450
|
+
if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
|
|
451
|
+
}
|
|
452
|
+
return url.toString();
|
|
453
|
+
}
|
|
454
|
+
function randomState() {
|
|
455
|
+
const bytes = new Uint8Array(16);
|
|
456
|
+
crypto.getRandomValues(bytes);
|
|
457
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/hooks/useHorusAuth.ts
|
|
280
461
|
function useHorusAuth() {
|
|
281
462
|
const ctx = useHorusContext();
|
|
282
463
|
const { state, http, signIn, signOut, refreshTokens, authPageUrl, appId, branding } = ctx;
|
|
@@ -376,7 +557,7 @@ function openPopupFlow(params) {
|
|
|
376
557
|
return;
|
|
377
558
|
}
|
|
378
559
|
const state = randomState();
|
|
379
|
-
const url = buildAuthUrl(params, state);
|
|
560
|
+
const url = buildAuthUrl({ ...params, mode: "popup" }, state);
|
|
380
561
|
const popup = window.open(url, "horus-auth", defaultPopupFeatures());
|
|
381
562
|
if (!popup) {
|
|
382
563
|
reject(new Error("Popup blocked \u2014 call from a click handler so the browser allows it."));
|
|
@@ -384,6 +565,7 @@ function openPopupFlow(params) {
|
|
|
384
565
|
}
|
|
385
566
|
const expectedOrigin = new URL(params.baseUrl).origin;
|
|
386
567
|
let settled = false;
|
|
568
|
+
let lastError = null;
|
|
387
569
|
const cleanup = () => {
|
|
388
570
|
window.removeEventListener("message", onMessage);
|
|
389
571
|
clearInterval(poll);
|
|
@@ -414,17 +596,11 @@ function openPopupFlow(params) {
|
|
|
414
596
|
popup.close();
|
|
415
597
|
} catch {
|
|
416
598
|
}
|
|
417
|
-
reject(new Error("User cancelled sign-in."));
|
|
599
|
+
reject(new Error(lastError ?? "User cancelled sign-in."));
|
|
418
600
|
return;
|
|
419
601
|
case "error":
|
|
420
602
|
if (settled) return;
|
|
421
|
-
|
|
422
|
-
cleanup();
|
|
423
|
-
try {
|
|
424
|
-
popup.close();
|
|
425
|
-
} catch {
|
|
426
|
-
}
|
|
427
|
-
reject(new Error(body.message || "Sign-in failed."));
|
|
603
|
+
lastError = body.message || "Sign-in failed.";
|
|
428
604
|
return;
|
|
429
605
|
}
|
|
430
606
|
};
|
|
@@ -433,31 +609,11 @@ function openPopupFlow(params) {
|
|
|
433
609
|
if (popup.closed && !settled) {
|
|
434
610
|
settled = true;
|
|
435
611
|
cleanup();
|
|
436
|
-
reject(new Error("Auth popup was closed before sign-in completed."));
|
|
612
|
+
reject(new Error(lastError ?? "Auth popup was closed before sign-in completed."));
|
|
437
613
|
}
|
|
438
614
|
}, 400);
|
|
439
615
|
});
|
|
440
616
|
}
|
|
441
|
-
function buildAuthUrl(p, state) {
|
|
442
|
-
const url = new URL(p.baseUrl);
|
|
443
|
-
url.searchParams.set("flow", p.flow);
|
|
444
|
-
url.searchParams.set("origin", window.location.origin);
|
|
445
|
-
url.searchParams.set("state", state);
|
|
446
|
-
url.searchParams.set("appKey", p.appId);
|
|
447
|
-
if (p.phone) url.searchParams.set("phone", p.phone);
|
|
448
|
-
const b = p.branding;
|
|
449
|
-
if (b) {
|
|
450
|
-
if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
|
|
451
|
-
if (b.brandName) url.searchParams.set("b_name", b.brandName);
|
|
452
|
-
if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
|
|
453
|
-
if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
|
|
454
|
-
if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
|
|
455
|
-
if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
|
|
456
|
-
if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
|
|
457
|
-
if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
|
|
458
|
-
}
|
|
459
|
-
return url.toString();
|
|
460
|
-
}
|
|
461
617
|
function defaultPopupFeatures() {
|
|
462
618
|
const w = 480, h = 640;
|
|
463
619
|
if (typeof window === "undefined") return `width=${w},height=${h}`;
|
|
@@ -465,11 +621,6 @@ function defaultPopupFeatures() {
|
|
|
465
621
|
const top = Math.max(0, (window.innerHeight - h) / 2 + (window.screenY ?? 0));
|
|
466
622
|
return `width=${w},height=${h},left=${left},top=${top},popup=1`;
|
|
467
623
|
}
|
|
468
|
-
function randomState() {
|
|
469
|
-
const bytes = new Uint8Array(16);
|
|
470
|
-
crypto.getRandomValues(bytes);
|
|
471
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
472
|
-
}
|
|
473
624
|
|
|
474
625
|
// src/hooks/useUser.ts
|
|
475
626
|
function useUser() {
|
|
@@ -480,7 +631,7 @@ function useUser() {
|
|
|
480
631
|
// src/hooks/useWallets.ts
|
|
481
632
|
import { useCallback as useCallback3, useEffect as useEffect2, useState as useState2 } from "react";
|
|
482
633
|
function useWallets() {
|
|
483
|
-
const { http, state } = useHorusContext();
|
|
634
|
+
const { http, state, walletsVersion } = useHorusContext();
|
|
484
635
|
const [wallets, setWallets] = useState2([]);
|
|
485
636
|
const [ready, setReady] = useState2(false);
|
|
486
637
|
const [error, setError] = useState2(void 0);
|
|
@@ -494,7 +645,7 @@ function useWallets() {
|
|
|
494
645
|
setError(void 0);
|
|
495
646
|
try {
|
|
496
647
|
const response = await http.get(
|
|
497
|
-
"/
|
|
648
|
+
"/getWallet"
|
|
498
649
|
);
|
|
499
650
|
const flat = [];
|
|
500
651
|
for (const [network, group] of Object.entries(response.wallets ?? {})) {
|
|
@@ -511,22 +662,94 @@ function useWallets() {
|
|
|
511
662
|
}, [http, state.status]);
|
|
512
663
|
useEffect2(() => {
|
|
513
664
|
void load();
|
|
514
|
-
}, [load]);
|
|
665
|
+
}, [load, walletsVersion]);
|
|
515
666
|
return { ready, wallets, refresh: load, error };
|
|
516
667
|
}
|
|
517
668
|
|
|
518
|
-
// src/hooks/
|
|
669
|
+
// src/hooks/useCreateWallet.ts
|
|
519
670
|
import { useCallback as useCallback4, useState as useState3 } from "react";
|
|
520
|
-
function
|
|
521
|
-
const { http } = useHorusContext();
|
|
671
|
+
function useCreateWallet() {
|
|
672
|
+
const { http, revalidateWallets } = useHorusContext();
|
|
522
673
|
const [pending, setPending] = useState3(false);
|
|
523
674
|
const [error, setError] = useState3(void 0);
|
|
524
|
-
const
|
|
675
|
+
const create = useCallback4(
|
|
676
|
+
async (input) => {
|
|
677
|
+
if (pending) {
|
|
678
|
+
const err = new Error(
|
|
679
|
+
"useCreateWallet: a previous create() call is still in flight"
|
|
680
|
+
);
|
|
681
|
+
setError(err);
|
|
682
|
+
throw err;
|
|
683
|
+
}
|
|
684
|
+
setPending(true);
|
|
685
|
+
setError(void 0);
|
|
686
|
+
try {
|
|
687
|
+
const response = await http.post("/createWallet", {
|
|
688
|
+
network: input.network,
|
|
689
|
+
networkType: input.networkType,
|
|
690
|
+
...input.password ? { password: input.password } : {}
|
|
691
|
+
});
|
|
692
|
+
revalidateWallets();
|
|
693
|
+
return response;
|
|
694
|
+
} catch (err) {
|
|
695
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
696
|
+
setError(e);
|
|
697
|
+
throw e;
|
|
698
|
+
} finally {
|
|
699
|
+
setPending(false);
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
[http, revalidateWallets, pending]
|
|
703
|
+
);
|
|
704
|
+
return { create, pending, error };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/hooks/useSwitchChain.ts
|
|
708
|
+
import { useCallback as useCallback5 } from "react";
|
|
709
|
+
var SUPPORTED_CHAINS = [
|
|
710
|
+
// EVM-family chains the backend accepts as `network` selectors.
|
|
711
|
+
// `EVM` itself is intentionally NOT here — it's a family label, not
|
|
712
|
+
// a chain, and the API rejects it on /createWallet, /signMessage, etc.
|
|
713
|
+
// Pick a specific chain when interacting with the API.
|
|
714
|
+
"ETHEREUM",
|
|
715
|
+
"BASE",
|
|
716
|
+
"POLYGON",
|
|
717
|
+
"BSC",
|
|
718
|
+
"ARBITRUM",
|
|
719
|
+
"OPTIMISM",
|
|
720
|
+
"TELOS",
|
|
721
|
+
"CHILIZ",
|
|
722
|
+
"FLARE",
|
|
723
|
+
// Non-EVM chains.
|
|
724
|
+
"BITCOIN",
|
|
725
|
+
"ICP",
|
|
726
|
+
"CASPER",
|
|
727
|
+
"AETERNITY"
|
|
728
|
+
];
|
|
729
|
+
function useSwitchChain() {
|
|
730
|
+
const { currentChain, setChain } = useHorusContext();
|
|
731
|
+
const switchChain = useCallback5(
|
|
732
|
+
(chain) => {
|
|
733
|
+
setChain(chain);
|
|
734
|
+
},
|
|
735
|
+
[setChain]
|
|
736
|
+
);
|
|
737
|
+
return { chain: currentChain, switchChain, supportedChains: SUPPORTED_CHAINS };
|
|
738
|
+
}
|
|
739
|
+
var useChain = useSwitchChain;
|
|
740
|
+
|
|
741
|
+
// src/hooks/useSignMessage.ts
|
|
742
|
+
import { useCallback as useCallback6, useState as useState4 } from "react";
|
|
743
|
+
function useSignMessage() {
|
|
744
|
+
const { http } = useHorusContext();
|
|
745
|
+
const [pending, setPending] = useState4(false);
|
|
746
|
+
const [error, setError] = useState4(void 0);
|
|
747
|
+
const signMessage = useCallback6(
|
|
525
748
|
async (input) => {
|
|
526
749
|
setPending(true);
|
|
527
750
|
setError(void 0);
|
|
528
751
|
try {
|
|
529
|
-
const response = await http.post("/
|
|
752
|
+
const response = await http.post("/signMessage", input);
|
|
530
753
|
return response.signature;
|
|
531
754
|
} catch (err) {
|
|
532
755
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -541,13 +764,79 @@ function useSignMessage() {
|
|
|
541
764
|
return { signMessage, pending, error };
|
|
542
765
|
}
|
|
543
766
|
|
|
767
|
+
// src/hooks/useSignTypedData.ts
|
|
768
|
+
import { useCallback as useCallback7, useState as useState5 } from "react";
|
|
769
|
+
function useSignTypedData() {
|
|
770
|
+
const { http } = useHorusContext();
|
|
771
|
+
const [pending, setPending] = useState5(false);
|
|
772
|
+
const [error, setError] = useState5(void 0);
|
|
773
|
+
const signTypedData = useCallback7(
|
|
774
|
+
async (input) => {
|
|
775
|
+
setPending(true);
|
|
776
|
+
setError(void 0);
|
|
777
|
+
try {
|
|
778
|
+
const response = await http.post("/signTypedData", {
|
|
779
|
+
network: input.network,
|
|
780
|
+
networkType: input.networkType,
|
|
781
|
+
typedData: input.typedData,
|
|
782
|
+
...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
|
|
783
|
+
...input.password ? { password: input.password } : {}
|
|
784
|
+
});
|
|
785
|
+
return response.signature;
|
|
786
|
+
} catch (err) {
|
|
787
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
788
|
+
setError(e);
|
|
789
|
+
throw e;
|
|
790
|
+
} finally {
|
|
791
|
+
setPending(false);
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
[http]
|
|
795
|
+
);
|
|
796
|
+
return { signTypedData, pending, error };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/hooks/useSendTransaction.ts
|
|
800
|
+
import { useCallback as useCallback8, useState as useState6 } from "react";
|
|
801
|
+
function useSendTransaction() {
|
|
802
|
+
const { http } = useHorusContext();
|
|
803
|
+
const [pending, setPending] = useState6(false);
|
|
804
|
+
const [error, setError] = useState6(void 0);
|
|
805
|
+
const sendTransaction = useCallback8(
|
|
806
|
+
async (input) => {
|
|
807
|
+
setPending(true);
|
|
808
|
+
setError(void 0);
|
|
809
|
+
try {
|
|
810
|
+
return await http.post("/sendTransaction", {
|
|
811
|
+
network: input.network,
|
|
812
|
+
networkType: input.networkType,
|
|
813
|
+
to: input.to,
|
|
814
|
+
...input.value !== void 0 ? { value: String(input.value) } : {},
|
|
815
|
+
...input.data !== void 0 ? { data: input.data } : {},
|
|
816
|
+
...input.gasLimit !== void 0 ? { gasLimit: String(input.gasLimit) } : {},
|
|
817
|
+
...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
|
|
818
|
+
...input.password ? { password: input.password } : {}
|
|
819
|
+
});
|
|
820
|
+
} catch (err) {
|
|
821
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
822
|
+
setError(e);
|
|
823
|
+
throw e;
|
|
824
|
+
} finally {
|
|
825
|
+
setPending(false);
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
[http]
|
|
829
|
+
);
|
|
830
|
+
return { sendTransaction, pending, error };
|
|
831
|
+
}
|
|
832
|
+
|
|
544
833
|
// src/hooks/useTransfer.ts
|
|
545
|
-
import { useCallback as
|
|
834
|
+
import { useCallback as useCallback9, useState as useState7 } from "react";
|
|
546
835
|
function useTransfer() {
|
|
547
836
|
const { http } = useHorusContext();
|
|
548
|
-
const [pending, setPending] =
|
|
549
|
-
const [error, setError] =
|
|
550
|
-
const wrap =
|
|
837
|
+
const [pending, setPending] = useState7(false);
|
|
838
|
+
const [error, setError] = useState7(void 0);
|
|
839
|
+
const wrap = useCallback9(
|
|
551
840
|
async (fn) => {
|
|
552
841
|
setPending(true);
|
|
553
842
|
setError(void 0);
|
|
@@ -563,18 +852,18 @@ function useTransfer() {
|
|
|
563
852
|
},
|
|
564
853
|
[]
|
|
565
854
|
);
|
|
566
|
-
const native =
|
|
855
|
+
const native = useCallback9(
|
|
567
856
|
(input) => wrap(
|
|
568
|
-
() => http.post("/
|
|
857
|
+
() => http.post("/nativeTransfer", {
|
|
569
858
|
...input,
|
|
570
859
|
amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
|
|
571
860
|
})
|
|
572
861
|
),
|
|
573
862
|
[http, wrap]
|
|
574
863
|
);
|
|
575
|
-
const token =
|
|
864
|
+
const token = useCallback9(
|
|
576
865
|
(input) => wrap(
|
|
577
|
-
() => http.post("/
|
|
866
|
+
() => http.post("/tokenTransfer", {
|
|
578
867
|
...input,
|
|
579
868
|
amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
|
|
580
869
|
})
|
|
@@ -585,7 +874,7 @@ function useTransfer() {
|
|
|
585
874
|
}
|
|
586
875
|
|
|
587
876
|
// src/components/HorusLoginButton.tsx
|
|
588
|
-
import { useState as
|
|
877
|
+
import { useState as useState8 } from "react";
|
|
589
878
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
590
879
|
function HorusLoginButton({
|
|
591
880
|
flow = "google",
|
|
@@ -598,7 +887,7 @@ function HorusLoginButton({
|
|
|
598
887
|
...rest
|
|
599
888
|
}) {
|
|
600
889
|
const auth = useHorusAuth();
|
|
601
|
-
const [busy, setBusy] =
|
|
890
|
+
const [busy, setBusy] = useState8(false);
|
|
602
891
|
const onClick = async () => {
|
|
603
892
|
if (busy) return;
|
|
604
893
|
setBusy(true);
|
|
@@ -636,13 +925,458 @@ function HorusLoginButton({
|
|
|
636
925
|
}
|
|
637
926
|
);
|
|
638
927
|
}
|
|
928
|
+
|
|
929
|
+
// src/components/HorusAuthModal.tsx
|
|
930
|
+
import { useCallback as useCallback10, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
931
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
932
|
+
var defaultBackdropStyle = {
|
|
933
|
+
position: "fixed",
|
|
934
|
+
inset: 0,
|
|
935
|
+
background: "rgba(0, 0, 0, 0.5)",
|
|
936
|
+
display: "flex",
|
|
937
|
+
alignItems: "center",
|
|
938
|
+
justifyContent: "center",
|
|
939
|
+
zIndex: 2147483600
|
|
940
|
+
};
|
|
941
|
+
var defaultDialogStyle = {
|
|
942
|
+
width: "480px",
|
|
943
|
+
maxWidth: "95vw",
|
|
944
|
+
height: "640px",
|
|
945
|
+
maxHeight: "95vh",
|
|
946
|
+
background: "#fff",
|
|
947
|
+
borderRadius: "16px",
|
|
948
|
+
boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
|
|
949
|
+
overflow: "hidden"
|
|
950
|
+
};
|
|
951
|
+
function HorusAuthModal(props) {
|
|
952
|
+
const {
|
|
953
|
+
flow,
|
|
954
|
+
enabledMethods,
|
|
955
|
+
phone,
|
|
956
|
+
open,
|
|
957
|
+
onClose,
|
|
958
|
+
onSuccess,
|
|
959
|
+
onError,
|
|
960
|
+
dialogStyle,
|
|
961
|
+
backdropStyle
|
|
962
|
+
} = props;
|
|
963
|
+
const { appId, authPageUrl, branding, signIn } = useHorusContext();
|
|
964
|
+
const stateRef = useRef2("");
|
|
965
|
+
if (stateRef.current === "" || !open) {
|
|
966
|
+
stateRef.current = randomState();
|
|
967
|
+
}
|
|
968
|
+
const iframeSrc = useMemo2(() => {
|
|
969
|
+
if (!open) return "";
|
|
970
|
+
return buildAuthUrl(
|
|
971
|
+
{
|
|
972
|
+
flow,
|
|
973
|
+
appId,
|
|
974
|
+
baseUrl: authPageUrl,
|
|
975
|
+
mode: "iframe",
|
|
976
|
+
branding,
|
|
977
|
+
phone,
|
|
978
|
+
enabledMethods
|
|
979
|
+
},
|
|
980
|
+
stateRef.current
|
|
981
|
+
);
|
|
982
|
+
}, [open, flow, appId, authPageUrl, branding, phone, enabledMethods]);
|
|
983
|
+
const expectedOrigin = useMemo2(() => {
|
|
984
|
+
try {
|
|
985
|
+
return new URL(authPageUrl).origin;
|
|
986
|
+
} catch {
|
|
987
|
+
return "";
|
|
988
|
+
}
|
|
989
|
+
}, [authPageUrl]);
|
|
990
|
+
useEffect3(() => {
|
|
991
|
+
if (!open) return;
|
|
992
|
+
const onMessage = (ev) => {
|
|
993
|
+
if (ev.origin !== expectedOrigin) return;
|
|
994
|
+
const data = ev.data;
|
|
995
|
+
if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
|
|
996
|
+
if (data.state !== void 0 && data.state !== stateRef.current) return;
|
|
997
|
+
const body = data.body;
|
|
998
|
+
if (!body) return;
|
|
999
|
+
switch (body.type) {
|
|
1000
|
+
case "success": {
|
|
1001
|
+
const stamped = stampExpiry({
|
|
1002
|
+
idToken: body.idToken,
|
|
1003
|
+
refreshToken: "",
|
|
1004
|
+
expiresIn: body.expiresAt ? Math.max(60, body.expiresAt - Math.floor(Date.now() / 1e3)) : 3600,
|
|
1005
|
+
localId: body.user?.uid ?? "",
|
|
1006
|
+
email: body.user?.email,
|
|
1007
|
+
displayName: body.user?.displayName,
|
|
1008
|
+
photoUrl: body.user?.photoURL,
|
|
1009
|
+
providerId: body.user?.providerId
|
|
1010
|
+
});
|
|
1011
|
+
signIn(stamped);
|
|
1012
|
+
onSuccess?.({
|
|
1013
|
+
uid: stamped.localId,
|
|
1014
|
+
email: stamped.email,
|
|
1015
|
+
emailVerified: stamped.emailVerified,
|
|
1016
|
+
displayName: stamped.displayName,
|
|
1017
|
+
photoUrl: stamped.photoUrl,
|
|
1018
|
+
providerId: stamped.providerId
|
|
1019
|
+
});
|
|
1020
|
+
onClose();
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
case "cancel":
|
|
1024
|
+
onClose();
|
|
1025
|
+
return;
|
|
1026
|
+
case "error":
|
|
1027
|
+
onError?.(new Error(body.message ?? "Sign-in failed."));
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
window.addEventListener("message", onMessage);
|
|
1032
|
+
return () => window.removeEventListener("message", onMessage);
|
|
1033
|
+
}, [open, expectedOrigin, signIn, onSuccess, onError, onClose]);
|
|
1034
|
+
useEffect3(() => {
|
|
1035
|
+
if (!open) return;
|
|
1036
|
+
const onKey = (ev) => {
|
|
1037
|
+
if (ev.key === "Escape") onClose();
|
|
1038
|
+
};
|
|
1039
|
+
window.addEventListener("keydown", onKey);
|
|
1040
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1041
|
+
}, [open, onClose]);
|
|
1042
|
+
useEffect3(() => {
|
|
1043
|
+
if (!open) return;
|
|
1044
|
+
if (typeof document === "undefined") return;
|
|
1045
|
+
const prev = document.body.style.overflow;
|
|
1046
|
+
document.body.style.overflow = "hidden";
|
|
1047
|
+
return () => {
|
|
1048
|
+
document.body.style.overflow = prev;
|
|
1049
|
+
};
|
|
1050
|
+
}, [open]);
|
|
1051
|
+
const backdropClick = useCallback10(
|
|
1052
|
+
(ev) => {
|
|
1053
|
+
if (ev.target === ev.currentTarget) onClose();
|
|
1054
|
+
},
|
|
1055
|
+
[onClose]
|
|
1056
|
+
);
|
|
1057
|
+
if (!open || typeof window === "undefined") return null;
|
|
1058
|
+
return /* @__PURE__ */ jsx3(
|
|
1059
|
+
"div",
|
|
1060
|
+
{
|
|
1061
|
+
role: "dialog",
|
|
1062
|
+
"aria-modal": "true",
|
|
1063
|
+
"aria-label": "Sign in with Horus",
|
|
1064
|
+
style: { ...defaultBackdropStyle, ...backdropStyle },
|
|
1065
|
+
onClick: backdropClick,
|
|
1066
|
+
children: /* @__PURE__ */ jsx3("div", { style: { ...defaultDialogStyle, ...dialogStyle }, children: /* @__PURE__ */ jsx3(
|
|
1067
|
+
"iframe",
|
|
1068
|
+
{
|
|
1069
|
+
src: iframeSrc,
|
|
1070
|
+
title: "Horus authentication",
|
|
1071
|
+
allow: "publickey-credentials-get; publickey-credentials-create; clipboard-write",
|
|
1072
|
+
style: { width: "100%", height: "100%", border: "none" }
|
|
1073
|
+
}
|
|
1074
|
+
) })
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// src/components/HorusRevealModal.tsx
|
|
1080
|
+
import { useCallback as useCallback11, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
1081
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1082
|
+
var defaultBackdropStyle2 = {
|
|
1083
|
+
position: "fixed",
|
|
1084
|
+
inset: 0,
|
|
1085
|
+
background: "rgba(0, 0, 0, 0.5)",
|
|
1086
|
+
display: "flex",
|
|
1087
|
+
alignItems: "center",
|
|
1088
|
+
justifyContent: "center",
|
|
1089
|
+
zIndex: 2147483600
|
|
1090
|
+
};
|
|
1091
|
+
var defaultDialogStyle2 = {
|
|
1092
|
+
width: "480px",
|
|
1093
|
+
maxWidth: "95vw",
|
|
1094
|
+
height: "640px",
|
|
1095
|
+
maxHeight: "95vh",
|
|
1096
|
+
background: "#fff",
|
|
1097
|
+
borderRadius: "16px",
|
|
1098
|
+
boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
|
|
1099
|
+
overflow: "hidden"
|
|
1100
|
+
};
|
|
1101
|
+
function HorusRevealModal(props) {
|
|
1102
|
+
const { revealToken, open, onClose, onComplete, onError, dialogStyle, backdropStyle } = props;
|
|
1103
|
+
const { appId, authPageUrl, branding } = useHorusContext();
|
|
1104
|
+
const stateRef = useRef3("");
|
|
1105
|
+
if (stateRef.current === "" || !open) {
|
|
1106
|
+
stateRef.current = randomState();
|
|
1107
|
+
}
|
|
1108
|
+
const iframeSrc = useMemo3(() => {
|
|
1109
|
+
if (!open || !revealToken) return "";
|
|
1110
|
+
const baseUrl = buildAuthUrl(
|
|
1111
|
+
{
|
|
1112
|
+
flow: "reveal",
|
|
1113
|
+
appId,
|
|
1114
|
+
baseUrl: authPageUrl,
|
|
1115
|
+
mode: "iframe",
|
|
1116
|
+
branding
|
|
1117
|
+
},
|
|
1118
|
+
stateRef.current
|
|
1119
|
+
);
|
|
1120
|
+
const u = new URL(baseUrl);
|
|
1121
|
+
u.searchParams.set("token", revealToken);
|
|
1122
|
+
return u.toString();
|
|
1123
|
+
}, [open, revealToken, appId, authPageUrl, branding]);
|
|
1124
|
+
const expectedOrigin = useMemo3(() => {
|
|
1125
|
+
try {
|
|
1126
|
+
return new URL(authPageUrl).origin;
|
|
1127
|
+
} catch {
|
|
1128
|
+
return "";
|
|
1129
|
+
}
|
|
1130
|
+
}, [authPageUrl]);
|
|
1131
|
+
useEffect4(() => {
|
|
1132
|
+
if (!open) return;
|
|
1133
|
+
const onMessage = (ev) => {
|
|
1134
|
+
if (ev.origin !== expectedOrigin) return;
|
|
1135
|
+
const data = ev.data;
|
|
1136
|
+
if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
|
|
1137
|
+
if (data.state !== void 0 && data.state !== stateRef.current) return;
|
|
1138
|
+
const body = data.body;
|
|
1139
|
+
if (!body) return;
|
|
1140
|
+
switch (body.type) {
|
|
1141
|
+
case "reveal_complete":
|
|
1142
|
+
onComplete?.(Boolean(body.viewed));
|
|
1143
|
+
onClose();
|
|
1144
|
+
return;
|
|
1145
|
+
case "cancel":
|
|
1146
|
+
onClose();
|
|
1147
|
+
return;
|
|
1148
|
+
case "error":
|
|
1149
|
+
onError?.(new Error(body.message ?? "Reveal failed."));
|
|
1150
|
+
onClose();
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
window.addEventListener("message", onMessage);
|
|
1155
|
+
return () => window.removeEventListener("message", onMessage);
|
|
1156
|
+
}, [open, expectedOrigin, onComplete, onClose, onError]);
|
|
1157
|
+
useEffect4(() => {
|
|
1158
|
+
if (!open) return;
|
|
1159
|
+
const onKey = (ev) => {
|
|
1160
|
+
if (ev.key === "Escape") onClose();
|
|
1161
|
+
};
|
|
1162
|
+
window.addEventListener("keydown", onKey);
|
|
1163
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1164
|
+
}, [open, onClose]);
|
|
1165
|
+
useEffect4(() => {
|
|
1166
|
+
if (!open) return;
|
|
1167
|
+
if (typeof document === "undefined") return;
|
|
1168
|
+
const prev = document.body.style.overflow;
|
|
1169
|
+
document.body.style.overflow = "hidden";
|
|
1170
|
+
return () => {
|
|
1171
|
+
document.body.style.overflow = prev;
|
|
1172
|
+
};
|
|
1173
|
+
}, [open]);
|
|
1174
|
+
const backdropClick = useCallback11(
|
|
1175
|
+
(ev) => {
|
|
1176
|
+
if (ev.target === ev.currentTarget) onClose();
|
|
1177
|
+
},
|
|
1178
|
+
[onClose]
|
|
1179
|
+
);
|
|
1180
|
+
if (!open || !revealToken || typeof window === "undefined") return null;
|
|
1181
|
+
return /* @__PURE__ */ jsx4(
|
|
1182
|
+
"div",
|
|
1183
|
+
{
|
|
1184
|
+
role: "dialog",
|
|
1185
|
+
"aria-modal": "true",
|
|
1186
|
+
"aria-label": "Reveal wallet private key",
|
|
1187
|
+
style: { ...defaultBackdropStyle2, ...backdropStyle },
|
|
1188
|
+
onClick: backdropClick,
|
|
1189
|
+
children: /* @__PURE__ */ jsx4("div", { style: { ...defaultDialogStyle2, ...dialogStyle }, children: /* @__PURE__ */ jsx4(
|
|
1190
|
+
"iframe",
|
|
1191
|
+
{
|
|
1192
|
+
src: iframeSrc,
|
|
1193
|
+
title: "Horus wallet export",
|
|
1194
|
+
allow: "clipboard-write",
|
|
1195
|
+
style: { width: "100%", height: "100%", border: "none" }
|
|
1196
|
+
}
|
|
1197
|
+
) })
|
|
1198
|
+
}
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/hooks/useExportWallet.ts
|
|
1203
|
+
import { useCallback as useCallback12, useState as useState9 } from "react";
|
|
1204
|
+
function useExportWallet() {
|
|
1205
|
+
const { http } = useHorusContext();
|
|
1206
|
+
const [pending, setPending] = useState9(false);
|
|
1207
|
+
const [error, setError] = useState9(void 0);
|
|
1208
|
+
const reveal = useCallback12(
|
|
1209
|
+
async (input) => {
|
|
1210
|
+
setPending(true);
|
|
1211
|
+
setError(void 0);
|
|
1212
|
+
try {
|
|
1213
|
+
const response = await http.post(
|
|
1214
|
+
"/exportWallet/grant",
|
|
1215
|
+
{
|
|
1216
|
+
network: input.network,
|
|
1217
|
+
networkType: input.networkType,
|
|
1218
|
+
password: input.password,
|
|
1219
|
+
...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {}
|
|
1220
|
+
}
|
|
1221
|
+
);
|
|
1222
|
+
return response;
|
|
1223
|
+
} catch (err) {
|
|
1224
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1225
|
+
setError(e);
|
|
1226
|
+
throw e;
|
|
1227
|
+
} finally {
|
|
1228
|
+
setPending(false);
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
[http]
|
|
1232
|
+
);
|
|
1233
|
+
return { reveal, pending, error };
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/hooks/useMcp.ts
|
|
1237
|
+
import { useCallback as useCallback13, useEffect as useEffect5, useState as useState10 } from "react";
|
|
1238
|
+
function useGenerateMcpKey() {
|
|
1239
|
+
const { http, walletsVersion } = useHorusContext();
|
|
1240
|
+
void walletsVersion;
|
|
1241
|
+
const [pending, setPending] = useState10(false);
|
|
1242
|
+
const [error, setError] = useState10(void 0);
|
|
1243
|
+
const [lastResult, setLastResult] = useState10(null);
|
|
1244
|
+
const generate = useCallback13(
|
|
1245
|
+
async (input) => {
|
|
1246
|
+
setPending(true);
|
|
1247
|
+
setError(void 0);
|
|
1248
|
+
try {
|
|
1249
|
+
const response = await http.post("/mcp/keys/generate", {
|
|
1250
|
+
name: input.name,
|
|
1251
|
+
network: input.network,
|
|
1252
|
+
wallet_index: typeof input.walletIndex === "number" ? input.walletIndex : 0,
|
|
1253
|
+
expires_at: input.expiresAt,
|
|
1254
|
+
allowed_ips: input.allowedIps,
|
|
1255
|
+
allowed_tools: input.allowedTools,
|
|
1256
|
+
max_requests_per_minute: input.maxRequestsPerMinute,
|
|
1257
|
+
mcp_password: input.mcpPassword
|
|
1258
|
+
});
|
|
1259
|
+
setLastResult(response);
|
|
1260
|
+
return response;
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1263
|
+
setError(e);
|
|
1264
|
+
throw e;
|
|
1265
|
+
} finally {
|
|
1266
|
+
setPending(false);
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
[http]
|
|
1270
|
+
);
|
|
1271
|
+
return { generate, pending, error, lastResult, clearLastResult: () => setLastResult(null) };
|
|
1272
|
+
}
|
|
1273
|
+
function useMcpKeys() {
|
|
1274
|
+
const { http, state, mcpKeysVersion } = useHorusContext();
|
|
1275
|
+
const [keys, setKeys] = useState10([]);
|
|
1276
|
+
const [ready, setReady] = useState10(false);
|
|
1277
|
+
const [error, setError] = useState10(void 0);
|
|
1278
|
+
const load = useCallback13(async () => {
|
|
1279
|
+
if (state.status !== "authenticated") {
|
|
1280
|
+
setKeys([]);
|
|
1281
|
+
setReady(true);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
setReady(false);
|
|
1285
|
+
setError(void 0);
|
|
1286
|
+
try {
|
|
1287
|
+
const response = await http.get("/mcp/keys");
|
|
1288
|
+
setKeys(response?.keys ?? []);
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1291
|
+
} finally {
|
|
1292
|
+
setReady(true);
|
|
1293
|
+
}
|
|
1294
|
+
}, [http, state.status]);
|
|
1295
|
+
useEffect5(() => {
|
|
1296
|
+
void load();
|
|
1297
|
+
}, [load, mcpKeysVersion]);
|
|
1298
|
+
return { ready, keys, refresh: load, error };
|
|
1299
|
+
}
|
|
1300
|
+
function useRevokeMcpKey() {
|
|
1301
|
+
const { http, revalidateMcpKeys } = useHorusContext();
|
|
1302
|
+
const [pending, setPending] = useState10(false);
|
|
1303
|
+
const [error, setError] = useState10(void 0);
|
|
1304
|
+
const revoke = useCallback13(
|
|
1305
|
+
async (prefix) => {
|
|
1306
|
+
if (!prefix) throw new Error("useRevokeMcpKey: prefix is required");
|
|
1307
|
+
setPending(true);
|
|
1308
|
+
setError(void 0);
|
|
1309
|
+
try {
|
|
1310
|
+
const response = await http.del(
|
|
1311
|
+
`/mcp/keys/${encodeURIComponent(prefix)}`
|
|
1312
|
+
);
|
|
1313
|
+
revalidateMcpKeys();
|
|
1314
|
+
return response;
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1317
|
+
setError(e);
|
|
1318
|
+
throw e;
|
|
1319
|
+
} finally {
|
|
1320
|
+
setPending(false);
|
|
1321
|
+
}
|
|
1322
|
+
},
|
|
1323
|
+
[http, revalidateMcpKeys]
|
|
1324
|
+
);
|
|
1325
|
+
return { revoke, pending, error };
|
|
1326
|
+
}
|
|
1327
|
+
function useUpdateMcpKey() {
|
|
1328
|
+
const { http, revalidateMcpKeys } = useHorusContext();
|
|
1329
|
+
const [pending, setPending] = useState10(false);
|
|
1330
|
+
const [error, setError] = useState10(void 0);
|
|
1331
|
+
const update = useCallback13(
|
|
1332
|
+
async (prefix, updates) => {
|
|
1333
|
+
if (!prefix) throw new Error("useUpdateMcpKey: prefix is required");
|
|
1334
|
+
setPending(true);
|
|
1335
|
+
setError(void 0);
|
|
1336
|
+
try {
|
|
1337
|
+
const response = await http.put(
|
|
1338
|
+
`/mcp/keys/${encodeURIComponent(prefix)}`,
|
|
1339
|
+
{
|
|
1340
|
+
expires_at: updates.expiresAt,
|
|
1341
|
+
allowed_ips: updates.allowedIps,
|
|
1342
|
+
allowed_tools: updates.allowedTools,
|
|
1343
|
+
max_requests_per_minute: updates.maxRequestsPerMinute
|
|
1344
|
+
}
|
|
1345
|
+
);
|
|
1346
|
+
revalidateMcpKeys();
|
|
1347
|
+
return response;
|
|
1348
|
+
} catch (err) {
|
|
1349
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1350
|
+
setError(e);
|
|
1351
|
+
throw e;
|
|
1352
|
+
} finally {
|
|
1353
|
+
setPending(false);
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
[http, revalidateMcpKeys]
|
|
1357
|
+
);
|
|
1358
|
+
return { update, pending, error };
|
|
1359
|
+
}
|
|
639
1360
|
export {
|
|
1361
|
+
HorusAuthModal,
|
|
640
1362
|
HorusHttpError,
|
|
641
1363
|
HorusLoginButton,
|
|
642
1364
|
HorusProvider,
|
|
1365
|
+
HorusRevealModal,
|
|
1366
|
+
SUPPORTED_CHAINS,
|
|
1367
|
+
useChain,
|
|
1368
|
+
useCreateWallet,
|
|
1369
|
+
useExportWallet,
|
|
1370
|
+
useGenerateMcpKey,
|
|
643
1371
|
useHorusAuth,
|
|
1372
|
+
useMcpKeys,
|
|
1373
|
+
useRevokeMcpKey,
|
|
1374
|
+
useSendTransaction,
|
|
644
1375
|
useSignMessage,
|
|
1376
|
+
useSignTypedData,
|
|
1377
|
+
useSwitchChain,
|
|
645
1378
|
useTransfer,
|
|
1379
|
+
useUpdateMcpKey,
|
|
646
1380
|
useUser,
|
|
647
1381
|
useWallets
|
|
648
1382
|
};
|