@aztec/wallet-sdk 0.0.1-commit.96bb3f7 → 0.0.1-commit.bf2612ae
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 +218 -355
- package/dest/base-wallet/base_wallet.d.ts +19 -10
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +38 -16
- package/dest/crypto.d.ts +73 -27
- package/dest/crypto.d.ts.map +1 -1
- package/dest/crypto.js +219 -41
- package/dest/emoji_alphabet.d.ts +35 -0
- package/dest/emoji_alphabet.d.ts.map +1 -0
- package/dest/emoji_alphabet.js +299 -0
- package/dest/extension/handlers/background_connection_handler.d.ts +158 -0
- package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/background_connection_handler.js +258 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts +56 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/content_script_connection_handler.js +174 -0
- package/dest/extension/handlers/index.d.ts +12 -0
- package/dest/extension/handlers/index.d.ts.map +1 -0
- package/dest/extension/handlers/index.js +10 -0
- package/dest/extension/handlers/internal_message_types.d.ts +63 -0
- package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
- package/dest/extension/handlers/internal_message_types.js +22 -0
- package/dest/extension/provider/extension_provider.d.ts +107 -0
- package/dest/extension/provider/extension_provider.d.ts.map +1 -0
- package/dest/extension/provider/extension_provider.js +160 -0
- package/dest/extension/provider/extension_wallet.d.ts +131 -0
- package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
- package/dest/extension/provider/extension_wallet.js +271 -0
- package/dest/extension/provider/index.d.ts +3 -0
- package/dest/extension/provider/index.d.ts.map +1 -0
- package/dest/{providers/extension → extension/provider}/index.js +0 -1
- package/dest/manager/index.d.ts +2 -7
- package/dest/manager/index.d.ts.map +1 -1
- package/dest/manager/index.js +0 -4
- package/dest/manager/types.d.ts +108 -5
- package/dest/manager/types.d.ts.map +1 -1
- package/dest/manager/types.js +17 -1
- package/dest/manager/wallet_manager.d.ts +50 -7
- package/dest/manager/wallet_manager.d.ts.map +1 -1
- package/dest/manager/wallet_manager.js +178 -29
- package/dest/types.d.ts +55 -15
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +10 -2
- package/package.json +11 -10
- package/src/base-wallet/base_wallet.ts +55 -28
- package/src/crypto.ts +263 -47
- package/src/emoji_alphabet.ts +317 -0
- package/src/extension/handlers/background_connection_handler.ts +423 -0
- package/src/extension/handlers/content_script_connection_handler.ts +246 -0
- package/src/extension/handlers/index.ts +25 -0
- package/src/extension/handlers/internal_message_types.ts +69 -0
- package/src/extension/provider/extension_provider.ts +233 -0
- package/src/extension/provider/extension_wallet.ts +321 -0
- package/src/extension/provider/index.ts +7 -0
- package/src/manager/index.ts +3 -9
- package/src/manager/types.ts +112 -4
- package/src/manager/wallet_manager.ts +204 -31
- package/src/types.ts +57 -14
- package/dest/providers/extension/extension_provider.d.ts +0 -17
- package/dest/providers/extension/extension_provider.d.ts.map +0 -1
- package/dest/providers/extension/extension_provider.js +0 -56
- package/dest/providers/extension/extension_wallet.d.ts +0 -95
- package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
- package/dest/providers/extension/extension_wallet.js +0 -225
- package/dest/providers/extension/index.d.ts +0 -5
- package/dest/providers/extension/index.d.ts.map +0 -1
- package/src/providers/extension/extension_provider.ts +0 -72
- package/src/providers/extension/extension_wallet.ts +0 -275
- package/src/providers/extension/index.ts +0 -11
package/README.md
CHANGED
|
@@ -4,19 +4,18 @@ 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,
|
|
13
|
+
KeyExchangeRequest,
|
|
14
|
+
KeyExchangeResponse,
|
|
15
15
|
WalletInfo,
|
|
16
16
|
WalletMessage,
|
|
17
17
|
WalletResponse,
|
|
18
|
-
} from '@aztec/wallet-sdk/
|
|
19
|
-
import { ChainInfoSchema, WalletSchema, jsonStringify } from '@aztec/wallet-sdk/manager';
|
|
18
|
+
} from '@aztec/wallet-sdk/types';
|
|
20
19
|
```
|
|
21
20
|
|
|
22
21
|
Cryptographic utilities for secure channel establishment are exported from `@aztec/wallet-sdk/crypto`:
|
|
@@ -25,434 +24,298 @@ Cryptographic utilities for secure channel establishment are exported from `@azt
|
|
|
25
24
|
import type { EncryptedPayload, ExportedPublicKey } from '@aztec/wallet-sdk/crypto';
|
|
26
25
|
import {
|
|
27
26
|
decrypt,
|
|
28
|
-
|
|
27
|
+
deriveSessionKeys,
|
|
29
28
|
encrypt,
|
|
30
29
|
exportPublicKey,
|
|
31
30
|
generateKeyPair,
|
|
31
|
+
hashToEmoji,
|
|
32
32
|
importPublicKey,
|
|
33
33
|
} from '@aztec/wallet-sdk/crypto';
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
The Wallet SDK uses a **request-based discovery** model with **end-to-end encryption**:
|
|
39
|
-
|
|
40
|
-
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
|
|
46
|
-
|
|
47
|
-
### Transport Mechanisms
|
|
48
|
-
|
|
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:
|
|
50
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
The message format remains the same regardless of transport - only the delivery mechanism changes.
|
|
56
|
-
|
|
57
|
-
## Discovery Protocol
|
|
58
|
-
|
|
59
|
-
### 1. Listen for Discovery Requests
|
|
60
|
-
|
|
61
|
-
**Extension wallet example:**
|
|
36
|
+
**For extension wallets**, pre-built connection handlers are available:
|
|
62
37
|
|
|
63
38
|
```typescript
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const data = JSON.parse(event.data);
|
|
70
|
-
|
|
71
|
-
if (data.type === 'aztec-wallet-discovery') {
|
|
72
|
-
handleDiscovery(data);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
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
|
-
// });
|
|
39
|
+
import {
|
|
40
|
+
BackgroundConnectionHandler,
|
|
41
|
+
ContentScriptConnectionHandler,
|
|
42
|
+
} from '@aztec/wallet-sdk/extension/handlers';
|
|
83
43
|
```
|
|
84
44
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Discovery messages have this structure:
|
|
45
|
+
## Overview
|
|
88
46
|
|
|
89
|
-
|
|
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
|
-
}
|
|
97
|
-
}
|
|
98
|
-
```
|
|
47
|
+
The Wallet SDK uses a **two-phase connection model** with **end-to-end encryption**:
|
|
99
48
|
|
|
100
|
-
###
|
|
49
|
+
### Phase 1: Discovery
|
|
101
50
|
|
|
102
|
-
|
|
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
|
|
103
55
|
|
|
104
|
-
|
|
105
|
-
import { ChainInfoSchema } from '@aztec/wallet-sdk/manager';
|
|
56
|
+
### Phase 2: Secure Channel Establishment
|
|
106
57
|
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
109
64
|
|
|
110
|
-
|
|
111
|
-
const { chainId, version } = ChainInfoSchema.parse(chainInfo);
|
|
65
|
+
### Key Security Features
|
|
112
66
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
115
70
|
|
|
116
|
-
|
|
117
|
-
// Do NOT respond if you don't support this network
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
71
|
+
## Architecture for Extension Wallets
|
|
120
72
|
|
|
121
|
-
// Respond if supported
|
|
122
|
-
respondToDiscovery(requestId);
|
|
123
|
-
}
|
|
124
73
|
```
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
import { jsonStringify } from '@aztec/wallet-sdk/manager';
|
|
134
|
-
|
|
135
|
-
// Your wallet should generate a key pair on initialization.
|
|
136
|
-
// This keypair should be recreated each session
|
|
137
|
-
let walletKeyPair: CryptoKeyPair;
|
|
138
|
-
|
|
139
|
-
async function initializeWallet() {
|
|
140
|
-
// Generate ECDH key pair for secure channel establishment
|
|
141
|
-
walletKeyPair = await generateKeyPair();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function respondToDiscovery(requestId: string) {
|
|
145
|
-
// Export the public key for sharing with dApps
|
|
146
|
-
const publicKey = await exportPublicKey(walletKeyPair.publicKey);
|
|
147
|
-
|
|
148
|
-
const response = {
|
|
149
|
-
type: 'aztec-wallet-discovery-response',
|
|
150
|
-
requestId,
|
|
151
|
-
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)
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Send as JSON string via window.postMessage
|
|
161
|
-
window.postMessage(jsonStringify(response), '*');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Using WebSocket:
|
|
165
|
-
// websocket.send(jsonStringify(response));
|
|
74
|
+
┌─────────────┐ window.postMessage ┌─────────────────┐ browser.runtime ┌──────────────────┐
|
|
75
|
+
│ dApp │◄──(discovery + port)────►│ Content Script │◄────────────────────►│ Background Script│
|
|
76
|
+
│ (web page) │ │ (message relay)│ │ (crypto+state) │
|
|
77
|
+
└─────────────┘ └─────────────────┘ └──────────────────┘
|
|
78
|
+
│ │
|
|
79
|
+
│ MessagePort │
|
|
80
|
+
└──────────(key exchange + encrypted)──────┘
|
|
166
81
|
```
|
|
167
82
|
|
|
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
|
|
83
|
+
**Security model:**
|
|
177
84
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
-
|
|
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
|
|
85
|
+
- The MessagePort is transferred via `window.postMessage` - other scripts on the page could intercept it
|
|
86
|
+
- **Security comes from encryption**: After key exchange, all communication is AES-256-GCM encrypted
|
|
87
|
+
- Content script never has access to private keys or session secrets
|
|
88
|
+
- All cryptographic operations happen in the background script (service worker)
|
|
89
|
+
- Anti-MITM verification (emoji grid) ensures both parties derived the same keys
|
|
188
90
|
|
|
189
|
-
|
|
91
|
+
## Using Pre-built Connection Handlers
|
|
190
92
|
|
|
191
|
-
|
|
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
|
|
197
|
-
}
|
|
198
|
-
```
|
|
93
|
+
The SDK provides `BackgroundConnectionHandler` and `ContentScriptConnectionHandler` to handle the connection flow. These are the recommended way to build extension wallets.
|
|
199
94
|
|
|
200
|
-
|
|
95
|
+
### Background Script Setup
|
|
201
96
|
|
|
202
97
|
```typescript
|
|
203
|
-
import {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
98
|
+
import {
|
|
99
|
+
BackgroundConnectionHandler,
|
|
100
|
+
type BackgroundConnectionConfig,
|
|
101
|
+
type BackgroundConnectionCallbacks,
|
|
102
|
+
type BackgroundTransport,
|
|
103
|
+
} from '@aztec/wallet-sdk/extension/handlers';
|
|
104
|
+
import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
|
|
105
|
+
|
|
106
|
+
// Configuration for your wallet
|
|
107
|
+
const config: BackgroundConnectionConfig = {
|
|
108
|
+
walletId: 'my-aztec-wallet',
|
|
109
|
+
walletName: 'My Aztec Wallet',
|
|
110
|
+
walletVersion: '1.0.0',
|
|
111
|
+
walletIcon: 'https://example.com/icon.png',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Transport for browser extension APIs
|
|
115
|
+
const transport: BackgroundTransport = {
|
|
116
|
+
sendToTab: (tabId, message) => browser.tabs.sendMessage(tabId, message),
|
|
117
|
+
addContentListener: (handler) => browser.runtime.onMessage.addListener(handler),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Event callbacks (all optional)
|
|
121
|
+
const callbacks: BackgroundConnectionCallbacks = {
|
|
122
|
+
// Called when a new discovery request is received
|
|
123
|
+
onPendingDiscovery: (discovery) => {
|
|
124
|
+
// Show pending connection in wallet UI
|
|
125
|
+
// Check if wallet supports this network (chainId AND version)
|
|
126
|
+
const supported = supportedNetworks.some(
|
|
127
|
+
n => n.chainId === discovery.chainInfo.chainId.toString() &&
|
|
128
|
+
n.version === discovery.chainInfo.version.toString()
|
|
129
|
+
);
|
|
130
|
+
if (supported) {
|
|
131
|
+
// Show the user so they can approve or reject
|
|
132
|
+
}
|
|
133
|
+
},
|
|
212
134
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
},
|
|
217
140
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
141
|
+
// Called when a session is terminated
|
|
142
|
+
onSessionTerminated: (requestId) => {
|
|
143
|
+
console.log('Session terminated:', requestId);
|
|
144
|
+
},
|
|
221
145
|
|
|
222
|
-
//
|
|
223
|
-
|
|
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
|
+
};
|
|
224
152
|
|
|
225
|
-
|
|
226
|
-
connections.set(request.appId, { sharedKey });
|
|
153
|
+
const handler = new BackgroundConnectionHandler(config, transport, callbacks);
|
|
227
154
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
await handleEncryptedMessage(request.appId, event.data);
|
|
231
|
-
};
|
|
155
|
+
// Initialize the handler to start listening
|
|
156
|
+
handler.initialize();
|
|
232
157
|
|
|
233
|
-
|
|
158
|
+
// User approves connection from wallet UI
|
|
159
|
+
function approveConnection(requestId: string) {
|
|
160
|
+
handler.approveDiscovery(requestId);
|
|
234
161
|
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### 2. Handle Encrypted Messages
|
|
238
|
-
|
|
239
|
-
All wallet method calls arrive as encrypted payloads:
|
|
240
162
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
ciphertext: string; // Base64-encoded encrypted data
|
|
163
|
+
// User denies connection
|
|
164
|
+
function denyConnection(requestId: string) {
|
|
165
|
+
handler.rejectDiscovery(requestId);
|
|
245
166
|
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
Decrypt incoming messages and encrypt responses:
|
|
249
167
|
|
|
250
|
-
|
|
251
|
-
async function
|
|
252
|
-
|
|
253
|
-
if (!connection) {
|
|
254
|
-
console.error('Unknown connection');
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
// Decrypt the incoming message
|
|
260
|
-
const message = await decrypt<WalletMessage>(connection.sharedKey, encrypted);
|
|
261
|
-
|
|
262
|
-
const { type, messageId, args, chainInfo, walletId } = message;
|
|
263
|
-
|
|
264
|
-
// Process the wallet method call
|
|
265
|
-
const wallet = await getWalletForChain(chainInfo);
|
|
266
|
-
const result = await wallet[type](...args);
|
|
267
|
-
|
|
268
|
-
// Create response
|
|
269
|
-
const response: WalletResponse = {
|
|
270
|
-
messageId,
|
|
271
|
-
result,
|
|
272
|
-
walletId,
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
// Encrypt and send the response
|
|
276
|
-
const encryptedResponse = await encrypt(connection.sharedKey, response);
|
|
277
|
-
sendEncryptedResponse(appId, encryptedResponse);
|
|
278
|
-
} catch (error) {
|
|
279
|
-
// Send encrypted error response
|
|
280
|
-
const errorResponse: WalletResponse = {
|
|
281
|
-
messageId: message?.messageId ?? '',
|
|
282
|
-
error: { message: error.message },
|
|
283
|
-
walletId: message?.walletId ?? '',
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const encryptedError = await encrypt(connection.sharedKey, errorResponse);
|
|
287
|
-
sendEncryptedResponse(appId, encryptedError);
|
|
288
|
-
}
|
|
168
|
+
// Send response back to dApp
|
|
169
|
+
async function sendWalletResponse(requestId: string, response: WalletResponse) {
|
|
170
|
+
await handler.sendResponse(requestId, response);
|
|
289
171
|
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### 3. Extension Wallet Architecture
|
|
293
|
-
|
|
294
|
-
For browser extension wallets, the recommended architecture separates concerns:
|
|
295
172
|
|
|
173
|
+
// Clean up on tab close/navigate
|
|
174
|
+
browser.tabs.onRemoved.addListener((tabId) => {
|
|
175
|
+
handler.terminateForTab(tabId);
|
|
176
|
+
});
|
|
296
177
|
```
|
|
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
178
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
### Wallet Method Request
|
|
317
|
-
|
|
318
|
-
After discovery, dApps will call wallet methods. These arrive as:
|
|
179
|
+
### Content Script Setup
|
|
319
180
|
|
|
320
181
|
```typescript
|
|
321
|
-
{
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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)
|
|
331
|
-
}
|
|
332
|
-
```
|
|
333
|
-
|
|
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
|
|
182
|
+
import {
|
|
183
|
+
ContentScriptConnectionHandler,
|
|
184
|
+
type ContentScriptTransport,
|
|
185
|
+
} from '@aztec/wallet-sdk/extension/handlers';
|
|
340
186
|
|
|
341
|
-
|
|
187
|
+
const transport: ContentScriptTransport = {
|
|
188
|
+
sendToBackground: (message) => browser.runtime.sendMessage(message),
|
|
189
|
+
addBackgroundListener: (handler) => browser.runtime.onMessage.addListener(handler),
|
|
190
|
+
};
|
|
342
191
|
|
|
343
|
-
|
|
192
|
+
const handler = new ContentScriptConnectionHandler(transport);
|
|
344
193
|
|
|
345
|
-
|
|
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
|
|
351
|
-
}
|
|
194
|
+
// Start listening for discovery requests and background messages
|
|
195
|
+
handler.start();
|
|
352
196
|
```
|
|
353
197
|
|
|
354
|
-
##
|
|
198
|
+
## Testing Your Integration (dApp Side)
|
|
355
199
|
|
|
356
|
-
|
|
200
|
+
The `WalletManager` supports two patterns for consuming discovered wallets.
|
|
357
201
|
|
|
358
|
-
|
|
202
|
+
### Async Iterator Pattern
|
|
359
203
|
|
|
360
204
|
```typescript
|
|
361
|
-
import {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const chainInfo = ChainInfoSchema.parse(message.chainInfo);
|
|
365
|
-
|
|
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
|
|
369
|
-
```
|
|
370
|
-
|
|
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:
|
|
205
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
206
|
+
import { WalletManager } from '@aztec/wallet-sdk/manager';
|
|
207
|
+
import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
|
|
378
208
|
|
|
379
|
-
|
|
380
|
-
{
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
stack?: string // Optional stack trace
|
|
209
|
+
const discovery = WalletManager.configure({
|
|
210
|
+
extensions: { enabled: true },
|
|
211
|
+
}).getAvailableWallets({
|
|
212
|
+
chainInfo: {
|
|
213
|
+
chainId: new Fr(31337),
|
|
214
|
+
version: new Fr(1),
|
|
386
215
|
},
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
### Common Error Scenarios
|
|
216
|
+
appId: 'my-dapp',
|
|
217
|
+
timeout: 60000,
|
|
218
|
+
});
|
|
392
219
|
|
|
393
|
-
|
|
220
|
+
// Iterate over discovered wallets as they're approved
|
|
221
|
+
for await (const provider of discovery.wallets) {
|
|
222
|
+
console.log(`Found: ${provider.name}`);
|
|
394
223
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
- **Invalid arguments**: Method arguments fail validation
|
|
398
|
-
- **User rejection**: User declined the transaction or action
|
|
224
|
+
// Establish secure channel (key exchange)
|
|
225
|
+
const pending = await provider.establishSecureChannel('my-dapp');
|
|
399
226
|
|
|
400
|
-
|
|
227
|
+
// Display verification emojis to user
|
|
228
|
+
const emojis = hashToEmoji(pending.verificationHash);
|
|
229
|
+
console.log('Verify this matches your wallet:', emojis);
|
|
401
230
|
|
|
402
|
-
|
|
231
|
+
// User confirms emojis match
|
|
232
|
+
const wallet = await pending.confirm();
|
|
403
233
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
error: {
|
|
408
|
-
message: 'User rejected the request',
|
|
409
|
-
code: 'USER_REJECTED'
|
|
410
|
-
},
|
|
411
|
-
walletId: 'my-wallet'
|
|
234
|
+
// All calls are now encrypted
|
|
235
|
+
const accounts = await wallet.getAccounts();
|
|
236
|
+
console.log('Accounts:', accounts);
|
|
412
237
|
}
|
|
413
|
-
```
|
|
414
238
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
239
|
+
// Cancel discovery when done or on cleanup
|
|
240
|
+
discovery.cancel();
|
|
241
|
+
```
|
|
418
242
|
|
|
419
|
-
|
|
243
|
+
### Callback Pattern
|
|
420
244
|
|
|
421
245
|
```typescript
|
|
422
|
-
import { Fr } from '@aztec/foundation/
|
|
423
|
-
import { WalletManager } from '@aztec/wallet-sdk/manager';
|
|
246
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
247
|
+
import { WalletManager, type WalletProvider } from '@aztec/wallet-sdk/manager';
|
|
248
|
+
import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
|
|
424
249
|
|
|
425
|
-
const
|
|
426
|
-
extensions: { enabled: true },
|
|
427
|
-
});
|
|
250
|
+
const discoveredProviders: WalletProvider[] = [];
|
|
428
251
|
|
|
429
|
-
|
|
430
|
-
|
|
252
|
+
const discovery = WalletManager.configure({
|
|
253
|
+
extensions: { enabled: true },
|
|
254
|
+
}).getAvailableWallets({
|
|
431
255
|
chainInfo: {
|
|
432
256
|
chainId: new Fr(31337),
|
|
433
|
-
version: new Fr(
|
|
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
|
|
434
265
|
},
|
|
435
|
-
timeout: 2000,
|
|
436
266
|
});
|
|
437
267
|
|
|
438
|
-
|
|
268
|
+
// Wait for discovery to complete (or cancel early with discovery.cancel())
|
|
269
|
+
await discovery.done;
|
|
270
|
+
console.log('Discovery complete, found:', discoveredProviders.length);
|
|
439
271
|
|
|
440
|
-
// Connect to
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const wallet = await walletProvider.connect('test-app');
|
|
272
|
+
// Connect to a selected provider
|
|
273
|
+
async function connectToWallet(provider: WalletProvider) {
|
|
274
|
+
const pending = await provider.establishSecureChannel('my-dapp');
|
|
444
275
|
|
|
445
|
-
//
|
|
446
|
-
const
|
|
447
|
-
|
|
276
|
+
// Show verification UI
|
|
277
|
+
const emojis = hashToEmoji(pending.verificationHash);
|
|
278
|
+
showVerificationDialog(emojis);
|
|
448
279
|
|
|
449
|
-
|
|
450
|
-
|
|
280
|
+
// User confirms
|
|
281
|
+
const wallet = await pending.confirm();
|
|
282
|
+
return wallet;
|
|
451
283
|
}
|
|
452
284
|
```
|
|
453
285
|
|
|
454
|
-
|
|
286
|
+
### React Hook Example
|
|
455
287
|
|
|
456
|
-
|
|
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));
|
|
312
|
+
|
|
313
|
+
return () => {
|
|
314
|
+
discovery.cancel();
|
|
315
|
+
discoveryRef.current = null;
|
|
316
|
+
};
|
|
317
|
+
}, [chainInfo.chainId.toString(), chainInfo.version.toString(), appId]);
|
|
457
318
|
|
|
458
|
-
|
|
319
|
+
return { providers, isDiscovering, cancel: () => discoveryRef.current?.cancel() };
|
|
320
|
+
}
|
|
321
|
+
```
|