@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.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: "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: { "Content-Type": "application/json" },
182
- body: JSON.stringify({ refreshToken: cur.refreshToken }),
183
- credentials: "same-origin"
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
- [state, http, authPageUrl, appId, branding, setTokens]
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
- settled = true;
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
- "/wallets"
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/useSignMessage.ts
669
+ // src/hooks/useCreateWallet.ts
519
670
  import { useCallback as useCallback4, useState as useState3 } from "react";
520
- function useSignMessage() {
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 signMessage = useCallback4(
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("/sign-message", input);
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 useCallback5, useState as useState4 } from "react";
834
+ import { useCallback as useCallback9, useState as useState7 } from "react";
546
835
  function useTransfer() {
547
836
  const { http } = useHorusContext();
548
- const [pending, setPending] = useState4(false);
549
- const [error, setError] = useState4(void 0);
550
- const wrap = useCallback5(
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 = useCallback5(
855
+ const native = useCallback9(
567
856
  (input) => wrap(
568
- () => http.post("/transfer/native", {
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 = useCallback5(
864
+ const token = useCallback9(
576
865
  (input) => wrap(
577
- () => http.post("/transfer/token", {
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 useState5 } from "react";
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] = useState5(false);
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
  };