@aztec/wallet-sdk 0.0.1-commit.d3ec352c → 0.0.1-commit.d58ff9d0
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 +321 -299
- package/dest/base-wallet/base_wallet.d.ts +129 -44
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +388 -110
- package/dest/base-wallet/get_gas_limits.d.ts +36 -0
- package/dest/base-wallet/get_gas_limits.d.ts.map +1 -0
- package/dest/base-wallet/get_gas_limits.js +55 -0
- package/dest/base-wallet/index.d.ts +4 -2
- package/dest/base-wallet/index.d.ts.map +1 -1
- package/dest/base-wallet/index.js +2 -0
- package/dest/base-wallet/utils.d.ts +52 -0
- package/dest/base-wallet/utils.d.ts.map +1 -0
- package/dest/base-wallet/utils.js +137 -0
- package/dest/crypto.d.ts +230 -0
- package/dest/crypto.d.ts.map +1 -0
- package/dest/crypto.js +482 -0
- 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 +168 -0
- package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/background_connection_handler.js +294 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts +57 -0
- package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -0
- package/dest/extension/handlers/content_script_connection_handler.js +193 -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 +65 -0
- package/dest/extension/handlers/internal_message_types.d.ts.map +1 -0
- package/dest/extension/handlers/internal_message_types.js +24 -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 +152 -0
- package/dest/extension/provider/extension_wallet.d.ts.map +1 -0
- package/dest/extension/provider/extension_wallet.js +349 -0
- package/dest/extension/provider/index.d.ts +3 -0
- package/dest/extension/provider/index.d.ts.map +1 -0
- package/dest/iframe/handlers/iframe_connection_handler.d.ts +122 -0
- package/dest/iframe/handlers/iframe_connection_handler.d.ts.map +1 -0
- package/dest/iframe/handlers/iframe_connection_handler.js +239 -0
- package/dest/iframe/handlers/index.d.ts +2 -0
- package/dest/iframe/handlers/index.d.ts.map +1 -0
- package/dest/iframe/handlers/index.js +1 -0
- package/dest/iframe/provider/iframe_discovery.d.ts +25 -0
- package/dest/iframe/provider/iframe_discovery.d.ts.map +1 -0
- package/dest/iframe/provider/iframe_discovery.js +167 -0
- package/dest/iframe/provider/iframe_provider.d.ts +65 -0
- package/dest/iframe/provider/iframe_provider.d.ts.map +1 -0
- package/dest/iframe/provider/iframe_provider.js +257 -0
- package/dest/iframe/provider/iframe_wallet.d.ts +85 -0
- package/dest/iframe/provider/iframe_wallet.d.ts.map +1 -0
- package/dest/iframe/provider/iframe_wallet.js +269 -0
- package/dest/iframe/provider/index.d.ts +4 -0
- package/dest/iframe/provider/index.d.ts.map +1 -0
- package/dest/iframe/provider/index.js +3 -0
- 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 +109 -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 +208 -29
- package/dest/types.d.ts +185 -0
- package/dest/types.d.ts.map +1 -0
- package/dest/types.js +40 -0
- package/package.json +24 -11
- package/src/base-wallet/base_wallet.ts +499 -176
- package/src/base-wallet/get_gas_limits.ts +88 -0
- package/src/base-wallet/index.ts +8 -1
- package/src/base-wallet/utils.ts +248 -0
- package/src/crypto.ts +603 -0
- package/src/emoji_alphabet.ts +317 -0
- package/src/extension/handlers/background_connection_handler.ts +456 -0
- package/src/extension/handlers/content_script_connection_handler.ts +264 -0
- package/src/extension/handlers/index.ts +25 -0
- package/src/extension/handlers/internal_message_types.ts +71 -0
- package/src/extension/provider/extension_provider.ts +233 -0
- package/src/extension/provider/extension_wallet.ts +410 -0
- package/src/extension/provider/index.ts +7 -0
- package/src/iframe/handlers/iframe_connection_handler.ts +341 -0
- package/src/iframe/handlers/index.ts +7 -0
- package/src/iframe/provider/iframe_discovery.ts +185 -0
- package/src/iframe/provider/iframe_provider.ts +331 -0
- package/src/iframe/provider/iframe_wallet.ts +323 -0
- package/src/iframe/provider/index.ts +3 -0
- package/src/manager/index.ts +3 -15
- package/src/manager/types.ts +113 -4
- package/src/manager/wallet_manager.ts +235 -30
- package/src/types.ts +204 -0
- 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 -23
- package/dest/providers/extension/extension_wallet.d.ts.map +0 -1
- package/dest/providers/extension/extension_wallet.js +0 -96
- package/dest/providers/extension/index.d.ts +0 -4
- package/dest/providers/extension/index.d.ts.map +0 -1
- 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/src/providers/extension/extension_provider.ts +0 -72
- package/src/providers/extension/extension_wallet.ts +0 -124
- package/src/providers/extension/index.ts +0 -3
- package/src/providers/types.ts +0 -71
- /package/dest/{providers/extension → extension/provider}/index.js +0 -0
package/README.md
CHANGED
|
@@ -4,421 +4,443 @@ 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,
|
|
13
|
+
KeyExchangeRequest,
|
|
14
|
+
KeyExchangeResponse,
|
|
14
15
|
WalletInfo,
|
|
15
16
|
WalletMessage,
|
|
16
17
|
WalletResponse,
|
|
17
|
-
} from '@aztec/wallet-sdk/
|
|
18
|
-
import { ChainInfoSchema, WalletSchema, jsonStringify } from '@aztec/wallet-sdk/manager';
|
|
18
|
+
} from '@aztec/wallet-sdk/types';
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Cryptographic utilities for secure channel establishment are exported from `@aztec/wallet-sdk/crypto`:
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
```typescript
|
|
24
|
+
import type { EncryptedPayload, ExportedPublicKey } from '@aztec/wallet-sdk/crypto';
|
|
25
|
+
import {
|
|
26
|
+
decrypt,
|
|
27
|
+
deriveSessionKeys,
|
|
28
|
+
encrypt,
|
|
29
|
+
exportPublicKey,
|
|
30
|
+
generateKeyPair,
|
|
31
|
+
hashToEmoji,
|
|
32
|
+
importPublicKey,
|
|
33
|
+
} from '@aztec/wallet-sdk/crypto';
|
|
34
|
+
```
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
2. **SDK broadcasts** a discovery message with chain information
|
|
27
|
-
3. **Your wallet responds** ONLY if it supports that specific network
|
|
28
|
-
4. **dApp receives** only compatible wallets
|
|
29
|
-
5. **dApp calls wallet methods** which your wallet handles and responds to
|
|
36
|
+
**For extension wallets**, pre-built connection handlers are available:
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
```typescript
|
|
39
|
+
import {
|
|
40
|
+
BackgroundConnectionHandler,
|
|
41
|
+
ContentScriptConnectionHandler,
|
|
42
|
+
} from '@aztec/wallet-sdk/extension/handlers';
|
|
43
|
+
```
|
|
32
44
|
|
|
33
|
-
|
|
45
|
+
## Overview
|
|
34
46
|
|
|
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
|
|
47
|
+
The Wallet SDK uses a **two-phase connection model** with **end-to-end encryption**:
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
### Phase 1: Discovery
|
|
40
50
|
|
|
41
|
-
|
|
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
|
|
42
55
|
|
|
43
|
-
###
|
|
56
|
+
### Phase 2: Secure Channel Establishment
|
|
44
57
|
|
|
45
|
-
**
|
|
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
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
window.addEventListener('message', event => {
|
|
49
|
-
if (event.source !== window) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
65
|
+
### Key Security Features
|
|
52
66
|
|
|
53
|
-
|
|
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
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
handleDiscovery(data);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
71
|
+
## Architecture for Extension Wallets
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
```
|
|
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)──────┘
|
|
67
81
|
```
|
|
68
82
|
|
|
69
|
-
|
|
83
|
+
**Security model:**
|
|
70
84
|
|
|
71
|
-
|
|
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
|
|
72
90
|
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
type: 'aztec-wallet-discovery',
|
|
76
|
-
requestId: string, // UUID for tracking this request
|
|
77
|
-
chainInfo: {
|
|
78
|
-
chainId: Fr, // Chain ID
|
|
79
|
-
version: Fr // Protocol version
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
```
|
|
91
|
+
## Using Pre-built Connection Handlers
|
|
83
92
|
|
|
84
|
-
|
|
93
|
+
The SDK provides `BackgroundConnectionHandler` and `ContentScriptConnectionHandler` to handle the connection flow. These are the recommended way to build extension wallets.
|
|
85
94
|
|
|
86
|
-
|
|
95
|
+
### Background Script Setup
|
|
87
96
|
|
|
88
97
|
```typescript
|
|
89
|
-
import {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
},
|
|
93
134
|
|
|
94
|
-
//
|
|
95
|
-
|
|
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
|
+
},
|
|
96
140
|
|
|
97
|
-
//
|
|
98
|
-
|
|
141
|
+
// Called when a session is terminated
|
|
142
|
+
onSessionTerminated: (requestId) => {
|
|
143
|
+
console.log('Session terminated:', requestId);
|
|
144
|
+
},
|
|
99
145
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
};
|
|
104
152
|
|
|
105
|
-
|
|
106
|
-
respondToDiscovery(requestId);
|
|
107
|
-
}
|
|
108
|
-
```
|
|
153
|
+
const handler = new BackgroundConnectionHandler(config, transport, callbacks);
|
|
109
154
|
|
|
110
|
-
|
|
155
|
+
// Initialize the handler to start listening
|
|
156
|
+
handler.initialize();
|
|
111
157
|
|
|
112
|
-
|
|
158
|
+
// User approves connection from wallet UI
|
|
159
|
+
function approveConnection(requestId: string) {
|
|
160
|
+
handler.approveDiscovery(requestId);
|
|
161
|
+
}
|
|
113
162
|
|
|
114
|
-
|
|
163
|
+
// User denies connection
|
|
164
|
+
function denyConnection(requestId: string) {
|
|
165
|
+
handler.rejectDiscovery(requestId);
|
|
166
|
+
}
|
|
115
167
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function respondToDiscovery(requestId: string) {
|
|
120
|
-
const response = {
|
|
121
|
-
type: 'aztec-wallet-discovery-response',
|
|
122
|
-
requestId,
|
|
123
|
-
walletInfo: {
|
|
124
|
-
id: 'my-aztec-wallet', // Unique wallet identifier
|
|
125
|
-
name: 'My Aztec Wallet', // Display name
|
|
126
|
-
icon: 'https://example.com/icon.png', // Optional icon URL
|
|
127
|
-
version: '1.0.0', // Wallet version
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Send as JSON string via window.postMessage
|
|
132
|
-
window.postMessage(jsonStringify(response), '*');
|
|
168
|
+
// Send response back to dApp
|
|
169
|
+
async function sendWalletResponse(requestId: string, response: WalletResponse) {
|
|
170
|
+
await handler.sendResponse(requestId, response);
|
|
133
171
|
}
|
|
134
172
|
|
|
135
|
-
//
|
|
136
|
-
|
|
173
|
+
// Clean up on tab close/navigate
|
|
174
|
+
browser.tabs.onRemoved.addListener((tabId) => {
|
|
175
|
+
handler.terminateForTab(tabId);
|
|
176
|
+
});
|
|
137
177
|
```
|
|
138
178
|
|
|
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
|
|
179
|
+
### Content Script Setup
|
|
145
180
|
|
|
146
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
import {
|
|
183
|
+
ContentScriptConnectionHandler,
|
|
184
|
+
type ContentScriptTransport,
|
|
185
|
+
} from '@aztec/wallet-sdk/extension/handlers';
|
|
147
186
|
|
|
148
|
-
|
|
187
|
+
const transport: ContentScriptTransport = {
|
|
188
|
+
sendToBackground: (message) => browser.runtime.sendMessage(message),
|
|
189
|
+
addBackgroundListener: (handler) => browser.runtime.onMessage.addListener(handler),
|
|
190
|
+
};
|
|
149
191
|
|
|
150
|
-
|
|
192
|
+
const handler = new ContentScriptConnectionHandler(transport);
|
|
151
193
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
type: string, // Wallet method name from the Wallet interface
|
|
155
|
-
messageId: string, // UUID for tracking this request
|
|
156
|
-
args: unknown[], // Method arguments
|
|
157
|
-
chainInfo: {
|
|
158
|
-
chainId: Fr, // Same chain that was used in discovery
|
|
159
|
-
version: Fr
|
|
160
|
-
},
|
|
161
|
-
appId: string, // Application identifier
|
|
162
|
-
walletId: string // Your wallet's ID (from discovery response)
|
|
163
|
-
}
|
|
194
|
+
// Start listening for discovery requests and background messages
|
|
195
|
+
handler.start();
|
|
164
196
|
```
|
|
165
197
|
|
|
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
|
|
198
|
+
## Testing Your Integration (dApp Side)
|
|
172
199
|
|
|
173
|
-
|
|
200
|
+
The `WalletManager` supports two patterns for consuming discovered wallets.
|
|
174
201
|
|
|
175
|
-
|
|
202
|
+
### Async Iterator Pattern
|
|
176
203
|
|
|
177
204
|
```typescript
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
error?: unknown, // Error (if failed)
|
|
182
|
-
walletId: string // Your wallet's ID
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
## Handling Wallet Methods
|
|
205
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
206
|
+
import { WalletManager } from '@aztec/wallet-sdk/manager';
|
|
207
|
+
import { hashToEmoji } from '@aztec/wallet-sdk/crypto';
|
|
187
208
|
|
|
188
|
-
|
|
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
|
+
});
|
|
189
219
|
|
|
190
|
-
|
|
220
|
+
// Iterate over discovered wallets as they're approved
|
|
221
|
+
for await (const provider of discovery.wallets) {
|
|
222
|
+
console.log(`Found: ${provider.name}`);
|
|
191
223
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (event.source !== window) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
224
|
+
// Establish secure channel (key exchange)
|
|
225
|
+
const pending = await provider.establishSecureChannel('my-dapp');
|
|
197
226
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
} catch {
|
|
202
|
-
return; // Not a valid JSON message
|
|
203
|
-
}
|
|
227
|
+
// Display verification emojis to user
|
|
228
|
+
const emojis = hashToEmoji(pending.verificationHash);
|
|
229
|
+
console.log('Verify this matches your wallet:', emojis);
|
|
204
230
|
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
handleDiscovery(data);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
231
|
+
// User confirms emojis match
|
|
232
|
+
const wallet = await pending.confirm();
|
|
210
233
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
234
|
+
// All calls are now encrypted
|
|
235
|
+
const accounts = await wallet.getAccounts();
|
|
236
|
+
console.log('Accounts:', accounts);
|
|
237
|
+
}
|
|
216
238
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
// const data = JSON.parse(message);
|
|
220
|
-
// if (data.type === 'aztec-wallet-discovery') {
|
|
221
|
-
// handleDiscovery(data);
|
|
222
|
-
// } else if (data.messageId && data.type) {
|
|
223
|
-
// handleWalletMethod(data);
|
|
224
|
-
// }
|
|
225
|
-
// });
|
|
239
|
+
// Cancel discovery when done or on cleanup
|
|
240
|
+
discovery.cancel();
|
|
226
241
|
```
|
|
227
242
|
|
|
228
|
-
###
|
|
243
|
+
### Callback Pattern
|
|
229
244
|
|
|
230
245
|
```typescript
|
|
231
|
-
import {
|
|
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';
|
|
232
249
|
|
|
233
|
-
|
|
234
|
-
const { type, messageId, args, chainInfo, appId, walletId } = message;
|
|
250
|
+
const discoveredProviders: WalletProvider[] = [];
|
|
235
251
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
252
|
+
const discovery = WalletManager.configure({
|
|
253
|
+
extensions: { enabled: true },
|
|
254
|
+
}).getAvailableWallets({
|
|
255
|
+
chainInfo: {
|
|
256
|
+
chainId: new Fr(31337),
|
|
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
|
|
265
|
+
},
|
|
266
|
+
});
|
|
239
267
|
|
|
240
|
-
|
|
241
|
-
|
|
268
|
+
// Wait for discovery to complete (or cancel early with discovery.cancel())
|
|
269
|
+
await discovery.done;
|
|
270
|
+
console.log('Discovery complete, found:', discoveredProviders.length);
|
|
242
271
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
272
|
+
// Connect to a selected provider
|
|
273
|
+
async function connectToWallet(provider: WalletProvider) {
|
|
274
|
+
const pending = await provider.establishSecureChannel('my-dapp');
|
|
247
275
|
|
|
248
|
-
|
|
249
|
-
|
|
276
|
+
// Show verification UI
|
|
277
|
+
const emojis = hashToEmoji(pending.verificationHash);
|
|
278
|
+
showVerificationDialog(emojis);
|
|
250
279
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// Send error response
|
|
255
|
-
sendError(messageId, walletId, error);
|
|
256
|
-
}
|
|
280
|
+
// User confirms
|
|
281
|
+
const wallet = await pending.confirm();
|
|
282
|
+
return wallet;
|
|
257
283
|
}
|
|
258
284
|
```
|
|
259
285
|
|
|
260
|
-
###
|
|
261
|
-
|
|
262
|
-
**Extension wallet example:**
|
|
286
|
+
### React Hook Example
|
|
263
287
|
|
|
264
288
|
```typescript
|
|
265
|
-
|
|
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]);
|
|
318
|
+
|
|
319
|
+
return { providers, isDiscovering, cancel: () => discoveryRef.current?.cancel() };
|
|
320
|
+
}
|
|
321
|
+
```
|
|
266
322
|
|
|
267
|
-
|
|
268
|
-
const response = {
|
|
269
|
-
messageId,
|
|
270
|
-
result,
|
|
271
|
-
walletId,
|
|
272
|
-
};
|
|
323
|
+
## Storage backends
|
|
273
324
|
|
|
274
|
-
|
|
275
|
-
window.postMessage(jsonStringify(response), '*');
|
|
276
|
-
}
|
|
325
|
+
Your wallet and the PXE it embeds persist state through a pluggable key-value store (`@aztec/kv-store`). In the browser there are two backends:
|
|
277
326
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
messageId,
|
|
281
|
-
error: {
|
|
282
|
-
message: error.message,
|
|
283
|
-
stack: error.stack,
|
|
284
|
-
},
|
|
285
|
-
walletId,
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
window.postMessage(jsonStringify(response), '*');
|
|
289
|
-
}
|
|
327
|
+
- **IndexedDB** (`@aztec/kv-store/deprecated/indexeddb`): the default in browser environments up to Aztec Alpha v4, now moved to a deprecated subpath. We plan to remove this backend, so new browser code should use the SQLite backend below.
|
|
328
|
+
- **SQLite-OPFS** (`@aztec/kv-store/sqlite-opfs`): the default KV store backend from Aztec Alpha v5 on. It's backed by the durable Origin Private File System web standard, and it offers a number of advantages over IndexedDB: a sane transaction model (IDB transactions auto-close the moment the event loop yields, which constrains the store layer), support for encryption at rest, and better performance in the access patterns we exercise the most from both wallet and PXE.
|
|
290
329
|
|
|
291
|
-
|
|
292
|
-
// websocket.send(jsonStringify({ messageId, result, walletId }));
|
|
293
|
-
```
|
|
330
|
+
The backend is chosen by *which store you construct and hand to the wallet* there is no runtime flag or environment variable.
|
|
294
331
|
|
|
295
|
-
|
|
332
|
+
> **Data migration is not supported between backends, by design.** The v4→v5 protocol upgrade discards all local state regardless, so switching to SQLite-OPFS simply means starting from a fresh store.
|
|
296
333
|
|
|
297
|
-
###
|
|
334
|
+
### Quick start: embedded wallet with an encrypted SQLite store
|
|
298
335
|
|
|
299
|
-
|
|
336
|
+
If you build on `@aztec/wallets`' `EmbeddedWallet`, open its two stores (PXE state + the wallet DB) with `openEncryptedEmbeddedStores`, then pass them in:
|
|
300
337
|
|
|
301
338
|
```typescript
|
|
302
|
-
import {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const chainInfo = ChainInfoSchema.parse(message.chainInfo);
|
|
339
|
+
import { EmbeddedWallet } from '@aztec/wallets/embedded';
|
|
340
|
+
import { openEncryptedEmbeddedStores } from '@aztec/wallets/embedded/store-encryption';
|
|
341
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
306
342
|
|
|
307
|
-
|
|
308
|
-
const accountsResult = await wallet.getAccounts(...args);
|
|
309
|
-
// The SDK handles schema validation on the client side
|
|
310
|
-
```
|
|
343
|
+
const log = createLogger('wallet:storage');
|
|
311
344
|
|
|
312
|
-
|
|
345
|
+
// Your wallet derives a 32-byte key (see "Key management" below).
|
|
346
|
+
// IMPORTANT: return a *fresh* Uint8Array each call. Opening a store consumes (empties)
|
|
347
|
+
// the key, so a reused array would be empty on the second open (see "important" below).
|
|
348
|
+
const getEncryptionKey = async () => new Uint8Array(myDerivedKey);
|
|
313
349
|
|
|
314
|
-
|
|
350
|
+
const { pxeStore, walletStore } = await openEncryptedEmbeddedStores(
|
|
351
|
+
{
|
|
352
|
+
pxe: { name: `pxe-${rollupAddress}`, poolDirectory: '/pxe' },
|
|
353
|
+
wallet: { name: `wallet-${rollupAddress}`, poolDirectory: '/wallet' },
|
|
354
|
+
},
|
|
355
|
+
getEncryptionKey,
|
|
356
|
+
log,
|
|
357
|
+
);
|
|
315
358
|
|
|
316
|
-
|
|
359
|
+
const wallet = await EmbeddedWallet.create(nodeUrl, {
|
|
360
|
+
pxe: { store: pxeStore },
|
|
361
|
+
walletDb: { store: walletStore },
|
|
362
|
+
});
|
|
363
|
+
```
|
|
317
364
|
|
|
318
|
-
|
|
365
|
+
If the supplied key cannot decrypt an existing store, `openEncryptedEmbeddedStores` throws `EmbeddedWalletEncryptionError` with `storeName: 'pxe' | 'wallet'`, which you can then surface as a "wrong password" error in your UI:
|
|
319
366
|
|
|
320
367
|
```typescript
|
|
321
|
-
{
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
368
|
+
import { EmbeddedWalletEncryptionError } from '@aztec/wallets/embedded/store-encryption';
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
await openEncryptedEmbeddedStores(/* ... */);
|
|
372
|
+
} catch (err) {
|
|
373
|
+
if (err instanceof EmbeddedWalletEncryptionError) {
|
|
374
|
+
showWrongPasswordError(); // err.storeName tells you which store failed
|
|
375
|
+
} else {
|
|
376
|
+
throw err;
|
|
377
|
+
}
|
|
329
378
|
}
|
|
330
379
|
```
|
|
331
380
|
|
|
332
|
-
###
|
|
381
|
+
### Important
|
|
333
382
|
|
|
334
|
-
|
|
335
|
-
|
|
383
|
+
1. **Opening a store consumes the key, it does not copy it.** So that raw key material does not linger in page memory, the SDK moves your key into the storage worker and detaches the buffer on your side. The `Uint8Array` you passed comes back empty, so the same array cannot be reused to open a second store. To open more than one store with the same key, hand each open a fresh copy (`new Uint8Array(key)`). `openEncryptedEmbeddedStores` does this for you by invoking your `getEncryptionKey` callback once per store.
|
|
384
|
+
2. **Each coexisting store needs its own `poolDirectory`.** The OPFS SAH Pool holds an *exclusive* lock on its directory, so two stores sharing the default pool fail with "Access Handles cannot be created if there is another open Access Handle…". Give every store a distinct, stable `poolDirectory` (stable so the same files re-open next session).
|
|
336
385
|
|
|
337
|
-
|
|
338
|
-
const { type, messageId, args, chainInfo, walletId } = message;
|
|
386
|
+
### No multi-tab access: assume one tab at a time
|
|
339
387
|
|
|
340
|
-
|
|
341
|
-
// 1. Parse and validate chain info
|
|
342
|
-
const parsedChainInfo = ChainInfoSchema.parse(chainInfo);
|
|
388
|
+
A store can be opened by **one browser tab at a time per origin**. If the user opens your wallet in a second tab of the same origin pointing at the same store, the second open contends for that lock.
|
|
343
389
|
|
|
344
|
-
|
|
345
|
-
if (!isNetworkSupported(parsedChainInfo)) {
|
|
346
|
-
throw new Error('Network not supported by wallet');
|
|
347
|
-
}
|
|
390
|
+
Thanks to the lock, the data is never corrupted, but the second open fails or hangs rather than succeeding, and there is no graceful "already open elsewhere" signal yet.
|
|
348
391
|
|
|
349
|
-
|
|
350
|
-
const wallet = await getWalletForChain(parsedChainInfo);
|
|
392
|
+
Until it does, design for a single active tab: detect a second instance (e.g. with the [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) or a `BroadcastChannel`) and steer the user back to the existing tab, or open the store read-only there.
|
|
351
393
|
|
|
352
|
-
|
|
353
|
-
if (typeof wallet[type] !== 'function') {
|
|
354
|
-
throw new Error(`Unknown wallet method: ${type}`);
|
|
355
|
-
}
|
|
394
|
+
If you need genuine concurrent multi-tab access, route all storage access through a single `SharedWorker` that you own and that holds the one connection.
|
|
356
395
|
|
|
357
|
-
|
|
358
|
-
const result = await wallet[type](...args);
|
|
359
|
-
sendResponse(messageId, walletId, result);
|
|
360
|
-
} catch (error) {
|
|
361
|
-
sendError(messageId, walletId, error);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
```
|
|
396
|
+
### Opting out of encryption
|
|
365
397
|
|
|
366
|
-
|
|
398
|
+
If you do not need at-rest encryption (you rely on full-disk encryption, or the device is trusted), an *unencrypted* SQLite-OPFS store is still a better default than IndexedDB.
|
|
367
399
|
|
|
368
|
-
|
|
400
|
+
The `createStore` convenience helper always uses the default OPFS pool directory and does not currently let you change it, so it only works for a single store per tab. The embedded wallet runs two stores (PXE + walletDB), so open them directly from `AztecSQLiteOPFSStore` with a distinct `poolDirectory` each:
|
|
369
401
|
|
|
370
402
|
```typescript
|
|
371
|
-
{
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
403
|
+
import { EmbeddedWallet } from '@aztec/wallets/embedded';
|
|
404
|
+
import { AztecSQLiteOPFSStore } from '@aztec/kv-store/sqlite-opfs';
|
|
405
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
406
|
+
|
|
407
|
+
const log = createLogger('wallet:storage');
|
|
408
|
+
|
|
409
|
+
// No key; just name, ephemeral=false, and a distinct poolDirectory per store.
|
|
410
|
+
const pxeStore = await AztecSQLiteOPFSStore.open(log, `pxe-${rollupAddress}`, false, '/pxe');
|
|
411
|
+
const walletStore = await AztecSQLiteOPFSStore.open(log, `wallet-${rollupAddress}`, false, '/wallet');
|
|
380
412
|
|
|
381
|
-
|
|
413
|
+
const wallet = await EmbeddedWallet.create(nodeUrl, {
|
|
414
|
+
pxe: { store: pxeStore },
|
|
415
|
+
walletDb: { store: walletStore },
|
|
416
|
+
});
|
|
417
|
+
```
|
|
382
418
|
|
|
383
|
-
###
|
|
419
|
+
### Building your own wallet (lower-level API)
|
|
384
420
|
|
|
385
|
-
|
|
421
|
+
If you are not using `EmbeddedWallet`, construct stores directly from `@aztec/kv-store/sqlite-opfs` and pass them wherever a store is accepted (e.g. `PXECreationOptions.store`):
|
|
386
422
|
|
|
387
423
|
```typescript
|
|
388
|
-
import {
|
|
389
|
-
import { WalletManager } from '@aztec/wallet-sdk/manager';
|
|
424
|
+
import { openEncryptedStore, createStore, SqliteEncryptionError } from '@aztec/kv-store/sqlite-opfs';
|
|
390
425
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
});
|
|
426
|
+
// Encrypted, persistent:
|
|
427
|
+
const store = await openEncryptedStore(new Uint8Array(myDerivedKey), 'my-store', '/my-pool');
|
|
394
428
|
|
|
395
|
-
//
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
chainId: new Fr(31337),
|
|
399
|
-
version: new Fr(0),
|
|
400
|
-
},
|
|
401
|
-
timeout: 2000,
|
|
402
|
-
});
|
|
429
|
+
// Or unencrypted:
|
|
430
|
+
const plain = await createStore('my-store', { dataStoreMapSizeKb: 2e10 });
|
|
431
|
+
```
|
|
403
432
|
|
|
404
|
-
|
|
433
|
+
Note: `dataStoreMapSizeKb` is an LMDB-specific ceiling (the maximum memory-map size). SQLite-OPFS grows its file dynamically and ignores the value, but it is a required field of the shared `DataStoreConfig` type, so you must still pass something (any number is fine). We will fix this implementation leak in coming versions.
|
|
405
434
|
|
|
406
|
-
|
|
407
|
-
const walletProvider = wallets.find(w => w.id === 'my-aztec-wallet');
|
|
408
|
-
if (walletProvider) {
|
|
409
|
-
const wallet = await walletProvider.connect('test-app');
|
|
435
|
+
Note: `openEncryptedStore` throws `SqliteEncryptionError` (with a typed `code`, e.g. `'decrypt_failed'`) on a bad key.
|
|
410
436
|
|
|
411
|
-
|
|
412
|
-
const accounts = await wallet.getAccounts();
|
|
413
|
-
console.log('Accounts:', accounts);
|
|
437
|
+
### Using SQLite-OPFS in a browser extension (MV3)
|
|
414
438
|
|
|
415
|
-
|
|
416
|
-
console.log('Chain info:', chainInfo);
|
|
417
|
-
}
|
|
418
|
-
```
|
|
439
|
+
SQLite-OPFS needs OPFS, a Web Worker, and cross-origin isolation (SharedArrayBuffer). In a Chrome MV3 extension:
|
|
419
440
|
|
|
420
|
-
|
|
441
|
+
- **Run it in an offscreen document, not the background service worker.** The service worker is ephemeral and does not reliably provide OPFS/SharedArrayBuffer; an offscreen document does, and it is where your PXE and stores should live.
|
|
442
|
+
- **No COOP/COEP header setup is needed inside the extension.** Extension pages are cross-origin-isolated by default. (A plain web page hosting the wallet *does* need those headers.)
|
|
421
443
|
|
|
422
|
-
|
|
444
|
+
### Key management is your responsibility
|
|
423
445
|
|
|
424
|
-
-
|
|
446
|
+
The store encrypts data at rest given a 32-byte key, but deriving and safeguarding that key is the wallet's job. A common pattern is to derive the key from a user password with a memory-hard KDF (e.g. Argon2id) and hold it only in memory while the wallet is unlocked. Adapt this to your own security model.
|