@horus-wallet/sdk-react 0.1.0-beta.2 → 0.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -174
- package/dist/connect.cjs +218 -0
- package/dist/connect.d.cts +155 -0
- package/dist/connect.d.ts +155 -0
- package/dist/connect.js +188 -0
- package/dist/index.cjs +811 -64
- package/dist/index.d.cts +605 -31
- package/dist/index.d.ts +605 -31
- package/dist/index.js +798 -64
- package/package.json +7 -2
package/dist/index.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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
-
*
|
|
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
|
|
189
|
-
*
|
|
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
|
-
* `
|
|
240
|
-
*
|
|
241
|
-
*
|
|
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
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
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 };
|