@aztec/wallet-sdk 0.0.1-commit.d431d1c → 0.0.1-commit.e3c1de76

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 (67) hide show
  1. package/README.md +217 -294
  2. package/dest/base-wallet/base_wallet.d.ts +19 -6
  3. package/dest/base-wallet/base_wallet.d.ts.map +1 -1
  4. package/dest/base-wallet/base_wallet.js +25 -9
  5. package/dest/crypto.d.ts +59 -50
  6. package/dest/crypto.d.ts.map +1 -1
  7. package/dest/crypto.js +202 -108
  8. package/dest/emoji_alphabet.d.ts +35 -0
  9. package/dest/emoji_alphabet.d.ts.map +1 -0
  10. package/dest/emoji_alphabet.js +299 -0
  11. package/dest/extension/handlers/background_connection_handler.d.ts +158 -0
  12. package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
  13. package/dest/extension/handlers/background_connection_handler.js +258 -0
  14. package/dest/extension/handlers/content_script_connection_handler.d.ts +56 -0
  15. package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
  16. package/dest/extension/handlers/content_script_connection_handler.js +174 -0
  17. package/dest/extension/handlers/index.d.ts +12 -0
  18. package/dest/extension/handlers/index.d.ts.map +1 -0
  19. package/dest/extension/handlers/index.js +10 -0
  20. package/dest/extension/handlers/internal_message_types.d.ts +63 -0
  21. package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
  22. package/dest/extension/handlers/internal_message_types.js +22 -0
  23. package/dest/extension/provider/extension_provider.d.ts +107 -0
  24. package/dest/extension/provider/extension_provider.d.ts.map +1 -0
  25. package/dest/extension/provider/extension_provider.js +160 -0
  26. package/dest/extension/provider/extension_wallet.d.ts +131 -0
  27. package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
  28. package/dest/{providers/extension → extension/provider}/extension_wallet.js +48 -95
  29. package/dest/extension/provider/index.d.ts +3 -0
  30. package/dest/extension/provider/index.d.ts.map +1 -0
  31. package/dest/{providers/extension → extension/provider}/index.js +0 -2
  32. package/dest/manager/index.d.ts +2 -8
  33. package/dest/manager/index.d.ts.map +1 -1
  34. package/dest/manager/index.js +0 -6
  35. package/dest/manager/types.d.ts +88 -6
  36. package/dest/manager/types.d.ts.map +1 -1
  37. package/dest/manager/types.js +17 -1
  38. package/dest/manager/wallet_manager.d.ts +50 -7
  39. package/dest/manager/wallet_manager.d.ts.map +1 -1
  40. package/dest/manager/wallet_manager.js +174 -44
  41. package/dest/types.d.ts +43 -12
  42. package/dest/types.d.ts.map +1 -1
  43. package/dest/types.js +3 -2
  44. package/package.json +10 -9
  45. package/src/base-wallet/base_wallet.ts +35 -17
  46. package/src/crypto.ts +237 -113
  47. package/src/emoji_alphabet.ts +317 -0
  48. package/src/extension/handlers/background_connection_handler.ts +423 -0
  49. package/src/extension/handlers/content_script_connection_handler.ts +246 -0
  50. package/src/extension/handlers/index.ts +25 -0
  51. package/src/extension/handlers/internal_message_types.ts +69 -0
  52. package/src/extension/provider/extension_provider.ts +233 -0
  53. package/src/{providers/extension → extension/provider}/extension_wallet.ts +52 -110
  54. package/src/extension/provider/index.ts +7 -0
  55. package/src/manager/index.ts +2 -10
  56. package/src/manager/types.ts +91 -5
  57. package/src/manager/wallet_manager.ts +192 -46
  58. package/src/types.ts +44 -10
  59. package/dest/providers/extension/extension_provider.d.ts +0 -63
  60. package/dest/providers/extension/extension_provider.d.ts.map +0 -1
  61. package/dest/providers/extension/extension_provider.js +0 -124
  62. package/dest/providers/extension/extension_wallet.d.ts +0 -155
  63. package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
  64. package/dest/providers/extension/index.d.ts +0 -6
  65. package/dest/providers/extension/index.d.ts.map +0 -1
  66. package/src/providers/extension/extension_provider.ts +0 -167
  67. package/src/providers/extension/index.ts +0 -5
package/README.md CHANGED
@@ -10,6 +10,8 @@ All types and utilities needed for wallet integration are exported from `@aztec/
10
10
  import type {
11
11
  DiscoveryRequest,
12
12
  DiscoveryResponse,
13
+ KeyExchangeRequest,
14
+ KeyExchangeResponse,
13
15
  WalletInfo,
14
16
  WalletMessage,
15
17
  WalletResponse,
@@ -22,377 +24,298 @@ Cryptographic utilities for secure channel establishment are exported from `@azt
22
24
  import type { EncryptedPayload, ExportedPublicKey } from '@aztec/wallet-sdk/crypto';
23
25
  import {
24
26
  decrypt,
25
- deriveSharedKey,
27
+ deriveSessionKeys,
26
28
  encrypt,
27
29
  exportPublicKey,
28
30
  generateKeyPair,
29
- hashSharedSecret,
30
31
  hashToEmoji,
31
32
  importPublicKey,
32
33
  } from '@aztec/wallet-sdk/crypto';
33
34
  ```
34
35
 
35
- ## Overview
36
-
37
- The Wallet SDK uses a **unified discovery and connection** model with **end-to-end encryption**:
38
-
39
- 1. **dApp requests wallets** for a specific chain/version via `WalletManager.getAvailableWallets({ chainInfo })`
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
45
-
46
- ### Key Features
47
-
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
51
-
52
- ### Transport Mechanisms
53
-
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.
55
-
56
- ## Discovery Protocol
57
-
58
- ### 1. Listen for Discovery Requests
59
-
60
- **Extension wallet (content script):**
61
-
62
- ```typescript
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 {
70
- return;
71
- }
72
-
73
- if (data.type === 'aztec-wallet-discovery') {
74
- await handleDiscoveryRequest(data);
75
- }
76
- });
77
- ```
78
-
79
- ### 2. Discovery Message Format
80
-
81
- Discovery messages have this structure:
82
-
83
- ```typescript
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
89
- }
90
- ```
91
-
92
- ### 3. Handle Discovery and Establish Secure Channel
93
-
94
- When your wallet receives a discovery request:
95
-
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
100
-
101
- **Extension wallet (background script):**
36
+ **For extension wallets**, pre-built connection handlers are available:
102
37
 
103
38
  ```typescript
104
39
  import {
105
- deriveSharedKey,
106
- exportPublicKey,
107
- generateKeyPair,
108
- hashSharedSecret,
109
- importPublicKey,
110
- } from '@aztec/wallet-sdk/crypto';
111
-
112
- // Generate key pair on wallet initialization (per session)
113
- let walletKeyPair = await generateKeyPair();
114
- let walletPublicKey = await exportPublicKey(walletKeyPair.publicKey);
115
-
116
- // Store sessions by requestId
117
- const sessions = new Map<string, { sharedKey: CryptoKey; verificationHash: string; tabId: number }>();
118
-
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
- }
127
-
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);
131
-
132
- // Compute verification hash for anti-MITM verification
133
- const verificationHash = await hashSharedSecret(sharedKey);
134
-
135
- // Store the session with verificationHash (emoji computed lazily for display)
136
- sessions.set(request.requestId, { sharedKey, verificationHash, tabId });
137
-
138
- const response: DiscoveryResponse = {
139
- type: 'aztec-wallet-discovery-response',
140
- requestId: request.requestId,
141
- walletInfo: {
142
- id: 'my-aztec-wallet',
143
- name: 'My Aztec Wallet',
144
- version: '1.0.0',
145
- publicKey: walletPublicKey,
146
- },
147
- };
148
-
149
- return { success: true, response };
150
- }
40
+ BackgroundConnectionHandler,
41
+ ContentScriptConnectionHandler,
42
+ } from '@aztec/wallet-sdk/extension/handlers';
151
43
  ```
152
44
 
153
- **Content script (creates MessageChannel and sends response):**
45
+ ## Overview
154
46
 
155
- ```typescript
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();
47
+ The Wallet SDK uses a **two-phase connection model** with **end-to-end encryption**:
177
48
 
178
- // Send response with port2 to the page
179
- window.postMessage(JSON.stringify(result.response), '*', [channel.port2]);
180
- }
181
- ```
49
+ ### Phase 1: Discovery
182
50
 
183
- ### 4. Discovery Response Format
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
184
55
 
185
- ```typescript
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
- }
56
+ ### Phase 2: Secure Channel Establishment
191
57
 
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
- ```
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
200
64
 
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.
65
+ ### Key Security Features
202
66
 
203
- ## Secure Communication
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
204
70
 
205
- ### Architecture for Extension Wallets
71
+ ## Architecture for Extension Wallets
206
72
 
207
73
  ```
208
74
  ┌─────────────┐ window.postMessage ┌─────────────────┐ browser.runtime ┌──────────────────┐
209
- │ dApp │◄───(discovery only)─────►│ Content Script │◄────────────────────►│ Background Script│
210
- │ (web page) │ │ (message relay)│ │ (decrypt+process)│
75
+ │ dApp │◄──(discovery + port)────►│ Content Script │◄────────────────────►│ Background Script│
76
+ │ (web page) │ │ (message relay)│ │ (crypto+state)
211
77
  └─────────────┘ └─────────────────┘ └──────────────────┘
212
78
  │ │
213
- MessagePort (private channel)
214
- └──────────(encrypted messages)────────────┘
79
+ MessagePort
80
+ └──────────(key exchange + encrypted)──────┘
215
81
  ```
216
82
 
217
- **Security benefits:**
83
+ **Security model:**
218
84
 
219
- - Content script never has access to private keys or shared secrets
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
220
88
  - 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
89
+ - Anti-MITM verification (emoji grid) ensures both parties derived the same keys
223
90
 
224
- ### Handle Encrypted Messages
91
+ ## Using Pre-built Connection Handlers
225
92
 
226
- All wallet method calls arrive as encrypted payloads on the MessagePort:
93
+ The SDK provides `BackgroundConnectionHandler` and `ContentScriptConnectionHandler` to handle the connection flow. These are the recommended way to build extension wallets.
94
+
95
+ ### Background Script Setup
227
96
 
228
97
  ```typescript
229
- interface EncryptedPayload {
230
- iv: string; // Base64-encoded initialization vector
231
- ciphertext: string; // Base64-encoded encrypted data
232
- }
233
- ```
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';
234
105
 
235
- **Background script:**
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
+ },
236
134
 
237
- ```typescript
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;
243
-
244
- try {
245
- // Decrypt the incoming message
246
- const message = await decrypt<WalletMessage>(session.sharedKey, encrypted);
247
- const { type, messageId, args, chainInfo, walletId } = message;
248
-
249
- // Process the wallet method call
250
- const wallet = await getWalletForChain(chainInfo);
251
- const result = await wallet[type](...args);
252
-
253
- // Create and encrypt response
254
- const response: WalletResponse = { messageId, result, walletId };
255
- const encryptedResponse = await encrypt(session.sharedKey, response);
256
-
257
- // Send back through content script
258
- browser.tabs.sendMessage(session.tabId, {
259
- type: 'secure-response',
260
- requestId,
261
- content: encryptedResponse,
262
- });
263
- } catch (error) {
264
- // Send encrypted error response
265
- const errorResponse: WalletResponse = {
266
- messageId: message?.messageId ?? '',
267
- error: { message: error.message },
268
- walletId: message?.walletId ?? '',
269
- };
270
- const encryptedError = await encrypt(session.sharedKey, errorResponse);
271
- // ... send error response
272
- }
273
- }
274
- ```
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
+ },
275
140
 
276
- ## Message Formats
141
+ // Called when a session is terminated
142
+ onSessionTerminated: (requestId) => {
143
+ console.log('Session terminated:', requestId);
144
+ },
277
145
 
278
- ### Wallet Method Request (Decrypted)
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
+ };
279
152
 
280
- ```typescript
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
153
+ const handler = new BackgroundConnectionHandler(config, transport, callbacks);
154
+
155
+ // Initialize the handler to start listening
156
+ handler.initialize();
157
+
158
+ // User approves connection from wallet UI
159
+ function approveConnection(requestId: string) {
160
+ handler.approveDiscovery(requestId);
288
161
  }
289
- ```
290
162
 
291
- ### Wallet Method Response
163
+ // User denies connection
164
+ function denyConnection(requestId: string) {
165
+ handler.rejectDiscovery(requestId);
166
+ }
292
167
 
293
- ```typescript
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
168
+ // Send response back to dApp
169
+ async function sendWalletResponse(requestId: string, response: WalletResponse) {
170
+ await handler.sendResponse(requestId, response);
299
171
  }
300
- ```
301
172
 
302
- ## Anti-MITM Verification
173
+ // Clean up on tab close/navigate
174
+ browser.tabs.onRemoved.addListener((tabId) => {
175
+ handler.terminateForTab(tabId);
176
+ });
177
+ ```
303
178
 
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.
179
+ ### Content Script Setup
305
180
 
306
181
  ```typescript
307
- import { hashSharedSecret } from '@aztec/wallet-sdk/crypto';
182
+ import {
183
+ ContentScriptConnectionHandler,
184
+ type ContentScriptTransport,
185
+ } from '@aztec/wallet-sdk/extension/handlers';
186
+
187
+ const transport: ContentScriptTransport = {
188
+ sendToBackground: (message) => browser.runtime.sendMessage(message),
189
+ addBackgroundListener: (handler) => browser.runtime.onMessage.addListener(handler),
190
+ };
308
191
 
309
- // Compute verification hash from shared key
310
- const verificationHash = await hashSharedSecret(sharedKey);
192
+ const handler = new ContentScriptConnectionHandler(transport);
311
193
 
312
- // Store verificationHash in session - this is the cryptographic proof
313
- sessions.set(requestId, { sharedKey, verificationHash, tabId });
194
+ // Start listening for discovery requests and background messages
195
+ handler.start();
314
196
  ```
315
197
 
316
- For user-friendly display, convert the hash to an emoji sequence:
198
+ ## Testing Your Integration (dApp Side)
199
+
200
+ The `WalletManager` supports two patterns for consuming discovered wallets.
201
+
202
+ ### Async Iterator Pattern
317
203
 
318
204
  ```typescript
205
+ import { Fr } from '@aztec/foundation/fields';
206
+ import { WalletManager } from '@aztec/wallet-sdk/manager';
319
207
  import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
320
208
 
321
- // Convert to emoji only when displaying to the user
322
- const emoji = hashToEmoji(verificationHash); // e.g., "🔵🦋🎯🐼"
323
- ```
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
+ });
324
219
 
325
- The dApp displays the same emoji sequence. If they match, the connection is secure.
220
+ // Iterate over discovered wallets as they're approved
221
+ for await (const provider of discovery.wallets) {
222
+ console.log(`Found: ${provider.name}`);
326
223
 
327
- ## Session Management
224
+ // Establish secure channel (key exchange)
225
+ const pending = await provider.establishSecureChannel('my-dapp');
328
226
 
329
- Sessions should be cleaned up when:
227
+ // Display verification emojis to user
228
+ const emojis = hashToEmoji(pending.verificationHash);
229
+ console.log('Verify this matches your wallet:', emojis);
330
230
 
331
- - **Tab closes**: Browser tabs API `onRemoved` event
332
- - **Tab navigates**: Browser tabs API `onUpdated` event with `status === 'loading'`
231
+ // User confirms emojis match
232
+ const wallet = await pending.confirm();
333
233
 
334
- ```typescript
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
- });
234
+ // All calls are now encrypted
235
+ const accounts = await wallet.getAccounts();
236
+ console.log('Accounts:', accounts);
237
+ }
343
238
 
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
- });
239
+ // Cancel discovery when done or on cleanup
240
+ discovery.cancel();
354
241
  ```
355
242
 
356
- ## Testing Your Integration
357
-
358
- ### Using WalletManager
243
+ ### Callback Pattern
359
244
 
360
245
  ```typescript
361
246
  import { Fr } from '@aztec/foundation/fields';
362
- import { WalletManager, hashToEmoji } from '@aztec/wallet-sdk/manager';
247
+ import { WalletManager, type WalletProvider } from '@aztec/wallet-sdk/manager';
248
+ import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
363
249
 
364
- const manager = WalletManager.configure({
365
- extensions: { enabled: true },
366
- });
250
+ const discoveredProviders: WalletProvider[] = [];
367
251
 
368
- // Discover wallets (secure channel established automatically)
369
- const wallets = await manager.getAvailableWallets({
252
+ const discovery = WalletManager.configure({
253
+ extensions: { enabled: true },
254
+ }).getAvailableWallets({
370
255
  chainInfo: {
371
256
  chainId: new Fr(31337),
372
- version: new Fr(0),
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
373
265
  },
374
- timeout: 2000,
375
266
  });
376
267
 
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
- }
268
+ // Wait for discovery to complete (or cancel early with discovery.cancel())
269
+ await discovery.done;
270
+ console.log('Discovery complete, found:', discoveredProviders.length);
382
271
 
383
- // Connect and use
384
- const walletProvider = wallets.find(w => w.id === 'my-aztec-wallet');
385
- if (walletProvider) {
386
- const wallet = await walletProvider.connect('my-app-id');
272
+ // Connect to a selected provider
273
+ async function connectToWallet(provider: WalletProvider) {
274
+ const pending = await provider.establishSecureChannel('my-dapp');
387
275
 
388
- // All calls are automatically encrypted
389
- const accounts = await wallet.getAccounts();
390
- console.log('Accounts:', accounts);
276
+ // Show verification UI
277
+ const emojis = hashToEmoji(pending.verificationHash);
278
+ showVerificationDialog(emojis);
279
+
280
+ // User confirms
281
+ const wallet = await pending.confirm();
282
+ return wallet;
391
283
  }
392
284
  ```
393
285
 
394
- ## Reference Implementation
286
+ ### React Hook Example
287
+
288
+ ```typescript
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));
395
312
 
396
- For a complete reference implementation, see the demo wallet at:
313
+ return () => {
314
+ discovery.cancel();
315
+ discoveryRef.current = null;
316
+ };
317
+ }, [chainInfo.chainId.toString(), chainInfo.version.toString(), appId]);
397
318
 
398
- - Repository: `~/repos/demo-wallet`
319
+ return { providers, isDiscovering, cancel: () => discoveryRef.current?.cancel() };
320
+ }
321
+ ```