@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/dist/index.cjs CHANGED
@@ -20,12 +20,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ HorusAuthModal: () => HorusAuthModal,
23
24
  HorusHttpError: () => HorusHttpError,
24
25
  HorusLoginButton: () => HorusLoginButton,
25
26
  HorusProvider: () => HorusProvider,
27
+ HorusRevealModal: () => HorusRevealModal,
28
+ SUPPORTED_CHAINS: () => SUPPORTED_CHAINS,
29
+ useChain: () => useChain,
30
+ useCreateWallet: () => useCreateWallet,
31
+ useExportWallet: () => useExportWallet,
32
+ useGenerateMcpKey: () => useGenerateMcpKey,
26
33
  useHorusAuth: () => useHorusAuth,
34
+ useMcpKeys: () => useMcpKeys,
35
+ useRevokeMcpKey: () => useRevokeMcpKey,
36
+ useSendTransaction: () => useSendTransaction,
27
37
  useSignMessage: () => useSignMessage,
38
+ useSignTypedData: () => useSignTypedData,
39
+ useSwitchChain: () => useSwitchChain,
28
40
  useTransfer: () => useTransfer,
41
+ useUpdateMcpKey: () => useUpdateMcpKey,
29
42
  useUser: () => useUser,
30
43
  useWallets: () => useWallets
31
44
  });
@@ -122,19 +135,21 @@ function makeHttp(deps) {
122
135
  return path.startsWith("/") ? `${base}${path}` : `${base}/${path}`;
123
136
  };
124
137
  async function rawRequest(method, path, body, options = {}) {
125
- const headers = { "Content-Type": "application/json" };
138
+ const headers = {
139
+ "Content-Type": "application/json",
140
+ "x-horus-key": deps.appId
141
+ };
126
142
  if (options.auth !== false) {
127
143
  const tokens = deps.getTokens();
128
144
  if (tokens?.idToken) {
129
- headers["x-horus-id-token"] = tokens.idToken;
145
+ headers["Authorization"] = `Bearer ${tokens.idToken}`;
130
146
  }
131
147
  }
132
148
  const res = await fetch(url(path), {
133
149
  method,
134
150
  headers,
135
151
  body: body !== void 0 ? JSON.stringify(body) : void 0,
136
- signal: options.signal,
137
- credentials: "same-origin"
152
+ signal: options.signal
138
153
  });
139
154
  const text = await res.text();
140
155
  let parsed = void 0;
@@ -175,9 +190,15 @@ function makeHttp(deps) {
175
190
 
176
191
  // src/HorusProvider.tsx
177
192
  var import_jsx_runtime = require("react/jsx-runtime");
178
- var DEFAULT_API_BASE = "/api/horus";
193
+ var DEFAULT_API_BASE = "https://api.horuswallet.com";
179
194
  var DEFAULT_AUTH_PAGE = "https://auth.horuswallet.com";
180
195
  var REFRESH_LEAD_SECONDS = 60;
196
+ var DEFAULT_AUTO_PROVISION = {
197
+ network: "ETHEREUM",
198
+ networkType: "MAINNET"
199
+ };
200
+ var DEFAULT_CHAIN = { network: "ETHEREUM", networkType: "MAINNET" };
201
+ var AUTO_PROVISION_STORAGE_KEY = "horus.autoProvisioned.localIds";
181
202
  function HorusProvider(props) {
182
203
  const {
183
204
  appId,
@@ -186,11 +207,25 @@ function HorusProvider(props) {
186
207
  branding,
187
208
  tokenStorage = "localStorage",
188
209
  autoRefresh = true,
210
+ autoProvisionWallet = DEFAULT_AUTO_PROVISION,
211
+ defaultChain = DEFAULT_CHAIN,
189
212
  children
190
213
  } = props;
191
214
  const tokenStoreRef = (0, import_react2.useRef)(createTokenStore(tokenStorage));
192
215
  const tokensRef = (0, import_react2.useRef)(null);
193
216
  const [state, setState] = (0, import_react2.useState)({ status: "loading" });
217
+ const [walletsVersion, setWalletsVersion] = (0, import_react2.useState)(0);
218
+ const revalidateWallets = (0, import_react2.useCallback)(() => {
219
+ setWalletsVersion((v) => v + 1);
220
+ }, []);
221
+ const [currentChain, setCurrentChain] = (0, import_react2.useState)(defaultChain);
222
+ const setChain = (0, import_react2.useCallback)((chain) => {
223
+ setCurrentChain(chain);
224
+ }, []);
225
+ const [mcpKeysVersion, setMcpKeysVersion] = (0, import_react2.useState)(0);
226
+ const revalidateMcpKeys = (0, import_react2.useCallback)(() => {
227
+ setMcpKeysVersion((v) => v + 1);
228
+ }, []);
194
229
  const setTokens = (0, import_react2.useCallback)((tokens) => {
195
230
  tokensRef.current = tokens;
196
231
  if (tokens) {
@@ -211,9 +246,11 @@ function HorusProvider(props) {
211
246
  if (!cur?.refreshToken) throw new Error("no refresh token cached");
212
247
  const fresh = await fetch(joinUrl(apiBase, "/auth/refresh"), {
213
248
  method: "POST",
214
- headers: { "Content-Type": "application/json" },
215
- body: JSON.stringify({ refreshToken: cur.refreshToken }),
216
- credentials: "same-origin"
249
+ headers: {
250
+ "Content-Type": "application/json",
251
+ "x-horus-key": appId
252
+ },
253
+ body: JSON.stringify({ refreshToken: cur.refreshToken })
217
254
  });
218
255
  if (!fresh.ok) throw new Error(`refresh failed: ${fresh.status}`);
219
256
  const raw = await fresh.json();
@@ -223,11 +260,12 @@ function HorusProvider(props) {
223
260
  };
224
261
  return makeHttp({
225
262
  apiBase,
263
+ appId,
226
264
  getTokens: () => tokensRef.current,
227
265
  refreshTokens: refreshOnce,
228
266
  autoRefresh
229
267
  });
230
- }, [apiBase, autoRefresh, setTokens]);
268
+ }, [apiBase, appId, autoRefresh, setTokens]);
231
269
  (0, import_react2.useEffect)(() => {
232
270
  const stored = tokenStoreRef.current.read();
233
271
  if (stored) {
@@ -241,6 +279,58 @@ function HorusProvider(props) {
241
279
  setState({ status: "unauthenticated" });
242
280
  }
243
281
  }, []);
282
+ (0, import_react2.useEffect)(() => {
283
+ if (typeof window === "undefined") return;
284
+ const params = new URLSearchParams(window.location.search);
285
+ const mode = params.get("mode");
286
+ const oobCode = params.get("oobCode");
287
+ if (mode !== "signIn" || !oobCode) return;
288
+ if (consumedMagicLinkCodes.has(oobCode)) return;
289
+ consumedMagicLinkCodes.add(oobCode);
290
+ const email = params.get("email") ?? (typeof window.localStorage !== "undefined" ? window.localStorage.getItem("horus.signinEmail") : null) ?? "";
291
+ if (!email) {
292
+ console.warn(
293
+ "@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."
294
+ );
295
+ return;
296
+ }
297
+ void (async () => {
298
+ try {
299
+ const stripUrlParams = () => {
300
+ const u = new URL(window.location.href);
301
+ ["mode", "oobCode", "apiKey", "continueUrl", "lang", "email"].forEach(
302
+ (k) => u.searchParams.delete(k)
303
+ );
304
+ window.history.replaceState({}, "", u.toString());
305
+ if (window.localStorage) {
306
+ window.localStorage.removeItem("horus.signinEmail");
307
+ }
308
+ };
309
+ const raw = await fetch(joinUrl(apiBase, "/auth/email-link/verify"), {
310
+ method: "POST",
311
+ headers: {
312
+ "Content-Type": "application/json",
313
+ "x-horus-key": appId
314
+ },
315
+ body: JSON.stringify({ email, oobCode })
316
+ });
317
+ if (!raw.ok) {
318
+ const body = await raw.text();
319
+ console.warn(
320
+ `@horus-wallet/sdk-react: magic-link verify failed (HTTP ${raw.status}): ${body}`
321
+ );
322
+ stripUrlParams();
323
+ return;
324
+ }
325
+ const json = await raw.json();
326
+ const stamped = stampExpiry(json);
327
+ setTokens(stamped);
328
+ stripUrlParams();
329
+ } catch (err) {
330
+ console.warn("@horus-wallet/sdk-react: magic-link verify threw", err);
331
+ }
332
+ })();
333
+ }, [apiBase, appId, setTokens]);
244
334
  (0, import_react2.useEffect)(() => {
245
335
  if (typeof window === "undefined") return;
246
336
  const onStorage = (ev) => {
@@ -266,6 +356,27 @@ function HorusProvider(props) {
266
356
  }, refreshIn);
267
357
  return () => clearTimeout(handle);
268
358
  }, [state, autoRefresh, http, setTokens]);
359
+ (0, import_react2.useEffect)(() => {
360
+ if (state.status !== "authenticated") return;
361
+ if (!autoProvisionWallet) return;
362
+ const localId = state.tokens.localId;
363
+ if (!localId) return;
364
+ if (alreadyAutoProvisioned(localId)) return;
365
+ let cancelled = false;
366
+ (async () => {
367
+ try {
368
+ await http.post("/createWallet", autoProvisionWallet);
369
+ if (cancelled) return;
370
+ markAutoProvisioned(localId);
371
+ revalidateWallets();
372
+ } catch (err) {
373
+ console.warn("@horus-wallet/sdk-react: auto-provision wallet failed", err);
374
+ }
375
+ })();
376
+ return () => {
377
+ cancelled = true;
378
+ };
379
+ }, [state, autoProvisionWallet, http, revalidateWallets]);
269
380
  const ctx = (0, import_react2.useMemo)(
270
381
  () => ({
271
382
  state,
@@ -286,9 +397,28 @@ function HorusProvider(props) {
286
397
  const stamped = stampExpiry(raw);
287
398
  setTokens(stamped);
288
399
  return stamped;
289
- }
400
+ },
401
+ walletsVersion,
402
+ revalidateWallets,
403
+ currentChain,
404
+ setChain,
405
+ mcpKeysVersion,
406
+ revalidateMcpKeys
290
407
  }),
291
- [state, http, authPageUrl, appId, branding, setTokens]
408
+ [
409
+ state,
410
+ http,
411
+ authPageUrl,
412
+ appId,
413
+ branding,
414
+ setTokens,
415
+ walletsVersion,
416
+ revalidateWallets,
417
+ currentChain,
418
+ setChain,
419
+ mcpKeysVersion,
420
+ revalidateMcpKeys
421
+ ]
292
422
  );
293
423
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HorusContext.Provider, { value: ctx, children });
294
424
  }
@@ -302,6 +432,37 @@ function userFromTokens(t) {
302
432
  providerId: t.providerId
303
433
  };
304
434
  }
435
+ var consumedMagicLinkCodes = /* @__PURE__ */ new Set();
436
+ var memoryAutoProvisioned = /* @__PURE__ */ new Set();
437
+ function readAutoProvisionedSet() {
438
+ if (typeof window === "undefined" || !window.localStorage) {
439
+ return memoryAutoProvisioned;
440
+ }
441
+ try {
442
+ const raw = window.localStorage.getItem(AUTO_PROVISION_STORAGE_KEY);
443
+ if (!raw) return /* @__PURE__ */ new Set();
444
+ const parsed = JSON.parse(raw);
445
+ return new Set(Array.isArray(parsed) ? parsed : []);
446
+ } catch {
447
+ return /* @__PURE__ */ new Set();
448
+ }
449
+ }
450
+ function alreadyAutoProvisioned(localId) {
451
+ return readAutoProvisionedSet().has(localId);
452
+ }
453
+ function markAutoProvisioned(localId) {
454
+ if (typeof window === "undefined" || !window.localStorage) {
455
+ memoryAutoProvisioned.add(localId);
456
+ return;
457
+ }
458
+ try {
459
+ const set = readAutoProvisionedSet();
460
+ set.add(localId);
461
+ window.localStorage.setItem(AUTO_PROVISION_STORAGE_KEY, JSON.stringify([...set]));
462
+ } catch {
463
+ memoryAutoProvisioned.add(localId);
464
+ }
465
+ }
305
466
  function joinUrl(base, path) {
306
467
  const b = base.endsWith("/") ? base.slice(0, -1) : base;
307
468
  const p = path.startsWith("/") ? path : `/${path}`;
@@ -310,6 +471,39 @@ function joinUrl(base, path) {
310
471
 
311
472
  // src/hooks/useHorusAuth.ts
312
473
  var import_react3 = require("react");
474
+
475
+ // src/internal/authUrl.ts
476
+ function buildAuthUrl(p, state) {
477
+ const url = new URL(p.baseUrl);
478
+ url.searchParams.set("flow", p.flow);
479
+ url.searchParams.set("origin", window.location.origin);
480
+ url.searchParams.set("state", state);
481
+ url.searchParams.set("appKey", p.appId);
482
+ url.searchParams.set("mode", p.mode);
483
+ if (p.phone) url.searchParams.set("phone", p.phone);
484
+ if (p.flow === "unified" && p.enabledMethods && p.enabledMethods.length > 0) {
485
+ url.searchParams.set("methods", p.enabledMethods.join(","));
486
+ }
487
+ const b = p.branding;
488
+ if (b) {
489
+ if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
490
+ if (b.brandName) url.searchParams.set("b_name", b.brandName);
491
+ if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
492
+ if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
493
+ if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
494
+ if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
495
+ if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
496
+ if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
497
+ }
498
+ return url.toString();
499
+ }
500
+ function randomState() {
501
+ const bytes = new Uint8Array(16);
502
+ crypto.getRandomValues(bytes);
503
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
504
+ }
505
+
506
+ // src/hooks/useHorusAuth.ts
313
507
  function useHorusAuth() {
314
508
  const ctx = useHorusContext();
315
509
  const { state, http, signIn, signOut, refreshTokens, authPageUrl, appId, branding } = ctx;
@@ -409,7 +603,7 @@ function openPopupFlow(params) {
409
603
  return;
410
604
  }
411
605
  const state = randomState();
412
- const url = buildAuthUrl(params, state);
606
+ const url = buildAuthUrl({ ...params, mode: "popup" }, state);
413
607
  const popup = window.open(url, "horus-auth", defaultPopupFeatures());
414
608
  if (!popup) {
415
609
  reject(new Error("Popup blocked \u2014 call from a click handler so the browser allows it."));
@@ -417,6 +611,7 @@ function openPopupFlow(params) {
417
611
  }
418
612
  const expectedOrigin = new URL(params.baseUrl).origin;
419
613
  let settled = false;
614
+ let lastError = null;
420
615
  const cleanup = () => {
421
616
  window.removeEventListener("message", onMessage);
422
617
  clearInterval(poll);
@@ -447,17 +642,11 @@ function openPopupFlow(params) {
447
642
  popup.close();
448
643
  } catch {
449
644
  }
450
- reject(new Error("User cancelled sign-in."));
645
+ reject(new Error(lastError ?? "User cancelled sign-in."));
451
646
  return;
452
647
  case "error":
453
648
  if (settled) return;
454
- settled = true;
455
- cleanup();
456
- try {
457
- popup.close();
458
- } catch {
459
- }
460
- reject(new Error(body.message || "Sign-in failed."));
649
+ lastError = body.message || "Sign-in failed.";
461
650
  return;
462
651
  }
463
652
  };
@@ -466,31 +655,11 @@ function openPopupFlow(params) {
466
655
  if (popup.closed && !settled) {
467
656
  settled = true;
468
657
  cleanup();
469
- reject(new Error("Auth popup was closed before sign-in completed."));
658
+ reject(new Error(lastError ?? "Auth popup was closed before sign-in completed."));
470
659
  }
471
660
  }, 400);
472
661
  });
473
662
  }
474
- function buildAuthUrl(p, state) {
475
- const url = new URL(p.baseUrl);
476
- url.searchParams.set("flow", p.flow);
477
- url.searchParams.set("origin", window.location.origin);
478
- url.searchParams.set("state", state);
479
- url.searchParams.set("appKey", p.appId);
480
- if (p.phone) url.searchParams.set("phone", p.phone);
481
- const b = p.branding;
482
- if (b) {
483
- if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
484
- if (b.brandName) url.searchParams.set("b_name", b.brandName);
485
- if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
486
- if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
487
- if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
488
- if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
489
- if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
490
- if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
491
- }
492
- return url.toString();
493
- }
494
663
  function defaultPopupFeatures() {
495
664
  const w = 480, h = 640;
496
665
  if (typeof window === "undefined") return `width=${w},height=${h}`;
@@ -498,11 +667,6 @@ function defaultPopupFeatures() {
498
667
  const top = Math.max(0, (window.innerHeight - h) / 2 + (window.screenY ?? 0));
499
668
  return `width=${w},height=${h},left=${left},top=${top},popup=1`;
500
669
  }
501
- function randomState() {
502
- const bytes = new Uint8Array(16);
503
- crypto.getRandomValues(bytes);
504
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
505
- }
506
670
 
507
671
  // src/hooks/useUser.ts
508
672
  function useUser() {
@@ -513,7 +677,7 @@ function useUser() {
513
677
  // src/hooks/useWallets.ts
514
678
  var import_react4 = require("react");
515
679
  function useWallets() {
516
- const { http, state } = useHorusContext();
680
+ const { http, state, walletsVersion } = useHorusContext();
517
681
  const [wallets, setWallets] = (0, import_react4.useState)([]);
518
682
  const [ready, setReady] = (0, import_react4.useState)(false);
519
683
  const [error, setError] = (0, import_react4.useState)(void 0);
@@ -527,7 +691,7 @@ function useWallets() {
527
691
  setError(void 0);
528
692
  try {
529
693
  const response = await http.get(
530
- "/wallets"
694
+ "/getWallet"
531
695
  );
532
696
  const flat = [];
533
697
  for (const [network, group] of Object.entries(response.wallets ?? {})) {
@@ -544,22 +708,94 @@ function useWallets() {
544
708
  }, [http, state.status]);
545
709
  (0, import_react4.useEffect)(() => {
546
710
  void load();
547
- }, [load]);
711
+ }, [load, walletsVersion]);
548
712
  return { ready, wallets, refresh: load, error };
549
713
  }
550
714
 
551
- // src/hooks/useSignMessage.ts
715
+ // src/hooks/useCreateWallet.ts
552
716
  var import_react5 = require("react");
553
- function useSignMessage() {
554
- const { http } = useHorusContext();
717
+ function useCreateWallet() {
718
+ const { http, revalidateWallets } = useHorusContext();
555
719
  const [pending, setPending] = (0, import_react5.useState)(false);
556
720
  const [error, setError] = (0, import_react5.useState)(void 0);
557
- const signMessage = (0, import_react5.useCallback)(
721
+ const create = (0, import_react5.useCallback)(
722
+ async (input) => {
723
+ if (pending) {
724
+ const err = new Error(
725
+ "useCreateWallet: a previous create() call is still in flight"
726
+ );
727
+ setError(err);
728
+ throw err;
729
+ }
730
+ setPending(true);
731
+ setError(void 0);
732
+ try {
733
+ const response = await http.post("/createWallet", {
734
+ network: input.network,
735
+ networkType: input.networkType,
736
+ ...input.password ? { password: input.password } : {}
737
+ });
738
+ revalidateWallets();
739
+ return response;
740
+ } catch (err) {
741
+ const e = err instanceof Error ? err : new Error(String(err));
742
+ setError(e);
743
+ throw e;
744
+ } finally {
745
+ setPending(false);
746
+ }
747
+ },
748
+ [http, revalidateWallets, pending]
749
+ );
750
+ return { create, pending, error };
751
+ }
752
+
753
+ // src/hooks/useSwitchChain.ts
754
+ var import_react6 = require("react");
755
+ var SUPPORTED_CHAINS = [
756
+ // EVM-family chains the backend accepts as `network` selectors.
757
+ // `EVM` itself is intentionally NOT here — it's a family label, not
758
+ // a chain, and the API rejects it on /createWallet, /signMessage, etc.
759
+ // Pick a specific chain when interacting with the API.
760
+ "ETHEREUM",
761
+ "BASE",
762
+ "POLYGON",
763
+ "BSC",
764
+ "ARBITRUM",
765
+ "OPTIMISM",
766
+ "TELOS",
767
+ "CHILIZ",
768
+ "FLARE",
769
+ // Non-EVM chains.
770
+ "BITCOIN",
771
+ "ICP",
772
+ "CASPER",
773
+ "AETERNITY"
774
+ ];
775
+ function useSwitchChain() {
776
+ const { currentChain, setChain } = useHorusContext();
777
+ const switchChain = (0, import_react6.useCallback)(
778
+ (chain) => {
779
+ setChain(chain);
780
+ },
781
+ [setChain]
782
+ );
783
+ return { chain: currentChain, switchChain, supportedChains: SUPPORTED_CHAINS };
784
+ }
785
+ var useChain = useSwitchChain;
786
+
787
+ // src/hooks/useSignMessage.ts
788
+ var import_react7 = require("react");
789
+ function useSignMessage() {
790
+ const { http } = useHorusContext();
791
+ const [pending, setPending] = (0, import_react7.useState)(false);
792
+ const [error, setError] = (0, import_react7.useState)(void 0);
793
+ const signMessage = (0, import_react7.useCallback)(
558
794
  async (input) => {
559
795
  setPending(true);
560
796
  setError(void 0);
561
797
  try {
562
- const response = await http.post("/sign-message", input);
798
+ const response = await http.post("/signMessage", input);
563
799
  return response.signature;
564
800
  } catch (err) {
565
801
  const e = err instanceof Error ? err : new Error(String(err));
@@ -574,13 +810,79 @@ function useSignMessage() {
574
810
  return { signMessage, pending, error };
575
811
  }
576
812
 
813
+ // src/hooks/useSignTypedData.ts
814
+ var import_react8 = require("react");
815
+ function useSignTypedData() {
816
+ const { http } = useHorusContext();
817
+ const [pending, setPending] = (0, import_react8.useState)(false);
818
+ const [error, setError] = (0, import_react8.useState)(void 0);
819
+ const signTypedData = (0, import_react8.useCallback)(
820
+ async (input) => {
821
+ setPending(true);
822
+ setError(void 0);
823
+ try {
824
+ const response = await http.post("/signTypedData", {
825
+ network: input.network,
826
+ networkType: input.networkType,
827
+ typedData: input.typedData,
828
+ ...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
829
+ ...input.password ? { password: input.password } : {}
830
+ });
831
+ return response.signature;
832
+ } catch (err) {
833
+ const e = err instanceof Error ? err : new Error(String(err));
834
+ setError(e);
835
+ throw e;
836
+ } finally {
837
+ setPending(false);
838
+ }
839
+ },
840
+ [http]
841
+ );
842
+ return { signTypedData, pending, error };
843
+ }
844
+
845
+ // src/hooks/useSendTransaction.ts
846
+ var import_react9 = require("react");
847
+ function useSendTransaction() {
848
+ const { http } = useHorusContext();
849
+ const [pending, setPending] = (0, import_react9.useState)(false);
850
+ const [error, setError] = (0, import_react9.useState)(void 0);
851
+ const sendTransaction = (0, import_react9.useCallback)(
852
+ async (input) => {
853
+ setPending(true);
854
+ setError(void 0);
855
+ try {
856
+ return await http.post("/sendTransaction", {
857
+ network: input.network,
858
+ networkType: input.networkType,
859
+ to: input.to,
860
+ ...input.value !== void 0 ? { value: String(input.value) } : {},
861
+ ...input.data !== void 0 ? { data: input.data } : {},
862
+ ...input.gasLimit !== void 0 ? { gasLimit: String(input.gasLimit) } : {},
863
+ ...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
864
+ ...input.password ? { password: input.password } : {}
865
+ });
866
+ } catch (err) {
867
+ const e = err instanceof Error ? err : new Error(String(err));
868
+ setError(e);
869
+ throw e;
870
+ } finally {
871
+ setPending(false);
872
+ }
873
+ },
874
+ [http]
875
+ );
876
+ return { sendTransaction, pending, error };
877
+ }
878
+
577
879
  // src/hooks/useTransfer.ts
578
- var import_react6 = require("react");
880
+ var import_react10 = require("react");
579
881
  function useTransfer() {
580
882
  const { http } = useHorusContext();
581
- const [pending, setPending] = (0, import_react6.useState)(false);
582
- const [error, setError] = (0, import_react6.useState)(void 0);
583
- const wrap = (0, import_react6.useCallback)(
883
+ const [pending, setPending] = (0, import_react10.useState)(false);
884
+ const [error, setError] = (0, import_react10.useState)(void 0);
885
+ const wrap = (0, import_react10.useCallback)(
584
886
  async (fn) => {
585
887
  setPending(true);
586
888
  setError(void 0);
@@ -596,18 +898,18 @@ function useTransfer() {
596
898
  },
597
899
  []
598
900
  );
599
- const native = (0, import_react6.useCallback)(
901
+ const native = (0, import_react10.useCallback)(
600
902
  (input) => wrap(
601
- () => http.post("/transfer/native", {
903
+ () => http.post("/nativeTransfer", {
602
904
  ...input,
603
905
  amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
604
906
  })
605
907
  ),
606
908
  [http, wrap]
607
909
  );
608
- const token = (0, import_react6.useCallback)(
910
+ const token = (0, import_react10.useCallback)(
609
911
  (input) => wrap(
610
- () => http.post("/transfer/token", {
912
+ () => http.post("/tokenTransfer", {
611
913
  ...input,
612
914
  amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
613
915
  })
@@ -618,7 +920,7 @@ function useTransfer() {
618
920
  }
619
921
 
620
922
  // src/components/HorusLoginButton.tsx
621
- var import_react7 = require("react");
923
+ var import_react11 = require("react");
622
924
  var import_jsx_runtime2 = require("react/jsx-runtime");
623
925
  function HorusLoginButton({
624
926
  flow = "google",
@@ -631,7 +933,7 @@ function HorusLoginButton({
631
933
  ...rest
632
934
  }) {
633
935
  const auth = useHorusAuth();
634
- const [busy, setBusy] = (0, import_react7.useState)(false);
936
+ const [busy, setBusy] = (0, import_react11.useState)(false);
635
937
  const onClick = async () => {
636
938
  if (busy) return;
637
939
  setBusy(true);
@@ -669,14 +971,459 @@ function HorusLoginButton({
669
971
  }
670
972
  );
671
973
  }
974
+
975
+ // src/components/HorusAuthModal.tsx
976
+ var import_react12 = require("react");
977
+ var import_jsx_runtime3 = require("react/jsx-runtime");
978
+ var defaultBackdropStyle = {
979
+ position: "fixed",
980
+ inset: 0,
981
+ background: "rgba(0, 0, 0, 0.5)",
982
+ display: "flex",
983
+ alignItems: "center",
984
+ justifyContent: "center",
985
+ zIndex: 2147483600
986
+ };
987
+ var defaultDialogStyle = {
988
+ width: "480px",
989
+ maxWidth: "95vw",
990
+ height: "640px",
991
+ maxHeight: "95vh",
992
+ background: "#fff",
993
+ borderRadius: "16px",
994
+ boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
995
+ overflow: "hidden"
996
+ };
997
+ function HorusAuthModal(props) {
998
+ const {
999
+ flow,
1000
+ enabledMethods,
1001
+ phone,
1002
+ open,
1003
+ onClose,
1004
+ onSuccess,
1005
+ onError,
1006
+ dialogStyle,
1007
+ backdropStyle
1008
+ } = props;
1009
+ const { appId, authPageUrl, branding, signIn } = useHorusContext();
1010
+ const stateRef = (0, import_react12.useRef)("");
1011
+ if (stateRef.current === "" || !open) {
1012
+ stateRef.current = randomState();
1013
+ }
1014
+ const iframeSrc = (0, import_react12.useMemo)(() => {
1015
+ if (!open) return "";
1016
+ return buildAuthUrl(
1017
+ {
1018
+ flow,
1019
+ appId,
1020
+ baseUrl: authPageUrl,
1021
+ mode: "iframe",
1022
+ branding,
1023
+ phone,
1024
+ enabledMethods
1025
+ },
1026
+ stateRef.current
1027
+ );
1028
+ }, [open, flow, appId, authPageUrl, branding, phone, enabledMethods]);
1029
+ const expectedOrigin = (0, import_react12.useMemo)(() => {
1030
+ try {
1031
+ return new URL(authPageUrl).origin;
1032
+ } catch {
1033
+ return "";
1034
+ }
1035
+ }, [authPageUrl]);
1036
+ (0, import_react12.useEffect)(() => {
1037
+ if (!open) return;
1038
+ const onMessage = (ev) => {
1039
+ if (ev.origin !== expectedOrigin) return;
1040
+ const data = ev.data;
1041
+ if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
1042
+ if (data.state !== void 0 && data.state !== stateRef.current) return;
1043
+ const body = data.body;
1044
+ if (!body) return;
1045
+ switch (body.type) {
1046
+ case "success": {
1047
+ const stamped = stampExpiry({
1048
+ idToken: body.idToken,
1049
+ refreshToken: "",
1050
+ expiresIn: body.expiresAt ? Math.max(60, body.expiresAt - Math.floor(Date.now() / 1e3)) : 3600,
1051
+ localId: body.user?.uid ?? "",
1052
+ email: body.user?.email,
1053
+ displayName: body.user?.displayName,
1054
+ photoUrl: body.user?.photoURL,
1055
+ providerId: body.user?.providerId
1056
+ });
1057
+ signIn(stamped);
1058
+ onSuccess?.({
1059
+ uid: stamped.localId,
1060
+ email: stamped.email,
1061
+ emailVerified: stamped.emailVerified,
1062
+ displayName: stamped.displayName,
1063
+ photoUrl: stamped.photoUrl,
1064
+ providerId: stamped.providerId
1065
+ });
1066
+ onClose();
1067
+ return;
1068
+ }
1069
+ case "cancel":
1070
+ onClose();
1071
+ return;
1072
+ case "error":
1073
+ onError?.(new Error(body.message ?? "Sign-in failed."));
1074
+ return;
1075
+ }
1076
+ };
1077
+ window.addEventListener("message", onMessage);
1078
+ return () => window.removeEventListener("message", onMessage);
1079
+ }, [open, expectedOrigin, signIn, onSuccess, onError, onClose]);
1080
+ (0, import_react12.useEffect)(() => {
1081
+ if (!open) return;
1082
+ const onKey = (ev) => {
1083
+ if (ev.key === "Escape") onClose();
1084
+ };
1085
+ window.addEventListener("keydown", onKey);
1086
+ return () => window.removeEventListener("keydown", onKey);
1087
+ }, [open, onClose]);
1088
+ (0, import_react12.useEffect)(() => {
1089
+ if (!open) return;
1090
+ if (typeof document === "undefined") return;
1091
+ const prev = document.body.style.overflow;
1092
+ document.body.style.overflow = "hidden";
1093
+ return () => {
1094
+ document.body.style.overflow = prev;
1095
+ };
1096
+ }, [open]);
1097
+ const backdropClick = (0, import_react12.useCallback)(
1098
+ (ev) => {
1099
+ if (ev.target === ev.currentTarget) onClose();
1100
+ },
1101
+ [onClose]
1102
+ );
1103
+ if (!open || typeof window === "undefined") return null;
1104
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1105
+ "div",
1106
+ {
1107
+ role: "dialog",
1108
+ "aria-modal": "true",
1109
+ "aria-label": "Sign in with Horus",
1110
+ style: { ...defaultBackdropStyle, ...backdropStyle },
1111
+ onClick: backdropClick,
1112
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...defaultDialogStyle, ...dialogStyle }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1113
+ "iframe",
1114
+ {
1115
+ src: iframeSrc,
1116
+ title: "Horus authentication",
1117
+ allow: "publickey-credentials-get; publickey-credentials-create; clipboard-write",
1118
+ style: { width: "100%", height: "100%", border: "none" }
1119
+ }
1120
+ ) })
1121
+ }
1122
+ );
1123
+ }
1124
+
1125
+ // src/components/HorusRevealModal.tsx
1126
+ var import_react13 = require("react");
1127
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1128
+ var defaultBackdropStyle2 = {
1129
+ position: "fixed",
1130
+ inset: 0,
1131
+ background: "rgba(0, 0, 0, 0.5)",
1132
+ display: "flex",
1133
+ alignItems: "center",
1134
+ justifyContent: "center",
1135
+ zIndex: 2147483600
1136
+ };
1137
+ var defaultDialogStyle2 = {
1138
+ width: "480px",
1139
+ maxWidth: "95vw",
1140
+ height: "640px",
1141
+ maxHeight: "95vh",
1142
+ background: "#fff",
1143
+ borderRadius: "16px",
1144
+ boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
1145
+ overflow: "hidden"
1146
+ };
1147
+ function HorusRevealModal(props) {
1148
+ const { revealToken, open, onClose, onComplete, onError, dialogStyle, backdropStyle } = props;
1149
+ const { appId, authPageUrl, branding } = useHorusContext();
1150
+ const stateRef = (0, import_react13.useRef)("");
1151
+ if (stateRef.current === "" || !open) {
1152
+ stateRef.current = randomState();
1153
+ }
1154
+ const iframeSrc = (0, import_react13.useMemo)(() => {
1155
+ if (!open || !revealToken) return "";
1156
+ const baseUrl = buildAuthUrl(
1157
+ {
1158
+ flow: "reveal",
1159
+ appId,
1160
+ baseUrl: authPageUrl,
1161
+ mode: "iframe",
1162
+ branding
1163
+ },
1164
+ stateRef.current
1165
+ );
1166
+ const u = new URL(baseUrl);
1167
+ u.searchParams.set("token", revealToken);
1168
+ return u.toString();
1169
+ }, [open, revealToken, appId, authPageUrl, branding]);
1170
+ const expectedOrigin = (0, import_react13.useMemo)(() => {
1171
+ try {
1172
+ return new URL(authPageUrl).origin;
1173
+ } catch {
1174
+ return "";
1175
+ }
1176
+ }, [authPageUrl]);
1177
+ (0, import_react13.useEffect)(() => {
1178
+ if (!open) return;
1179
+ const onMessage = (ev) => {
1180
+ if (ev.origin !== expectedOrigin) return;
1181
+ const data = ev.data;
1182
+ if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
1183
+ if (data.state !== void 0 && data.state !== stateRef.current) return;
1184
+ const body = data.body;
1185
+ if (!body) return;
1186
+ switch (body.type) {
1187
+ case "reveal_complete":
1188
+ onComplete?.(Boolean(body.viewed));
1189
+ onClose();
1190
+ return;
1191
+ case "cancel":
1192
+ onClose();
1193
+ return;
1194
+ case "error":
1195
+ onError?.(new Error(body.message ?? "Reveal failed."));
1196
+ onClose();
1197
+ return;
1198
+ }
1199
+ };
1200
+ window.addEventListener("message", onMessage);
1201
+ return () => window.removeEventListener("message", onMessage);
1202
+ }, [open, expectedOrigin, onComplete, onClose, onError]);
1203
+ (0, import_react13.useEffect)(() => {
1204
+ if (!open) return;
1205
+ const onKey = (ev) => {
1206
+ if (ev.key === "Escape") onClose();
1207
+ };
1208
+ window.addEventListener("keydown", onKey);
1209
+ return () => window.removeEventListener("keydown", onKey);
1210
+ }, [open, onClose]);
1211
+ (0, import_react13.useEffect)(() => {
1212
+ if (!open) return;
1213
+ if (typeof document === "undefined") return;
1214
+ const prev = document.body.style.overflow;
1215
+ document.body.style.overflow = "hidden";
1216
+ return () => {
1217
+ document.body.style.overflow = prev;
1218
+ };
1219
+ }, [open]);
1220
+ const backdropClick = (0, import_react13.useCallback)(
1221
+ (ev) => {
1222
+ if (ev.target === ev.currentTarget) onClose();
1223
+ },
1224
+ [onClose]
1225
+ );
1226
+ if (!open || !revealToken || typeof window === "undefined") return null;
1227
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1228
+ "div",
1229
+ {
1230
+ role: "dialog",
1231
+ "aria-modal": "true",
1232
+ "aria-label": "Reveal wallet private key",
1233
+ style: { ...defaultBackdropStyle2, ...backdropStyle },
1234
+ onClick: backdropClick,
1235
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { ...defaultDialogStyle2, ...dialogStyle }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1236
+ "iframe",
1237
+ {
1238
+ src: iframeSrc,
1239
+ title: "Horus wallet export",
1240
+ allow: "clipboard-write",
1241
+ style: { width: "100%", height: "100%", border: "none" }
1242
+ }
1243
+ ) })
1244
+ }
1245
+ );
1246
+ }
1247
+
1248
+ // src/hooks/useExportWallet.ts
1249
+ var import_react14 = require("react");
1250
+ function useExportWallet() {
1251
+ const { http } = useHorusContext();
1252
+ const [pending, setPending] = (0, import_react14.useState)(false);
1253
+ const [error, setError] = (0, import_react14.useState)(void 0);
1254
+ const reveal = (0, import_react14.useCallback)(
1255
+ async (input) => {
1256
+ setPending(true);
1257
+ setError(void 0);
1258
+ try {
1259
+ const response = await http.post(
1260
+ "/exportWallet/grant",
1261
+ {
1262
+ network: input.network,
1263
+ networkType: input.networkType,
1264
+ password: input.password,
1265
+ ...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {}
1266
+ }
1267
+ );
1268
+ return response;
1269
+ } catch (err) {
1270
+ const e = err instanceof Error ? err : new Error(String(err));
1271
+ setError(e);
1272
+ throw e;
1273
+ } finally {
1274
+ setPending(false);
1275
+ }
1276
+ },
1277
+ [http]
1278
+ );
1279
+ return { reveal, pending, error };
1280
+ }
1281
+
1282
+ // src/hooks/useMcp.ts
1283
+ var import_react15 = require("react");
1284
+ function useGenerateMcpKey() {
1285
+ const { http, walletsVersion } = useHorusContext();
1286
+ void walletsVersion;
1287
+ const [pending, setPending] = (0, import_react15.useState)(false);
1288
+ const [error, setError] = (0, import_react15.useState)(void 0);
1289
+ const [lastResult, setLastResult] = (0, import_react15.useState)(null);
1290
+ const generate = (0, import_react15.useCallback)(
1291
+ async (input) => {
1292
+ setPending(true);
1293
+ setError(void 0);
1294
+ try {
1295
+ const response = await http.post("/mcp/keys/generate", {
1296
+ name: input.name,
1297
+ network: input.network,
1298
+ wallet_index: typeof input.walletIndex === "number" ? input.walletIndex : 0,
1299
+ expires_at: input.expiresAt,
1300
+ allowed_ips: input.allowedIps,
1301
+ allowed_tools: input.allowedTools,
1302
+ max_requests_per_minute: input.maxRequestsPerMinute,
1303
+ mcp_password: input.mcpPassword
1304
+ });
1305
+ setLastResult(response);
1306
+ return response;
1307
+ } catch (err) {
1308
+ const e = err instanceof Error ? err : new Error(String(err));
1309
+ setError(e);
1310
+ throw e;
1311
+ } finally {
1312
+ setPending(false);
1313
+ }
1314
+ },
1315
+ [http]
1316
+ );
1317
+ return { generate, pending, error, lastResult, clearLastResult: () => setLastResult(null) };
1318
+ }
1319
+ function useMcpKeys() {
1320
+ const { http, state, mcpKeysVersion } = useHorusContext();
1321
+ const [keys, setKeys] = (0, import_react15.useState)([]);
1322
+ const [ready, setReady] = (0, import_react15.useState)(false);
1323
+ const [error, setError] = (0, import_react15.useState)(void 0);
1324
+ const load = (0, import_react15.useCallback)(async () => {
1325
+ if (state.status !== "authenticated") {
1326
+ setKeys([]);
1327
+ setReady(true);
1328
+ return;
1329
+ }
1330
+ setReady(false);
1331
+ setError(void 0);
1332
+ try {
1333
+ const response = await http.get("/mcp/keys");
1334
+ setKeys(response?.keys ?? []);
1335
+ } catch (err) {
1336
+ setError(err instanceof Error ? err : new Error(String(err)));
1337
+ } finally {
1338
+ setReady(true);
1339
+ }
1340
+ }, [http, state.status]);
1341
+ (0, import_react15.useEffect)(() => {
1342
+ void load();
1343
+ }, [load, mcpKeysVersion]);
1344
+ return { ready, keys, refresh: load, error };
1345
+ }
1346
+ function useRevokeMcpKey() {
1347
+ const { http, revalidateMcpKeys } = useHorusContext();
1348
+ const [pending, setPending] = (0, import_react15.useState)(false);
1349
+ const [error, setError] = (0, import_react15.useState)(void 0);
1350
+ const revoke = (0, import_react15.useCallback)(
1351
+ async (prefix) => {
1352
+ if (!prefix) throw new Error("useRevokeMcpKey: prefix is required");
1353
+ setPending(true);
1354
+ setError(void 0);
1355
+ try {
1356
+ const response = await http.del(
1357
+ `/mcp/keys/${encodeURIComponent(prefix)}`
1358
+ );
1359
+ revalidateMcpKeys();
1360
+ return response;
1361
+ } catch (err) {
1362
+ const e = err instanceof Error ? err : new Error(String(err));
1363
+ setError(e);
1364
+ throw e;
1365
+ } finally {
1366
+ setPending(false);
1367
+ }
1368
+ },
1369
+ [http, revalidateMcpKeys]
1370
+ );
1371
+ return { revoke, pending, error };
1372
+ }
1373
+ function useUpdateMcpKey() {
1374
+ const { http, revalidateMcpKeys } = useHorusContext();
1375
+ const [pending, setPending] = (0, import_react15.useState)(false);
1376
+ const [error, setError] = (0, import_react15.useState)(void 0);
1377
+ const update = (0, import_react15.useCallback)(
1378
+ async (prefix, updates) => {
1379
+ if (!prefix) throw new Error("useUpdateMcpKey: prefix is required");
1380
+ setPending(true);
1381
+ setError(void 0);
1382
+ try {
1383
+ const response = await http.put(
1384
+ `/mcp/keys/${encodeURIComponent(prefix)}`,
1385
+ {
1386
+ expires_at: updates.expiresAt,
1387
+ allowed_ips: updates.allowedIps,
1388
+ allowed_tools: updates.allowedTools,
1389
+ max_requests_per_minute: updates.maxRequestsPerMinute
1390
+ }
1391
+ );
1392
+ revalidateMcpKeys();
1393
+ return response;
1394
+ } catch (err) {
1395
+ const e = err instanceof Error ? err : new Error(String(err));
1396
+ setError(e);
1397
+ throw e;
1398
+ } finally {
1399
+ setPending(false);
1400
+ }
1401
+ },
1402
+ [http, revalidateMcpKeys]
1403
+ );
1404
+ return { update, pending, error };
1405
+ }
672
1406
  // Annotate the CommonJS export names for ESM import in node:
673
1407
  0 && (module.exports = {
1408
+ HorusAuthModal,
674
1409
  HorusHttpError,
675
1410
  HorusLoginButton,
676
1411
  HorusProvider,
1412
+ HorusRevealModal,
1413
+ SUPPORTED_CHAINS,
1414
+ useChain,
1415
+ useCreateWallet,
1416
+ useExportWallet,
1417
+ useGenerateMcpKey,
677
1418
  useHorusAuth,
1419
+ useMcpKeys,
1420
+ useRevokeMcpKey,
1421
+ useSendTransaction,
678
1422
  useSignMessage,
1423
+ useSignTypedData,
1424
+ useSwitchChain,
679
1425
  useTransfer,
1426
+ useUpdateMcpKey,
680
1427
  useUser,
681
1428
  useWallets
682
1429
  });