@aztec/wallet-sdk 0.0.1-commit.9ef841308 → 0.0.1-commit.a4600f49
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 +125 -0
- package/dest/base-wallet/base_wallet.d.ts +65 -35
- package/dest/base-wallet/base_wallet.d.ts.map +1 -1
- package/dest/base-wallet/base_wallet.js +187 -81
- 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 +3 -2
- package/dest/base-wallet/index.d.ts.map +1 -1
- package/dest/base-wallet/index.js +1 -0
- package/dest/base-wallet/utils.d.ts +7 -4
- package/dest/base-wallet/utils.d.ts.map +1 -1
- package/dest/base-wallet/utils.js +11 -5
- package/dest/extension/handlers/background_connection_handler.d.ts +12 -2
- package/dest/extension/handlers/background_connection_handler.d.ts.map +1 -1
- package/dest/extension/handlers/background_connection_handler.js +44 -8
- package/dest/extension/handlers/content_script_connection_handler.d.ts +2 -1
- package/dest/extension/handlers/content_script_connection_handler.d.ts.map +1 -1
- package/dest/extension/handlers/content_script_connection_handler.js +19 -0
- package/dest/extension/handlers/internal_message_types.d.ts +3 -1
- package/dest/extension/handlers/internal_message_types.d.ts.map +1 -1
- package/dest/extension/handlers/internal_message_types.js +3 -1
- package/dest/extension/provider/extension_wallet.d.ts +26 -3
- package/dest/extension/provider/extension_wallet.d.ts.map +1 -1
- package/dest/extension/provider/extension_wallet.js +80 -9
- package/dest/iframe/handlers/iframe_connection_handler.d.ts +6 -2
- package/dest/iframe/handlers/iframe_connection_handler.d.ts.map +1 -1
- package/dest/iframe/handlers/iframe_connection_handler.js +18 -7
- package/dest/iframe/provider/iframe_wallet.d.ts +20 -3
- package/dest/iframe/provider/iframe_wallet.d.ts.map +1 -1
- package/dest/iframe/provider/iframe_wallet.js +79 -10
- package/dest/types.d.ts +52 -2
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +25 -0
- package/package.json +8 -8
- package/src/base-wallet/base_wallet.ts +221 -108
- package/src/base-wallet/get_gas_limits.ts +88 -0
- package/src/base-wallet/index.ts +7 -1
- package/src/base-wallet/utils.ts +15 -5
- package/src/extension/handlers/background_connection_handler.ts +42 -9
- package/src/extension/handlers/content_script_connection_handler.ts +18 -0
- package/src/extension/handlers/internal_message_types.ts +2 -0
- package/src/extension/provider/extension_wallet.ts +94 -8
- package/src/iframe/handlers/iframe_connection_handler.ts +21 -8
- package/src/iframe/provider/iframe_wallet.ts +103 -9
- package/src/types.ts +59 -0
|
@@ -10,11 +10,21 @@ import type { ChainInfo } from '@aztec/aztec.js/account';
|
|
|
10
10
|
import { type Wallet, WalletSchema } from '@aztec/aztec.js/wallet';
|
|
11
11
|
import { jsonStringify } from '@aztec/foundation/json-rpc';
|
|
12
12
|
import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
|
|
13
|
-
import { schemaHasMethod } from '@aztec/foundation/schemas';
|
|
13
|
+
import { getSchemaReturnType, schemaHasMethod } from '@aztec/foundation/schemas';
|
|
14
14
|
import type { FunctionsOf } from '@aztec/foundation/types';
|
|
15
15
|
|
|
16
16
|
import { type EncryptedPayload, decrypt, encrypt } from '../../crypto.js';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_HEARTBEAT_DEAD_AFTER_MS,
|
|
19
|
+
DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
20
|
+
type DisconnectCallback,
|
|
21
|
+
type HeartbeatOptions,
|
|
22
|
+
NOOP_LOGGER,
|
|
23
|
+
type WalletMessage,
|
|
24
|
+
WalletMessageType,
|
|
25
|
+
type WalletResponse,
|
|
26
|
+
type WalletSdkLogger,
|
|
27
|
+
} from '../../types.js';
|
|
18
28
|
|
|
19
29
|
/**
|
|
20
30
|
* Internal type representing a wallet method call before encryption.
|
|
@@ -46,6 +56,11 @@ export class IframeWallet {
|
|
|
46
56
|
private disconnected = false;
|
|
47
57
|
private disconnectCallbacks: DisconnectCallback[] = [];
|
|
48
58
|
private messageListener: ((e: MessageEvent) => void) | null = null;
|
|
59
|
+
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
60
|
+
private lastInboundAt = 0;
|
|
61
|
+
private log: WalletSdkLogger;
|
|
62
|
+
private heartbeatIntervalMs: number;
|
|
63
|
+
private heartbeatDeadAfterMs: number;
|
|
49
64
|
|
|
50
65
|
private constructor(
|
|
51
66
|
private chainInfo: ChainInfo,
|
|
@@ -55,7 +70,13 @@ export class IframeWallet {
|
|
|
55
70
|
private iframeWindow: Window,
|
|
56
71
|
private walletOrigin: string,
|
|
57
72
|
private sharedKey: CryptoKey,
|
|
58
|
-
|
|
73
|
+
logger?: WalletSdkLogger,
|
|
74
|
+
heartbeatOptions?: HeartbeatOptions,
|
|
75
|
+
) {
|
|
76
|
+
this.log = logger ?? NOOP_LOGGER;
|
|
77
|
+
this.heartbeatIntervalMs = heartbeatOptions?.intervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
78
|
+
this.heartbeatDeadAfterMs = heartbeatOptions?.deadAfterMs ?? DEFAULT_HEARTBEAT_DEAD_AFTER_MS;
|
|
79
|
+
}
|
|
59
80
|
|
|
60
81
|
/**
|
|
61
82
|
* Creates a proxied IframeWallet that implements the {@link Wallet} interface.
|
|
@@ -71,6 +92,8 @@ export class IframeWallet {
|
|
|
71
92
|
* @param sharedKey - AES-256-GCM key derived from ECDH key exchange
|
|
72
93
|
* @param chainInfo - Network information (chainId and version)
|
|
73
94
|
* @param appId - Application identifier for the requesting dApp
|
|
95
|
+
* @param logger - Optional logger; defaults to a no-op logger to keep extension/page bundles small
|
|
96
|
+
* @param heartbeatOptions - Optional override for heartbeat tuning (mostly useful for tests)
|
|
74
97
|
* @returns A proxied IframeWallet — call `.asWallet()` to get the typed `Wallet`
|
|
75
98
|
*/
|
|
76
99
|
static create(
|
|
@@ -81,8 +104,20 @@ export class IframeWallet {
|
|
|
81
104
|
sharedKey: CryptoKey,
|
|
82
105
|
chainInfo: ChainInfo,
|
|
83
106
|
appId: string,
|
|
107
|
+
logger?: WalletSdkLogger,
|
|
108
|
+
heartbeatOptions?: HeartbeatOptions,
|
|
84
109
|
): IframeWallet {
|
|
85
|
-
const wallet = new IframeWallet(
|
|
110
|
+
const wallet = new IframeWallet(
|
|
111
|
+
chainInfo,
|
|
112
|
+
appId,
|
|
113
|
+
walletId,
|
|
114
|
+
sessionId,
|
|
115
|
+
iframeWindow,
|
|
116
|
+
walletOrigin,
|
|
117
|
+
sharedKey,
|
|
118
|
+
logger,
|
|
119
|
+
heartbeatOptions,
|
|
120
|
+
);
|
|
86
121
|
|
|
87
122
|
wallet.messageListener = (event: MessageEvent) => {
|
|
88
123
|
if (event.origin !== walletOrigin) {
|
|
@@ -93,9 +128,16 @@ export class IframeWallet {
|
|
|
93
128
|
return;
|
|
94
129
|
}
|
|
95
130
|
|
|
96
|
-
if (msg.
|
|
131
|
+
if (msg.sessionId !== sessionId) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Any inbound traffic on our session counts as proof of liveness.
|
|
136
|
+
wallet.lastInboundAt = Date.now();
|
|
137
|
+
|
|
138
|
+
if (msg.type === WalletMessageType.SECURE_RESPONSE) {
|
|
97
139
|
void wallet.handleEncryptedResponse(msg.encrypted as EncryptedPayload);
|
|
98
|
-
} else if (msg.type === WalletMessageType.SESSION_DISCONNECTED
|
|
140
|
+
} else if (msg.type === WalletMessageType.SESSION_DISCONNECTED) {
|
|
99
141
|
wallet.handleDisconnect();
|
|
100
142
|
}
|
|
101
143
|
};
|
|
@@ -111,7 +153,7 @@ export class IframeWallet {
|
|
|
111
153
|
type: prop.toString() as keyof FunctionsOf<Wallet>,
|
|
112
154
|
args,
|
|
113
155
|
});
|
|
114
|
-
return WalletSchema[prop.toString() as keyof typeof WalletSchema]
|
|
156
|
+
return getSchemaReturnType(WalletSchema[prop.toString() as keyof typeof WalletSchema]).parseAsync(result);
|
|
115
157
|
};
|
|
116
158
|
} else {
|
|
117
159
|
return target[prop as keyof IframeWallet];
|
|
@@ -148,8 +190,9 @@ export class IframeWallet {
|
|
|
148
190
|
pending.resolve(result);
|
|
149
191
|
}
|
|
150
192
|
this.inFlight.delete(messageId);
|
|
151
|
-
|
|
152
|
-
|
|
193
|
+
this.maybeStopHeartbeat();
|
|
194
|
+
} catch (err) {
|
|
195
|
+
this.log.warn('Failed to decrypt wallet response', { err });
|
|
153
196
|
}
|
|
154
197
|
}
|
|
155
198
|
|
|
@@ -176,15 +219,66 @@ export class IframeWallet {
|
|
|
176
219
|
|
|
177
220
|
const { promise, resolve, reject } = promiseWithResolvers<unknown>();
|
|
178
221
|
this.inFlight.set(messageId, { promise, resolve, reject });
|
|
222
|
+
this.startHeartbeat();
|
|
179
223
|
return promise;
|
|
180
224
|
}
|
|
181
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Start liveness probing while at least one request is in flight. PINGs are
|
|
228
|
+
* unencrypted control messages — older wallet handlers that don't understand
|
|
229
|
+
* them simply drop them, but any inbound traffic (PONG, encrypted response,
|
|
230
|
+
* disconnect notice) resets the idle timer, so a slow-but-alive legacy wallet
|
|
231
|
+
* never trips a false disconnect.
|
|
232
|
+
*/
|
|
233
|
+
private startHeartbeat(): void {
|
|
234
|
+
if (this.heartbeatTimer !== null || this.disconnected) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.lastInboundAt = Date.now();
|
|
238
|
+
this.heartbeatTimer = setInterval(() => this.heartbeatTick(), this.heartbeatIntervalMs);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private maybeStopHeartbeat(): void {
|
|
242
|
+
if (this.inFlight.size === 0 && this.heartbeatTimer !== null) {
|
|
243
|
+
clearInterval(this.heartbeatTimer);
|
|
244
|
+
this.heartbeatTimer = null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private heartbeatTick(): void {
|
|
249
|
+
if (this.disconnected || this.inFlight.size === 0) {
|
|
250
|
+
this.maybeStopHeartbeat();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const idleMs = Date.now() - this.lastInboundAt;
|
|
255
|
+
if (idleMs >= this.heartbeatDeadAfterMs) {
|
|
256
|
+
this.log.warn('Iframe wallet channel unresponsive — declaring disconnect', {
|
|
257
|
+
idleMs,
|
|
258
|
+
inFlight: this.inFlight.size,
|
|
259
|
+
});
|
|
260
|
+
this.handleDisconnect();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
this.iframeWindow.postMessage({ type: WalletMessageType.PING, sessionId: this.sessionId }, this.walletOrigin);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
this.log.warn('Failed to send heartbeat PING', { err });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
182
271
|
private handleDisconnect(): void {
|
|
183
272
|
if (this.disconnected) {
|
|
184
273
|
return;
|
|
185
274
|
}
|
|
186
275
|
this.disconnected = true;
|
|
187
276
|
|
|
277
|
+
if (this.heartbeatTimer !== null) {
|
|
278
|
+
clearInterval(this.heartbeatTimer);
|
|
279
|
+
this.heartbeatTimer = null;
|
|
280
|
+
}
|
|
281
|
+
|
|
188
282
|
if (this.messageListener) {
|
|
189
283
|
window.removeEventListener('message', this.messageListener);
|
|
190
284
|
this.messageListener = null;
|
package/src/types.ts
CHANGED
|
@@ -25,6 +25,13 @@ export enum WalletMessageType {
|
|
|
25
25
|
SECURE_RESPONSE = 'aztec-wallet-secure-response',
|
|
26
26
|
/** Session disconnected notification */
|
|
27
27
|
SESSION_DISCONNECTED = 'aztec-wallet-session-disconnected',
|
|
28
|
+
/** Liveness probe sent by the dApp while a request is in flight */
|
|
29
|
+
PING = 'aztec-wallet-ping',
|
|
30
|
+
/** Liveness response from the wallet. Any inbound message (including a regular
|
|
31
|
+
* encrypted response) is treated as proof of liveness, so a missing PONG alone
|
|
32
|
+
* never trips disconnect — the dApp also resets its liveness timer on traffic.
|
|
33
|
+
*/
|
|
34
|
+
PONG = 'aztec-wallet-pong',
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
/**
|
|
@@ -143,3 +150,55 @@ export interface KeyExchangeResponse {
|
|
|
143
150
|
* Callback invoked when a wallet connection is disconnected.
|
|
144
151
|
*/
|
|
145
152
|
export type DisconnectCallback = () => void;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Default heartbeat tuning shared by both transports.
|
|
156
|
+
*
|
|
157
|
+
* - `intervalMs`: how often the dApp sends PING probes while a request is in flight.
|
|
158
|
+
* - `deadAfterMs`: how long the channel can stay silent (no PONG, no encrypted
|
|
159
|
+
* response, no DISCONNECT) before the dApp declares the wallet unreachable
|
|
160
|
+
* and rejects all in-flight requests. Generous enough that long-running
|
|
161
|
+
* operations (proveTx, sendTx) on legacy wallets that don't reply to PING
|
|
162
|
+
* still succeed — any inbound traffic resets the timer.
|
|
163
|
+
*/
|
|
164
|
+
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 5_000;
|
|
165
|
+
export const DEFAULT_HEARTBEAT_DEAD_AFTER_MS = 300_000;
|
|
166
|
+
|
|
167
|
+
/** Override knobs for the heartbeat — mostly useful for tests. */
|
|
168
|
+
export interface HeartbeatOptions {
|
|
169
|
+
/** How often to send PING probes while a request is in flight (ms). */
|
|
170
|
+
intervalMs?: number;
|
|
171
|
+
/** Idle ceiling before declaring disconnect (ms). */
|
|
172
|
+
deadAfterMs?: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Minimal logger surface used by the wallet SDK.
|
|
177
|
+
*
|
|
178
|
+
* Defined locally so that wallet hosts (browser extensions, iframe wallet pages)
|
|
179
|
+
* can pass a simple `console`-backed logger without pulling in the full
|
|
180
|
+
* `@aztec/foundation` logging runtime, which is non-trivial to bundle in those
|
|
181
|
+
* contexts. Structurally compatible with `Logger` from `@aztec/foundation/log`,
|
|
182
|
+
* so dApp-side callers can pass that type directly.
|
|
183
|
+
*/
|
|
184
|
+
export interface WalletSdkLogger {
|
|
185
|
+
/** Diagnostic messages — typically discarded in production. */
|
|
186
|
+
debug: (message: string, data?: unknown) => void;
|
|
187
|
+
/** Informational messages — significant lifecycle events. */
|
|
188
|
+
info: (message: string, data?: unknown) => void;
|
|
189
|
+
/** Recoverable problems — channel decryption failure, missed heartbeats, etc. */
|
|
190
|
+
warn: (message: string, data?: unknown) => void;
|
|
191
|
+
/** Errors that prevent normal operation. */
|
|
192
|
+
error: (message: string, errOrData?: unknown, data?: unknown) => void;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* No-op logger used as the default when callers don't provide one. Discards all
|
|
197
|
+
* messages — wallet hosts that want diagnostics should pass their own logger.
|
|
198
|
+
*/
|
|
199
|
+
export const NOOP_LOGGER: WalletSdkLogger = {
|
|
200
|
+
debug: () => {},
|
|
201
|
+
info: () => {},
|
|
202
|
+
warn: () => {},
|
|
203
|
+
error: () => {},
|
|
204
|
+
};
|