@waaskey/sdk 0.0.1 → 0.1.0
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 +157 -15
- package/dist/index.cjs +2594 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2629 -43
- package/dist/index.d.ts +2629 -43
- package/dist/index.js +2563 -40
- package/dist/index.js.map +1 -1
- package/package.json +29 -22
package/README.md
CHANGED
|
@@ -16,28 +16,170 @@ pnpm add @waaskey/sdk
|
|
|
16
16
|
## Quickstart
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
|
-
import { Waaskey } from '@waaskey/sdk';
|
|
19
|
+
import { Waaskey, WasmMpcCore, EncryptedShareStore } from '@waaskey/sdk';
|
|
20
20
|
|
|
21
|
-
const waaskey = new Waaskey({
|
|
21
|
+
const waaskey = new Waaskey({
|
|
22
|
+
apiKey: process.env.WAASKEY_PUBLISHABLE_KEY!,
|
|
23
|
+
// Non-custodial: the device runs its half of the ceremony and seals its key share.
|
|
24
|
+
mpc: new WasmMpcCore(loadClientWasm), // device-party crypto core (WASM)
|
|
25
|
+
shareStore: EncryptedShareStore.browser(sessionSecret), // sealed in IndexedDB
|
|
26
|
+
});
|
|
22
27
|
|
|
23
|
-
// Create an MPC wallet
|
|
24
|
-
const wallet = await waaskey.wallets.create({
|
|
25
|
-
// →
|
|
28
|
+
// Create an MPC wallet — runs the device keygen, seals the share, waits until active.
|
|
29
|
+
const wallet = await waaskey.wallets.create({ chain: 'ethereum' });
|
|
30
|
+
// → wallet.id, wallet.address, wallet.status === 'active'
|
|
26
31
|
|
|
27
|
-
// Sign a
|
|
28
|
-
const signature = await wallet.
|
|
32
|
+
// Sign a 32-byte digest (hex; 0x optional)
|
|
33
|
+
const signature = await wallet.sign(digestHex);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Send a transaction — WaaS signs, **you** broadcast
|
|
37
|
+
|
|
38
|
+
WaaS is a **signing service, not a broadcaster** (non-custodial: it never owns the
|
|
39
|
+
mempool, nonce, or status-tracking). `wallet.send(...)` builds and MPC-signs the
|
|
40
|
+
transaction and returns the **signed raw tx** — you submit it from your own
|
|
41
|
+
node/provider:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const { signedTx, txHash } = await wallet.send({ chainId: 'evm:1', to, value });
|
|
45
|
+
// signedTx → the signed raw tx to broadcast; txHash → a deterministic offline id (not a confirmation)
|
|
46
|
+
|
|
47
|
+
// Broadcast from YOUR node — either the optional helper…
|
|
48
|
+
const { txHash: broadcastHash } = await waaskey.broadcast(signedTx, { rpcUrl: 'https://your-rpc' });
|
|
49
|
+
// …or your own submitter / provider (recommended for production — you own tracking & retries).
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The `broadcast` helper is best-effort by design — one `eth_sendRawTransaction`
|
|
53
|
+
call, **no status polling and no retries**. Tracking the tx to confirmation is the
|
|
54
|
+
integrator's concern.
|
|
55
|
+
|
|
56
|
+
### Custody policy — choose the threshold topology (advanced)
|
|
57
|
+
|
|
58
|
+
`wallets.create({ chain })` defaults to the **embedded 2-of-3** topology (the platform
|
|
59
|
+
alone can meet the threshold — the honest default posture). To change the `(t, n)`
|
|
60
|
+
threshold or the custody guarantee, pass a custody policy; the SDK validates it
|
|
61
|
+
client-side and the backend enforces the attested invariant `platformShares < t ⟺
|
|
62
|
+
non-custodial`:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const wallet = await waaskey.wallets.create({
|
|
66
|
+
chain: 'ethereum',
|
|
67
|
+
threshold: 2, // t
|
|
68
|
+
parties: ['device', 'backup', 'server'], // n = 3 party roles
|
|
69
|
+
custodyKinds: ['user_device', 'user_backup', 'platform_signer'], // parallel to parties
|
|
70
|
+
custodyType: 'shared', // requested posture — the server refuses (400) on a mismatch
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// The returned wallet surfaces the attestation so you can display the guarantee:
|
|
74
|
+
wallet.threshold; // 2
|
|
75
|
+
wallet.parties; // ['device','backup','server']
|
|
76
|
+
wallet.custodyKinds; // ['user_device','user_backup','platform_signer']
|
|
77
|
+
wallet.platformShareCount; // 1
|
|
78
|
+
wallet.custodyType; // 'shared'
|
|
79
|
+
wallet.isNonCustodial; // true — platform's 1 share alone can't reach t=2
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`CustodyKind` ∈ `user_device | user_backup | platform_signer | platform_recovery |
|
|
83
|
+
external_party`; `CustodyType` ∈ `embedded | shared | self_custody`. The standalone
|
|
84
|
+
`isNonCustodial(wallet)` helper is also exported.
|
|
85
|
+
|
|
86
|
+
> The device runs whatever `t`-of-`n` the create ceremony describes — keygen is not
|
|
87
|
+
> pinned to 2-of-3. Coordinating an **interactive** `t`-of-`n` _sign_ across more than
|
|
88
|
+
> one user-held party (each running its half over the relay) is a separate follow-up;
|
|
89
|
+
> today's `wallet.sign(...)` covers the device+server quorum.
|
|
90
|
+
|
|
91
|
+
Read the wallet's signing activity (raw signs + send/sweep signed txs, newest first):
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const { items } = await wallet.signatures({ page: 1, limit: 20 });
|
|
29
95
|
```
|
|
30
96
|
|
|
31
97
|
## API
|
|
32
98
|
|
|
33
|
-
| Method
|
|
34
|
-
|
|
|
35
|
-
| `new Waaskey({ apiKey, baseUrl?, fetch? })`
|
|
36
|
-
| `waaskey.wallets.create({ chain,
|
|
37
|
-
| `waaskey.wallets.get(id)`
|
|
38
|
-
| `wallet.
|
|
99
|
+
| Method | Description |
|
|
100
|
+
| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
101
|
+
| `new Waaskey({ apiKey, mpc, shareStore, baseUrl?, fetch? })` | Create a client. `mpc` + `shareStore` are required to create wallets. |
|
|
102
|
+
| `waaskey.wallets.create({ chain, label?, threshold?, parties?, custodyKinds?, custodyType? }, options?)` | Create (keygen + sealed share). Optional custody policy (default embedded 2-of-3). Returns a `Wallet`. |
|
|
103
|
+
| `waaskey.wallets.get(id, options?)` | Load an existing wallet. |
|
|
104
|
+
| `wallet.sign(digestHex, options?)` | Sign a 32-byte digest with the wallet's MPC key. |
|
|
105
|
+
| `wallet.send(params, options?)` | Build + MPC-sign a tx. Returns the **signed raw tx** (you broadcast). |
|
|
106
|
+
| `wallet.signatures(query?, options?)` | The wallet's signing activity (paginated). |
|
|
107
|
+
| `waaskey.broadcast(signedTx, { rpcUrl })` | **Optional** best-effort submit of a signed tx from your own node. |
|
|
108
|
+
|
|
109
|
+
`create` / `sign` accept `{ signal }` for cancellation; `create` also takes
|
|
110
|
+
`{ waitForActive?, activationTimeoutMs?, pollIntervalMs? }`.
|
|
111
|
+
|
|
112
|
+
Errors are thrown as `WaaskeyError` with a typed `code` (e.g. `unauthorized`,
|
|
113
|
+
`forbidden`, `validation`, `device_core_required`, `keygen_failed`, `aborted`) plus
|
|
114
|
+
`status?` and `details?` — branch on `error.code`, never on the message text.
|
|
115
|
+
|
|
116
|
+
### Secure share storage
|
|
117
|
+
|
|
118
|
+
The device key share is the user's half of the key. `EncryptedShareStore` seals it
|
|
119
|
+
with AES-256-GCM (key derived from a secret **you** supply — the user's session,
|
|
120
|
+
a passkey, or a device secret — never embedded) and persists the ciphertext in
|
|
121
|
+
IndexedDB (`MemoryKeyValueStore` for tests/SSR). Wipe everything on logout:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
await shareStore.clear();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Multi-factor recovery
|
|
128
|
+
|
|
129
|
+
Back up the device share so a user can recover after losing their device. The share
|
|
130
|
+
is encrypted **client-side** with a recovery code (the server only ever stores the
|
|
131
|
+
opaque ciphertext), and its release is gated behind 3 factors: recovery code, TOTP,
|
|
132
|
+
and email OTP.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// Enrol — show the returned recoveryCode to the user once (it's the only key).
|
|
136
|
+
const { recoveryCode } = await waaskey.recovery.register(wallet.id, {
|
|
137
|
+
share: (await shareStore.get(wallet.id))!,
|
|
138
|
+
totpSecret, // base32 authenticator secret
|
|
139
|
+
email,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Later, on a new device — verify factors, re-key, and restore the share locally.
|
|
143
|
+
const { challengeId, requiredFactors } = await waaskey.recovery.challenge(wallet.id);
|
|
144
|
+
await waaskey.recovery.recover(wallet.id, {
|
|
145
|
+
challengeId,
|
|
146
|
+
recoveryCode,
|
|
147
|
+
verifications: [
|
|
148
|
+
{ type: 'recovery_code', token: recoveryCode },
|
|
149
|
+
{ type: 'totp', token: otpFromAuthenticator },
|
|
150
|
+
{ type: 'email_otp', token: otpFromEmail },
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Method | Description |
|
|
156
|
+
| ------------------------------------------ | -------------------------------------------------------------- |
|
|
157
|
+
| `recovery.register(walletId, params)` | Encrypt the share + enrol factors. Returns the `recoveryCode`. |
|
|
158
|
+
| `recovery.getInfo(walletId)` | The wallet's registered factors. |
|
|
159
|
+
| `recovery.challenge(walletId)` | Start a recovery session (`challengeId` + required factors). |
|
|
160
|
+
| `recovery.recover(walletId, params)` | Verify, re-key, decrypt, and restore the share to the store. |
|
|
161
|
+
| `recovery.retrieveShare(walletId, params)` | Verify + decrypt the share without re-keying (read-only). |
|
|
162
|
+
|
|
163
|
+
### Balances (client-side, no backend)
|
|
164
|
+
|
|
165
|
+
WaaS is non-custodial and the backend does not index chain state — balances are read
|
|
166
|
+
**directly from a chain provider**, dApp-style. EVM chains work out of the box via a
|
|
167
|
+
default public RPC; override per chain (or plug a custom provider for non-EVM):
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const waaskey = new Waaskey({
|
|
171
|
+
apiKey,
|
|
172
|
+
chains: { ethereum: { rpcUrl: 'https://your-rpc' } }, // optional; EVM has defaults
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const eth = await waaskey.balances.getBalance('ethereum', wallet.address!);
|
|
176
|
+
// → { raw: 1000000000000000000n, decimals: 18, symbol: 'ETH', formatted: '1' }
|
|
177
|
+
|
|
178
|
+
const usdc = await waaskey.balances.getTokenBalance('ethereum', usdcAddress, wallet.address!, { symbol: 'USDC' });
|
|
179
|
+
```
|
|
39
180
|
|
|
40
|
-
|
|
181
|
+
Balances are exact `bigint` base units plus a `formatted` decimal string (no float).
|
|
182
|
+
`formatUnits(raw, decimals)` is exported for rendering.
|
|
41
183
|
|
|
42
184
|
## Development
|
|
43
185
|
|
|
@@ -51,4 +193,4 @@ pnpm build # tsup → dist (esm + cjs + d.ts)
|
|
|
51
193
|
|
|
52
194
|
## License
|
|
53
195
|
|
|
54
|
-
[MIT](./LICENSE) ©
|
|
196
|
+
[MIT](./LICENSE) © WAASKey
|