@aztec/wallet-sdk 4.0.0-nightly.20260113 → 4.0.0-nightly.20260115

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 CHANGED
@@ -4,19 +4,16 @@ 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
- ConnectRequest,
13
11
  DiscoveryRequest,
14
12
  DiscoveryResponse,
15
13
  WalletInfo,
16
14
  WalletMessage,
17
15
  WalletResponse,
18
- } from '@aztec/wallet-sdk/manager';
19
- import { ChainInfoSchema, WalletSchema, jsonStringify } from '@aztec/wallet-sdk/manager';
16
+ } from '@aztec/wallet-sdk/types';
20
17
  ```
21
18
 
22
19
  Cryptographic utilities for secure channel establishment are exported from `@aztec/wallet-sdk/crypto`:
@@ -29,57 +26,54 @@ import {
29
26
  encrypt,
30
27
  exportPublicKey,
31
28
  generateKeyPair,
29
+ hashSharedSecret,
30
+ hashToEmoji,
32
31
  importPublicKey,
33
32
  } from '@aztec/wallet-sdk/crypto';
34
33
  ```
35
34
 
36
35
  ## Overview
37
36
 
38
- The Wallet SDK uses a **request-based discovery** model with **end-to-end encryption**:
37
+ The Wallet SDK uses a **unified discovery and connection** model with **end-to-end encryption**:
39
38
 
40
39
  1. **dApp requests wallets** for a specific chain/version via `WalletManager.getAvailableWallets({ chainInfo })`
41
- 2. **SDK broadcasts** a discovery message with chain information
42
- 3. **Your wallet responds** with its ECDH public key ONLY if it supports that specific network
43
- 4. **dApp receives** only compatible wallets
44
- 5. **dApp establishes secure channel** via ECDH key exchange (see [Secure Channel](#secure-channel))
45
- 6. **All subsequent communication** is encrypted using AES-256-GCM
40
+ 2. **SDK broadcasts** a discovery message with chain information and the dApp's ECDH public key
41
+ 3. **Your wallet responds** with its ECDH public key and a MessagePort ONLY if it supports that network
42
+ 4. **Both parties derive** the same shared secret via ECDH key exchange
43
+ 5. **SDK receives** discovered wallets with secure channel already established (port + sharedKey)
44
+ 6. **All subsequent communication** is encrypted using AES-256-GCM over the private MessagePort
46
45
 
47
- ### Transport Mechanisms
46
+ ### Key Features
48
47
 
49
- 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:
48
+ - **No separate connection step**: The secure channel is established during discovery
49
+ - **MessagePort transferred immediately**: The discovery response includes a MessagePort for private communication
50
+ - **Anti-MITM verification**: Both parties can display emoji verification codes derived from the shared secret
50
51
 
51
- - **Extension wallets**: Use `window.postMessage` (examples shown throughout this guide)
52
- - **Web wallets**: Could use WebSockets, HTTP, or other protocols (see comments in examples for hypothetical WebSocket usage)
53
- - **Mobile wallets**: Could use deep links, app-to-app communication, or custom protocols
52
+ ### Transport Mechanisms
54
53
 
55
- The message format remains the same regardless of transport - only the delivery mechanism changes.
54
+ This guide uses **browser extension wallets** as the primary example, which communicate via `window.postMessage` for discovery and MessageChannel for secure communication. The same message protocol can be adapted for other transport mechanisms.
56
55
 
57
56
  ## Discovery Protocol
58
57
 
59
58
  ### 1. Listen for Discovery Requests
60
59
 
61
- **Extension wallet example:**
60
+ **Extension wallet (content script):**
62
61
 
63
62
  ```typescript
64
- window.addEventListener('message', event => {
65
- if (event.source !== window) {
63
+ window.addEventListener('message', async (event) => {
64
+ if (event.source !== window) return;
65
+
66
+ let data: DiscoveryRequest;
67
+ try {
68
+ data = JSON.parse(event.data);
69
+ } catch {
66
70
  return;
67
71
  }
68
72
 
69
- const data = JSON.parse(event.data);
70
-
71
73
  if (data.type === 'aztec-wallet-discovery') {
72
- handleDiscovery(data);
74
+ await handleDiscoveryRequest(data);
73
75
  }
74
76
  });
75
-
76
- // Using WebSocket:
77
- // websocket.on('message', (message) => {
78
- // const data = JSON.parse(message);
79
- // if (data.type === 'aztec-wallet-discovery') {
80
- // handleDiscovery(data);
81
- // }
82
- // });
83
77
  ```
84
78
 
85
79
  ### 2. Discovery Message Format
@@ -87,194 +81,185 @@ window.addEventListener('message', event => {
87
81
  Discovery messages have this structure:
88
82
 
89
83
  ```typescript
90
- {
91
- type: 'aztec-wallet-discovery',
92
- requestId: string, // UUID for tracking this request
93
- chainInfo: {
94
- chainId: Fr, // Chain ID
95
- version: Fr // Protocol version
96
- }
84
+ interface DiscoveryRequest {
85
+ type: 'aztec-wallet-discovery';
86
+ requestId: string; // UUID for tracking this request
87
+ chainInfo: ChainInfo; // Chain ID and protocol version
88
+ publicKey: ExportedPublicKey; // dApp's ECDH public key for key exchange
97
89
  }
98
90
  ```
99
91
 
100
- ### 3. Check Network Support
101
-
102
- Before responding, verify your wallet supports the requested network:
92
+ ### 3. Handle Discovery and Establish Secure Channel
103
93
 
104
- ```typescript
105
- import { ChainInfoSchema } from '@aztec/wallet-sdk/manager';
106
-
107
- function handleDiscovery(message: any) {
108
- const { requestId, chainInfo } = message;
94
+ When your wallet receives a discovery request:
109
95
 
110
- // Parse and validate chain info
111
- const { chainId, version } = ChainInfoSchema.parse(chainInfo);
96
+ 1. Check if you support the requested network
97
+ 2. Derive the shared secret from the dApp's public key
98
+ 3. Create a MessageChannel for secure communication
99
+ 4. Respond with your wallet info and transfer one end of the channel
112
100
 
113
- // Check if your wallet supports this network
114
- const isSupported = checkNetworkSupport(chainId, version);
101
+ **Extension wallet (background script):**
115
102
 
116
- if (!isSupported) {
117
- // Do NOT respond if you don't support this network
118
- return;
119
- }
120
-
121
- // Respond if supported
122
- respondToDiscovery(requestId);
123
- }
124
- ```
125
-
126
- ### 4. Respond to Discovery
103
+ ```typescript
104
+ import {
105
+ deriveSharedKey,
106
+ exportPublicKey,
107
+ generateKeyPair,
108
+ hashSharedSecret,
109
+ importPublicKey,
110
+ } from '@aztec/wallet-sdk/crypto';
127
111
 
128
- If your wallet supports the network, respond with your wallet information:
112
+ // Generate key pair on wallet initialization (per session)
113
+ let walletKeyPair = await generateKeyPair();
114
+ let walletPublicKey = await exportPublicKey(walletKeyPair.publicKey);
129
115
 
130
- **Extension wallet example:**
116
+ // Store sessions by requestId
117
+ const sessions = new Map<string, { sharedKey: CryptoKey; verificationHash: string; tabId: number }>();
131
118
 
132
- ```typescript
133
- import { jsonStringify } from '@aztec/wallet-sdk/manager';
119
+ async function handleDiscovery(
120
+ request: DiscoveryRequest,
121
+ tabId: number
122
+ ): Promise<{ success: true; response: DiscoveryResponse }> {
123
+ // Check network support
124
+ if (!supportsNetwork(request.chainInfo)) {
125
+ throw new Error('Network not supported');
126
+ }
134
127
 
135
- // Your wallet should generate a key pair on initialization.
136
- // This keypair should be recreated each session
137
- let walletKeyPair: CryptoKeyPair;
128
+ // Import dApp's public key and derive shared secret
129
+ const dAppPublicKey = await importPublicKey(request.publicKey);
130
+ const sharedKey = await deriveSharedKey(walletKeyPair.privateKey, dAppPublicKey);
138
131
 
139
- async function initializeWallet() {
140
- // Generate ECDH key pair for secure channel establishment
141
- walletKeyPair = await generateKeyPair();
142
- }
132
+ // Compute verification hash for anti-MITM verification
133
+ const verificationHash = await hashSharedSecret(sharedKey);
143
134
 
144
- async function respondToDiscovery(requestId: string) {
145
- // Export the public key for sharing with dApps
146
- const publicKey = await exportPublicKey(walletKeyPair.publicKey);
135
+ // Store the session with verificationHash (emoji computed lazily for display)
136
+ sessions.set(request.requestId, { sharedKey, verificationHash, tabId });
147
137
 
148
- const response = {
138
+ const response: DiscoveryResponse = {
149
139
  type: 'aztec-wallet-discovery-response',
150
- requestId,
140
+ requestId: request.requestId,
151
141
  walletInfo: {
152
- id: 'my-aztec-wallet', // Unique wallet identifier
153
- name: 'My Aztec Wallet', // Display name
154
- icon: 'https://example.com/icon.png', // Optional icon URL
155
- version: '1.0.0', // Wallet version
156
- publicKey, // ECDH public key for secure channel (required)
142
+ id: 'my-aztec-wallet',
143
+ name: 'My Aztec Wallet',
144
+ version: '1.0.0',
145
+ publicKey: walletPublicKey,
157
146
  },
158
147
  };
159
148
 
160
- // Send as JSON string via window.postMessage
161
- window.postMessage(jsonStringify(response), '*');
149
+ return { success: true, response };
162
150
  }
163
-
164
- // Using WebSocket:
165
- // websocket.send(jsonStringify(response));
166
151
  ```
167
152
 
168
- **Important Notes:**
169
-
170
- - Both the SDK and wallets send messages as JSON strings (using `jsonStringify`)
171
- - Both the SDK and wallets must parse incoming JSON strings
172
- - Always use `jsonStringify` from `@aztec/foundation/json-rpc` for sending messages
173
- - Always parse incoming messages with `JSON.parse` and the proper schemas
174
- - The `publicKey` field is required for secure channel establishment
175
-
176
- ## Secure Channel
177
-
178
- After discovery, the dApp establishes a secure encrypted channel with your wallet using ECDH key exchange and AES-256-GCM encryption. This ensures all wallet method calls and responses are encrypted end-to-end.
179
-
180
- ### Security Model
181
-
182
- - **ECDH Key Exchange**: Uses P-256 (secp256r1) elliptic curve for key agreement
183
- - **AES-256-GCM Encryption**: All messages after channel establishment are encrypted
184
- - **Per-Session Keys**: Each connection derives a unique shared secret
185
- - **MessageChannel (Extension wallets)**: Uses a private MessagePort for communication, not visible to other page scripts
186
-
187
- ### 1. Handle Connection Requests
188
-
189
- When a dApp connects, it sends a `ConnectRequest` containing its ECDH public key:
153
+ **Content script (creates MessageChannel and sends response):**
190
154
 
191
155
  ```typescript
192
- interface ConnectRequest {
193
- type: 'aztec-wallet-connect';
194
- walletId: string; // Your wallet's ID
195
- appId: string; // Application identifier
196
- publicKey: ExportedPublicKey; // dApp's ECDH public key
156
+ async function handleDiscoveryRequest(request: DiscoveryRequest) {
157
+ // Forward to background script for key derivation
158
+ const result = await browser.runtime.sendMessage({
159
+ type: 'aztec-wallet-discovery',
160
+ content: request,
161
+ });
162
+
163
+ if (!result?.success) return;
164
+
165
+ // Create MessageChannel for secure communication
166
+ const channel = new MessageChannel();
167
+
168
+ // Set up relay from page to background
169
+ channel.port1.onmessage = (event) => {
170
+ browser.runtime.sendMessage({
171
+ type: 'secure-message',
172
+ requestId: request.requestId,
173
+ content: event.data, // Encrypted payload
174
+ });
175
+ };
176
+ channel.port1.start();
177
+
178
+ // Send response with port2 to the page
179
+ window.postMessage(JSON.stringify(result.response), '*', [channel.port2]);
197
180
  }
198
181
  ```
199
182
 
200
- **Extension wallet example:**
183
+ ### 4. Discovery Response Format
201
184
 
202
185
  ```typescript
203
- import { decrypt, deriveSharedKey, encrypt, importPublicKey } from '@aztec/wallet-sdk/crypto';
204
-
205
- // Store connections by appId
206
- const connections = new Map<string, { sharedKey: CryptoKey }>();
207
-
208
- window.addEventListener('message', async event => {
209
- if (event.source !== window) return;
186
+ interface DiscoveryResponse {
187
+ type: 'aztec-wallet-discovery-response';
188
+ requestId: string; // Must match the request
189
+ walletInfo: WalletInfo; // Wallet info including public key
190
+ }
210
191
 
211
- const data = JSON.parse(event.data);
192
+ interface WalletInfo {
193
+ id: string; // Unique wallet identifier
194
+ name: string; // Display name
195
+ icon?: string; // Optional icon URL
196
+ version: string; // Wallet version
197
+ publicKey: ExportedPublicKey; // ECDH public key for key exchange
198
+ }
199
+ ```
212
200
 
213
- if (data.type === 'aztec-wallet-connect') {
214
- await handleConnect(data, event.ports[0]);
215
- }
216
- });
201
+ **Important:** The response is sent via `window.postMessage` with a MessagePort transferred as the third argument. The SDK receives the port and uses it for all subsequent encrypted communication.
217
202
 
218
- async function handleConnect(request: ConnectRequest, port: MessagePort) {
219
- // Import dApp's public key
220
- const dappPublicKey = await importPublicKey(request.publicKey);
203
+ ## Secure Communication
221
204
 
222
- // Derive shared secret using our private key and dApp's public key
223
- const sharedKey = await deriveSharedKey(walletKeyPair.privateKey, dappPublicKey);
205
+ ### Architecture for Extension Wallets
224
206
 
225
- // Store the connection
226
- connections.set(request.appId, { sharedKey });
207
+ ```
208
+ ┌─────────────┐ window.postMessage ┌─────────────────┐ browser.runtime ┌──────────────────┐
209
+ │ dApp │◄───(discovery only)─────►│ Content Script │◄────────────────────►│ Background Script│
210
+ │ (web page) │ │ (message relay)│ │ (decrypt+process)│
211
+ └─────────────┘ └─────────────────┘ └──────────────────┘
212
+ │ │
213
+ │ MessagePort (private channel) │
214
+ └──────────(encrypted messages)────────────┘
215
+ ```
227
216
 
228
- // Set up encrypted message handler on the MessagePort
229
- port.onmessage = async event => {
230
- await handleEncryptedMessage(request.appId, event.data);
231
- };
217
+ **Security benefits:**
232
218
 
233
- port.start();
234
- }
235
- ```
219
+ - Content script never has access to private keys or shared secrets
220
+ - All cryptographic operations happen in the background script (service worker)
221
+ - MessagePort provides a private channel not visible to other page scripts
222
+ - Only discovery uses `window.postMessage`; all wallet calls are encrypted on the MessagePort
236
223
 
237
- ### 2. Handle Encrypted Messages
224
+ ### Handle Encrypted Messages
238
225
 
239
- All wallet method calls arrive as encrypted payloads:
226
+ All wallet method calls arrive as encrypted payloads on the MessagePort:
240
227
 
241
228
  ```typescript
242
229
  interface EncryptedPayload {
243
- iv: string; // Base64-encoded initialization vector
230
+ iv: string; // Base64-encoded initialization vector
244
231
  ciphertext: string; // Base64-encoded encrypted data
245
232
  }
246
233
  ```
247
234
 
248
- Decrypt incoming messages and encrypt responses:
235
+ **Background script:**
249
236
 
250
237
  ```typescript
251
- async function handleEncryptedMessage(appId: string, encrypted: EncryptedPayload) {
252
- const connection = connections.get(appId);
253
- if (!connection) {
254
- console.error('Unknown connection');
255
- return;
256
- }
238
+ import { decrypt, encrypt } from '@aztec/wallet-sdk/crypto';
239
+
240
+ async function handleSecureMessage(requestId: string, encrypted: EncryptedPayload) {
241
+ const session = sessions.get(requestId);
242
+ if (!session) return;
257
243
 
258
244
  try {
259
245
  // Decrypt the incoming message
260
- const message = await decrypt<WalletMessage>(connection.sharedKey, encrypted);
261
-
246
+ const message = await decrypt<WalletMessage>(session.sharedKey, encrypted);
262
247
  const { type, messageId, args, chainInfo, walletId } = message;
263
248
 
264
249
  // Process the wallet method call
265
250
  const wallet = await getWalletForChain(chainInfo);
266
251
  const result = await wallet[type](...args);
267
252
 
268
- // Create response
269
- const response: WalletResponse = {
270
- messageId,
271
- result,
272
- walletId,
273
- };
253
+ // Create and encrypt response
254
+ const response: WalletResponse = { messageId, result, walletId };
255
+ const encryptedResponse = await encrypt(session.sharedKey, response);
274
256
 
275
- // Encrypt and send the response
276
- const encryptedResponse = await encrypt(connection.sharedKey, response);
277
- sendEncryptedResponse(appId, encryptedResponse);
257
+ // Send back through content script
258
+ browser.tabs.sendMessage(session.tabId, {
259
+ type: 'secure-response',
260
+ requestId,
261
+ content: encryptedResponse,
262
+ });
278
263
  } catch (error) {
279
264
  // Send encrypted error response
280
265
  const errorResponse: WalletResponse = {
@@ -282,151 +267,105 @@ async function handleEncryptedMessage(appId: string, encrypted: EncryptedPayload
282
267
  error: { message: error.message },
283
268
  walletId: message?.walletId ?? '',
284
269
  };
285
-
286
- const encryptedError = await encrypt(connection.sharedKey, errorResponse);
287
- sendEncryptedResponse(appId, encryptedError);
270
+ const encryptedError = await encrypt(session.sharedKey, errorResponse);
271
+ // ... send error response
288
272
  }
289
273
  }
290
274
  ```
291
275
 
292
- ### 3. Extension Wallet Architecture
293
-
294
- For browser extension wallets, the recommended architecture separates concerns:
295
-
296
- ```
297
- ┌─────────────┐ window.postMessage ┌─────────────────┐ browser.runtime ┌──────────────────┐
298
- │ dApp │◄────────────────────────►│ Content Script │◄────────────────────►│ Background Script│
299
- │ (web page) │ (discovery only) │ (message relay)│ (encrypted msgs) │ (decrypt+process)│
300
- └─────────────┘ └─────────────────┘ └──────────────────┘
301
- │ │
302
- │ MessagePort (private channel) │
303
- └────────────────────────────────────────────┘
304
- (encrypted wallet method calls)
305
- ```
306
-
307
- **Security benefits:**
308
-
309
- - Content script never has access to private keys or shared secrets
310
- - All cryptographic operations happen in the background script (service worker)
311
- - MessagePort provides a private channel not visible to other page scripts
312
- - Only the initial connection handshake uses `window.postMessage`
313
-
314
- ## Message Format
315
-
316
- ### Wallet Method Request
276
+ ## Message Formats
317
277
 
318
- After discovery, dApps will call wallet methods. These arrive as:
278
+ ### Wallet Method Request (Decrypted)
319
279
 
320
280
  ```typescript
321
- {
322
- type: string, // Wallet method name from the Wallet interface
323
- messageId: string, // UUID for tracking this request
324
- args: unknown[], // Method arguments
325
- chainInfo: {
326
- chainId: Fr, // Same chain that was used in discovery
327
- version: Fr
328
- },
329
- appId: string, // Application identifier
330
- walletId: string // Your wallet's ID (from discovery response)
281
+ interface WalletMessage {
282
+ type: string; // Wallet method name (e.g., 'getAccounts', 'sendTx')
283
+ messageId: string; // UUID for tracking this request
284
+ args: unknown[]; // Method arguments
285
+ chainInfo: ChainInfo;
286
+ appId: string; // Application identifier
287
+ walletId: string; // Your wallet's ID
331
288
  }
332
289
  ```
333
290
 
334
- Example method calls:
335
-
336
- - `type: 'getAccounts'` - Get list of accounts
337
- - `type: 'getChainInfo'` - Get chain information
338
- - `type: 'sendTx'` - Send a transaction
339
- - `type: 'registerContract'` - Register a contract instance
340
-
341
291
  ### Wallet Method Response
342
292
 
343
- Your wallet must respond with:
344
-
345
293
  ```typescript
346
- {
347
- messageId: string, // MUST match the request's messageId
348
- result?: unknown, // Method result (if successful)
349
- error?: unknown, // Error (if failed)
350
- walletId: string // Your wallet's ID
294
+ interface WalletResponse {
295
+ messageId: string; // Must match the request
296
+ result?: unknown; // Method result (if successful)
297
+ error?: unknown; // Error (if failed)
298
+ walletId: string; // Your wallet's ID
351
299
  }
352
300
  ```
353
301
 
354
- ## Parsing Messages
302
+ ## Anti-MITM Verification
355
303
 
356
- ### Using Zod Schemas
357
-
358
- Use the provided Zod schemas to parse and validate incoming messages:
304
+ Both the dApp and wallet independently compute a `verificationHash` from the shared secret. If both parties compute the same hash, they know there's no man-in-the-middle attack.
359
305
 
360
306
  ```typescript
361
- import { ChainInfoSchema, WalletSchema } from '@aztec/wallet-sdk/manager';
307
+ import { hashSharedSecret } from '@aztec/wallet-sdk/crypto';
362
308
 
363
- // Parse chain info
364
- const chainInfo = ChainInfoSchema.parse(message.chainInfo);
309
+ // Compute verification hash from shared key
310
+ const verificationHash = await hashSharedSecret(sharedKey);
365
311
 
366
- // Validate result against expected schema for a method
367
- const accountsResult = await wallet.getAccounts(...args);
368
- // The SDK handles schema validation on the client side
312
+ // Store verificationHash in session - this is the cryptographic proof
313
+ sessions.set(requestId, { sharedKey, verificationHash, tabId });
369
314
  ```
370
315
 
371
- 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.
372
-
373
- ## Error Handling
374
-
375
- ### Error Response Format
376
-
377
- Always send error responses with this structure:
316
+ For user-friendly display, convert the hash to an emoji sequence:
378
317
 
379
318
  ```typescript
380
- {
381
- messageId: string, // Match the request
382
- error: {
383
- message: string, // Error message
384
- code?: string, // Optional error code
385
- stack?: string // Optional stack trace
386
- },
387
- walletId: string
388
- }
389
- ```
319
+ import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
390
320
 
391
- ### Common Error Scenarios
321
+ // Convert to emoji only when displaying to the user
322
+ const emoji = hashToEmoji(verificationHash); // e.g., "🔵🦋🎯🐼"
323
+ ```
392
324
 
393
- Common errors to handle within the encrypted message handler:
325
+ The dApp displays the same emoji sequence. If they match, the connection is secure.
394
326
 
395
- - **Network not supported**: Chain info doesn't match wallet's supported networks
396
- - **Unknown method**: The requested wallet method doesn't exist
397
- - **Invalid arguments**: Method arguments fail validation
398
- - **User rejection**: User declined the transaction or action
327
+ ## Session Management
399
328
 
400
- ### User Rejection Handling
329
+ Sessions should be cleaned up when:
401
330
 
402
- If a user rejects an action:
331
+ - **Tab closes**: Browser tabs API `onRemoved` event
332
+ - **Tab navigates**: Browser tabs API `onUpdated` event with `status === 'loading'`
403
333
 
404
334
  ```typescript
405
- {
406
- messageId: 'abc-123',
407
- error: {
408
- message: 'User rejected the request',
409
- code: 'USER_REJECTED'
410
- },
411
- walletId: 'my-wallet'
412
- }
335
+ // Clean up when tab closes
336
+ browser.tabs.onRemoved.addListener((tabId) => {
337
+ for (const [requestId, session] of sessions) {
338
+ if (session.tabId === tabId) {
339
+ sessions.delete(requestId);
340
+ }
341
+ }
342
+ });
343
+
344
+ // Clean up when tab navigates
345
+ browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
346
+ if (changeInfo.status === 'loading') {
347
+ for (const [requestId, session] of sessions) {
348
+ if (session.tabId === tabId) {
349
+ sessions.delete(requestId);
350
+ }
351
+ }
352
+ }
353
+ });
413
354
  ```
414
355
 
415
356
  ## Testing Your Integration
416
357
 
417
- ### WalletManager
418
-
419
- In a dApp using the Wallet SDK:
358
+ ### Using WalletManager
420
359
 
421
360
  ```typescript
422
- import { Fr } from '@aztec/foundation/curves/bn254';
423
- import { WalletManager } from '@aztec/wallet-sdk/manager';
361
+ import { Fr } from '@aztec/foundation/fields';
362
+ import { WalletManager, hashToEmoji } from '@aztec/wallet-sdk/manager';
424
363
 
425
364
  const manager = WalletManager.configure({
426
365
  extensions: { enabled: true },
427
366
  });
428
367
 
429
- // Discover wallets
368
+ // Discover wallets (secure channel established automatically)
430
369
  const wallets = await manager.getAvailableWallets({
431
370
  chainInfo: {
432
371
  chainId: new Fr(31337),
@@ -435,19 +374,20 @@ const wallets = await manager.getAvailableWallets({
435
374
  timeout: 2000,
436
375
  });
437
376
 
438
- console.log('Discovered wallets:', wallets);
377
+ // Each wallet provider has verification info
378
+ for (const provider of wallets) {
379
+ const emoji = hashToEmoji(provider.metadata.verificationHash);
380
+ console.log(`${provider.name}: ${emoji}`);
381
+ }
439
382
 
440
- // Connect to your wallet
383
+ // Connect and use
441
384
  const walletProvider = wallets.find(w => w.id === 'my-aztec-wallet');
442
385
  if (walletProvider) {
443
- const wallet = await walletProvider.connect('test-app');
386
+ const wallet = await walletProvider.connect('my-app-id');
444
387
 
445
- // Test wallet methods from the Wallet interface
388
+ // All calls are automatically encrypted
446
389
  const accounts = await wallet.getAccounts();
447
390
  console.log('Accounts:', accounts);
448
-
449
- const chainInfo = await wallet.getChainInfo();
450
- console.log('Chain info:', chainInfo);
451
391
  }
452
392
  ```
453
393