w3pk 0.10.2 → 0.10.4

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.
@@ -36,13 +36,13 @@ await w3pk.getAddress()
36
36
 
37
37
  ### Don't Import Internal Classes
38
38
 
39
- Internal implementation classes like `WalletSigner`, `SessionManager`, `IndexedDBWalletStorage`, etc. are **not part of the public API** and may change without notice:
39
+ Internal implementation classes like `WalletSigner`, `SessionManager`, `LocalStorageWalletStorage`, etc. are **not part of the public API** and may change without notice:
40
40
 
41
41
  ```typescript
42
42
  // ❌ Don't do this - internal APIs
43
43
  import { WalletSigner } from 'w3pk/wallet/signing'
44
44
  import { SessionManager } from 'w3pk/core/session'
45
- import { IndexedDBWalletStorage } from 'w3pk/wallet/storage'
45
+ import { LocalStorageWalletStorage } from 'w3pk/wallet/storage'
46
46
 
47
47
  // ✅ Do this instead - public SDK API
48
48
  import { createWeb3Passkey } from 'w3pk'
@@ -90,9 +90,9 @@ const auth2 = await w3pk.signAuthorization({
90
90
  ```typescript
91
91
  // ❌ Don't use internal WalletSigner class directly
92
92
  import { WalletSigner } from 'w3pk/wallet/signing'
93
- import { IndexedDBWalletStorage } from 'w3pk/wallet/storage'
93
+ import { LocalStorageWalletStorage } from 'w3pk/wallet/storage'
94
94
 
95
- const storage = new IndexedDBWalletStorage()
95
+ const storage = new LocalStorageWalletStorage()
96
96
  const signer = new WalletSigner(storage)
97
97
 
98
98
  // This bypasses session management and requires manual credential handling
@@ -120,6 +120,7 @@ await signer.signAuthorization(
120
120
 
121
121
  **Backup & Recovery:**
122
122
  - `createBackupFile()` - Create encrypted backup
123
+ - `confirmRecoveryPhraseBackup()` - Record that the user wrote down the recovery phrase
123
124
  - `restoreFromBackupFile()` - Restore from backup
124
125
  - `setupSocialRecovery()` - Split backup among guardians
125
126
  - `recoverFromGuardians()` - Recover from guardian shares
@@ -696,6 +697,8 @@ const result = await w3pk.signMessage(message, {
696
697
  })
697
698
  ```
698
699
 
700
+ SIWE works in STANDARD, STRICT and YOLO modes; PRIMARY mode throws. Get the address with the same mode and tag you sign with. In production, have the server issue the nonce and tie it to the address, as EIP-4361 recommends; see [examples/siwe-login.ts](../examples/siwe-login.ts).
701
+
699
702
  **Use for:**
700
703
  - Web3 authentication flows
701
704
  - App login
@@ -1220,23 +1223,46 @@ async function promptBackupOptions() {
1220
1223
 
1221
1224
  ### Allow Users Multiple Backup Options
1222
1225
 
1223
- **Critical:** Users should set up at least one backup method immediately after registration.
1226
+ **Critical:** Users should set up at least one backup method immediately after registration. Browser storage can be wiped (eviction, Safari's 7-day cap, clearing site data), and a wallet without a backup is then lost for good.
1227
+
1228
+ `evictionRisk` tells you whether this browser is likely to wipe the wallet: it is `true` on WebKit (every iOS browser, Safari and other WebKit browsers on desktop) in a tab whose storage is not persisted, and `false` in a home-screen app, once storage is persisted, and on Chromium and Firefox. Use it to choose how hard to push:
1229
+
1230
+ - `!hasBackup && evictionRisk`: block the app until the user backs up
1231
+ - `!hasBackup` otherwise: show a dismissible banner
1232
+ - On iOS, also suggest "Add to Home Screen": home-screen apps are exempt from Safari's 7-day cap
1224
1233
 
1225
1234
  ```typescript
1226
- // ✅ Provide multiple backup options
1227
- async function setupBackups() {
1228
- const status = await w3pk.getBackupStatus()
1235
+ // ✅ Ask for a backup right after registration
1236
+ const { hasBackup, evictionRisk } = await w3pk.register({ username })
1229
1237
 
1230
- console.log('Security Score:', status.securityScore.score)
1231
- console.log('Has passkey sync:', status.securityScore.hasPasskeyBackup)
1232
- console.log('Has encrypted backup:', status.securityScore.hasEncryptedBackup)
1233
- console.log('Has social recovery:', status.securityScore.hasSocialRecovery)
1238
+ if (!hasBackup) {
1239
+ // Blocking where the browser may wipe the wallet, dismissible otherwise
1240
+ // "Back up now": a backup file, or the recovery phrase written down
1241
+ const choice = await showBackupNowPrompt({ blocking: evictionRisk })
1234
1242
 
1235
- // Warn if security score is low
1236
- if (status.securityScore.score < 60) {
1237
- showBackupWarning()
1243
+ if (choice === 'file') {
1244
+ const { blob, filename } = await w3pk.createBackupFile('password', password)
1245
+ downloadFile(blob, filename)
1246
+ } else if (choice === 'phrase') {
1247
+ await showRecoveryPhrase()
1248
+ await w3pk.confirmRecoveryPhraseBackup()
1238
1249
  }
1239
1250
  }
1251
+
1252
+ // ✅ Keep warning while no backup exists
1253
+ async function checkBackup() {
1254
+ const status = await w3pk.getBackupStatus()
1255
+
1256
+ if (!status.hasBackup && status.evictionRisk) {
1257
+ showBackupGate() // blocking; on iOS, suggest "Add to Home Screen"
1258
+ } else if (!status.hasBackup) {
1259
+ showBackupWarning() // the score is capped at 20 ('vulnerable')
1260
+ }
1261
+
1262
+ console.log('Security Score:', status.securityScore.total)
1263
+ console.log('Backup methods:', status.methods) // 'file' | 'qr' | 'phrase'
1264
+ console.log('Last backup:', status.lastBackupAt)
1265
+ }
1240
1266
  ```
1241
1267
 
1242
1268
  ### Three-Layer Backup Strategy
@@ -1252,7 +1278,7 @@ Encourage users to enable multiple backup methods:
1252
1278
  // - Microsoft Account (Windows/Edge)
1253
1279
 
1254
1280
  const status = await w3pk.getBackupStatus()
1255
- if (status.securityScore.hasPasskeyBackup) {
1281
+ if (status.passkeySync.enabled) {
1256
1282
  console.log('✅ Passkey synced to cloud')
1257
1283
  }
1258
1284
  ```
@@ -1316,11 +1342,10 @@ async function setupSocialRecovery() {
1316
1342
  // Check backup status regularly
1317
1343
  async function checkBackupReminder() {
1318
1344
  const status = await w3pk.getBackupStatus()
1319
- const daysSinceRegistration = calculateDaysSince(status.createdAt)
1345
+ const daysSinceRegistration = calculateDaysSince(registeredAt) // tracked by your app
1320
1346
 
1321
1347
  // Remind users who haven't set up backup
1322
- if (!status.securityScore.hasEncryptedBackup &&
1323
- !status.securityScore.hasSocialRecovery) {
1348
+ if (!status.hasBackup) {
1324
1349
 
1325
1350
  if (daysSinceRegistration === 1) {
1326
1351
  showBackupReminder('urgent')
@@ -1413,7 +1438,7 @@ const strictWallet = await w3pk.deriveWallet('STRICT')
1413
1438
 
1414
1439
  **Persistent Session Security:**
1415
1440
  - STANDARD / YOLO: persistent sessions allowed. STRICT: ❌ NEVER allowed.
1416
- - Time-limited expiration; origin-isolated via IndexedDB.
1441
+ - Time-limited expiration; origin-isolated via localStorage.
1417
1442
  - ⚠️ The at-rest key is derived from **public** credential metadata in the same browser profile, not an authenticator secret — a persistent session is **decryptable by anyone who can read this origin's storage**, for the whole duration it's enabled. "Remember Me" is a UX-for-security trade-off that widens the exposure window; it does not add a new trust dependency beyond the origin-code integrity you already rely on. Keep the duration short for higher-value wallets and don't enable on shared devices.
1418
1443
  - Decide this deliberately against your threat model: **[SECURITY.md → At-Rest Encryption](./SECURITY.md#at-rest-encryption-what-it-protects-and-what-it-doesnt)**.
1419
1444
 
@@ -9,10 +9,12 @@ This guide explains how to move, backup, and link your W3PK wallet across differ
9
9
  - [For End-Users](#for-end-users)
10
10
  - [Linking an External Wallet (MetaMask, etc.)](#linking-an-external-wallet-metamask-etc)
11
11
  - [Creating and Restoring Backups](#creating-and-restoring-backups)
12
+ - [Moving Your Wallet to a New Device](#moving-your-wallet-to-a-new-device)
12
13
  - [Extracting Your Mnemonic](#extracting-your-mnemonic)
13
14
  - [For Developers](#for-developers)
14
15
  - [Implementing External Wallet Linking](#implementing-external-wallet-linking)
15
16
  - [Implementing Backup/Restore Features](#implementing-backuprestore-features)
17
+ - [Implementing Device Sync](#implementing-device-sync)
16
18
  - [Building Mnemonic Export Tools](#building-mnemonic-export-tools)
17
19
 
18
20
  ---
@@ -99,6 +101,21 @@ Your backup contains:
99
101
 
100
102
  ---
101
103
 
104
+ ### Moving Your Wallet to a New Device
105
+
106
+ If your passkey syncs to the new device (iCloud Keychain on Apple devices, Google Password Manager on Android and Chrome), you can move your wallet without a password:
107
+
108
+ 1. **On the device that has your wallet**, choose "Sync to another device" (or similar) and confirm with your passkey
109
+ 2. **Transfer the sync file**: scan the QR code, or send the file by AirDrop, email, etc.
110
+ 3. **On the new device**, open the app, load the sync file and confirm with the same passkey
111
+ 4. **Done**: your wallet is on the new device, with the same address
112
+
113
+ The sync file can't be opened with the file alone: it also takes your passkey and your fingerprint or face. Still, keep it private and delete it once the sync is done.
114
+
115
+ **When it doesn't work**: this needs a browser that supports the passkey PRF extension (Chrome 108+, Safari 17+ / iOS 17+; not Firefox), and passkeys only sync within one ecosystem (Apple or Google). In other cases, use a password-encrypted backup: the app asks for a password when it creates the sync file, and the new device asks for the same password.
116
+
117
+ ---
118
+
102
119
  ### Extracting Your Mnemonic
103
120
 
104
121
  If you need to import your W3PK wallet into another wallet application (like MetaMask, Trust Wallet, etc.), you can extract your mnemonic phrase.
@@ -378,6 +395,55 @@ See [src/core/sdk.ts](../src/core/sdk.ts) for implementation details of `createB
378
395
 
379
396
  ---
380
397
 
398
+ ### Implementing Device Sync
399
+
400
+ `exportForSync()` makes a sync file encrypted with a key derived from the passkey's [WebAuthn PRF](https://w3c.github.io/webauthn/#prf-extension) output (`encryptionMethod: 'prf'`). Any device where the passkey synced restores it with one prompt and no password, even a fresh device with no data for the passkey.
401
+
402
+ ```typescript
403
+ // Device A, logged in: one passkey prompt
404
+ const { blob, filename, qrCode } = await sdk.exportForSync()
405
+
406
+ // Device B, logged out: one passkey prompt, restricted to the file's passkey
407
+ const { ethereumAddress } = await sdk.importFromSync(syncData)
408
+ ```
409
+
410
+ `syncWalletWithPasskey(syncData)` restores the same files and also takes password backups.
411
+
412
+ **Password fallback**: PRF works in Chrome 108+ and Safari 17+ / iOS 17+, not in Firefox. Without it, `exportForSync()` throws `PrfUnsupportedError`. Pass a password to fall back to a password backup, which the new device restores with `syncWalletWithPasskey(syncData, password)`:
413
+
414
+ ```typescript
415
+ import { PrfUnsupportedError } from 'w3pk'
416
+
417
+ let sync
418
+ try {
419
+ sync = await sdk.exportForSync()
420
+ } catch (error) {
421
+ if (!(error instanceof PrfUnsupportedError)) throw error
422
+ sync = await sdk.exportForSync(await askUserForPassword())
423
+ }
424
+ ```
425
+
426
+ Or pass the password up front: `exportForSync(password)` uses it only when PRF is unavailable. Passkeys sync within one ecosystem only (Apple or Google), so moving between ecosystems also takes a password backup.
427
+
428
+ **PRF sync file structure:**
429
+
430
+ ```json
431
+ {
432
+ "createdAt": "2026-10-25T10:30:00.000Z",
433
+ "ethereumAddress": "0x...",
434
+ "encryptedMnemonic": "base64(iv || AES-256-GCM ciphertext)",
435
+ "encryptionMethod": "prf",
436
+ "credential": { "id": "passkey credential id", "publicKey": "base64url SPKI" },
437
+ "prfSalt": "base64url, 32 random bytes",
438
+ "username": "alice",
439
+ "addressChecksum": "..."
440
+ }
441
+ ```
442
+
443
+ The key is HKDF-SHA256 over the PRF output evaluated at `SHA-256("w3pk-prf-sync-v1" || prfSalt)`. The public key doesn't decrypt anything; the new device needs it for later logins and checks it against the assertion's signature. The file fits in a QR code (`generateSyncQR()`).
444
+
445
+ ---
446
+
381
447
  ### Building Mnemonic Export Tools
382
448
 
383
449
  For advanced users who need to export their mnemonic to use in other wallets, W3PK provides a standalone extraction tool.