@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @horus-wallet/sdk-react
2
2
 
3
- React provider + hooks + drop-in components for the Horus embedded-wallet platform. Sign-in, wallets, signing, and on-chain transfers in **15 minutes**.
3
+ React provider + hooks + drop-in components for the Horus embedded-wallet platform. Sign-in, wallets, signing, and on-chain transfers in **5 minutes**.
4
4
 
5
5
  ```tsx
6
6
  import { HorusProvider, useHorusAuth, useWallets, HorusLoginButton } from '@horus-wallet/sdk-react';
@@ -30,16 +30,17 @@ function Page() {
30
30
  }
31
31
  ```
32
32
 
33
- That's it. Sign-in, token persistence, auto-refresh, and wallet fetching all happen inside the provider.
33
+ That's it. **No partner backend, no proxy.** The SDK calls Horus directly with your publishable `app_*` identifier. Sign-in, token persistence, auto-refresh, and wallet fetching all happen inside the provider.
34
34
 
35
35
  ## What you get
36
36
 
37
- - **Drop-in React DX** — one provider, a handful of hooks, no wallet-management code in your app
37
+ - **Browser-direct** — wrap your app once, you're done. Same shape as Privy / Stripe Elements
38
+ - **Drop-in React DX** — one provider, a handful of hooks, no wallet code in your app
38
39
  - **Built-in auth** — email + password, magic-link email, Google / Apple / phone via popup
39
40
  - **Multi-chain wallets** — EVM, Bitcoin, ICP, Casper, Aeternity — same hook, pick `network`
40
41
  - **Signing + transfers** — `signMessage`, native + ERC-20 transfers, all behind a hook
41
42
  - **Token persistence** — sessions survive page reloads; cross-tab sync; auto-refresh near expiry
42
- - **Drop-in components** — `<HorusLoginButton>` for one-click signin if you don't want to build the UI yourself
43
+ - **Drop-in components** — `<HorusLoginButton>` for one-click signin if you don't want to build the UI
43
44
  - **White-label friendly** — partner branding (logo, colors, terms links) flows through to the sign-in popup
44
45
 
45
46
  ## Install
@@ -50,7 +51,7 @@ npm install @horus-wallet/sdk-react
50
51
 
51
52
  Peer dependency: React 17+. Works in React 18, Next.js (App Router and Pages), Vite, Remix.
52
53
 
53
- ## Quick setup (3 steps)
54
+ ## Quick setup
54
55
 
55
56
  ### 1. Wrap your app in `<HorusProvider>`
56
57
 
@@ -66,7 +67,7 @@ export function App({ children }) {
66
67
  }
67
68
  ```
68
69
 
69
- You get `appId` from Horus during onboarding.
70
+ You get `appId` from Horus during onboarding (format `app_<16-hex>`). Register the domains you'll serve the SDK from on the same app record — that allow-list is the security boundary that stops anyone else from using your `app_*`.
70
71
 
71
72
  ### 2. Mount a sign-in button
72
73
 
@@ -80,30 +81,28 @@ import { HorusLoginButton } from '@horus-wallet/sdk-react';
80
81
 
81
82
  Or build your own form and call `useHorusAuth()` methods directly (see hooks below).
82
83
 
83
- ### 3. Add a server-side proxy
84
+ ### 3. There is no step 3.
84
85
 
85
- The React SDK calls **your backend** so your secret API key never reaches the browser:
86
-
87
- ```
88
- React app ──► Your backend (~50 lines) ──► api.horuswallet.com
89
- ```
90
-
91
- The proxy is a thin pass-through. Reference implementations for Express and Next.js are below.
86
+ You're integrated.
92
87
 
93
88
  ## Provider config
94
89
 
95
90
  ```tsx
96
91
  <HorusProvider
97
- appId="app_acme123" // required — server-assigned during onboarding
98
- apiBase="/api/horus" // where YOUR backend exposes the proxy. Default: /api/horus
99
-
92
+ appId="app_acme123" // required — your publishable app identifier
93
+
94
+ // Optional — defaults to https://api.horuswallet.com (prod).
95
+ // Override for dev / staging:
96
+ // apiBase="https://dev-api.horuswallet.com"
97
+ apiBase={process.env.NEXT_PUBLIC_HORUS_API_BASE}
98
+
100
99
  branding={{ // optional — passed to the sign-in popup
101
100
  logoUrl: 'https://acme.com/logo.png',
102
101
  brandName: 'Acme',
103
102
  primaryColor: '#FF5733',
104
103
  showPoweredByHorus: false, // requires the white-label tier
105
104
  }}
106
-
105
+
107
106
  tokenStorage="localStorage" // 'localStorage' | 'sessionStorage' | 'memory'
108
107
  autoRefresh={true} // refresh tokens before expiry; default true
109
108
  >
@@ -120,18 +119,18 @@ const {
120
119
  ready, // true once the provider has hydrated from storage
121
120
  authenticated, // true if user is signed in
122
121
  user, // { uid, email, displayName, ... } or undefined
123
-
122
+
124
123
  // Headless flows — your form, your UI
125
124
  loginWithEmail, // ({ email, password }) => Promise<User>
126
125
  signUpWithEmail, // ({ email, password }) => Promise<User>
127
126
  sendEmailLink, // ({ email, continueUrl }) => Promise<void>
128
127
  verifyEmailLink, // ({ email, oobCode }) => Promise<User>
129
-
128
+
130
129
  // Popup flows — Horus-hosted UI for OAuth providers
131
130
  loginWithGoogle, // () => Promise<User>
132
131
  loginWithEmailLink, // popup variant of email-link
133
132
  loginWithPhone, // ({ phone? }) => Promise<User>
134
-
133
+
135
134
  logout, // clears tokens
136
135
  refresh, // manual token refresh
137
136
  } = useHorusAuth();
@@ -198,152 +197,6 @@ const { txHash } = await token({
198
197
 
199
198
  For total UI control, skip the component and call `useHorusAuth()` methods from your own form.
200
199
 
201
- ## Backend setup (the proxy)
202
-
203
- A thin pass-through that forwards SDK calls to Horus, holding your secret key server-side. Roughly **50 lines**.
204
-
205
- ### Express / Connect
206
-
207
- ```ts
208
- // server/horus-proxy.ts
209
- import express from 'express';
210
- import { HorusClient } from '@horus-wallet/sdk';
211
-
212
- export function horusProxy() {
213
- const router = express.Router();
214
-
215
- // Per-request client — picks up the user's auth token from the SDK header.
216
- const clientFor = (req: express.Request) =>
217
- new HorusClient({
218
- baseUrl: process.env.HORUS_BASE_URL!,
219
- apiKey: process.env.HORUS_API_KEY!, // hk_sk_*
220
- getIdToken: async () => req.header('x-horus-id-token') ?? '',
221
- });
222
-
223
- // Auth — unauthenticated; mints tokens on success
224
- router.post('/auth/email/signup', async (req, res, next) => {
225
- try { res.json(await clientFor(req).auth.signUpWithEmail(req.body)); }
226
- catch (err) { next(err); }
227
- });
228
- router.post('/auth/email/signin', async (req, res, next) => {
229
- try { res.json(await clientFor(req).auth.signInWithEmail(req.body)); }
230
- catch (err) { next(err); }
231
- });
232
- router.post('/auth/email-link/send', async (req, res, next) => {
233
- try { res.json(await clientFor(req).auth.sendEmailLink(req.body)); }
234
- catch (err) { next(err); }
235
- });
236
- router.post('/auth/email-link/verify', async (req, res, next) => {
237
- try { res.json(await clientFor(req).auth.verifyEmailLink(req.body)); }
238
- catch (err) { next(err); }
239
- });
240
- router.post('/auth/oauth', async (req, res, next) => {
241
- try { res.json(await clientFor(req).auth.signInWithOAuth(req.body)); }
242
- catch (err) { next(err); }
243
- });
244
- router.post('/auth/refresh', async (req, res, next) => {
245
- try { res.json(await clientFor(req).auth.refresh(req.body.refreshToken)); }
246
- catch (err) { next(err); }
247
- });
248
-
249
- // Wallet ops — authenticated; SDK forwards the user's token
250
- router.get('/wallets', async (req, res, next) => {
251
- try { res.json(await clientFor(req).wallets.get()); }
252
- catch (err) { next(err); }
253
- });
254
- router.post('/sign-message', async (req, res, next) => {
255
- try { res.json(await clientFor(req).wallets.signMessage(req.body)); }
256
- catch (err) { next(err); }
257
- });
258
- router.post('/transfer/native', async (req, res, next) => {
259
- try { res.json(await clientFor(req).transfers.native(req.body)); }
260
- catch (err) { next(err); }
261
- });
262
- router.post('/transfer/token', async (req, res, next) => {
263
- try { res.json(await clientFor(req).transfers.token(req.body)); }
264
- catch (err) { next(err); }
265
- });
266
-
267
- return router;
268
- }
269
- ```
270
-
271
- Mount it:
272
-
273
- ```ts
274
- import express from 'express';
275
- import { horusProxy } from './horus-proxy';
276
-
277
- const app = express();
278
- app.use(express.json());
279
- app.use('/api/horus', horusProxy());
280
- app.listen(3001);
281
- ```
282
-
283
- That's the whole backend integration.
284
-
285
- ### Next.js (App Router)
286
-
287
- ```tsx
288
- // app/providers.tsx
289
- 'use client';
290
- import { HorusProvider } from '@horus-wallet/sdk-react';
291
-
292
- export function Providers({ children }: { children: React.ReactNode }) {
293
- return (
294
- <HorusProvider appId={process.env.NEXT_PUBLIC_HORUS_APP_ID!}>
295
- {children}
296
- </HorusProvider>
297
- );
298
- }
299
-
300
- // app/layout.tsx
301
- import { Providers } from './providers';
302
-
303
- export default function RootLayout({ children }: { children: React.ReactNode }) {
304
- return <html><body><Providers>{children}</Providers></body></html>;
305
- }
306
- ```
307
-
308
- ```ts
309
- // app/api/horus/[...path]/route.ts
310
- import { HorusClient } from '@horus-wallet/sdk';
311
-
312
- const client = (req: Request) =>
313
- new HorusClient({
314
- baseUrl: process.env.HORUS_BASE_URL!,
315
- apiKey: process.env.HORUS_API_KEY!,
316
- getIdToken: async () => req.headers.get('x-horus-id-token') ?? '',
317
- });
318
-
319
- const handlers: Record<string, (req: Request, body: any) => Promise<unknown>> = {
320
- 'auth/email/signin': (req, body) => client(req).auth.signInWithEmail(body),
321
- 'auth/email/signup': (req, body) => client(req).auth.signUpWithEmail(body),
322
- 'auth/email-link/send': (req, body) => client(req).auth.sendEmailLink(body),
323
- 'auth/email-link/verify': (req, body) => client(req).auth.verifyEmailLink(body),
324
- 'auth/oauth': (req, body) => client(req).auth.signInWithOAuth(body),
325
- 'auth/refresh': (req, body) => client(req).auth.refresh(body.refreshToken),
326
- 'wallets': (req) => client(req).wallets.get(),
327
- 'sign-message': (req, body) => client(req).wallets.signMessage(body),
328
- 'transfer/native': (req, body) => client(req).transfers.native(body),
329
- 'transfer/token': (req, body) => client(req).transfers.token(body),
330
- };
331
-
332
- async function dispatch(req: Request, ctx: { params: { path: string[] } }) {
333
- const fn = handlers[ctx.params.path.join('/')];
334
- if (!fn) return Response.json({ message: 'Not found' }, { status: 404 });
335
- try {
336
- const body = req.headers.get('content-type')?.includes('json') ? await req.json() : undefined;
337
- return Response.json(await fn(req, body));
338
- } catch (err: any) {
339
- return Response.json({ message: err.message }, { status: err.status ?? 500 });
340
- }
341
- }
342
-
343
- export const GET = dispatch;
344
- export const POST = dispatch;
345
- ```
346
-
347
200
  ## Errors
348
201
 
349
202
  ```tsx
@@ -367,11 +220,15 @@ The hooks (`useWallets`, `useSignMessage`, `useTransfer`) also expose `error` as
367
220
 
368
221
  ## Security
369
222
 
370
- - The `hk_sk_*` secret stays on your backend it never reaches the browser
223
+ - The `app_*` identifier is **publishable** — safe to embed in your frontend bundle, source code, or environment variable. It is bounded by the `allowedOrigins` allow-list you register on the app record, so a leaked `app_*` cannot be used from a domain you don't control
371
224
  - Tokens persist in `localStorage` by default. For stricter privacy, use `sessionStorage` (cleared on tab close) or `memory` (RAM only) via the `tokenStorage` prop
372
225
  - The popup flows pin their `postMessage` target origin to the partner's window — tokens cannot be exfiltrated to other origins
373
226
  - White-label settings (hiding the Horus footer in the popup) are server-enforced — partners on the standard tier always see the footer regardless of client config
374
227
 
228
+ ## Server-side use
229
+
230
+ This package is browser-only. For signing flows that need to run on your backend (cron jobs, server-rendered payment receipts, etc.), use the sibling [`@horus-wallet/sdk`](https://www.npmjs.com/package/@horus-wallet/sdk) package with a secret `hk_sk_*` key.
231
+
375
232
  ## Coming from Privy / Magic / Web3Auth?
376
233
 
377
234
  | Privy | Horus |
@@ -384,15 +241,117 @@ The hooks (`useWallets`, `useSignMessage`, `useTransfer`) also expose `error` as
384
241
  | Hosted modal | `<HorusLoginButton>` (popup) |
385
242
  | Backend SDK | `@horus-wallet/sdk` |
386
243
 
387
- Conceptual differences:
388
- - Horus uses a **partner-backend proxy** so your secret never leaves your server. Privy talks to its API directly from the browser.
389
- - **More chains supported**EVM + Bitcoin + ICP + Casper + Aeternity all in one SDK.
244
+ Conceptually identical to Privy's browser-direct model. Horus adds **more chains** out of the box — EVM + Bitcoin + ICP + Casper + Aeternity all in one SDK.
245
+
246
+ ## Inline auth modal`<HorusAuthModal>`
247
+
248
+ For partners who prefer an inline iframe over the popup flow:
249
+
250
+ ```tsx
251
+ import { HorusAuthModal } from '@horus-wallet/sdk-react';
252
+
253
+ const [open, setOpen] = useState(false);
254
+
255
+ <button onClick={() => setOpen(true)}>Sign in</button>
256
+ <HorusAuthModal
257
+ flow="google"
258
+ open={open}
259
+ onClose={() => setOpen(false)}
260
+ onSuccess={(user) => console.log('signed in:', user)}
261
+ />
262
+ ```
263
+
264
+ Same security boundaries as `<HorusLoginButton>`: tokens flow via
265
+ postMessage pinned to the iframe's origin; partner JS never sees the
266
+ auth-page DOM.
267
+
268
+ ## Wallet export — `useExportWallet`
269
+
270
+ Two-step flow that displays the user's private key in a Horus-hosted
271
+ iframe so partner JS never touches the key material:
272
+
273
+ ```tsx
274
+ import { useExportWallet, HorusRevealModal } from '@horus-wallet/sdk-react';
275
+
276
+ function ExportButton() {
277
+ const { reveal, pending, error } = useExportWallet();
278
+ const [revealToken, setRevealToken] = useState<string | null>(null);
279
+
280
+ return (
281
+ <>
282
+ <button
283
+ disabled={pending}
284
+ onClick={async () => {
285
+ const password = prompt('Wallet password');
286
+ if (password === null) return;
287
+ const { revealToken } = await reveal({
288
+ network: 'EVM',
289
+ networkType: 'MAINNET',
290
+ password,
291
+ });
292
+ setRevealToken(revealToken);
293
+ }}
294
+ >
295
+ Export private key
296
+ </button>
297
+ <HorusRevealModal
298
+ revealToken={revealToken}
299
+ open={revealToken !== null}
300
+ onClose={() => setRevealToken(null)}
301
+ onComplete={(viewed) => console.log('user viewed key:', viewed)}
302
+ />
303
+ </>
304
+ );
305
+ }
306
+ ```
307
+
308
+ The reveal modal re-prompts for the password inside its own iframe
309
+ (at `auth.horuswallet.com`). The private key is rendered there, behind
310
+ a tap-to-reveal blur, and a "Done" button returns control without
311
+ crossing the iframe → parent boundary with any key material.
312
+
313
+ ## External wallets (MetaMask / Coinbase Wallet / etc.)
314
+
315
+ Opt-in sub-entrypoint — `import from '@horus-wallet/sdk-react/connect'`.
316
+ Partners who only use embedded wallets don't pay the bundle cost.
317
+
318
+ ```tsx
319
+ import {
320
+ useConnectWallet,
321
+ useExternalSignMessage,
322
+ } from '@horus-wallet/sdk-react/connect';
323
+
324
+ function ConnectButton() {
325
+ const { wallet, connect, available } = useConnectWallet();
326
+ const { signMessage } = useExternalSignMessage();
327
+
328
+ if (!available) return <p>Install MetaMask to continue.</p>;
329
+ if (!wallet) return <button onClick={connect}>Connect wallet</button>;
330
+ return (
331
+ <button onClick={async () => {
332
+ const sig = await signMessage(wallet, 'Sign in to Acme — nonce 1234');
333
+ console.log(sig);
334
+ }}>
335
+ Sign with {wallet.walletName}
336
+ </button>
337
+ );
338
+ }
339
+ ```
340
+
341
+ The external-wallet path NEVER calls Horus — keys stay in the user's
342
+ extension. You can mix embedded + external in the same app (e.g.,
343
+ "Sign in with Horus" alongside "Connect MetaMask").
344
+
345
+ v1 supports any injected EIP-1193 provider (MetaMask, Coinbase Wallet,
346
+ Rabby, Brave). WalletConnect is on the roadmap as a separate opt-in
347
+ peer-dep.
390
348
 
391
349
  ## What's not yet here
392
350
 
393
- - External wallet connect (MetaMask / WalletConnect) — coming
351
+ - WalletConnect (separate peer-dep, opt-in)
394
352
  - Mobile SDKs (iOS / Android / React Native) — coming
395
- - Drop-in `<HorusAuthModal>` (inline UI without popup) coming
396
- - Pre-generated wallets for unsigned-in users — coming
353
+ - Mnemonic export (current export is private key; mnemonic requires a
354
+ forward-only schema change)
355
+ - Account linking + MFA (identity-model rework, planned)
397
356
 
398
357
  For partner integration questions: `info@horuswallet.com`.
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/connect/index.ts
21
+ var connect_exports = {};
22
+ __export(connect_exports, {
23
+ useConnectWallet: () => useConnectWallet,
24
+ useExternalSendTransaction: () => useExternalSendTransaction,
25
+ useExternalSignMessage: () => useExternalSignMessage,
26
+ useExternalSignTypedData: () => useExternalSignTypedData
27
+ });
28
+ module.exports = __toCommonJS(connect_exports);
29
+
30
+ // src/connect/useConnectWallet.ts
31
+ var import_react = require("react");
32
+ function detectProvider() {
33
+ if (typeof window === "undefined") return null;
34
+ const provider = window.ethereum;
35
+ return provider ?? null;
36
+ }
37
+ function nameFor(provider) {
38
+ if (provider.isMetaMask) return "MetaMask";
39
+ if (provider.isCoinbaseWallet) return "Coinbase Wallet";
40
+ if (provider.isRabby) return "Rabby";
41
+ if (provider.isBraveWallet) return "Brave Wallet";
42
+ return "Injected Wallet";
43
+ }
44
+ function useConnectWallet() {
45
+ const [wallet, setWallet] = (0, import_react.useState)(null);
46
+ const [pending, setPending] = (0, import_react.useState)(false);
47
+ const [error, setError] = (0, import_react.useState)(void 0);
48
+ const [available, setAvailable] = (0, import_react.useState)(false);
49
+ (0, import_react.useEffect)(() => {
50
+ setAvailable(detectProvider() !== null);
51
+ }, []);
52
+ (0, import_react.useEffect)(() => {
53
+ const provider = detectProvider();
54
+ if (!provider || !provider.on) return;
55
+ const onAccountsChanged = (accounts) => {
56
+ if (!accounts || accounts.length === 0) {
57
+ setWallet(null);
58
+ return;
59
+ }
60
+ setWallet((prev) => prev ? { ...prev, address: accounts[0] } : prev);
61
+ };
62
+ const onChainChanged = (chainIdHex) => {
63
+ const chainId = Number(chainIdHex);
64
+ if (!Number.isFinite(chainId)) return;
65
+ setWallet((prev) => prev ? { ...prev, chainId } : prev);
66
+ };
67
+ provider.on("accountsChanged", onAccountsChanged);
68
+ provider.on("chainChanged", onChainChanged);
69
+ return () => {
70
+ provider.removeListener?.("accountsChanged", onAccountsChanged);
71
+ provider.removeListener?.("chainChanged", onChainChanged);
72
+ };
73
+ }, []);
74
+ const connect = (0, import_react.useCallback)(async () => {
75
+ const provider = detectProvider();
76
+ if (!provider) {
77
+ const err = new Error(
78
+ "No injected wallet provider detected. Install MetaMask (or similar) and reload."
79
+ );
80
+ setError(err);
81
+ throw err;
82
+ }
83
+ setPending(true);
84
+ setError(void 0);
85
+ try {
86
+ const accounts = await provider.request({
87
+ method: "eth_requestAccounts"
88
+ });
89
+ if (!accounts || accounts.length === 0) {
90
+ throw new Error("No accounts returned from wallet.");
91
+ }
92
+ const chainIdHex = await provider.request({
93
+ method: "eth_chainId"
94
+ });
95
+ const chainId = Number(chainIdHex);
96
+ const next = {
97
+ address: accounts[0],
98
+ chainId: Number.isFinite(chainId) ? chainId : 0,
99
+ walletName: nameFor(provider),
100
+ provider
101
+ };
102
+ setWallet(next);
103
+ return next;
104
+ } catch (err) {
105
+ const e = err instanceof Error ? err : new Error(String(err));
106
+ setError(e);
107
+ throw e;
108
+ } finally {
109
+ setPending(false);
110
+ }
111
+ }, []);
112
+ const disconnect = (0, import_react.useCallback)(() => {
113
+ setWallet(null);
114
+ }, []);
115
+ return { wallet, pending, error, available, connect, disconnect };
116
+ }
117
+
118
+ // src/connect/useExternalSign.ts
119
+ var import_react2 = require("react");
120
+ function useExternalSignMessage() {
121
+ const [pending, setPending] = (0, import_react2.useState)(false);
122
+ const [error, setError] = (0, import_react2.useState)(void 0);
123
+ const signMessage = (0, import_react2.useCallback)(
124
+ async (wallet, message) => {
125
+ setPending(true);
126
+ setError(void 0);
127
+ try {
128
+ const sig = await wallet.provider.request({
129
+ method: "personal_sign",
130
+ params: [message, wallet.address]
131
+ });
132
+ return sig;
133
+ } catch (err) {
134
+ const e = err instanceof Error ? err : new Error(String(err));
135
+ setError(e);
136
+ throw e;
137
+ } finally {
138
+ setPending(false);
139
+ }
140
+ },
141
+ []
142
+ );
143
+ return { signMessage, pending, error };
144
+ }
145
+ function useExternalSignTypedData() {
146
+ const [pending, setPending] = (0, import_react2.useState)(false);
147
+ const [error, setError] = (0, import_react2.useState)(void 0);
148
+ const signTypedData = (0, import_react2.useCallback)(
149
+ async (wallet, typedData) => {
150
+ setPending(true);
151
+ setError(void 0);
152
+ try {
153
+ const sig = await wallet.provider.request({
154
+ method: "eth_signTypedData_v4",
155
+ params: [wallet.address, JSON.stringify(typedData)]
156
+ });
157
+ return sig;
158
+ } catch (err) {
159
+ const e = err instanceof Error ? err : new Error(String(err));
160
+ setError(e);
161
+ throw e;
162
+ } finally {
163
+ setPending(false);
164
+ }
165
+ },
166
+ []
167
+ );
168
+ return { signTypedData, pending, error };
169
+ }
170
+ function useExternalSendTransaction() {
171
+ const [pending, setPending] = (0, import_react2.useState)(false);
172
+ const [error, setError] = (0, import_react2.useState)(void 0);
173
+ const sendTransaction = (0, import_react2.useCallback)(
174
+ async (wallet, tx) => {
175
+ setPending(true);
176
+ setError(void 0);
177
+ try {
178
+ if (tx.chainId && tx.chainId !== wallet.chainId) {
179
+ await wallet.provider.request({
180
+ method: "wallet_switchEthereumChain",
181
+ params: [{ chainId: `0x${tx.chainId.toString(16)}` }]
182
+ });
183
+ }
184
+ const params = {
185
+ from: wallet.address,
186
+ to: tx.to
187
+ };
188
+ if (tx.value !== void 0) params.value = toHex(tx.value);
189
+ if (tx.data !== void 0) params.data = tx.data;
190
+ if (tx.gas !== void 0) params.gas = toHex(tx.gas);
191
+ const hash = await wallet.provider.request({
192
+ method: "eth_sendTransaction",
193
+ params: [params]
194
+ });
195
+ return { hash };
196
+ } catch (err) {
197
+ const e = err instanceof Error ? err : new Error(String(err));
198
+ setError(e);
199
+ throw e;
200
+ } finally {
201
+ setPending(false);
202
+ }
203
+ },
204
+ []
205
+ );
206
+ return { sendTransaction, pending, error };
207
+ }
208
+ function toHex(v) {
209
+ if (v.startsWith("0x")) return v;
210
+ return `0x${BigInt(v).toString(16)}`;
211
+ }
212
+ // Annotate the CommonJS export names for ESM import in node:
213
+ 0 && (module.exports = {
214
+ useConnectWallet,
215
+ useExternalSendTransaction,
216
+ useExternalSignMessage,
217
+ useExternalSignTypedData
218
+ });