@waaskey/react 0.3.2 → 0.4.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 +131 -31
- package/dist/index.cjs +583 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +140 -22
- package/dist/index.d.ts +140 -22
- package/dist/index.js +438 -138
- package/dist/index.js.map +1 -1
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -2,51 +2,153 @@
|
|
|
2
2
|
|
|
3
3
|
React hooks + provider for the [Waaskey](https://waaskey.com) SDK — embedded,
|
|
4
4
|
non-custodial **MPC wallets**. A thin layer over [`@waaskey/sdk`](../sdk); the same
|
|
5
|
-
hooks work in React Native.
|
|
5
|
+
hooks work in React Native (via [`@waaskey/react-native`](../react-native)).
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
+
`@waaskey/sdk` and `react` are **peer dependencies** — the SDK holds your end-user session and
|
|
10
|
+
the MPC/share state, so a second copy in the tree would mean a second client and a lost session.
|
|
11
|
+
|
|
9
12
|
```bash
|
|
10
13
|
pnpm add @waaskey/react @waaskey/sdk @waaskey/client-wasm react
|
|
11
14
|
```
|
|
12
15
|
|
|
16
|
+
`@waaskey/client-wasm` carries the browser MPC engine; `qrcode` is optional (the Receive tab shows
|
|
17
|
+
the address either way), as is `@simplewebauthn/browser` (only for passkey sign-in).
|
|
18
|
+
|
|
13
19
|
## Use
|
|
14
20
|
|
|
15
21
|
```tsx
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
22
|
+
import { useMemo, useState } from 'react';
|
|
23
|
+
import { ConnectModal, WaasProvider, useAuth, useBalance, useCreateWallet } from '@waaskey/react';
|
|
24
|
+
import { EncryptedShareStore, WasmMpcCore, loadClientWasm } from '@waaskey/sdk';
|
|
25
|
+
import type { EmbeddedSession } from '@waaskey/sdk';
|
|
26
|
+
|
|
27
|
+
export default function App() {
|
|
28
|
+
// The device share is sealed with a secret derived from the signed-in user, so the MPC pieces
|
|
29
|
+
// can only be wired once you have a session. `useMemo` keeps the options (and therefore the
|
|
30
|
+
// client) stable — see "Client identity" below.
|
|
31
|
+
const [session, setSession] = useState<EmbeddedSession | null>(null);
|
|
32
|
+
const options = useMemo(() => {
|
|
33
|
+
const base = { apiKey: import.meta.env.VITE_WAASKEY_KEY as string };
|
|
34
|
+
if (!session) return base;
|
|
35
|
+
return {
|
|
36
|
+
...base,
|
|
37
|
+
mpc: new WasmMpcCore(loadClientWasm),
|
|
38
|
+
shareStore: EncryptedShareStore.browser(`${session.token.slice(0, 32)}:my-app`),
|
|
39
|
+
};
|
|
40
|
+
}, [session]);
|
|
18
41
|
|
|
19
|
-
const options = {
|
|
20
|
-
apiKey: import.meta.env.VITE_WAASKEY_KEY,
|
|
21
|
-
mpc: new WasmMpcCore(loadClientWasm),
|
|
22
|
-
shareStore: EncryptedShareStore.browser(sessionSecret),
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
function App() {
|
|
26
42
|
return (
|
|
27
|
-
<
|
|
28
|
-
<Wallet />
|
|
29
|
-
</
|
|
43
|
+
<WaasProvider options={options} persistSession="session">
|
|
44
|
+
<Wallet onSession={setSession} />
|
|
45
|
+
</WaasProvider>
|
|
30
46
|
);
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
function Wallet() {
|
|
34
|
-
const {
|
|
49
|
+
function Wallet({ onSession }: { onSession: (s: EmbeddedSession) => void }) {
|
|
50
|
+
const { isAuthenticated, ready, user, logout } = useAuth();
|
|
51
|
+
const { create, wallet, isPending, error } = useCreateWallet();
|
|
35
52
|
const { data: balance } = useBalance('ethereum', wallet?.address);
|
|
36
|
-
|
|
53
|
+
|
|
54
|
+
if (!ready) return null; // a persisted session may still be restoring
|
|
55
|
+
if (!isAuthenticated) return <ConnectModal open onClose={() => undefined} onConnect={onSession} />;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
<p>
|
|
60
|
+
{user?.email} · <button onClick={logout}>Sign out</button>
|
|
61
|
+
</p>
|
|
62
|
+
{error && <p role="alert">{error.message}</p>}
|
|
63
|
+
{wallet ? (
|
|
64
|
+
<p>
|
|
65
|
+
{wallet.address} — {balance?.formatted ?? '…'} ETH
|
|
66
|
+
</p>
|
|
67
|
+
) : (
|
|
68
|
+
<button disabled={isPending} onClick={() => create({ chain: 'ethereum' })}>
|
|
69
|
+
{isPending ? 'Creating…' : 'Create wallet'}
|
|
70
|
+
</button>
|
|
71
|
+
)}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
37
74
|
}
|
|
38
75
|
```
|
|
39
76
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
|
77
|
+
Keygen runs in the browser and takes a while (Paillier safe primes) — render the pending state.
|
|
78
|
+
Persist `wallet.id` in your own database and reload it later with `useWallet(id)`; the device share
|
|
79
|
+
stays in the `shareStore`.
|
|
80
|
+
|
|
81
|
+
### Hooks
|
|
82
|
+
|
|
83
|
+
| Export | Description |
|
|
84
|
+
| ---------------------------- | ------------------------------------------------------------------- |
|
|
85
|
+
| `useWaaskey()` | The `Waaskey` client from context. |
|
|
86
|
+
| `useWaas()` | `{ client, theme, auth }` — the unified surface. |
|
|
87
|
+
| `useAuth()` | `{ user, session, isAuthenticated, ready, logout, setSession, … }`. |
|
|
88
|
+
| `useUser()` | The signed-in end-user, or `undefined`. |
|
|
89
|
+
| `useLogin()` | Headless login: email/phone OTP, Google, Firebase, passkey. |
|
|
90
|
+
| `useCreateWallet()` | `{ create, wallet, error, isPending }`. |
|
|
91
|
+
| `useWallet(id)` | `{ data, loading, error, refresh }`. |
|
|
92
|
+
| `useWallets(query?)` | `{ data, total, loading, error, refresh }` — the tenant's wallets. |
|
|
93
|
+
| `useBalance(chain, address)` | `{ data, loading, error, refresh }` — client-side read. |
|
|
94
|
+
| `useBalances(chains, addr)` | The same across several chains. |
|
|
95
|
+
| `useSend(wallet)` | `{ send, status, result, error, reset }` — `idle → pending → sent`. |
|
|
96
|
+
| `useSignatures(wallet)` | A wallet's signing activity. |
|
|
97
|
+
| `useQrCode(text)` | A QR data URL (needs the optional `qrcode` package). |
|
|
98
|
+
|
|
99
|
+
`useSend`/`useSignatures` accept a wallet **id or a loaded `Wallet`** — pass the object you already
|
|
100
|
+
have to skip a `wallets.get`. Every read cancels the request it supersedes, so a slow earlier
|
|
101
|
+
response can't overwrite a newer one.
|
|
47
102
|
|
|
48
103
|
All of `@waaskey/sdk` is re-exported, so a single import covers types and values.
|
|
49
104
|
|
|
105
|
+
### Signing in
|
|
106
|
+
|
|
107
|
+
`<ConnectModal>` drives `useLogin`, and the session it establishes is published to the provider's
|
|
108
|
+
shared auth state — so `useAuth()` sees it everywhere, not only inside the modal.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<ConnectModal open={open} onClose={() => setOpen(false)} methods={['email', 'phone', 'passkey']} onConnect={(s) => console.log(s.endUser)} />
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Google is available through `useLogin().loginWithGoogle(idToken)` — and in the modal by passing a
|
|
115
|
+
`googleIdToken` resolver. Your app owns the Google button (Google Identity Services /
|
|
116
|
+
`@react-oauth/google`); the kit ships no Google SDK, so without that prop the option is hidden.
|
|
117
|
+
|
|
118
|
+
### Staying signed in
|
|
119
|
+
|
|
120
|
+
The SDK keeps the session **in memory**, so by default a reload signs the user out. Opt in with
|
|
121
|
+
`persistSession`:
|
|
122
|
+
|
|
123
|
+
| Value | Where the session goes |
|
|
124
|
+
| -------------------- | ------------------------------------------------------------------------------- |
|
|
125
|
+
| `'none'` _(default)_ | Memory only — a reload signs out. |
|
|
126
|
+
| `'session'` | `sessionStorage` — survives a reload, cleared when the tab closes. |
|
|
127
|
+
| `'local'` | `localStorage` — survives a restart. |
|
|
128
|
+
| a custom store | Your `{ load, save, clear }` — e.g. an httpOnly cookie set by your own backend. |
|
|
129
|
+
|
|
130
|
+
A session token in web storage is readable by any XSS or malicious extension, which is why the
|
|
131
|
+
default is `'none'` and `'session'` is the safer built-in. On restore the token is validated with
|
|
132
|
+
`auth.me()`: a token the API no longer accepts signs the user out instead of failing on the first
|
|
133
|
+
call. Gate your signed-out UI on `ready`, or the app flashes the login screen on every reload.
|
|
134
|
+
On React Native use `createSecureSessionStore(storage)` from `@waaskey/react-native` (Keychain /
|
|
135
|
+
Keystore) — the web values do nothing there.
|
|
136
|
+
|
|
137
|
+
### Client identity
|
|
138
|
+
|
|
139
|
+
The client is built **once** and kept while the option values keep their identity, so an inline
|
|
140
|
+
`options={{ … }}` literal is safe: primitives are compared by value, objects (MPC core, share store)
|
|
141
|
+
by reference. Changing a value really does build a new client — abandoning any running ceremony and
|
|
142
|
+
cached state — and the kit warns in development when that happens. The end-user session is
|
|
143
|
+
re-adopted on the new client automatically, so enriching the options once after login (the pattern
|
|
144
|
+
above) does not sign the user out.
|
|
145
|
+
|
|
146
|
+
## Next.js (App Router)
|
|
147
|
+
|
|
148
|
+
The package ships a `'use client'` directive, so importing it from a server component works; the
|
|
149
|
+
components themselves still have to be rendered inside a client boundary. Build the options in a
|
|
150
|
+
client module (they hold browser-only objects) and mount `<WaasProvider>` there.
|
|
151
|
+
|
|
50
152
|
## Widget kit (Privy-style, drop-in)
|
|
51
153
|
|
|
52
154
|
Wrap the app once and drop in prebuilt, themeable components instead of building wallet
|
|
@@ -62,22 +164,20 @@ function App() {
|
|
|
62
164
|
</WaasProvider>
|
|
63
165
|
);
|
|
64
166
|
}
|
|
65
|
-
|
|
66
|
-
function Connect() {
|
|
67
|
-
const [open, setOpen] = useState(true);
|
|
68
|
-
return <ConnectModal open={open} onClose={() => setOpen(false)} onConnect={(s) => console.log(s.endUser)} />;
|
|
69
|
-
}
|
|
70
167
|
```
|
|
71
168
|
|
|
72
169
|
| Component / hook | What it does |
|
|
73
170
|
| ------------------------------ | ---------------------------------------------------------------------------------------- |
|
|
74
|
-
| `<WaasProvider options/theme>` | Client + theme context (alias of `WaaskeyProvider`; also accepts `client`).
|
|
75
|
-
|
|
|
76
|
-
| `<ConnectModal>` | Email-OTP login modal (`auth`), `onConnect(session)`. |
|
|
171
|
+
| `<WaasProvider options/theme>` | Client + theme + auth context (alias of `WaaskeyProvider`; also accepts `client`). |
|
|
172
|
+
| `<ConnectModal>` | Login modal — email/phone OTP, passkey, Google; `onConnect(session)`. |
|
|
77
173
|
| `<WalletWidget>` | Assets / Receive (QR) / Send / Activity panel. |
|
|
78
174
|
| `<FundWidget>` | Fiat on-ramp (provider widget URL) to fund the wallet. |
|
|
79
175
|
| `useSignPrompt()` | `requestSignature(tx)` → resolves on approve, rejects on cancel; renders `<SignPrompt>`. |
|
|
80
176
|
|
|
177
|
+
**WaaS signs, it does not broadcast.** A send returns `result.signedTx` (plus an offline
|
|
178
|
+
`result.txHash`); submitting it to a node is the integrator's step — `waaskey.broadcast(signedTx,
|
|
179
|
+
{ rpcUrl })` or your own infrastructure. The widget says so on the success screen.
|
|
180
|
+
|
|
81
181
|
### Theming / white-label
|
|
82
182
|
|
|
83
183
|
Pass a partial `theme` to the provider (merged onto `lightTheme`); `darkTheme` and
|