@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 +211 -271
- package/dest/base-wallet/base_wallet.d.ts +14 -4
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +29 -10
- package/dest/crypto.d.ts +38 -1
- package/dest/crypto.d.ts.map +1 -1
- package/dest/crypto.js +85 -1
- package/dest/manager/wallet_manager.d.ts +1 -1
- package/dest/manager/wallet_manager.d.ts.map +1 -1
- package/dest/manager/wallet_manager.js +9 -13
- package/dest/providers/extension/extension_provider.d.ts +52 -6
- package/dest/providers/extension/extension_provider.d.ts.map +1 -1
- package/dest/providers/extension/extension_provider.js +78 -11
- package/dest/providers/extension/extension_wallet.d.ts +30 -30
- package/dest/providers/extension/extension_wallet.d.ts.map +1 -1
- package/dest/providers/extension/extension_wallet.js +40 -73
- package/dest/providers/extension/index.d.ts +3 -3
- package/dest/providers/extension/index.d.ts.map +1 -1
- package/dest/types.d.ts +9 -14
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +1 -1
- package/package.json +9 -9
- package/src/base-wallet/base_wallet.ts +37 -13
- package/src/crypto.ts +93 -1
- package/src/manager/wallet_manager.ts +9 -13
- package/src/providers/extension/extension_provider.ts +109 -14
- package/src/providers/extension/extension_wallet.ts +45 -91
- package/src/providers/extension/index.ts +2 -9
- package/src/types.ts +8 -14
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/
|
|
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/
|
|
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 **
|
|
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
|
|
43
|
-
4. **
|
|
44
|
-
5. **
|
|
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
|
-
###
|
|
46
|
+
### Key Features
|
|
48
47
|
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
93
|
-
chainInfo:
|
|
94
|
-
|
|
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.
|
|
101
|
-
|
|
102
|
-
Before responding, verify your wallet supports the requested network:
|
|
92
|
+
### 3. Handle Discovery and Establish Secure Channel
|
|
103
93
|
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
const isSupported = checkNetworkSupport(chainId, version);
|
|
101
|
+
**Extension wallet (background script):**
|
|
115
102
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
112
|
+
// Generate key pair on wallet initialization (per session)
|
|
113
|
+
let walletKeyPair = await generateKeyPair();
|
|
114
|
+
let walletPublicKey = await exportPublicKey(walletKeyPair.publicKey);
|
|
129
115
|
|
|
130
|
-
|
|
116
|
+
// Store sessions by requestId
|
|
117
|
+
const sessions = new Map<string, { sharedKey: CryptoKey; verificationHash: string; tabId: number }>();
|
|
131
118
|
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
walletKeyPair = await generateKeyPair();
|
|
142
|
-
}
|
|
132
|
+
// Compute verification hash for anti-MITM verification
|
|
133
|
+
const verificationHash = await hashSharedSecret(sharedKey);
|
|
143
134
|
|
|
144
|
-
|
|
145
|
-
|
|
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',
|
|
153
|
-
name: 'My Aztec Wallet',
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
183
|
+
### 4. Discovery Response Format
|
|
201
184
|
|
|
202
185
|
```typescript
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
// Import dApp's public key
|
|
220
|
-
const dappPublicKey = await importPublicKey(request.publicKey);
|
|
203
|
+
## Secure Communication
|
|
221
204
|
|
|
222
|
-
|
|
223
|
-
const sharedKey = await deriveSharedKey(walletKeyPair.privateKey, dappPublicKey);
|
|
205
|
+
### Architecture for Extension Wallets
|
|
224
206
|
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
229
|
-
port.onmessage = async event => {
|
|
230
|
-
await handleEncryptedMessage(request.appId, event.data);
|
|
231
|
-
};
|
|
217
|
+
**Security benefits:**
|
|
232
218
|
|
|
233
|
-
|
|
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
|
-
###
|
|
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;
|
|
230
|
+
iv: string; // Base64-encoded initialization vector
|
|
244
231
|
ciphertext: string; // Base64-encoded encrypted data
|
|
245
232
|
}
|
|
246
233
|
```
|
|
247
234
|
|
|
248
|
-
|
|
235
|
+
**Background script:**
|
|
249
236
|
|
|
250
237
|
```typescript
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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>(
|
|
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
|
-
|
|
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
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
+
### Wallet Method Request (Decrypted)
|
|
319
279
|
|
|
320
280
|
```typescript
|
|
321
|
-
{
|
|
322
|
-
type: string
|
|
323
|
-
messageId: string
|
|
324
|
-
args: unknown[]
|
|
325
|
-
chainInfo:
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
348
|
-
result?: unknown
|
|
349
|
-
error?: unknown
|
|
350
|
-
walletId: string
|
|
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
|
-
##
|
|
302
|
+
## Anti-MITM Verification
|
|
355
303
|
|
|
356
|
-
|
|
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 {
|
|
307
|
+
import { hashSharedSecret } from '@aztec/wallet-sdk/crypto';
|
|
362
308
|
|
|
363
|
-
//
|
|
364
|
-
const
|
|
309
|
+
// Compute verification hash from shared key
|
|
310
|
+
const verificationHash = await hashSharedSecret(sharedKey);
|
|
365
311
|
|
|
366
|
-
//
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
321
|
+
// Convert to emoji only when displaying to the user
|
|
322
|
+
const emoji = hashToEmoji(verificationHash); // e.g., "🔵🦋🎯🐼"
|
|
323
|
+
```
|
|
392
324
|
|
|
393
|
-
|
|
325
|
+
The dApp displays the same emoji sequence. If they match, the connection is secure.
|
|
394
326
|
|
|
395
|
-
|
|
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
|
-
|
|
329
|
+
Sessions should be cleaned up when:
|
|
401
330
|
|
|
402
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
|
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('
|
|
386
|
+
const wallet = await walletProvider.connect('my-app-id');
|
|
444
387
|
|
|
445
|
-
//
|
|
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
|
|