@horus-wallet/sdk-react 0.1.0-beta.2 → 0.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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 = { "Content-Type": "application/json" };
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["x-horus-id-token"] = tokens.idToken;
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 = "/api/horus";
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: "EVM",
152
+ networkType: "MAINNET"
153
+ };
154
+ var DEFAULT_CHAIN = { network: "EVM", 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,21 @@ 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
+ }, []);
161
179
  const setTokens = useCallback((tokens) => {
162
180
  tokensRef.current = tokens;
163
181
  if (tokens) {
@@ -178,9 +196,11 @@ function HorusProvider(props) {
178
196
  if (!cur?.refreshToken) throw new Error("no refresh token cached");
179
197
  const fresh = await fetch(joinUrl(apiBase, "/auth/refresh"), {
180
198
  method: "POST",
181
- headers: { "Content-Type": "application/json" },
182
- body: JSON.stringify({ refreshToken: cur.refreshToken }),
183
- credentials: "same-origin"
199
+ headers: {
200
+ "Content-Type": "application/json",
201
+ "x-horus-key": appId
202
+ },
203
+ body: JSON.stringify({ refreshToken: cur.refreshToken })
184
204
  });
185
205
  if (!fresh.ok) throw new Error(`refresh failed: ${fresh.status}`);
186
206
  const raw = await fresh.json();
@@ -190,11 +210,12 @@ function HorusProvider(props) {
190
210
  };
191
211
  return makeHttp({
192
212
  apiBase,
213
+ appId,
193
214
  getTokens: () => tokensRef.current,
194
215
  refreshTokens: refreshOnce,
195
216
  autoRefresh
196
217
  });
197
- }, [apiBase, autoRefresh, setTokens]);
218
+ }, [apiBase, appId, autoRefresh, setTokens]);
198
219
  useEffect(() => {
199
220
  const stored = tokenStoreRef.current.read();
200
221
  if (stored) {
@@ -233,6 +254,33 @@ function HorusProvider(props) {
233
254
  }, refreshIn);
234
255
  return () => clearTimeout(handle);
235
256
  }, [state, autoRefresh, http, setTokens]);
257
+ useEffect(() => {
258
+ if (state.status !== "authenticated") return;
259
+ if (!autoProvisionWallet) return;
260
+ const localId = state.tokens.localId;
261
+ if (!localId) return;
262
+ if (alreadyAutoProvisioned(localId)) return;
263
+ let cancelled = false;
264
+ (async () => {
265
+ try {
266
+ const existing = await http.get("/getWallet");
267
+ if (cancelled) return;
268
+ const hasAny = Object.values(existing?.wallets ?? {}).some(
269
+ (group) => Array.isArray(group?.wallets) && group.wallets.length > 0
270
+ );
271
+ markAutoProvisioned(localId);
272
+ if (hasAny) return;
273
+ await http.post("/createWallet", autoProvisionWallet);
274
+ if (cancelled) return;
275
+ revalidateWallets();
276
+ } catch (err) {
277
+ console.warn("@horus-wallet/sdk-react: auto-provision wallet failed", err);
278
+ }
279
+ })();
280
+ return () => {
281
+ cancelled = true;
282
+ };
283
+ }, [state, autoProvisionWallet, http, revalidateWallets]);
236
284
  const ctx = useMemo(
237
285
  () => ({
238
286
  state,
@@ -253,9 +301,24 @@ function HorusProvider(props) {
253
301
  const stamped = stampExpiry(raw);
254
302
  setTokens(stamped);
255
303
  return stamped;
256
- }
304
+ },
305
+ walletsVersion,
306
+ revalidateWallets,
307
+ currentChain,
308
+ setChain
257
309
  }),
258
- [state, http, authPageUrl, appId, branding, setTokens]
310
+ [
311
+ state,
312
+ http,
313
+ authPageUrl,
314
+ appId,
315
+ branding,
316
+ setTokens,
317
+ walletsVersion,
318
+ revalidateWallets,
319
+ currentChain,
320
+ setChain
321
+ ]
259
322
  );
260
323
  return /* @__PURE__ */ jsx(HorusContext.Provider, { value: ctx, children });
261
324
  }
@@ -269,6 +332,36 @@ function userFromTokens(t) {
269
332
  providerId: t.providerId
270
333
  };
271
334
  }
335
+ var memoryAutoProvisioned = /* @__PURE__ */ new Set();
336
+ function readAutoProvisionedSet() {
337
+ if (typeof window === "undefined" || !window.localStorage) {
338
+ return memoryAutoProvisioned;
339
+ }
340
+ try {
341
+ const raw = window.localStorage.getItem(AUTO_PROVISION_STORAGE_KEY);
342
+ if (!raw) return /* @__PURE__ */ new Set();
343
+ const parsed = JSON.parse(raw);
344
+ return new Set(Array.isArray(parsed) ? parsed : []);
345
+ } catch {
346
+ return /* @__PURE__ */ new Set();
347
+ }
348
+ }
349
+ function alreadyAutoProvisioned(localId) {
350
+ return readAutoProvisionedSet().has(localId);
351
+ }
352
+ function markAutoProvisioned(localId) {
353
+ if (typeof window === "undefined" || !window.localStorage) {
354
+ memoryAutoProvisioned.add(localId);
355
+ return;
356
+ }
357
+ try {
358
+ const set = readAutoProvisionedSet();
359
+ set.add(localId);
360
+ window.localStorage.setItem(AUTO_PROVISION_STORAGE_KEY, JSON.stringify([...set]));
361
+ } catch {
362
+ memoryAutoProvisioned.add(localId);
363
+ }
364
+ }
272
365
  function joinUrl(base, path) {
273
366
  const b = base.endsWith("/") ? base.slice(0, -1) : base;
274
367
  const p = path.startsWith("/") ? path : `/${path}`;
@@ -277,6 +370,36 @@ function joinUrl(base, path) {
277
370
 
278
371
  // src/hooks/useHorusAuth.ts
279
372
  import { useCallback as useCallback2 } from "react";
373
+
374
+ // src/internal/authUrl.ts
375
+ function buildAuthUrl(p, state) {
376
+ const url = new URL(p.baseUrl);
377
+ url.searchParams.set("flow", p.flow);
378
+ url.searchParams.set("origin", window.location.origin);
379
+ url.searchParams.set("state", state);
380
+ url.searchParams.set("appKey", p.appId);
381
+ url.searchParams.set("mode", p.mode);
382
+ if (p.phone) url.searchParams.set("phone", p.phone);
383
+ const b = p.branding;
384
+ if (b) {
385
+ if (b.logoUrl) url.searchParams.set("b_logo", b.logoUrl);
386
+ if (b.brandName) url.searchParams.set("b_name", b.brandName);
387
+ if (b.primaryColor) url.searchParams.set("b_color", b.primaryColor);
388
+ if (b.backgroundColor) url.searchParams.set("b_bg", b.backgroundColor);
389
+ if (b.fontFamily) url.searchParams.set("b_font", b.fontFamily);
390
+ if (b.termsUrl) url.searchParams.set("b_terms", b.termsUrl);
391
+ if (b.privacyUrl) url.searchParams.set("b_privacy", b.privacyUrl);
392
+ if (b.showPoweredByHorus === false) url.searchParams.set("b_poweredby", "0");
393
+ }
394
+ return url.toString();
395
+ }
396
+ function randomState() {
397
+ const bytes = new Uint8Array(16);
398
+ crypto.getRandomValues(bytes);
399
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
400
+ }
401
+
402
+ // src/hooks/useHorusAuth.ts
280
403
  function useHorusAuth() {
281
404
  const ctx = useHorusContext();
282
405
  const { state, http, signIn, signOut, refreshTokens, authPageUrl, appId, branding } = ctx;
@@ -376,7 +499,7 @@ function openPopupFlow(params) {
376
499
  return;
377
500
  }
378
501
  const state = randomState();
379
- const url = buildAuthUrl(params, state);
502
+ const url = buildAuthUrl({ ...params, mode: "popup" }, state);
380
503
  const popup = window.open(url, "horus-auth", defaultPopupFeatures());
381
504
  if (!popup) {
382
505
  reject(new Error("Popup blocked \u2014 call from a click handler so the browser allows it."));
@@ -438,26 +561,6 @@ function openPopupFlow(params) {
438
561
  }, 400);
439
562
  });
440
563
  }
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
564
  function defaultPopupFeatures() {
462
565
  const w = 480, h = 640;
463
566
  if (typeof window === "undefined") return `width=${w},height=${h}`;
@@ -465,11 +568,6 @@ function defaultPopupFeatures() {
465
568
  const top = Math.max(0, (window.innerHeight - h) / 2 + (window.screenY ?? 0));
466
569
  return `width=${w},height=${h},left=${left},top=${top},popup=1`;
467
570
  }
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
571
 
474
572
  // src/hooks/useUser.ts
475
573
  function useUser() {
@@ -480,7 +578,7 @@ function useUser() {
480
578
  // src/hooks/useWallets.ts
481
579
  import { useCallback as useCallback3, useEffect as useEffect2, useState as useState2 } from "react";
482
580
  function useWallets() {
483
- const { http, state } = useHorusContext();
581
+ const { http, state, walletsVersion } = useHorusContext();
484
582
  const [wallets, setWallets] = useState2([]);
485
583
  const [ready, setReady] = useState2(false);
486
584
  const [error, setError] = useState2(void 0);
@@ -494,7 +592,7 @@ function useWallets() {
494
592
  setError(void 0);
495
593
  try {
496
594
  const response = await http.get(
497
- "/wallets"
595
+ "/getWallet"
498
596
  );
499
597
  const flat = [];
500
598
  for (const [network, group] of Object.entries(response.wallets ?? {})) {
@@ -511,22 +609,87 @@ function useWallets() {
511
609
  }, [http, state.status]);
512
610
  useEffect2(() => {
513
611
  void load();
514
- }, [load]);
612
+ }, [load, walletsVersion]);
515
613
  return { ready, wallets, refresh: load, error };
516
614
  }
517
615
 
518
- // src/hooks/useSignMessage.ts
616
+ // src/hooks/useCreateWallet.ts
519
617
  import { useCallback as useCallback4, useState as useState3 } from "react";
520
- function useSignMessage() {
521
- const { http } = useHorusContext();
618
+ function useCreateWallet() {
619
+ const { http, revalidateWallets } = useHorusContext();
522
620
  const [pending, setPending] = useState3(false);
523
621
  const [error, setError] = useState3(void 0);
524
- const signMessage = useCallback4(
622
+ const create = useCallback4(
525
623
  async (input) => {
624
+ if (pending) {
625
+ const err = new Error(
626
+ "useCreateWallet: a previous create() call is still in flight"
627
+ );
628
+ setError(err);
629
+ throw err;
630
+ }
526
631
  setPending(true);
527
632
  setError(void 0);
528
633
  try {
529
- const response = await http.post("/sign-message", input);
634
+ const response = await http.post("/createWallet", {
635
+ network: input.network,
636
+ networkType: input.networkType,
637
+ ...input.password ? { password: input.password } : {}
638
+ });
639
+ revalidateWallets();
640
+ return response;
641
+ } catch (err) {
642
+ const e = err instanceof Error ? err : new Error(String(err));
643
+ setError(e);
644
+ throw e;
645
+ } finally {
646
+ setPending(false);
647
+ }
648
+ },
649
+ [http, revalidateWallets, pending]
650
+ );
651
+ return { create, pending, error };
652
+ }
653
+
654
+ // src/hooks/useSwitchChain.ts
655
+ import { useCallback as useCallback5 } from "react";
656
+ var SUPPORTED_CHAINS = [
657
+ "EVM",
658
+ "ETHEREUM",
659
+ "BASE",
660
+ "POLYGON",
661
+ "BSC",
662
+ "ARBITRUM",
663
+ "OPTIMISM",
664
+ "BITCOIN",
665
+ "ICP",
666
+ "CASPER",
667
+ "AETERNITY"
668
+ ];
669
+ function useSwitchChain() {
670
+ const { currentChain, setChain } = useHorusContext();
671
+ const switchChain = useCallback5(
672
+ (chain) => {
673
+ setChain(chain);
674
+ },
675
+ [setChain]
676
+ );
677
+ return { chain: currentChain, switchChain, supportedChains: SUPPORTED_CHAINS };
678
+ }
679
+ var useChain = useSwitchChain;
680
+
681
+ // src/hooks/useSignMessage.ts
682
+ import { useCallback as useCallback6, useState as useState4 } from "react";
683
+ function useSignMessage() {
684
+ const { http } = useHorusContext();
685
+ const [pending, setPending] = useState4(false);
686
+ const [error, setError] = useState4(void 0);
687
+ const signMessage = useCallback6(
688
+ async (input) => {
689
+ setPending(true);
690
+ setError(void 0);
691
+ try {
692
+ const response = await http.post("/signMessage", input);
530
693
  return response.signature;
531
694
  } catch (err) {
532
695
  const e = err instanceof Error ? err : new Error(String(err));
@@ -541,13 +704,79 @@ function useSignMessage() {
541
704
  return { signMessage, pending, error };
542
705
  }
543
706
 
707
+ // src/hooks/useSignTypedData.ts
708
+ import { useCallback as useCallback7, useState as useState5 } from "react";
709
+ function useSignTypedData() {
710
+ const { http } = useHorusContext();
711
+ const [pending, setPending] = useState5(false);
712
+ const [error, setError] = useState5(void 0);
713
+ const signTypedData = useCallback7(
714
+ async (input) => {
715
+ setPending(true);
716
+ setError(void 0);
717
+ try {
718
+ const response = await http.post("/signTypedData", {
719
+ network: input.network,
720
+ networkType: input.networkType,
721
+ typedData: input.typedData,
722
+ ...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
723
+ ...input.password ? { password: input.password } : {}
724
+ });
725
+ return response.signature;
726
+ } catch (err) {
727
+ const e = err instanceof Error ? err : new Error(String(err));
728
+ setError(e);
729
+ throw e;
730
+ } finally {
731
+ setPending(false);
732
+ }
733
+ },
734
+ [http]
735
+ );
736
+ return { signTypedData, pending, error };
737
+ }
738
+
739
+ // src/hooks/useSendTransaction.ts
740
+ import { useCallback as useCallback8, useState as useState6 } from "react";
741
+ function useSendTransaction() {
742
+ const { http } = useHorusContext();
743
+ const [pending, setPending] = useState6(false);
744
+ const [error, setError] = useState6(void 0);
745
+ const sendTransaction = useCallback8(
746
+ async (input) => {
747
+ setPending(true);
748
+ setError(void 0);
749
+ try {
750
+ return await http.post("/sendTransaction", {
751
+ network: input.network,
752
+ networkType: input.networkType,
753
+ to: input.to,
754
+ ...input.value !== void 0 ? { value: String(input.value) } : {},
755
+ ...input.data !== void 0 ? { data: input.data } : {},
756
+ ...input.gasLimit !== void 0 ? { gasLimit: String(input.gasLimit) } : {},
757
+ ...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {},
758
+ ...input.password ? { password: input.password } : {}
759
+ });
760
+ } catch (err) {
761
+ const e = err instanceof Error ? err : new Error(String(err));
762
+ setError(e);
763
+ throw e;
764
+ } finally {
765
+ setPending(false);
766
+ }
767
+ },
768
+ [http]
769
+ );
770
+ return { sendTransaction, pending, error };
771
+ }
772
+
544
773
  // src/hooks/useTransfer.ts
545
- import { useCallback as useCallback5, useState as useState4 } from "react";
774
+ import { useCallback as useCallback9, useState as useState7 } from "react";
546
775
  function useTransfer() {
547
776
  const { http } = useHorusContext();
548
- const [pending, setPending] = useState4(false);
549
- const [error, setError] = useState4(void 0);
550
- const wrap = useCallback5(
777
+ const [pending, setPending] = useState7(false);
778
+ const [error, setError] = useState7(void 0);
779
+ const wrap = useCallback9(
551
780
  async (fn) => {
552
781
  setPending(true);
553
782
  setError(void 0);
@@ -563,18 +792,18 @@ function useTransfer() {
563
792
  },
564
793
  []
565
794
  );
566
- const native = useCallback5(
795
+ const native = useCallback9(
567
796
  (input) => wrap(
568
- () => http.post("/transfer/native", {
797
+ () => http.post("/nativeTransfer", {
569
798
  ...input,
570
799
  amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
571
800
  })
572
801
  ),
573
802
  [http, wrap]
574
803
  );
575
- const token = useCallback5(
804
+ const token = useCallback9(
576
805
  (input) => wrap(
577
- () => http.post("/transfer/token", {
806
+ () => http.post("/tokenTransfer", {
578
807
  ...input,
579
808
  amount: typeof input.amount === "bigint" ? input.amount.toString() : String(input.amount)
580
809
  })
@@ -585,7 +814,7 @@ function useTransfer() {
585
814
  }
586
815
 
587
816
  // src/components/HorusLoginButton.tsx
588
- import { useState as useState5 } from "react";
817
+ import { useState as useState8 } from "react";
589
818
  import { jsx as jsx2 } from "react/jsx-runtime";
590
819
  function HorusLoginButton({
591
820
  flow = "google",
@@ -598,7 +827,7 @@ function HorusLoginButton({
598
827
  ...rest
599
828
  }) {
600
829
  const auth = useHorusAuth();
601
- const [busy, setBusy] = useState5(false);
830
+ const [busy, setBusy] = useState8(false);
602
831
  const onClick = async () => {
603
832
  if (busy) return;
604
833
  setBusy(true);
@@ -636,12 +865,327 @@ function HorusLoginButton({
636
865
  }
637
866
  );
638
867
  }
868
+
869
+ // src/components/HorusAuthModal.tsx
870
+ import { useCallback as useCallback10, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2 } from "react";
871
+ import { jsx as jsx3 } from "react/jsx-runtime";
872
+ var defaultBackdropStyle = {
873
+ position: "fixed",
874
+ inset: 0,
875
+ background: "rgba(0, 0, 0, 0.5)",
876
+ display: "flex",
877
+ alignItems: "center",
878
+ justifyContent: "center",
879
+ zIndex: 2147483600
880
+ };
881
+ var defaultDialogStyle = {
882
+ width: "480px",
883
+ maxWidth: "95vw",
884
+ height: "640px",
885
+ maxHeight: "95vh",
886
+ background: "#fff",
887
+ borderRadius: "16px",
888
+ boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
889
+ overflow: "hidden"
890
+ };
891
+ function HorusAuthModal(props) {
892
+ const {
893
+ flow,
894
+ phone,
895
+ open,
896
+ onClose,
897
+ onSuccess,
898
+ onError,
899
+ dialogStyle,
900
+ backdropStyle
901
+ } = props;
902
+ const { appId, authPageUrl, branding, signIn } = useHorusContext();
903
+ const stateRef = useRef2("");
904
+ if (stateRef.current === "" || !open) {
905
+ stateRef.current = randomState();
906
+ }
907
+ const iframeSrc = useMemo2(() => {
908
+ if (!open) return "";
909
+ return buildAuthUrl(
910
+ {
911
+ flow,
912
+ appId,
913
+ baseUrl: authPageUrl,
914
+ mode: "iframe",
915
+ branding,
916
+ phone
917
+ },
918
+ stateRef.current
919
+ );
920
+ }, [open, flow, appId, authPageUrl, branding, phone]);
921
+ const expectedOrigin = useMemo2(() => {
922
+ try {
923
+ return new URL(authPageUrl).origin;
924
+ } catch {
925
+ return "";
926
+ }
927
+ }, [authPageUrl]);
928
+ useEffect3(() => {
929
+ if (!open) return;
930
+ const onMessage = (ev) => {
931
+ if (ev.origin !== expectedOrigin) return;
932
+ const data = ev.data;
933
+ if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
934
+ if (data.state !== void 0 && data.state !== stateRef.current) return;
935
+ const body = data.body;
936
+ if (!body) return;
937
+ switch (body.type) {
938
+ case "success": {
939
+ const stamped = stampExpiry({
940
+ idToken: body.idToken,
941
+ refreshToken: "",
942
+ expiresIn: body.expiresAt ? Math.max(60, body.expiresAt - Math.floor(Date.now() / 1e3)) : 3600,
943
+ localId: body.user?.uid ?? "",
944
+ email: body.user?.email,
945
+ displayName: body.user?.displayName,
946
+ photoUrl: body.user?.photoURL,
947
+ providerId: body.user?.providerId
948
+ });
949
+ signIn(stamped);
950
+ onSuccess?.({
951
+ uid: stamped.localId,
952
+ email: stamped.email,
953
+ emailVerified: stamped.emailVerified,
954
+ displayName: stamped.displayName,
955
+ photoUrl: stamped.photoUrl,
956
+ providerId: stamped.providerId
957
+ });
958
+ onClose();
959
+ return;
960
+ }
961
+ case "cancel":
962
+ onClose();
963
+ return;
964
+ case "error":
965
+ onError?.(new Error(body.message ?? "Sign-in failed."));
966
+ onClose();
967
+ return;
968
+ }
969
+ };
970
+ window.addEventListener("message", onMessage);
971
+ return () => window.removeEventListener("message", onMessage);
972
+ }, [open, expectedOrigin, signIn, onSuccess, onError, onClose]);
973
+ useEffect3(() => {
974
+ if (!open) return;
975
+ const onKey = (ev) => {
976
+ if (ev.key === "Escape") onClose();
977
+ };
978
+ window.addEventListener("keydown", onKey);
979
+ return () => window.removeEventListener("keydown", onKey);
980
+ }, [open, onClose]);
981
+ useEffect3(() => {
982
+ if (!open) return;
983
+ if (typeof document === "undefined") return;
984
+ const prev = document.body.style.overflow;
985
+ document.body.style.overflow = "hidden";
986
+ return () => {
987
+ document.body.style.overflow = prev;
988
+ };
989
+ }, [open]);
990
+ const backdropClick = useCallback10(
991
+ (ev) => {
992
+ if (ev.target === ev.currentTarget) onClose();
993
+ },
994
+ [onClose]
995
+ );
996
+ if (!open || typeof window === "undefined") return null;
997
+ return /* @__PURE__ */ jsx3(
998
+ "div",
999
+ {
1000
+ role: "dialog",
1001
+ "aria-modal": "true",
1002
+ "aria-label": "Sign in with Horus",
1003
+ style: { ...defaultBackdropStyle, ...backdropStyle },
1004
+ onClick: backdropClick,
1005
+ children: /* @__PURE__ */ jsx3("div", { style: { ...defaultDialogStyle, ...dialogStyle }, children: /* @__PURE__ */ jsx3(
1006
+ "iframe",
1007
+ {
1008
+ src: iframeSrc,
1009
+ title: "Horus authentication",
1010
+ allow: "publickey-credentials-get; publickey-credentials-create; clipboard-write",
1011
+ style: { width: "100%", height: "100%", border: "none" }
1012
+ }
1013
+ ) })
1014
+ }
1015
+ );
1016
+ }
1017
+
1018
+ // src/components/HorusRevealModal.tsx
1019
+ import { useCallback as useCallback11, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3 } from "react";
1020
+ import { jsx as jsx4 } from "react/jsx-runtime";
1021
+ var defaultBackdropStyle2 = {
1022
+ position: "fixed",
1023
+ inset: 0,
1024
+ background: "rgba(0, 0, 0, 0.5)",
1025
+ display: "flex",
1026
+ alignItems: "center",
1027
+ justifyContent: "center",
1028
+ zIndex: 2147483600
1029
+ };
1030
+ var defaultDialogStyle2 = {
1031
+ width: "480px",
1032
+ maxWidth: "95vw",
1033
+ height: "640px",
1034
+ maxHeight: "95vh",
1035
+ background: "#fff",
1036
+ borderRadius: "16px",
1037
+ boxShadow: "0 24px 56px rgba(0, 0, 0, 0.32)",
1038
+ overflow: "hidden"
1039
+ };
1040
+ function HorusRevealModal(props) {
1041
+ const { revealToken, open, onClose, onComplete, onError, dialogStyle, backdropStyle } = props;
1042
+ const { appId, authPageUrl, branding } = useHorusContext();
1043
+ const stateRef = useRef3("");
1044
+ if (stateRef.current === "" || !open) {
1045
+ stateRef.current = randomState();
1046
+ }
1047
+ const iframeSrc = useMemo3(() => {
1048
+ if (!open || !revealToken) return "";
1049
+ const baseUrl = buildAuthUrl(
1050
+ {
1051
+ flow: "reveal",
1052
+ appId,
1053
+ baseUrl: authPageUrl,
1054
+ mode: "iframe",
1055
+ branding
1056
+ },
1057
+ stateRef.current
1058
+ );
1059
+ const u = new URL(baseUrl);
1060
+ u.searchParams.set("token", revealToken);
1061
+ return u.toString();
1062
+ }, [open, revealToken, appId, authPageUrl, branding]);
1063
+ const expectedOrigin = useMemo3(() => {
1064
+ try {
1065
+ return new URL(authPageUrl).origin;
1066
+ } catch {
1067
+ return "";
1068
+ }
1069
+ }, [authPageUrl]);
1070
+ useEffect4(() => {
1071
+ if (!open) return;
1072
+ const onMessage = (ev) => {
1073
+ if (ev.origin !== expectedOrigin) return;
1074
+ const data = ev.data;
1075
+ if (!data || data.channel !== "horus.auth" || data.version !== 1) return;
1076
+ if (data.state !== void 0 && data.state !== stateRef.current) return;
1077
+ const body = data.body;
1078
+ if (!body) return;
1079
+ switch (body.type) {
1080
+ case "reveal_complete":
1081
+ onComplete?.(Boolean(body.viewed));
1082
+ onClose();
1083
+ return;
1084
+ case "cancel":
1085
+ onClose();
1086
+ return;
1087
+ case "error":
1088
+ onError?.(new Error(body.message ?? "Reveal failed."));
1089
+ onClose();
1090
+ return;
1091
+ }
1092
+ };
1093
+ window.addEventListener("message", onMessage);
1094
+ return () => window.removeEventListener("message", onMessage);
1095
+ }, [open, expectedOrigin, onComplete, onClose, onError]);
1096
+ useEffect4(() => {
1097
+ if (!open) return;
1098
+ const onKey = (ev) => {
1099
+ if (ev.key === "Escape") onClose();
1100
+ };
1101
+ window.addEventListener("keydown", onKey);
1102
+ return () => window.removeEventListener("keydown", onKey);
1103
+ }, [open, onClose]);
1104
+ useEffect4(() => {
1105
+ if (!open) return;
1106
+ if (typeof document === "undefined") return;
1107
+ const prev = document.body.style.overflow;
1108
+ document.body.style.overflow = "hidden";
1109
+ return () => {
1110
+ document.body.style.overflow = prev;
1111
+ };
1112
+ }, [open]);
1113
+ const backdropClick = useCallback11(
1114
+ (ev) => {
1115
+ if (ev.target === ev.currentTarget) onClose();
1116
+ },
1117
+ [onClose]
1118
+ );
1119
+ if (!open || !revealToken || typeof window === "undefined") return null;
1120
+ return /* @__PURE__ */ jsx4(
1121
+ "div",
1122
+ {
1123
+ role: "dialog",
1124
+ "aria-modal": "true",
1125
+ "aria-label": "Reveal wallet private key",
1126
+ style: { ...defaultBackdropStyle2, ...backdropStyle },
1127
+ onClick: backdropClick,
1128
+ children: /* @__PURE__ */ jsx4("div", { style: { ...defaultDialogStyle2, ...dialogStyle }, children: /* @__PURE__ */ jsx4(
1129
+ "iframe",
1130
+ {
1131
+ src: iframeSrc,
1132
+ title: "Horus wallet export",
1133
+ allow: "clipboard-write",
1134
+ style: { width: "100%", height: "100%", border: "none" }
1135
+ }
1136
+ ) })
1137
+ }
1138
+ );
1139
+ }
1140
+
1141
+ // src/hooks/useExportWallet.ts
1142
+ import { useCallback as useCallback12, useState as useState9 } from "react";
1143
+ function useExportWallet() {
1144
+ const { http } = useHorusContext();
1145
+ const [pending, setPending] = useState9(false);
1146
+ const [error, setError] = useState9(void 0);
1147
+ const reveal = useCallback12(
1148
+ async (input) => {
1149
+ setPending(true);
1150
+ setError(void 0);
1151
+ try {
1152
+ const response = await http.post(
1153
+ "/exportWallet/grant",
1154
+ {
1155
+ network: input.network,
1156
+ networkType: input.networkType,
1157
+ password: input.password,
1158
+ ...typeof input.walletIndex === "number" ? { walletIndex: input.walletIndex } : {}
1159
+ }
1160
+ );
1161
+ return response;
1162
+ } catch (err) {
1163
+ const e = err instanceof Error ? err : new Error(String(err));
1164
+ setError(e);
1165
+ throw e;
1166
+ } finally {
1167
+ setPending(false);
1168
+ }
1169
+ },
1170
+ [http]
1171
+ );
1172
+ return { reveal, pending, error };
1173
+ }
639
1174
  export {
1175
+ HorusAuthModal,
640
1176
  HorusHttpError,
641
1177
  HorusLoginButton,
642
1178
  HorusProvider,
1179
+ HorusRevealModal,
1180
+ SUPPORTED_CHAINS,
1181
+ useChain,
1182
+ useCreateWallet,
1183
+ useExportWallet,
643
1184
  useHorusAuth,
1185
+ useSendTransaction,
644
1186
  useSignMessage,
1187
+ useSignTypedData,
1188
+ useSwitchChain,
645
1189
  useTransfer,
646
1190
  useUser,
647
1191
  useWallets