@horus-wallet/sdk-react 0.1.0-beta.2 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +608 -55
- package/dist/index.d.cts +449 -31
- package/dist/index.d.ts +449 -31
- package/dist/index.js +599 -55
- package/package.json +7 -2
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 **
|
|
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
|
-
- **
|
|
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
|
|
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
|
|
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.
|
|
84
|
+
### 3. There is no step 3.
|
|
84
85
|
|
|
85
|
-
|
|
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 —
|
|
98
|
-
|
|
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 `
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
-
|
|
351
|
+
- WalletConnect (separate peer-dep, opt-in)
|
|
394
352
|
- Mobile SDKs (iOS / Android / React Native) — coming
|
|
395
|
-
-
|
|
396
|
-
-
|
|
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`.
|
package/dist/connect.cjs
ADDED
|
@@ -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
|
+
});
|