@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.d.ts CHANGED
@@ -3,12 +3,18 @@ import { ReactNode, ButtonHTMLAttributes } from 'react';
3
3
  /**
4
4
  * Public types for `@horus-wallet/sdk-react`.
5
5
  *
6
- * The React package targets the partner's BACKEND-PROXY pattern:
7
- * partner runs a thin proxy on their server (which holds the
8
- * `hk_sk_*` secret) and the React hooks call that proxy. Each hook's
9
- * `apiBase` defaults to `/api/horus` partners can override via
10
- * provider config. v0.2 will add a publishable-key tier that lets
11
- * the React SDK call Horus directly without the partner-backend hop.
6
+ * The React package calls the Horus API directly from the browser
7
+ * using the partner's PUBLISHABLE app identifier (`app_*`). No
8
+ * partner backend is required. Auth is carried as `x-horus-key:
9
+ * <appId>` + `Authorization: Bearer <idToken>` headers on every
10
+ * request; the backend's per-app `allowedOrigins` allow-list is the
11
+ * security boundary, so partners MUST register the domains they'll
12
+ * serve the SDK from before shipping.
13
+ *
14
+ * Partners that need a server-side path (managed signing services,
15
+ * cron jobs, backoffice tools) use the sibling `@horus-wallet/sdk`
16
+ * package with an `hk_sk_*` secret — the publishable React surface
17
+ * intentionally has no awareness of secret keys.
12
18
  */
13
19
  /** Tokens returned from any successful auth flow. */
14
20
  interface AuthTokens {
@@ -24,6 +30,16 @@ interface AuthTokens {
24
30
  photoUrl?: string;
25
31
  providerId?: string;
26
32
  }
33
+ /**
34
+ * Network type — Horus splits each chain into mainnet and testnet
35
+ * surfaces so partners don't mix them up at the API level.
36
+ */
37
+ type NetworkType = 'MAINNET' | 'TESTNET';
38
+ /** A chain selector — `(network, networkType)` pair. */
39
+ interface ChainSelector {
40
+ network: string;
41
+ networkType: NetworkType;
42
+ }
27
43
  /** Subset of the tokens that's safe to expose to consuming hooks. */
28
44
  interface User {
29
45
  uid: string;
@@ -56,25 +72,22 @@ interface HorusBranding {
56
72
  }
57
73
  /** Configuration for `<HorusProvider>`. */
58
74
  interface HorusProviderConfig {
59
- /** Server-assigned id of the partner's app. Used by the popup for white-label policy lookup. */
75
+ /**
76
+ * Partner's publishable app identifier (`app_*`). Created via the
77
+ * Horus dashboard or admin API. Sent as `x-horus-key` on every
78
+ * request to identify which app the call belongs to. The host page's
79
+ * Origin must be on the app's `allowedOrigins` allow-list — this is
80
+ * the load-bearing security boundary for the publishable path.
81
+ */
60
82
  appId: string;
61
83
  /**
62
- * URL prefix where the partner's backend exposes the Horus proxy
63
- * routes. Defaults to `/api/horus`. The proxy routes the SDK
64
- * expects:
65
- * - POST {apiBase}/auth/email/signin → tokens
66
- * - POST {apiBase}/auth/email/signup → tokens
67
- * - POST {apiBase}/auth/email-link/send → ok
68
- * - POST {apiBase}/auth/email-link/verify → tokens
69
- * - POST {apiBase}/auth/oauth → tokens (federate Google/Apple/etc.)
70
- * - POST {apiBase}/auth/refresh → tokens
71
- * - GET {apiBase}/wallets → wallets list
72
- * - GET {apiBase}/wallets/:network → wallet details
73
- * - POST {apiBase}/sign-message → signature
74
- * - POST {apiBase}/transfer/native → tx hash
75
- * - POST {apiBase}/transfer/token → tx hash
84
+ * Horus API base URL. Defaults to `https://api.horuswallet.com` (prod).
85
+ * Override for staging / dev:
86
+ *
87
+ * <HorusProvider appId="app_xxx" apiBase="https://dev-api.horuswallet.com">
76
88
  *
77
- * The README shows a reference backend implementation in <50 lines.
89
+ * Partners NEVER need to expose any path of their own — the SDK
90
+ * calls the Horus API directly.
78
91
  */
79
92
  apiBase?: string;
80
93
  /**
@@ -92,6 +105,41 @@ interface HorusProviderConfig {
92
105
  * Default true. Disable when you're handling expiry manually.
93
106
  */
94
107
  autoRefresh?: boolean;
108
+ /**
109
+ * Auto-create a wallet for the user on first sign-in if they don't
110
+ * already have one. Defaults to `{ network: 'ETHEREUM', networkType: 'MAINNET' }`
111
+ * to match Privy's behavior — new users land in your app with a
112
+ * wallet already provisioned, no extra round-trip required.
113
+ *
114
+ * `network` must be a specific chain name (ETHEREUM, BASE, POLYGON,
115
+ * BITCOIN, ICP, CASPER, …) — the API rejects family labels like
116
+ * `'EVM'`. The wallet is stored by family on the backend, so a
117
+ * wallet provisioned as ETHEREUM is reachable via BASE / POLYGON /
118
+ * etc. at sign time (same key, different chain selector).
119
+ *
120
+ * Set to `false` to opt out (your app must call `useCreateWallet`
121
+ * explicitly to provision wallets).
122
+ *
123
+ * The provision is idempotent on the backend, runs once per user
124
+ * per session, and fails silently — a network blip will not block
125
+ * sign-in. Subsequent auto-provisions for the same user during the
126
+ * same session are skipped via a localStorage marker.
127
+ */
128
+ autoProvisionWallet?: false | {
129
+ network: string;
130
+ networkType: 'MAINNET' | 'TESTNET';
131
+ };
132
+ /**
133
+ * Initial active chain exposed via `useSwitchChain()` / `useChain()`.
134
+ * Defaults to `{ network: 'ETHEREUM', networkType: 'MAINNET' }`.
135
+ *
136
+ * Partners can read the current chain in their UI (e.g., to highlight
137
+ * the active network in a switcher) and pass it explicitly when
138
+ * calling `signMessage` / `sendTransaction` / etc. The active chain
139
+ * is a UI convenience — it is NOT implicitly used by the signing
140
+ * hooks unless the partner forwards it themselves.
141
+ */
142
+ defaultChain?: ChainSelector;
95
143
  }
96
144
 
97
145
  /**
@@ -185,8 +233,8 @@ declare function useHorusAuth(): UseHorusAuthResult;
185
233
  declare function useUser(): User | undefined;
186
234
 
187
235
  /**
188
- * `useWallets()` — fetches the signed-in user's wallets from the
189
- * partner's backend proxy. Refetches when auth state changes.
236
+ * `useWallets()` — fetches the signed-in user's wallets directly from
237
+ * the Horus API. Refetches when auth state changes.
190
238
  *
191
239
  * Mirror of Privy's `useWallets()` — partners coming from there get
192
240
  * a familiar `{ wallets, ready }` shape. Each wallet is a thin
@@ -212,6 +260,93 @@ interface UseWalletsResult {
212
260
  }
213
261
  declare function useWallets(): UseWalletsResult;
214
262
 
263
+ /**
264
+ * `useCreateWallet()` — explicit wallet provisioning.
265
+ *
266
+ * const { create, pending, error } = useCreateWallet();
267
+ * await create({ network: 'EVM', networkType: 'MAINNET' });
268
+ *
269
+ * The backend's `/createWallet` is idempotent on `(user, network family)`,
270
+ * so calling this twice for the same network returns the existing wallet
271
+ * rather than duplicating. After a successful call the provider's
272
+ * `walletsVersion` counter is bumped, which triggers every active
273
+ * `useWallets()` to re-fetch — partners don't need to wire a manual
274
+ * `.refresh()` call.
275
+ *
276
+ * For most apps, you'll prefer the provider's `autoProvisionWallet`
277
+ * config (auto-fires on first sign-in). Reach for this hook when you
278
+ * need to provision an *additional* chain after the auto-provision,
279
+ * or for apps that opted out of auto-provision.
280
+ */
281
+ interface CreateWalletInput {
282
+ /** Chain name — `'EVM' | 'BASE' | 'POLYGON' | 'BITCOIN' | 'ICP' | …` */
283
+ network: string;
284
+ /** `'MAINNET' | 'TESTNET'` */
285
+ networkType: 'MAINNET' | 'TESTNET';
286
+ /**
287
+ * Optional password — encrypts the wallet's private key at rest.
288
+ * Required for spending operations later if set. Recoverable only
289
+ * by the user.
290
+ */
291
+ password?: string;
292
+ }
293
+ interface CreateWalletResult {
294
+ /** Server response: nested by-network wallet record + a status message. */
295
+ wallets: unknown;
296
+ message: string;
297
+ }
298
+ interface UseCreateWalletResult {
299
+ /** Provision a new wallet. Resolves with the server response. */
300
+ create: (input: CreateWalletInput) => Promise<CreateWalletResult>;
301
+ /** True while a `create()` call is in flight. */
302
+ pending: boolean;
303
+ /** Last error, if any. Cleared on the next attempt. */
304
+ error: Error | undefined;
305
+ }
306
+ declare function useCreateWallet(): UseCreateWalletResult;
307
+
308
+ /**
309
+ * `useSwitchChain()` — read and update the active chain selector.
310
+ *
311
+ * const { chain, switchChain } = useSwitchChain();
312
+ * <button onClick={() => switchChain({ network: 'BASE', networkType: 'MAINNET' })}>
313
+ * Use Base
314
+ * </button>
315
+ *
316
+ * Mirror of Privy's `useSwitchChain`. Pure UI state — partners forward
317
+ * `chain` explicitly into `signMessage` / `sendTransaction` / etc.
318
+ * calls. We deliberately don't auto-thread it through the signing
319
+ * hooks because an implicit "last switched chain" easily becomes a
320
+ * footgun (user switches to mainnet, dapp signs a testnet tx, etc).
321
+ *
322
+ * `supportedChains` is the static union of chain names the Horus
323
+ * backend supports today, listed in the order partners are most
324
+ * likely to want to surface them. Re-exported here so partners can
325
+ * render a chain picker without hardcoding the list themselves.
326
+ */
327
+
328
+ /**
329
+ * Chain names the Horus backend supports today. Frozen tuple — typed
330
+ * as readonly so a partner who pins on a specific value gets a TS
331
+ * error when we drop a chain rather than a silent runtime miss.
332
+ */
333
+ declare const SUPPORTED_CHAINS: readonly ["ETHEREUM", "BASE", "POLYGON", "BSC", "ARBITRUM", "OPTIMISM", "TELOS", "CHILIZ", "FLARE", "BITCOIN", "ICP", "CASPER", "AETERNITY"];
334
+ type SupportedChain = (typeof SUPPORTED_CHAINS)[number];
335
+ interface UseSwitchChainResult {
336
+ /** Currently-active chain selector. */
337
+ chain: ChainSelector;
338
+ /** Set the active chain. Triggers a re-render in every consumer. */
339
+ switchChain: (chain: ChainSelector) => void;
340
+ /** Static list of chain names this SDK supports. */
341
+ supportedChains: readonly SupportedChain[];
342
+ }
343
+ declare function useSwitchChain(): UseSwitchChainResult;
344
+ /**
345
+ * Alias for partners who prefer the "read-only" framing. Returns
346
+ * exactly the same shape as `useSwitchChain()`.
347
+ */
348
+ declare const useChain: typeof useSwitchChain;
349
+
215
350
  /**
216
351
  * `useSignMessage()` — signs a UTF-8 message with the user's wallet.
217
352
  * EVM applies EIP-191 prefix server-side; Casper raw-signs; other
@@ -236,9 +371,113 @@ interface UseSignMessageResult {
236
371
  declare function useSignMessage(): UseSignMessageResult;
237
372
 
238
373
  /**
239
- * `useTransfer()` — native + token transfer helpers backed by the
240
- * partner's proxy. Each variant returns `{ pending, error }` so
241
- * forms can drive disabled-state without local plumbing.
374
+ * `useSignTypedData()` — EIP-712 typed-data signing.
375
+ *
376
+ * const { signTypedData, pending, error } = useSignTypedData();
377
+ *
378
+ * const sig = await signTypedData({
379
+ * network: 'BASE',
380
+ * networkType: 'MAINNET',
381
+ * typedData: {
382
+ * domain: { name: 'Acme', version: '1', chainId: 8453, verifyingContract: '0x…' },
383
+ * types: {
384
+ * Order: [
385
+ * { name: 'buyer', type: 'address' },
386
+ * { name: 'amount', type: 'uint256' },
387
+ * ],
388
+ * },
389
+ * primaryType: 'Order',
390
+ * message: { buyer: '0x…', amount: '1000000' },
391
+ * },
392
+ * });
393
+ *
394
+ * EVM-only. Non-EVM networks return a 400 (`HorusHttpError`). The
395
+ * `typedData` argument is forwarded verbatim — partners can pass the
396
+ * exact payload they'd hand to `eth_signTypedData_v4`, including a
397
+ * `EIP712Domain` entry in `types` (the backend strips it before
398
+ * hashing, matching MetaMask's behavior).
399
+ */
400
+
401
+ interface Eip712TypedData {
402
+ domain: Record<string, unknown>;
403
+ types: Record<string, Array<{
404
+ name: string;
405
+ type: string;
406
+ }>>;
407
+ /** Optional — the backend derives the primary type from the structure. */
408
+ primaryType?: string;
409
+ message: Record<string, unknown>;
410
+ }
411
+ interface SignTypedDataInput {
412
+ network: string;
413
+ networkType: NetworkType;
414
+ typedData: Eip712TypedData;
415
+ walletIndex?: number;
416
+ /** Wallet password if the user has one. */
417
+ password?: string;
418
+ }
419
+ interface UseSignTypedDataResult {
420
+ signTypedData: (input: SignTypedDataInput) => Promise<string>;
421
+ pending: boolean;
422
+ error: Error | undefined;
423
+ }
424
+ declare function useSignTypedData(): UseSignTypedDataResult;
425
+
426
+ /**
427
+ * `useSendTransaction()` — generic EVM transaction sign + submit.
428
+ *
429
+ * const { sendTransaction, pending, error } = useSendTransaction();
430
+ *
431
+ * const { hash } = await sendTransaction({
432
+ * network: 'BASE',
433
+ * networkType: 'MAINNET',
434
+ * to: '0xContractAddress',
435
+ * data: '0xabcdef…', // encoded contract call
436
+ * value: '0',
437
+ * });
438
+ *
439
+ * Use this when `useTransfer().native()` / `.token()` aren't enough —
440
+ * e.g., calling an arbitrary contract method, deploying a contract,
441
+ * setting an allowance, etc. The backend handles nonce + gas; partners
442
+ * only fill in what they want to override.
443
+ *
444
+ * Returns the broadcast tx hash. Confirmation polling is the caller's
445
+ * job (subscribe via your own RPC, or poll a block explorer).
446
+ *
447
+ * EVM-only. Non-EVM chains use their own submit primitives (`/withdraw`
448
+ * for ICP / Casper, etc.) which today aren't exposed through this hook.
449
+ */
450
+
451
+ interface SendTransactionInput {
452
+ network: string;
453
+ networkType: NetworkType;
454
+ /** Recipient or contract address (`0x` + 40 hex chars). */
455
+ to: string;
456
+ /**
457
+ * Value in wei. Pass as a string (or bigint) — JS numbers lose bits
458
+ * above 2^53. Default: 0.
459
+ */
460
+ value?: string | bigint;
461
+ /** ABI-encoded calldata for contract calls. */
462
+ data?: string;
463
+ /** Explicit gas limit override. Omit to let the RPC estimate. */
464
+ gasLimit?: string | bigint;
465
+ walletIndex?: number;
466
+ password?: string;
467
+ }
468
+ interface UseSendTransactionResult {
469
+ sendTransaction: (input: SendTransactionInput) => Promise<{
470
+ hash: string;
471
+ }>;
472
+ pending: boolean;
473
+ error: Error | undefined;
474
+ }
475
+ declare function useSendTransaction(): UseSendTransactionResult;
476
+
477
+ /**
478
+ * `useTransfer()` — native + token transfer helpers. Each variant
479
+ * returns `{ pending, error }` so forms can drive disabled-state
480
+ * without local plumbing. Calls Horus directly.
242
481
  */
243
482
  interface NativeTransferInput {
244
483
  network: string;
@@ -299,9 +538,344 @@ interface HorusLoginButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElem
299
538
  declare function HorusLoginButton({ flow, phone, onAuthenticated, onError, children, disabled, style, ...rest }: HorusLoginButtonProps): JSX.Element;
300
539
 
301
540
  /**
302
- * Fetch wrapper for calls to the partner's backend proxy. Handles
303
- * the auto-refresh-on-401 dance + JSON serialization + a typed
304
- * error so consuming hooks can branch on `err.status`.
541
+ * Shared helpers for building auth-page URLs. Reused by the popup
542
+ * (`openPopupFlow` in `useHorusAuth`) and the inline iframe modal
543
+ * (`<HorusAuthModal>`). Centralizing here keeps the wire shape
544
+ * exactly aligned across both — partners get identical behavior
545
+ * regardless of which presentation surface they pick.
546
+ *
547
+ * Not exported to partners; internal-only.
548
+ */
549
+
550
+ type AuthFlow = 'google' | 'email_link' | 'phone' | 'unified';
551
+ /**
552
+ * Subset of methods the `unified` chooser may surface. Aligns with the
553
+ * auth-page's `HorusUnifiedMethod`. Includes `email_password` because
554
+ * the unified flow exposes it as a button even though direct
555
+ * `?flow=email_password` from the SDK isn't useful (that path is
556
+ * already headless via `useHorusAuth().loginWithEmail`).
557
+ */
558
+ type UnifiedMethod = 'google' | 'email_password' | 'email_link' | 'phone';
559
+
560
+ /**
561
+ * `<HorusAuthModal>` — inline auth flow.
562
+ *
563
+ * Renders an iframe pointed at `auth.horuswallet.com` with a backdrop
564
+ * + focus-trap shell. Partners use this as a drop-in alternative to
565
+ * `<HorusLoginButton>`'s popup model — matches Privy / Magic's inline
566
+ * modal UX.
567
+ *
568
+ * const [open, setOpen] = useState(false);
569
+ *
570
+ * return (
571
+ * <>
572
+ * <button onClick={() => setOpen(true)}>Sign in</button>
573
+ * <HorusAuthModal
574
+ * flow="google"
575
+ * open={open}
576
+ * onClose={() => setOpen(false)}
577
+ * onSuccess={(user) => { setOpen(false); console.log(user); }}
578
+ * />
579
+ * </>
580
+ * );
581
+ *
582
+ * The component is controlled (partner owns `open`). On `success`, the
583
+ * provider's `signIn` is invoked under the hood — partners don't need
584
+ * to call it themselves — and the partner-supplied `onSuccess` fires
585
+ * with the resulting user. `onClose` is called for both user cancel
586
+ * and Escape-key dismissal.
587
+ *
588
+ * Requires the Horus auth page to be deployed with iframe-friendly
589
+ * CSP headers (frame-ancestors permitting the partner origin). The
590
+ * postMessage protocol is identical to the popup flow.
591
+ */
592
+
593
+ interface HorusAuthModalProps {
594
+ /**
595
+ * Which sign-in flow to render in the iframe. Use `'unified'` for
596
+ * the Privy-style "one button → chooser modal" pattern: the iframe
597
+ * lands on a chooser screen with one button per enabled method,
598
+ * then transitions to whichever flow the end-user picks.
599
+ */
600
+ flow: AuthFlow;
601
+ /**
602
+ * Only applies when `flow === 'unified'`. Comma-joined into the URL
603
+ * as `&methods=...` so the chooser only shows what you list. Omit
604
+ * to surface all four (google, email_password, email_link, phone).
605
+ *
606
+ * <HorusAuthModal
607
+ * flow="unified"
608
+ * enabledMethods={['google', 'email_password']}
609
+ * open onClose={...}
610
+ * />
611
+ */
612
+ enabledMethods?: UnifiedMethod[];
613
+ /** Optional phone-flow pre-fill (when `flow === 'phone'`). */
614
+ phone?: string;
615
+ /** Controlled open state — partner toggles to show/hide the modal. */
616
+ open: boolean;
617
+ /** Called when the modal is dismissed (user cancel, Escape, success). */
618
+ onClose: () => void;
619
+ /** Called after a successful sign-in — receives the new user object. */
620
+ onSuccess?: (user: User) => void;
621
+ /** Called when the iframe surfaces an error. */
622
+ onError?: (err: Error) => void;
623
+ /**
624
+ * Inline style override for the dialog. Default is a centered
625
+ * 480×640 panel with rounded corners — works for the canonical
626
+ * Horus auth-page; partners with strong design systems can override.
627
+ */
628
+ dialogStyle?: React.CSSProperties;
629
+ /** Style override for the backdrop. */
630
+ backdropStyle?: React.CSSProperties;
631
+ }
632
+ declare function HorusAuthModal(props: HorusAuthModalProps): JSX.Element | null;
633
+
634
+ /**
635
+ * `<HorusRevealModal>` — iframe-based private-key reveal surface.
636
+ *
637
+ * const { reveal } = useExportWallet();
638
+ * const [revealToken, setRevealToken] = useState<string | null>(null);
639
+ *
640
+ * <button
641
+ * onClick={async () => {
642
+ * const { revealToken } = await reveal({ network: 'EVM', networkType: 'MAINNET', password });
643
+ * setRevealToken(revealToken);
644
+ * }}
645
+ * >Export private key</button>
646
+ *
647
+ * <HorusRevealModal
648
+ * revealToken={revealToken}
649
+ * open={revealToken !== null}
650
+ * onClose={() => setRevealToken(null)}
651
+ * />
652
+ *
653
+ * The modal renders the auth-page's reveal flow inline. The iframe
654
+ * runs at `auth.horuswallet.com` — partner JS cannot read its DOM
655
+ * cross-origin, so the user's private key never enters the partner
656
+ * page's address space. The partner only receives a `reveal_complete`
657
+ * postMessage at the end carrying `{ viewed: boolean }` — useful for
658
+ * analytics, but no key material.
659
+ */
660
+ interface HorusRevealModalProps {
661
+ /** Token obtained from `useExportWallet().reveal(...)`. */
662
+ revealToken: string | null;
663
+ /** Controlled open state. */
664
+ open: boolean;
665
+ /** Called when the modal is dismissed (user cancel, success, error, Escape). */
666
+ onClose: () => void;
667
+ /**
668
+ * Called when the iframe signals the reveal finished. `viewed` is
669
+ * true only if the user actually clicked through to see the key.
670
+ */
671
+ onComplete?: (viewed: boolean) => void;
672
+ /** Called on a flow-level error (network blip, bad token). */
673
+ onError?: (err: Error) => void;
674
+ dialogStyle?: React.CSSProperties;
675
+ backdropStyle?: React.CSSProperties;
676
+ }
677
+ declare function HorusRevealModal(props: HorusRevealModalProps): JSX.Element | null;
678
+
679
+ /**
680
+ * `useExportWallet()` — initiate a private-key reveal flow.
681
+ *
682
+ * const { reveal, pending, error } = useExportWallet();
683
+ *
684
+ * // In a click handler:
685
+ * const { revealToken } = await reveal({ network: 'EVM', networkType: 'MAINNET', password });
686
+ * // Then mount <HorusRevealModal revealToken={revealToken} ... />
687
+ *
688
+ * Two-step flow (matches Privy's reveal model):
689
+ *
690
+ * 1. This hook calls `/exportWallet/grant` with the user's session
691
+ * and wallet password. The backend validates and returns a
692
+ * short-lived (60s) `revealToken`.
693
+ * 2. The partner page mounts `<HorusRevealModal>` with that token.
694
+ * The modal opens an iframe at `auth.horuswallet.com?flow=reveal`,
695
+ * the iframe re-prompts for the password, calls /redeem itself,
696
+ * displays the key, and posts a "done" back. Partner JS never
697
+ * sees the private key.
698
+ *
699
+ * Why the password is collected twice (once for grant, once in the
700
+ * iframe for redeem): the grant proves the user authorized the export
701
+ * AND that they know the password right now — without that step,
702
+ * a stolen session alone could trigger the reveal modal and bombard
703
+ * the user with a password prompt. The iframe re-prompt is the layer
704
+ * that proves the holder of the partner page is the same human who
705
+ * authorized the grant (the iframe never trusts partner JS for the
706
+ * password input).
707
+ */
708
+
709
+ interface ExportWalletInput {
710
+ network: string;
711
+ networkType: NetworkType;
712
+ walletIndex?: number;
713
+ /**
714
+ * Wallet password. Pass `''` for password-less wallets — the backend
715
+ * uses the attempted decrypt as the proof of ownership, so an empty
716
+ * string against a password-less wallet succeeds where the same
717
+ * empty string against a password-protected wallet returns 401.
718
+ */
719
+ password: string;
720
+ }
721
+ interface ExportWalletResult {
722
+ /**
723
+ * Opaque token to hand to `<HorusRevealModal>`. Expires in 60s.
724
+ * Single-use: after the modal redeems it, a second redeem returns 409.
725
+ */
726
+ revealToken: string;
727
+ /** Seconds the token is valid for. */
728
+ expiresIn: number;
729
+ }
730
+ interface UseExportWalletResult {
731
+ reveal: (input: ExportWalletInput) => Promise<ExportWalletResult>;
732
+ pending: boolean;
733
+ error: Error | undefined;
734
+ }
735
+ declare function useExportWallet(): UseExportWalletResult;
736
+
737
+ /**
738
+ * MCP / agentic key management hooks.
739
+ *
740
+ * Mints, lists, revokes, and updates the "agent keys" that an end-user
741
+ * issues to an MCP runtime (Claude Desktop, custom agent, etc.) so the
742
+ * agent can act on the user's behalf — sign messages, transfer tokens,
743
+ * call partner-allowed tools — without the agent ever seeing the
744
+ * underlying wallet credentials.
745
+ *
746
+ * Scope is the wallet-OWNER side: minting + lifecycle. The actual
747
+ * JSON-RPC tool execution endpoint (`POST /mcp`) is consumed by MCP
748
+ * runtimes directly with the minted key; partner React code never
749
+ * calls it.
750
+ *
751
+ * Wire shapes mirror the Node SDK's `client.mcp.*` exactly so the
752
+ * two stay in sync.
753
+ */
754
+ interface GenerateMcpKeyInput {
755
+ /** Display name shown in `listKeys` (and in any partner UI). */
756
+ name: string;
757
+ /** Wallet network this key signs against. */
758
+ network: string;
759
+ walletIndex?: number;
760
+ /** ISO-8601 expiry. The backend mirrors this to the row's TTL attribute. */
761
+ expiresAt?: string;
762
+ /** Optional IP allow-list (exact IPs only — no CIDRs). */
763
+ allowedIps?: string[];
764
+ /** Optional whitelist of MCP tool names this key may invoke. */
765
+ allowedTools?: string[];
766
+ /** Per-key rate cap. Backend default ~600 RPM. */
767
+ maxRequestsPerMinute?: number;
768
+ /**
769
+ * Wraps the wallet password so the key holder can sign without
770
+ * re-prompting. Required if the wallet is password-protected.
771
+ */
772
+ mcpPassword?: string;
773
+ }
774
+ interface GenerateMcpKeyResponse {
775
+ /** PLAINTEXT — returned exactly once at creation. Never recoverable. */
776
+ key: string;
777
+ name: string;
778
+ prefix: string;
779
+ wallet_index: number;
780
+ network: string;
781
+ wallet_address?: string;
782
+ expires_at?: string;
783
+ allowed_ips: string[];
784
+ allowed_tools: string[];
785
+ max_requests_per_minute: number;
786
+ created_at: string;
787
+ message: string;
788
+ }
789
+ interface McpKeyMetadata {
790
+ prefix: string;
791
+ name: string;
792
+ wallet_index: number;
793
+ network: string;
794
+ expires_at?: string;
795
+ last_used_at?: string;
796
+ created_at: string;
797
+ allowed_ips: string[];
798
+ allowed_tools: string[];
799
+ max_requests_per_minute: number;
800
+ }
801
+ interface UpdateMcpKeyInput {
802
+ expiresAt?: string;
803
+ allowedIps?: string[];
804
+ allowedTools?: string[];
805
+ maxRequestsPerMinute?: number;
806
+ }
807
+ /**
808
+ * `useGenerateMcpKey()` — mint a new MCP key.
809
+ *
810
+ * const { generate, pending, error, lastResult } = useGenerateMcpKey();
811
+ * const result = await generate({ name: 'Claude Desktop', network: 'ETHEREUM' });
812
+ * // Show `result.key` to the user immediately — it is never returned again.
813
+ *
814
+ * After a successful call, `lastResult` holds the response so partners
815
+ * can render a one-time secret banner without threading the value
816
+ * through their own state. It's cleared on the next `generate()`.
817
+ */
818
+ declare function useGenerateMcpKey(): {
819
+ generate: (input: GenerateMcpKeyInput) => Promise<GenerateMcpKeyResponse>;
820
+ pending: boolean;
821
+ error: Error | undefined;
822
+ lastResult: GenerateMcpKeyResponse | null;
823
+ clearLastResult: () => void;
824
+ };
825
+ /**
826
+ * `useMcpKeys()` — read the current user's MCP key list.
827
+ *
828
+ * const { ready, keys, refresh, error } = useMcpKeys();
829
+ *
830
+ * Mutating hooks (`useGenerateMcpKey`, `useRevokeMcpKey`, `useUpdateMcpKey`)
831
+ * each bump an internal version counter so this hook re-fetches
832
+ * automatically after every change — no manual `refresh()` needed.
833
+ */
834
+ declare function useMcpKeys(): {
835
+ ready: boolean;
836
+ keys: McpKeyMetadata[];
837
+ refresh: () => Promise<void>;
838
+ error: Error | undefined;
839
+ };
840
+ /** `useRevokeMcpKey()` — revoke a key by its prefix. Idempotent on the backend. */
841
+ declare function useRevokeMcpKey(): {
842
+ revoke: (prefix: string) => Promise<{
843
+ revoked: boolean;
844
+ message: string;
845
+ }>;
846
+ pending: boolean;
847
+ error: Error | undefined;
848
+ };
849
+ /**
850
+ * `useUpdateMcpKey()` — patch a key's policy fields (expiry, allowed
851
+ * IPs, allowed tools, RPM cap). Returns `{ updated, message }`.
852
+ */
853
+ declare function useUpdateMcpKey(): {
854
+ update: (prefix: string, updates: UpdateMcpKeyInput) => Promise<{
855
+ updated: boolean;
856
+ message: string;
857
+ }>;
858
+ pending: boolean;
859
+ error: Error | undefined;
860
+ };
861
+
862
+ /**
863
+ * Fetch wrapper for browser-direct calls to the Horus API.
864
+ *
865
+ * Two headers are stamped on every request:
866
+ * - `x-horus-key: <publishable appId>` — the partner's `app_*`
867
+ * identifier. Tells Horus which app this call belongs to and
868
+ * anchors the Origin allow-list check on the backend.
869
+ * - `Authorization: Bearer <idToken>` — the end-user's session.
870
+ * Skipped when `options.auth` is `false` (sign-in / refresh
871
+ * endpoints that mint the token in the first place).
872
+ *
873
+ * On 401 we run a single refresh-then-retry. If refresh fails, the
874
+ * original 401 bubbles up so the provider can transition to the
875
+ * `unauthenticated` state.
876
+ *
877
+ * Returns the parsed JSON body on success, or throws a typed
878
+ * `HorusHttpError` so consumer hooks can branch on `err.status`.
305
879
  */
306
880
 
307
881
  declare class HorusHttpError extends Error {
@@ -311,4 +885,4 @@ declare class HorusHttpError extends Error {
311
885
  constructor(status: number, message: string, body: unknown, code?: string);
312
886
  }
313
887
 
314
- export { type AuthState, type AuthTokens, type HorusBranding, HorusHttpError, HorusLoginButton, type HorusLoginButtonProps, HorusProvider, type HorusProviderConfig, type HorusProviderProps, type NativeTransferInput, type SignMessageInput, type TokenTransferInput, type UseHorusAuthResult, type User, type WalletDescriptor, useHorusAuth, useSignMessage, useTransfer, useUser, useWallets };
888
+ export { type AuthState, type AuthTokens, type ChainSelector, type CreateWalletInput, type CreateWalletResult, type Eip712TypedData, type ExportWalletInput, type ExportWalletResult, type GenerateMcpKeyInput, type GenerateMcpKeyResponse, HorusAuthModal, type HorusAuthModalProps, type HorusBranding, HorusHttpError, HorusLoginButton, type HorusLoginButtonProps, HorusProvider, type HorusProviderConfig, type HorusProviderProps, HorusRevealModal, type HorusRevealModalProps, type McpKeyMetadata, type NativeTransferInput, type NetworkType, SUPPORTED_CHAINS, type SendTransactionInput, type SignMessageInput, type SignTypedDataInput, type SupportedChain, type TokenTransferInput, type UnifiedMethod, type UpdateMcpKeyInput, type UseHorusAuthResult, type User, type WalletDescriptor, useChain, useCreateWallet, useExportWallet, useGenerateMcpKey, useHorusAuth, useMcpKeys, useRevokeMcpKey, useSendTransaction, useSignMessage, useSignTypedData, useSwitchChain, useTransfer, useUpdateMcpKey, useUser, useWallets };