@aztec/wallet-sdk 0.0.1-commit.d3ec352c → 0.0.1-commit.d58ff9d0

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.
Files changed (109) hide show
  1. package/README.md +321 -299
  2. package/dest/base-wallet/base_wallet.d.ts +129 -44
  3. package/dest/base-wallet/base_wallet.d.ts.map +1 -1
  4. package/dest/base-wallet/base_wallet.js +388 -110
  5. package/dest/base-wallet/get_gas_limits.d.ts +36 -0
  6. package/dest/base-wallet/get_gas_limits.d.ts.map +1 -0
  7. package/dest/base-wallet/get_gas_limits.js +55 -0
  8. package/dest/base-wallet/index.d.ts +4 -2
  9. package/dest/base-wallet/index.d.ts.map +1 -1
  10. package/dest/base-wallet/index.js +2 -0
  11. package/dest/base-wallet/utils.d.ts +52 -0
  12. package/dest/base-wallet/utils.d.ts.map +1 -0
  13. package/dest/base-wallet/utils.js +137 -0
  14. package/dest/crypto.d.ts +230 -0
  15. package/dest/crypto.d.ts.map +1 -0
  16. package/dest/crypto.js +482 -0
  17. package/dest/emoji_alphabet.d.ts +35 -0
  18. package/dest/emoji_alphabet.d.ts.map +1 -0
  19. package/dest/emoji_alphabet.js +299 -0
  20. package/dest/extension/handlers/background_connection_handler.d.ts +168 -0
  21. package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
  22. package/dest/extension/handlers/background_connection_handler.js +294 -0
  23. package/dest/extension/handlers/content_script_connection_handler.d.ts +57 -0
  24. package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
  25. package/dest/extension/handlers/content_script_connection_handler.js +193 -0
  26. package/dest/extension/handlers/index.d.ts +12 -0
  27. package/dest/extension/handlers/index.d.ts.map +1 -0
  28. package/dest/extension/handlers/index.js +10 -0
  29. package/dest/extension/handlers/internal_message_types.d.ts +65 -0
  30. package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
  31. package/dest/extension/handlers/internal_message_types.js +24 -0
  32. package/dest/extension/provider/extension_provider.d.ts +107 -0
  33. package/dest/extension/provider/extension_provider.d.ts.map +1 -0
  34. package/dest/extension/provider/extension_provider.js +160 -0
  35. package/dest/extension/provider/extension_wallet.d.ts +152 -0
  36. package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
  37. package/dest/extension/provider/extension_wallet.js +349 -0
  38. package/dest/extension/provider/index.d.ts +3 -0
  39. package/dest/extension/provider/index.d.ts.map +1 -0
  40. package/dest/iframe/handlers/iframe_connection_handler.d.ts +122 -0
  41. package/dest/iframe/handlers/iframe_connection_handler.d.ts.map +1 -0
  42. package/dest/iframe/handlers/iframe_connection_handler.js +239 -0
  43. package/dest/iframe/handlers/index.d.ts +2 -0
  44. package/dest/iframe/handlers/index.d.ts.map +1 -0
  45. package/dest/iframe/handlers/index.js +1 -0
  46. package/dest/iframe/provider/iframe_discovery.d.ts +25 -0
  47. package/dest/iframe/provider/iframe_discovery.d.ts.map +1 -0
  48. package/dest/iframe/provider/iframe_discovery.js +167 -0
  49. package/dest/iframe/provider/iframe_provider.d.ts +65 -0
  50. package/dest/iframe/provider/iframe_provider.d.ts.map +1 -0
  51. package/dest/iframe/provider/iframe_provider.js +257 -0
  52. package/dest/iframe/provider/iframe_wallet.d.ts +85 -0
  53. package/dest/iframe/provider/iframe_wallet.d.ts.map +1 -0
  54. package/dest/iframe/provider/iframe_wallet.js +269 -0
  55. package/dest/iframe/provider/index.d.ts +4 -0
  56. package/dest/iframe/provider/index.d.ts.map +1 -0
  57. package/dest/iframe/provider/index.js +3 -0
  58. package/dest/manager/index.d.ts +2 -7
  59. package/dest/manager/index.d.ts.map +1 -1
  60. package/dest/manager/index.js +0 -4
  61. package/dest/manager/types.d.ts +109 -5
  62. package/dest/manager/types.d.ts.map +1 -1
  63. package/dest/manager/types.js +17 -1
  64. package/dest/manager/wallet_manager.d.ts +50 -7
  65. package/dest/manager/wallet_manager.d.ts.map +1 -1
  66. package/dest/manager/wallet_manager.js +208 -29
  67. package/dest/types.d.ts +185 -0
  68. package/dest/types.d.ts.map +1 -0
  69. package/dest/types.js +40 -0
  70. package/package.json +24 -11
  71. package/src/base-wallet/base_wallet.ts +499 -176
  72. package/src/base-wallet/get_gas_limits.ts +88 -0
  73. package/src/base-wallet/index.ts +8 -1
  74. package/src/base-wallet/utils.ts +248 -0
  75. package/src/crypto.ts +603 -0
  76. package/src/emoji_alphabet.ts +317 -0
  77. package/src/extension/handlers/background_connection_handler.ts +456 -0
  78. package/src/extension/handlers/content_script_connection_handler.ts +264 -0
  79. package/src/extension/handlers/index.ts +25 -0
  80. package/src/extension/handlers/internal_message_types.ts +71 -0
  81. package/src/extension/provider/extension_provider.ts +233 -0
  82. package/src/extension/provider/extension_wallet.ts +410 -0
  83. package/src/extension/provider/index.ts +7 -0
  84. package/src/iframe/handlers/iframe_connection_handler.ts +341 -0
  85. package/src/iframe/handlers/index.ts +7 -0
  86. package/src/iframe/provider/iframe_discovery.ts +185 -0
  87. package/src/iframe/provider/iframe_provider.ts +331 -0
  88. package/src/iframe/provider/iframe_wallet.ts +323 -0
  89. package/src/iframe/provider/index.ts +3 -0
  90. package/src/manager/index.ts +3 -15
  91. package/src/manager/types.ts +113 -4
  92. package/src/manager/wallet_manager.ts +235 -30
  93. package/src/types.ts +204 -0
  94. package/dest/providers/extension/extension_provider.d.ts +0 -17
  95. package/dest/providers/extension/extension_provider.d.ts.map +0 -1
  96. package/dest/providers/extension/extension_provider.js +0 -56
  97. package/dest/providers/extension/extension_wallet.d.ts +0 -23
  98. package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
  99. package/dest/providers/extension/extension_wallet.js +0 -96
  100. package/dest/providers/extension/index.d.ts +0 -4
  101. package/dest/providers/extension/index.d.ts.map +0 -1
  102. package/dest/providers/types.d.ts +0 -67
  103. package/dest/providers/types.d.ts.map +0 -1
  104. package/dest/providers/types.js +0 -3
  105. package/src/providers/extension/extension_provider.ts +0 -72
  106. package/src/providers/extension/extension_wallet.ts +0 -124
  107. package/src/providers/extension/index.ts +0 -3
  108. package/src/providers/types.ts +0 -71
  109. /package/dest/{providers/extension → extension/provider}/index.js +0 -0
package/README.md CHANGED
@@ -4,421 +4,443 @@ This guide explains how to integrate your wallet with the Aztec Wallet SDK, enab
4
4
 
5
5
  ## Available Types
6
6
 
7
- All types and utilities needed for wallet integration are exported from `@aztec/wallet-sdk/manager`:
7
+ All types and utilities needed for wallet integration are exported from `@aztec/wallet-sdk/types`:
8
8
 
9
9
  ```typescript
10
10
  import type {
11
- ChainInfo,
12
11
  DiscoveryRequest,
13
12
  DiscoveryResponse,
13
+ KeyExchangeRequest,
14
+ KeyExchangeResponse,
14
15
  WalletInfo,
15
16
  WalletMessage,
16
17
  WalletResponse,
17
- } from '@aztec/wallet-sdk/manager';
18
- import { ChainInfoSchema, WalletSchema, jsonStringify } from '@aztec/wallet-sdk/manager';
18
+ } from '@aztec/wallet-sdk/types';
19
19
  ```
20
20
 
21
- ## Overview
21
+ Cryptographic utilities for secure channel establishment are exported from `@aztec/wallet-sdk/crypto`:
22
22
 
23
- The Wallet SDK uses a **request-based discovery** model:
23
+ ```typescript
24
+ import type { EncryptedPayload, ExportedPublicKey } from '@aztec/wallet-sdk/crypto';
25
+ import {
26
+ decrypt,
27
+ deriveSessionKeys,
28
+ encrypt,
29
+ exportPublicKey,
30
+ generateKeyPair,
31
+ hashToEmoji,
32
+ importPublicKey,
33
+ } from '@aztec/wallet-sdk/crypto';
34
+ ```
24
35
 
25
- 1. **dApp requests wallets** for a specific chain/version via `WalletManager.getAvailableWallets({ chainInfo })`
26
- 2. **SDK broadcasts** a discovery message with chain information
27
- 3. **Your wallet responds** ONLY if it supports that specific network
28
- 4. **dApp receives** only compatible wallets
29
- 5. **dApp calls wallet methods** which your wallet handles and responds to
36
+ **For extension wallets**, pre-built connection handlers are available:
30
37
 
31
- ### Transport Mechanisms
38
+ ```typescript
39
+ import {
40
+ BackgroundConnectionHandler,
41
+ ContentScriptConnectionHandler,
42
+ } from '@aztec/wallet-sdk/extension/handlers';
43
+ ```
32
44
 
33
- This guide uses **browser extension wallets** as the primary example, which communicate via `window.postMessage`. However, the same message protocol can be used with other transport mechanisms:
45
+ ## Overview
34
46
 
35
- - **Extension wallets**: Use `window.postMessage` (examples shown throughout this guide)
36
- - **Web wallets**: Could use WebSockets, HTTP, or other protocols (see comments in examples for hypothetical WebSocket usage)
37
- - **Mobile wallets**: Could use deep links, app-to-app communication, or custom protocols
47
+ The Wallet SDK uses a **two-phase connection model** with **end-to-end encryption**:
38
48
 
39
- The message format remains the same regardless of transport - only the delivery mechanism changes.
49
+ ### Phase 1: Discovery
40
50
 
41
- ## Discovery Protocol
51
+ 1. **dApp broadcasts** a discovery request with chain information (NO public keys)
52
+ 2. **Your wallet shows** a pending connection request to the user
53
+ 3. **User approves** the connection request
54
+ 4. **Your wallet responds** with basic wallet info and a MessagePort
42
55
 
43
- ### 1. Listen for Discovery Requests
56
+ ### Phase 2: Secure Channel Establishment
44
57
 
45
- **Extension wallet example:**
58
+ 5. **dApp initiates key exchange** by sending its ECDH public key over the MessagePort
59
+ 6. **Wallet generates** ephemeral key pair and derives session keys using HKDF
60
+ 7. **Both parties compute** the same verification hash independently
61
+ 8. **User verifies** the has matches on both sides. A util for conversion to an emoji grid is provided
62
+ 9. **User confirms** the connection in the dApp
63
+ 10. **All subsequent communication** is encrypted using AES-256-GCM
46
64
 
47
- ```typescript
48
- window.addEventListener('message', event => {
49
- if (event.source !== window) {
50
- return;
51
- }
65
+ ### Key Security Features
52
66
 
53
- const data = JSON.parse(event.data);
67
+ - **User approval required**: Wallet never reveals itself without explicit user consent
68
+ - **Ephemeral keys**: New key pairs generated for each session
69
+ - **Anti-MITM verification**: 3x3 emoji grid (72 bits of security) for visual confirmation
54
70
 
55
- if (data.type === 'aztec-wallet-discovery') {
56
- handleDiscovery(data);
57
- }
58
- });
71
+ ## Architecture for Extension Wallets
59
72
 
60
- // Using WebSocket:
61
- // websocket.on('message', (message) => {
62
- // const data = JSON.parse(message);
63
- // if (data.type === 'aztec-wallet-discovery') {
64
- // handleDiscovery(data);
65
- // }
66
- // });
73
+ ```
74
+ ┌─────────────┐ window.postMessage ┌─────────────────┐ browser.runtime ┌──────────────────┐
75
+ dApp │◄──(discovery + port)────►│ Content Script │◄────────────────────►│ Background Script│
76
+ (web page) │ │ (message relay)│ │ (crypto+state) │
77
+ └─────────────┘ └─────────────────┘ └──────────────────┘
78
+ │ │
79
+ │ MessagePort │
80
+ └──────────(key exchange + encrypted)──────┘
67
81
  ```
68
82
 
69
- ### 2. Discovery Message Format
83
+ **Security model:**
70
84
 
71
- Discovery messages have this structure:
85
+ - The MessagePort is transferred via `window.postMessage` - other scripts on the page could intercept it
86
+ - **Security comes from encryption**: After key exchange, all communication is AES-256-GCM encrypted
87
+ - Content script never has access to private keys or session secrets
88
+ - All cryptographic operations happen in the background script (service worker)
89
+ - Anti-MITM verification (emoji grid) ensures both parties derived the same keys
72
90
 
73
- ```typescript
74
- {
75
- type: 'aztec-wallet-discovery',
76
- requestId: string, // UUID for tracking this request
77
- chainInfo: {
78
- chainId: Fr, // Chain ID
79
- version: Fr // Protocol version
80
- }
81
- }
82
- ```
91
+ ## Using Pre-built Connection Handlers
83
92
 
84
- ### 3. Check Network Support
93
+ The SDK provides `BackgroundConnectionHandler` and `ContentScriptConnectionHandler` to handle the connection flow. These are the recommended way to build extension wallets.
85
94
 
86
- Before responding, verify your wallet supports the requested network:
95
+ ### Background Script Setup
87
96
 
88
97
  ```typescript
89
- import { ChainInfoSchema } from '@aztec/wallet-sdk/manager';
90
-
91
- function handleDiscovery(message: any) {
92
- const { requestId, chainInfo } = message;
98
+ import {
99
+ BackgroundConnectionHandler,
100
+ type BackgroundConnectionConfig,
101
+ type BackgroundConnectionCallbacks,
102
+ type BackgroundTransport,
103
+ } from '@aztec/wallet-sdk/extension/handlers';
104
+ import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
105
+
106
+ // Configuration for your wallet
107
+ const config: BackgroundConnectionConfig = {
108
+ walletId: 'my-aztec-wallet',
109
+ walletName: 'My Aztec Wallet',
110
+ walletVersion: '1.0.0',
111
+ walletIcon: 'https://example.com/icon.png',
112
+ };
113
+
114
+ // Transport for browser extension APIs
115
+ const transport: BackgroundTransport = {
116
+ sendToTab: (tabId, message) => browser.tabs.sendMessage(tabId, message),
117
+ addContentListener: (handler) => browser.runtime.onMessage.addListener(handler),
118
+ };
119
+
120
+ // Event callbacks (all optional)
121
+ const callbacks: BackgroundConnectionCallbacks = {
122
+ // Called when a new discovery request is received
123
+ onPendingDiscovery: (discovery) => {
124
+ // Show pending connection in wallet UI
125
+ // Check if wallet supports this network (chainId AND version)
126
+ const supported = supportedNetworks.some(
127
+ n => n.chainId === discovery.chainInfo.chainId.toString() &&
128
+ n.version === discovery.chainInfo.version.toString()
129
+ );
130
+ if (supported) {
131
+ // Show the user so they can approve or reject
132
+ }
133
+ },
93
134
 
94
- // Parse and validate chain info
95
- const { chainId, version } = ChainInfoSchema.parse(chainInfo);
135
+ // Called when key exchange completes and session is ready
136
+ onSessionEstablished: (session) => {
137
+ // Display verification emojis for user reference
138
+ console.log('Session emojis:', hashToEmoji(session.verificationHash));
139
+ },
96
140
 
97
- // Check if your wallet supports this network
98
- const isSupported = checkNetworkSupport(chainId, version);
141
+ // Called when a session is terminated
142
+ onSessionTerminated: (requestId) => {
143
+ console.log('Session terminated:', requestId);
144
+ },
99
145
 
100
- if (!isSupported) {
101
- // Do NOT respond if you don't support this network
102
- return;
103
- }
146
+ // Called when a decrypted wallet message is received
147
+ onWalletMessage: (session, message) => {
148
+ // Forward to your wallet backend
149
+ wallet.postMessage(message);
150
+ },
151
+ };
104
152
 
105
- // Respond if supported
106
- respondToDiscovery(requestId);
107
- }
108
- ```
153
+ const handler = new BackgroundConnectionHandler(config, transport, callbacks);
109
154
 
110
- ### 4. Respond to Discovery
155
+ // Initialize the handler to start listening
156
+ handler.initialize();
111
157
 
112
- If your wallet supports the network, respond with your wallet information:
158
+ // User approves connection from wallet UI
159
+ function approveConnection(requestId: string) {
160
+ handler.approveDiscovery(requestId);
161
+ }
113
162
 
114
- **Extension wallet example:**
163
+ // User denies connection
164
+ function denyConnection(requestId: string) {
165
+ handler.rejectDiscovery(requestId);
166
+ }
115
167
 
116
- ```typescript
117
- import { jsonStringify } from '@aztec/wallet-sdk/manager';
118
-
119
- function respondToDiscovery(requestId: string) {
120
- const response = {
121
- type: 'aztec-wallet-discovery-response',
122
- requestId,
123
- walletInfo: {
124
- id: 'my-aztec-wallet', // Unique wallet identifier
125
- name: 'My Aztec Wallet', // Display name
126
- icon: 'https://example.com/icon.png', // Optional icon URL
127
- version: '1.0.0', // Wallet version
128
- },
129
- };
130
-
131
- // Send as JSON string via window.postMessage
132
- window.postMessage(jsonStringify(response), '*');
168
+ // Send response back to dApp
169
+ async function sendWalletResponse(requestId: string, response: WalletResponse) {
170
+ await handler.sendResponse(requestId, response);
133
171
  }
134
172
 
135
- // Using WebSocket:
136
- // websocket.send(jsonStringify(response));
173
+ // Clean up on tab close/navigate
174
+ browser.tabs.onRemoved.addListener((tabId) => {
175
+ handler.terminateForTab(tabId);
176
+ });
137
177
  ```
138
178
 
139
- **Important Notes:**
140
-
141
- - Both the SDK and wallets send messages as JSON strings (using `jsonStringify`)
142
- - Both the SDK and wallets must parse incoming JSON strings
143
- - Always use `jsonStringify` from `@aztec/foundation/json-rpc` for sending messages
144
- - Always parse incoming messages with `JSON.parse` and the proper schemas
179
+ ### Content Script Setup
145
180
 
146
- ## Message Format
181
+ ```typescript
182
+ import {
183
+ ContentScriptConnectionHandler,
184
+ type ContentScriptTransport,
185
+ } from '@aztec/wallet-sdk/extension/handlers';
147
186
 
148
- ### Wallet Method Request
187
+ const transport: ContentScriptTransport = {
188
+ sendToBackground: (message) => browser.runtime.sendMessage(message),
189
+ addBackgroundListener: (handler) => browser.runtime.onMessage.addListener(handler),
190
+ };
149
191
 
150
- After discovery, dApps will call wallet methods. These arrive as:
192
+ const handler = new ContentScriptConnectionHandler(transport);
151
193
 
152
- ```typescript
153
- {
154
- type: string, // Wallet method name from the Wallet interface
155
- messageId: string, // UUID for tracking this request
156
- args: unknown[], // Method arguments
157
- chainInfo: {
158
- chainId: Fr, // Same chain that was used in discovery
159
- version: Fr
160
- },
161
- appId: string, // Application identifier
162
- walletId: string // Your wallet's ID (from discovery response)
163
- }
194
+ // Start listening for discovery requests and background messages
195
+ handler.start();
164
196
  ```
165
197
 
166
- Example method calls:
167
-
168
- - `type: 'getAccounts'` - Get list of accounts
169
- - `type: 'getChainInfo'` - Get chain information
170
- - `type: 'sendTx'` - Send a transaction
171
- - `type: 'registerContract'` - Register a contract instance
198
+ ## Testing Your Integration (dApp Side)
172
199
 
173
- ### Wallet Method Response
200
+ The `WalletManager` supports two patterns for consuming discovered wallets.
174
201
 
175
- Your wallet must respond with:
202
+ ### Async Iterator Pattern
176
203
 
177
204
  ```typescript
178
- {
179
- messageId: string, // MUST match the request's messageId
180
- result?: unknown, // Method result (if successful)
181
- error?: unknown, // Error (if failed)
182
- walletId: string // Your wallet's ID
183
- }
184
- ```
185
-
186
- ## Handling Wallet Methods
205
+ import { Fr } from '@aztec/foundation/fields';
206
+ import { WalletManager } from '@aztec/wallet-sdk/manager';
207
+ import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
187
208
 
188
- ### 1. Set Up Message Listener
209
+ const discovery = WalletManager.configure({
210
+ extensions: { enabled: true },
211
+ }).getAvailableWallets({
212
+ chainInfo: {
213
+ chainId: new Fr(31337),
214
+ version: new Fr(1),
215
+ },
216
+ appId: 'my-dapp',
217
+ timeout: 60000,
218
+ });
189
219
 
190
- **Extension wallet example:**
220
+ // Iterate over discovered wallets as they're approved
221
+ for await (const provider of discovery.wallets) {
222
+ console.log(`Found: ${provider.name}`);
191
223
 
192
- ```typescript
193
- window.addEventListener('message', event => {
194
- if (event.source !== window) {
195
- return;
196
- }
224
+ // Establish secure channel (key exchange)
225
+ const pending = await provider.establishSecureChannel('my-dapp');
197
226
 
198
- let data;
199
- try {
200
- data = JSON.parse(event.data);
201
- } catch {
202
- return; // Not a valid JSON message
203
- }
227
+ // Display verification emojis to user
228
+ const emojis = hashToEmoji(pending.verificationHash);
229
+ console.log('Verify this matches your wallet:', emojis);
204
230
 
205
- // Handle discovery
206
- if (data.type === 'aztec-wallet-discovery') {
207
- handleDiscovery(data);
208
- return;
209
- }
231
+ // User confirms emojis match
232
+ const wallet = await pending.confirm();
210
233
 
211
- // Handle wallet methods
212
- if (data.messageId && data.type && data.walletId === 'my-aztec-wallet') {
213
- handleWalletMethod(data);
214
- }
215
- });
234
+ // All calls are now encrypted
235
+ const accounts = await wallet.getAccounts();
236
+ console.log('Accounts:', accounts);
237
+ }
216
238
 
217
- // Using WebSocket:
218
- // websocket.on('message', (message) => {
219
- // const data = JSON.parse(message);
220
- // if (data.type === 'aztec-wallet-discovery') {
221
- // handleDiscovery(data);
222
- // } else if (data.messageId && data.type) {
223
- // handleWalletMethod(data);
224
- // }
225
- // });
239
+ // Cancel discovery when done or on cleanup
240
+ discovery.cancel();
226
241
  ```
227
242
 
228
- ### 2. Route to Wallet Implementation
243
+ ### Callback Pattern
229
244
 
230
245
  ```typescript
231
- import { ChainInfoSchema } from '@aztec/wallet-sdk/manager';
246
+ import { Fr } from '@aztec/foundation/fields';
247
+ import { WalletManager, type WalletProvider } from '@aztec/wallet-sdk/manager';
248
+ import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
232
249
 
233
- async function handleWalletMethod(message: any) {
234
- const { type, messageId, args, chainInfo, appId, walletId } = message;
250
+ const discoveredProviders: WalletProvider[] = [];
235
251
 
236
- try {
237
- // Parse and validate chain info
238
- const parsedChainInfo = ChainInfoSchema.parse(chainInfo);
252
+ const discovery = WalletManager.configure({
253
+ extensions: { enabled: true },
254
+ }).getAvailableWallets({
255
+ chainInfo: {
256
+ chainId: new Fr(31337),
257
+ version: new Fr(1),
258
+ },
259
+ appId: 'my-dapp',
260
+ timeout: 60000,
261
+ // Callback fires as each wallet is discovered
262
+ onWalletDiscovered: (provider) => {
263
+ discoveredProviders.push(provider);
264
+ updateUI(); // Your UI update function
265
+ },
266
+ });
239
267
 
240
- // Get the wallet instance for this chain
241
- const wallet = await getWalletForChain(parsedChainInfo);
268
+ // Wait for discovery to complete (or cancel early with discovery.cancel())
269
+ await discovery.done;
270
+ console.log('Discovery complete, found:', discoveredProviders.length);
242
271
 
243
- // Verify the method exists on the Wallet interface
244
- if (typeof wallet[type] !== 'function') {
245
- throw new Error(`Unknown wallet method: ${type}`);
246
- }
272
+ // Connect to a selected provider
273
+ async function connectToWallet(provider: WalletProvider) {
274
+ const pending = await provider.establishSecureChannel('my-dapp');
247
275
 
248
- // Call the wallet method
249
- const result = await wallet[type](...args);
276
+ // Show verification UI
277
+ const emojis = hashToEmoji(pending.verificationHash);
278
+ showVerificationDialog(emojis);
250
279
 
251
- // Send success response
252
- sendResponse(messageId, walletId, result);
253
- } catch (error) {
254
- // Send error response
255
- sendError(messageId, walletId, error);
256
- }
280
+ // User confirms
281
+ const wallet = await pending.confirm();
282
+ return wallet;
257
283
  }
258
284
  ```
259
285
 
260
- ### 3. Send Response
261
-
262
- **Extension wallet example:**
286
+ ### React Hook Example
263
287
 
264
288
  ```typescript
265
- import { jsonStringify } from '@aztec/wallet-sdk/manager';
289
+ function useWalletDiscovery(chainInfo: ChainInfo, appId: string) {
290
+ const [providers, setProviders] = useState<WalletProvider[]>([]);
291
+ const [isDiscovering, setIsDiscovering] = useState(true);
292
+ const discoveryRef = useRef<DiscoverySession | null>(null);
293
+
294
+ useEffect(() => {
295
+ setProviders([]);
296
+ setIsDiscovering(true);
297
+
298
+ const discovery = WalletManager.configure({
299
+ extensions: { enabled: true },
300
+ }).getAvailableWallets({
301
+ chainInfo,
302
+ appId,
303
+ timeout: 60000,
304
+ onWalletDiscovered: (provider) => {
305
+ setProviders(prev => [...prev, provider]);
306
+ },
307
+ });
308
+
309
+ discoveryRef.current = discovery;
310
+
311
+ discovery.done.then(() => setIsDiscovering(false));
312
+
313
+ return () => {
314
+ discovery.cancel();
315
+ discoveryRef.current = null;
316
+ };
317
+ }, [chainInfo.chainId.toString(), chainInfo.version.toString(), appId]);
318
+
319
+ return { providers, isDiscovering, cancel: () => discoveryRef.current?.cancel() };
320
+ }
321
+ ```
266
322
 
267
- function sendResponse(messageId: string, walletId: string, result: unknown) {
268
- const response = {
269
- messageId,
270
- result,
271
- walletId,
272
- };
323
+ ## Storage backends
273
324
 
274
- // Send as JSON string
275
- window.postMessage(jsonStringify(response), '*');
276
- }
325
+ Your wallet and the PXE it embeds persist state through a pluggable key-value store (`@aztec/kv-store`). In the browser there are two backends:
277
326
 
278
- function sendError(messageId: string, walletId: string, error: Error) {
279
- const response = {
280
- messageId,
281
- error: {
282
- message: error.message,
283
- stack: error.stack,
284
- },
285
- walletId,
286
- };
287
-
288
- window.postMessage(jsonStringify(response), '*');
289
- }
327
+ - **IndexedDB** (`@aztec/kv-store/deprecated/indexeddb`): the default in browser environments up to Aztec Alpha v4, now moved to a deprecated subpath. We plan to remove this backend, so new browser code should use the SQLite backend below.
328
+ - **SQLite-OPFS** (`@aztec/kv-store/sqlite-opfs`): the default KV store backend from Aztec Alpha v5 on. It's backed by the durable Origin Private File System web standard, and it offers a number of advantages over IndexedDB: a sane transaction model (IDB transactions auto-close the moment the event loop yields, which constrains the store layer), support for encryption at rest, and better performance in the access patterns we exercise the most from both wallet and PXE.
290
329
 
291
- // Using WebSocket:
292
- // websocket.send(jsonStringify({ messageId, result, walletId }));
293
- ```
330
+ The backend is chosen by *which store you construct and hand to the wallet* there is no runtime flag or environment variable.
294
331
 
295
- ## Parsing Messages
332
+ > **Data migration is not supported between backends, by design.** The v4→v5 protocol upgrade discards all local state regardless, so switching to SQLite-OPFS simply means starting from a fresh store.
296
333
 
297
- ### Using Zod Schemas
334
+ ### Quick start: embedded wallet with an encrypted SQLite store
298
335
 
299
- Use the provided Zod schemas to parse and validate incoming messages:
336
+ If you build on `@aztec/wallets`' `EmbeddedWallet`, open its two stores (PXE state + the wallet DB) with `openEncryptedEmbeddedStores`, then pass them in:
300
337
 
301
338
  ```typescript
302
- import { ChainInfoSchema, WalletSchema } from '@aztec/wallet-sdk/manager';
303
-
304
- // Parse chain info
305
- const chainInfo = ChainInfoSchema.parse(message.chainInfo);
339
+ import { EmbeddedWallet } from '@aztec/wallets/embedded';
340
+ import { openEncryptedEmbeddedStores } from '@aztec/wallets/embedded/store-encryption';
341
+ import { createLogger } from '@aztec/foundation/log';
306
342
 
307
- // Validate result against expected schema for a method
308
- const accountsResult = await wallet.getAccounts(...args);
309
- // The SDK handles schema validation on the client side
310
- ```
343
+ const log = createLogger('wallet:storage');
311
344
 
312
- The Wallet SDK automatically validates return values using `WalletSchema` on the client side, so your wallet implementation should return values that match the `Wallet` interface specification.
345
+ // Your wallet derives a 32-byte key (see "Key management" below).
346
+ // IMPORTANT: return a *fresh* Uint8Array each call. Opening a store consumes (empties)
347
+ // the key, so a reused array would be empty on the second open (see "important" below).
348
+ const getEncryptionKey = async () => new Uint8Array(myDerivedKey);
313
349
 
314
- ## Error Handling
350
+ const { pxeStore, walletStore } = await openEncryptedEmbeddedStores(
351
+ {
352
+ pxe: { name: `pxe-${rollupAddress}`, poolDirectory: '/pxe' },
353
+ wallet: { name: `wallet-${rollupAddress}`, poolDirectory: '/wallet' },
354
+ },
355
+ getEncryptionKey,
356
+ log,
357
+ );
315
358
 
316
- ### Error Response Format
359
+ const wallet = await EmbeddedWallet.create(nodeUrl, {
360
+ pxe: { store: pxeStore },
361
+ walletDb: { store: walletStore },
362
+ });
363
+ ```
317
364
 
318
- Always send error responses with this structure:
365
+ If the supplied key cannot decrypt an existing store, `openEncryptedEmbeddedStores` throws `EmbeddedWalletEncryptionError` with `storeName: 'pxe' | 'wallet'`, which you can then surface as a "wrong password" error in your UI:
319
366
 
320
367
  ```typescript
321
- {
322
- messageId: string, // Match the request
323
- error: {
324
- message: string, // Error message
325
- code?: string, // Optional error code
326
- stack?: string // Optional stack trace
327
- },
328
- walletId: string
368
+ import { EmbeddedWalletEncryptionError } from '@aztec/wallets/embedded/store-encryption';
369
+
370
+ try {
371
+ await openEncryptedEmbeddedStores(/* ... */);
372
+ } catch (err) {
373
+ if (err instanceof EmbeddedWalletEncryptionError) {
374
+ showWrongPasswordError(); // err.storeName tells you which store failed
375
+ } else {
376
+ throw err;
377
+ }
329
378
  }
330
379
  ```
331
380
 
332
- ### Common Error Scenarios
381
+ ### Important
333
382
 
334
- ```typescript
335
- import { ChainInfoSchema } from '@aztec/wallet-sdk/manager';
383
+ 1. **Opening a store consumes the key, it does not copy it.** So that raw key material does not linger in page memory, the SDK moves your key into the storage worker and detaches the buffer on your side. The `Uint8Array` you passed comes back empty, so the same array cannot be reused to open a second store. To open more than one store with the same key, hand each open a fresh copy (`new Uint8Array(key)`). `openEncryptedEmbeddedStores` does this for you by invoking your `getEncryptionKey` callback once per store.
384
+ 2. **Each coexisting store needs its own `poolDirectory`.** The OPFS SAH Pool holds an *exclusive* lock on its directory, so two stores sharing the default pool fail with "Access Handles cannot be created if there is another open Access Handle…". Give every store a distinct, stable `poolDirectory` (stable so the same files re-open next session).
336
385
 
337
- async function handleWalletMethod(message: any) {
338
- const { type, messageId, args, chainInfo, walletId } = message;
386
+ ### No multi-tab access: assume one tab at a time
339
387
 
340
- try {
341
- // 1. Parse and validate chain info
342
- const parsedChainInfo = ChainInfoSchema.parse(chainInfo);
388
+ A store can be opened by **one browser tab at a time per origin**. If the user opens your wallet in a second tab of the same origin pointing at the same store, the second open contends for that lock.
343
389
 
344
- // 2. Check network support
345
- if (!isNetworkSupported(parsedChainInfo)) {
346
- throw new Error('Network not supported by wallet');
347
- }
390
+ Thanks to the lock, the data is never corrupted, but the second open fails or hangs rather than succeeding, and there is no graceful "already open elsewhere" signal yet.
348
391
 
349
- // 3. Get wallet instance
350
- const wallet = await getWalletForChain(parsedChainInfo);
392
+ Until it does, design for a single active tab: detect a second instance (e.g. with the [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) or a `BroadcastChannel`) and steer the user back to the existing tab, or open the store read-only there.
351
393
 
352
- // 4. Validate method exists
353
- if (typeof wallet[type] !== 'function') {
354
- throw new Error(`Unknown wallet method: ${type}`);
355
- }
394
+ If you need genuine concurrent multi-tab access, route all storage access through a single `SharedWorker` that you own and that holds the one connection.
356
395
 
357
- // 5. Execute method
358
- const result = await wallet[type](...args);
359
- sendResponse(messageId, walletId, result);
360
- } catch (error) {
361
- sendError(messageId, walletId, error);
362
- }
363
- }
364
- ```
396
+ ### Opting out of encryption
365
397
 
366
- ### User Rejection Handling
398
+ If you do not need at-rest encryption (you rely on full-disk encryption, or the device is trusted), an *unencrypted* SQLite-OPFS store is still a better default than IndexedDB.
367
399
 
368
- If a user rejects an action:
400
+ The `createStore` convenience helper always uses the default OPFS pool directory and does not currently let you change it, so it only works for a single store per tab. The embedded wallet runs two stores (PXE + walletDB), so open them directly from `AztecSQLiteOPFSStore` with a distinct `poolDirectory` each:
369
401
 
370
402
  ```typescript
371
- {
372
- messageId: 'abc-123',
373
- error: {
374
- message: 'User rejected the request',
375
- code: 'USER_REJECTED'
376
- },
377
- walletId: 'my-wallet'
378
- }
379
- ```
403
+ import { EmbeddedWallet } from '@aztec/wallets/embedded';
404
+ import { AztecSQLiteOPFSStore } from '@aztec/kv-store/sqlite-opfs';
405
+ import { createLogger } from '@aztec/foundation/log';
406
+
407
+ const log = createLogger('wallet:storage');
408
+
409
+ // No key; just name, ephemeral=false, and a distinct poolDirectory per store.
410
+ const pxeStore = await AztecSQLiteOPFSStore.open(log, `pxe-${rollupAddress}`, false, '/pxe');
411
+ const walletStore = await AztecSQLiteOPFSStore.open(log, `wallet-${rollupAddress}`, false, '/wallet');
380
412
 
381
- ## Testing Your Integration
413
+ const wallet = await EmbeddedWallet.create(nodeUrl, {
414
+ pxe: { store: pxeStore },
415
+ walletDb: { store: walletStore },
416
+ });
417
+ ```
382
418
 
383
- ### WalletManager
419
+ ### Building your own wallet (lower-level API)
384
420
 
385
- In a dApp using the Wallet SDK:
421
+ If you are not using `EmbeddedWallet`, construct stores directly from `@aztec/kv-store/sqlite-opfs` and pass them wherever a store is accepted (e.g. `PXECreationOptions.store`):
386
422
 
387
423
  ```typescript
388
- import { Fr } from '@aztec/foundation/fields';
389
- import { WalletManager } from '@aztec/wallet-sdk/manager';
424
+ import { openEncryptedStore, createStore, SqliteEncryptionError } from '@aztec/kv-store/sqlite-opfs';
390
425
 
391
- const manager = WalletManager.configure({
392
- extensions: { enabled: true },
393
- });
426
+ // Encrypted, persistent:
427
+ const store = await openEncryptedStore(new Uint8Array(myDerivedKey), 'my-store', '/my-pool');
394
428
 
395
- // Discover wallets
396
- const wallets = await manager.getAvailableWallets({
397
- chainInfo: {
398
- chainId: new Fr(31337),
399
- version: new Fr(0),
400
- },
401
- timeout: 2000,
402
- });
429
+ // Or unencrypted:
430
+ const plain = await createStore('my-store', { dataStoreMapSizeKb: 2e10 });
431
+ ```
403
432
 
404
- console.log('Discovered wallets:', wallets);
433
+ Note: `dataStoreMapSizeKb` is an LMDB-specific ceiling (the maximum memory-map size). SQLite-OPFS grows its file dynamically and ignores the value, but it is a required field of the shared `DataStoreConfig` type, so you must still pass something (any number is fine). We will fix this implementation leak in coming versions.
405
434
 
406
- // Connect to your wallet
407
- const walletProvider = wallets.find(w => w.id === 'my-aztec-wallet');
408
- if (walletProvider) {
409
- const wallet = await walletProvider.connect('test-app');
435
+ Note: `openEncryptedStore` throws `SqliteEncryptionError` (with a typed `code`, e.g. `'decrypt_failed'`) on a bad key.
410
436
 
411
- // Test wallet methods from the Wallet interface
412
- const accounts = await wallet.getAccounts();
413
- console.log('Accounts:', accounts);
437
+ ### Using SQLite-OPFS in a browser extension (MV3)
414
438
 
415
- const chainInfo = await wallet.getChainInfo();
416
- console.log('Chain info:', chainInfo);
417
- }
418
- ```
439
+ SQLite-OPFS needs OPFS, a Web Worker, and cross-origin isolation (SharedArrayBuffer). In a Chrome MV3 extension:
419
440
 
420
- ## Reference Implementation
441
+ - **Run it in an offscreen document, not the background service worker.** The service worker is ephemeral and does not reliably provide OPFS/SharedArrayBuffer; an offscreen document does, and it is where your PXE and stores should live.
442
+ - **No COOP/COEP header setup is needed inside the extension.** Extension pages are cross-origin-isolated by default. (A plain web page hosting the wallet *does* need those headers.)
421
443
 
422
- For a complete reference implementation, see the demo wallet at:
444
+ ### Key management is your responsibility
423
445
 
424
- - Repository: `~/repos/demo-wallet`
446
+ The store encrypts data at rest given a 32-byte key, but deriving and safeguarding that key is the wallet's job. A common pattern is to derive the key from a user password with a memory-hard KDF (e.g. Argon2id) and hold it only in memory while the wallet is unlocked. Adapt this to your own security model.