@aztec/wallet-sdk 0.0.1-commit.d3ec352c → 0.0.1-commit.f295ac2
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 +240 -266
- package/dest/base-wallet/base_wallet.d.ts +19 -9
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +47 -19
- package/dest/crypto.d.ts +183 -0
- package/dest/crypto.d.ts.map +1 -0
- package/dest/crypto.js +300 -0
- package/dest/manager/index.d.ts +4 -3
- package/dest/manager/index.d.ts.map +1 -1
- package/dest/manager/index.js +2 -0
- package/dest/manager/types.d.ts +22 -1
- package/dest/manager/types.d.ts.map +1 -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 +34 -15
- package/dest/providers/extension/extension_provider.d.ts +53 -7
- package/dest/providers/extension/extension_provider.d.ts.map +1 -1
- package/dest/providers/extension/extension_provider.js +81 -13
- package/dest/providers/extension/extension_wallet.d.ts +140 -8
- package/dest/providers/extension/extension_wallet.d.ts.map +1 -1
- package/dest/providers/extension/extension_wallet.js +268 -46
- package/dest/providers/extension/index.d.ts +6 -4
- package/dest/providers/extension/index.d.ts.map +1 -1
- package/dest/providers/extension/index.js +2 -0
- package/dest/types.d.ts +92 -0
- package/dest/types.d.ts.map +1 -0
- package/dest/types.js +10 -0
- package/package.json +11 -9
- package/src/base-wallet/base_wallet.ts +57 -30
- package/src/crypto.ts +375 -0
- package/src/manager/index.ts +4 -8
- package/src/manager/types.ts +22 -0
- package/src/manager/wallet_manager.ts +43 -16
- package/src/providers/extension/extension_provider.ts +112 -17
- package/src/providers/extension/extension_wallet.ts +310 -55
- package/src/providers/extension/index.ts +5 -3
- package/src/{providers/types.ts → types.ts} +33 -6
- package/dest/providers/types.d.ts +0 -67
- package/dest/providers/types.d.ts.map +0 -1
- package/dest/providers/types.js +0 -3
package/README.md
CHANGED
|
@@ -4,66 +4,76 @@ 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
11
|
DiscoveryRequest,
|
|
13
12
|
DiscoveryResponse,
|
|
14
13
|
WalletInfo,
|
|
15
14
|
WalletMessage,
|
|
16
15
|
WalletResponse,
|
|
17
|
-
} from '@aztec/wallet-sdk/
|
|
18
|
-
|
|
16
|
+
} from '@aztec/wallet-sdk/types';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Cryptographic utilities for secure channel establishment are exported from `@aztec/wallet-sdk/crypto`:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import type { EncryptedPayload, ExportedPublicKey } from '@aztec/wallet-sdk/crypto';
|
|
23
|
+
import {
|
|
24
|
+
decrypt,
|
|
25
|
+
deriveSharedKey,
|
|
26
|
+
encrypt,
|
|
27
|
+
exportPublicKey,
|
|
28
|
+
generateKeyPair,
|
|
29
|
+
hashSharedSecret,
|
|
30
|
+
hashToEmoji,
|
|
31
|
+
importPublicKey,
|
|
32
|
+
} from '@aztec/wallet-sdk/crypto';
|
|
19
33
|
```
|
|
20
34
|
|
|
21
35
|
## Overview
|
|
22
36
|
|
|
23
|
-
The Wallet SDK uses a **
|
|
37
|
+
The Wallet SDK uses a **unified discovery and connection** model with **end-to-end encryption**:
|
|
24
38
|
|
|
25
39
|
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
|
|
28
|
-
4. **
|
|
29
|
-
5. **
|
|
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
|
|
30
45
|
|
|
31
|
-
###
|
|
46
|
+
### Key Features
|
|
32
47
|
|
|
33
|
-
|
|
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
|
|
34
51
|
|
|
35
|
-
|
|
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
|
|
52
|
+
### Transport Mechanisms
|
|
38
53
|
|
|
39
|
-
|
|
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.
|
|
40
55
|
|
|
41
56
|
## Discovery Protocol
|
|
42
57
|
|
|
43
58
|
### 1. Listen for Discovery Requests
|
|
44
59
|
|
|
45
|
-
**Extension wallet
|
|
60
|
+
**Extension wallet (content script):**
|
|
46
61
|
|
|
47
62
|
```typescript
|
|
48
|
-
window.addEventListener('message', event => {
|
|
49
|
-
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 {
|
|
50
70
|
return;
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
const data = JSON.parse(event.data);
|
|
54
|
-
|
|
55
73
|
if (data.type === 'aztec-wallet-discovery') {
|
|
56
|
-
|
|
74
|
+
await handleDiscoveryRequest(data);
|
|
57
75
|
}
|
|
58
76
|
});
|
|
59
|
-
|
|
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
|
-
// });
|
|
67
77
|
```
|
|
68
78
|
|
|
69
79
|
### 2. Discovery Message Format
|
|
@@ -71,328 +81,291 @@ window.addEventListener('message', event => {
|
|
|
71
81
|
Discovery messages have this structure:
|
|
72
82
|
|
|
73
83
|
```typescript
|
|
74
|
-
{
|
|
75
|
-
type: 'aztec-wallet-discovery'
|
|
76
|
-
requestId: string
|
|
77
|
-
chainInfo:
|
|
78
|
-
|
|
79
|
-
version: Fr // Protocol version
|
|
80
|
-
}
|
|
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
|
|
81
89
|
}
|
|
82
90
|
```
|
|
83
91
|
|
|
84
|
-
### 3.
|
|
85
|
-
|
|
86
|
-
Before responding, verify your wallet supports the requested network:
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
import { ChainInfoSchema } from '@aztec/wallet-sdk/manager';
|
|
92
|
+
### 3. Handle Discovery and Establish Secure Channel
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
const { requestId, chainInfo } = message;
|
|
94
|
+
When your wallet receives a discovery request:
|
|
93
95
|
|
|
94
|
-
|
|
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
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
const isSupported = checkNetworkSupport(chainId, version);
|
|
101
|
+
**Extension wallet (background script):**
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
```typescript
|
|
104
|
+
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');
|
|
103
126
|
}
|
|
104
127
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### 4. Respond to Discovery
|
|
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);
|
|
111
131
|
|
|
112
|
-
|
|
132
|
+
// Compute verification hash for anti-MITM verification
|
|
133
|
+
const verificationHash = await hashSharedSecret(sharedKey);
|
|
113
134
|
|
|
114
|
-
|
|
135
|
+
// Store the session with verificationHash (emoji computed lazily for display)
|
|
136
|
+
sessions.set(request.requestId, { sharedKey, verificationHash, tabId });
|
|
115
137
|
|
|
116
|
-
|
|
117
|
-
import { jsonStringify } from '@aztec/wallet-sdk/manager';
|
|
118
|
-
|
|
119
|
-
function respondToDiscovery(requestId: string) {
|
|
120
|
-
const response = {
|
|
138
|
+
const response: DiscoveryResponse = {
|
|
121
139
|
type: 'aztec-wallet-discovery-response',
|
|
122
|
-
requestId,
|
|
140
|
+
requestId: request.requestId,
|
|
123
141
|
walletInfo: {
|
|
124
|
-
id: 'my-aztec-wallet',
|
|
125
|
-
name: 'My Aztec Wallet',
|
|
126
|
-
|
|
127
|
-
|
|
142
|
+
id: 'my-aztec-wallet',
|
|
143
|
+
name: 'My Aztec Wallet',
|
|
144
|
+
version: '1.0.0',
|
|
145
|
+
publicKey: walletPublicKey,
|
|
128
146
|
},
|
|
129
147
|
};
|
|
130
148
|
|
|
131
|
-
|
|
132
|
-
window.postMessage(jsonStringify(response), '*');
|
|
149
|
+
return { success: true, response };
|
|
133
150
|
}
|
|
134
|
-
|
|
135
|
-
// Using WebSocket:
|
|
136
|
-
// websocket.send(jsonStringify(response));
|
|
137
151
|
```
|
|
138
152
|
|
|
139
|
-
**
|
|
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
|
|
145
|
-
|
|
146
|
-
## Message Format
|
|
147
|
-
|
|
148
|
-
### Wallet Method Request
|
|
149
|
-
|
|
150
|
-
After discovery, dApps will call wallet methods. These arrive as:
|
|
153
|
+
**Content script (creates MessageChannel and sends response):**
|
|
151
154
|
|
|
152
155
|
```typescript
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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]);
|
|
163
180
|
}
|
|
164
181
|
```
|
|
165
182
|
|
|
166
|
-
|
|
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
|
|
172
|
-
|
|
173
|
-
### Wallet Method Response
|
|
174
|
-
|
|
175
|
-
Your wallet must respond with:
|
|
183
|
+
### 4. Discovery Response Format
|
|
176
184
|
|
|
177
185
|
```typescript
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
}
|
|
191
|
+
|
|
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
|
|
183
198
|
}
|
|
184
199
|
```
|
|
185
200
|
|
|
186
|
-
|
|
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.
|
|
187
202
|
|
|
188
|
-
|
|
203
|
+
## Secure Communication
|
|
189
204
|
|
|
190
|
-
|
|
205
|
+
### Architecture for Extension Wallets
|
|
191
206
|
|
|
192
|
-
```
|
|
193
|
-
window.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
+
```
|
|
197
216
|
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
data = JSON.parse(event.data);
|
|
201
|
-
} catch {
|
|
202
|
-
return; // Not a valid JSON message
|
|
203
|
-
}
|
|
217
|
+
**Security benefits:**
|
|
204
218
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
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
|
|
210
223
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
});
|
|
224
|
+
### Handle Encrypted Messages
|
|
225
|
+
|
|
226
|
+
All wallet method calls arrive as encrypted payloads on the MessagePort:
|
|
216
227
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// } else if (data.messageId && data.type) {
|
|
223
|
-
// handleWalletMethod(data);
|
|
224
|
-
// }
|
|
225
|
-
// });
|
|
228
|
+
```typescript
|
|
229
|
+
interface EncryptedPayload {
|
|
230
|
+
iv: string; // Base64-encoded initialization vector
|
|
231
|
+
ciphertext: string; // Base64-encoded encrypted data
|
|
232
|
+
}
|
|
226
233
|
```
|
|
227
234
|
|
|
228
|
-
|
|
235
|
+
**Background script:**
|
|
229
236
|
|
|
230
237
|
```typescript
|
|
231
|
-
import {
|
|
238
|
+
import { decrypt, encrypt } from '@aztec/wallet-sdk/crypto';
|
|
232
239
|
|
|
233
|
-
async function
|
|
234
|
-
const
|
|
240
|
+
async function handleSecureMessage(requestId: string, encrypted: EncryptedPayload) {
|
|
241
|
+
const session = sessions.get(requestId);
|
|
242
|
+
if (!session) return;
|
|
235
243
|
|
|
236
244
|
try {
|
|
237
|
-
//
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
// Get the wallet instance for this chain
|
|
241
|
-
const wallet = await getWalletForChain(parsedChainInfo);
|
|
242
|
-
|
|
243
|
-
// Verify the method exists on the Wallet interface
|
|
244
|
-
if (typeof wallet[type] !== 'function') {
|
|
245
|
-
throw new Error(`Unknown wallet method: ${type}`);
|
|
246
|
-
}
|
|
245
|
+
// Decrypt the incoming message
|
|
246
|
+
const message = await decrypt<WalletMessage>(session.sharedKey, encrypted);
|
|
247
|
+
const { type, messageId, args, chainInfo, walletId } = message;
|
|
247
248
|
|
|
248
|
-
//
|
|
249
|
+
// Process the wallet method call
|
|
250
|
+
const wallet = await getWalletForChain(chainInfo);
|
|
249
251
|
const result = await wallet[type](...args);
|
|
250
252
|
|
|
251
|
-
//
|
|
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
|
+
});
|
|
253
263
|
} catch (error) {
|
|
254
|
-
// Send error response
|
|
255
|
-
|
|
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
|
|
256
272
|
}
|
|
257
273
|
}
|
|
258
274
|
```
|
|
259
275
|
|
|
260
|
-
|
|
276
|
+
## Message Formats
|
|
261
277
|
|
|
262
|
-
|
|
278
|
+
### Wallet Method Request (Decrypted)
|
|
263
279
|
|
|
264
280
|
```typescript
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
// Send as JSON string
|
|
275
|
-
window.postMessage(jsonStringify(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
|
|
276
288
|
}
|
|
289
|
+
```
|
|
277
290
|
|
|
278
|
-
|
|
279
|
-
const response = {
|
|
280
|
-
messageId,
|
|
281
|
-
error: {
|
|
282
|
-
message: error.message,
|
|
283
|
-
stack: error.stack,
|
|
284
|
-
},
|
|
285
|
-
walletId,
|
|
286
|
-
};
|
|
291
|
+
### Wallet Method Response
|
|
287
292
|
|
|
288
|
-
|
|
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
|
|
289
299
|
}
|
|
290
|
-
|
|
291
|
-
// Using WebSocket:
|
|
292
|
-
// websocket.send(jsonStringify({ messageId, result, walletId }));
|
|
293
300
|
```
|
|
294
301
|
|
|
295
|
-
##
|
|
296
|
-
|
|
297
|
-
### Using Zod Schemas
|
|
302
|
+
## Anti-MITM Verification
|
|
298
303
|
|
|
299
|
-
|
|
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.
|
|
300
305
|
|
|
301
306
|
```typescript
|
|
302
|
-
import {
|
|
307
|
+
import { hashSharedSecret } from '@aztec/wallet-sdk/crypto';
|
|
303
308
|
|
|
304
|
-
//
|
|
305
|
-
const
|
|
309
|
+
// Compute verification hash from shared key
|
|
310
|
+
const verificationHash = await hashSharedSecret(sharedKey);
|
|
306
311
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
// 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 });
|
|
310
314
|
```
|
|
311
315
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
## Error Handling
|
|
315
|
-
|
|
316
|
-
### Error Response Format
|
|
317
|
-
|
|
318
|
-
Always send error responses with this structure:
|
|
316
|
+
For user-friendly display, convert the hash to an emoji sequence:
|
|
319
317
|
|
|
320
318
|
```typescript
|
|
321
|
-
{
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
code?: string, // Optional error code
|
|
326
|
-
stack?: string // Optional stack trace
|
|
327
|
-
},
|
|
328
|
-
walletId: string
|
|
329
|
-
}
|
|
319
|
+
import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
|
|
320
|
+
|
|
321
|
+
// Convert to emoji only when displaying to the user
|
|
322
|
+
const emoji = hashToEmoji(verificationHash); // e.g., "🔵🦋🎯🐼"
|
|
330
323
|
```
|
|
331
324
|
|
|
332
|
-
|
|
325
|
+
The dApp displays the same emoji sequence. If they match, the connection is secure.
|
|
333
326
|
|
|
334
|
-
|
|
335
|
-
import { ChainInfoSchema } from '@aztec/wallet-sdk/manager';
|
|
327
|
+
## Session Management
|
|
336
328
|
|
|
337
|
-
|
|
338
|
-
const { type, messageId, args, chainInfo, walletId } = message;
|
|
329
|
+
Sessions should be cleaned up when:
|
|
339
330
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const parsedChainInfo = ChainInfoSchema.parse(chainInfo);
|
|
331
|
+
- **Tab closes**: Browser tabs API `onRemoved` event
|
|
332
|
+
- **Tab navigates**: Browser tabs API `onUpdated` event with `status === 'loading'`
|
|
343
333
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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);
|
|
347
340
|
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
348
343
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
+
}
|
|
355
351
|
}
|
|
356
|
-
|
|
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
352
|
}
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### User Rejection Handling
|
|
367
|
-
|
|
368
|
-
If a user rejects an action:
|
|
369
|
-
|
|
370
|
-
```typescript
|
|
371
|
-
{
|
|
372
|
-
messageId: 'abc-123',
|
|
373
|
-
error: {
|
|
374
|
-
message: 'User rejected the request',
|
|
375
|
-
code: 'USER_REJECTED'
|
|
376
|
-
},
|
|
377
|
-
walletId: 'my-wallet'
|
|
378
|
-
}
|
|
353
|
+
});
|
|
379
354
|
```
|
|
380
355
|
|
|
381
356
|
## Testing Your Integration
|
|
382
357
|
|
|
383
|
-
### WalletManager
|
|
384
|
-
|
|
385
|
-
In a dApp using the Wallet SDK:
|
|
358
|
+
### Using WalletManager
|
|
386
359
|
|
|
387
360
|
```typescript
|
|
388
361
|
import { Fr } from '@aztec/foundation/fields';
|
|
389
|
-
import { WalletManager } from '@aztec/wallet-sdk/manager';
|
|
362
|
+
import { WalletManager, hashToEmoji } from '@aztec/wallet-sdk/manager';
|
|
390
363
|
|
|
391
364
|
const manager = WalletManager.configure({
|
|
392
365
|
extensions: { enabled: true },
|
|
393
366
|
});
|
|
394
367
|
|
|
395
|
-
// Discover wallets
|
|
368
|
+
// Discover wallets (secure channel established automatically)
|
|
396
369
|
const wallets = await manager.getAvailableWallets({
|
|
397
370
|
chainInfo: {
|
|
398
371
|
chainId: new Fr(31337),
|
|
@@ -401,19 +374,20 @@ const wallets = await manager.getAvailableWallets({
|
|
|
401
374
|
timeout: 2000,
|
|
402
375
|
});
|
|
403
376
|
|
|
404
|
-
|
|
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
|
+
}
|
|
405
382
|
|
|
406
|
-
// Connect
|
|
383
|
+
// Connect and use
|
|
407
384
|
const walletProvider = wallets.find(w => w.id === 'my-aztec-wallet');
|
|
408
385
|
if (walletProvider) {
|
|
409
|
-
const wallet = await walletProvider.connect('
|
|
386
|
+
const wallet = await walletProvider.connect('my-app-id');
|
|
410
387
|
|
|
411
|
-
//
|
|
388
|
+
// All calls are automatically encrypted
|
|
412
389
|
const accounts = await wallet.getAccounts();
|
|
413
390
|
console.log('Accounts:', accounts);
|
|
414
|
-
|
|
415
|
-
const chainInfo = await wallet.getChainInfo();
|
|
416
|
-
console.log('Chain info:', chainInfo);
|
|
417
391
|
}
|
|
418
392
|
```
|
|
419
393
|
|