@aztec/wallet-sdk 4.0.0-devnet.2-patch.3 → 4.0.0-devnet.3-patch.0

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.
Files changed (57) hide show
  1. package/dest/base-wallet/base_wallet.d.ts +31 -20
  2. package/dest/base-wallet/base_wallet.d.ts.map +1 -1
  3. package/dest/base-wallet/base_wallet.js +108 -48
  4. package/dest/base-wallet/index.d.ts +2 -2
  5. package/dest/base-wallet/index.d.ts.map +1 -1
  6. package/dest/base-wallet/utils.d.ts +3 -2
  7. package/dest/base-wallet/utils.d.ts.map +1 -1
  8. package/dest/base-wallet/utils.js +6 -3
  9. package/dest/crypto.d.ts +39 -1
  10. package/dest/crypto.d.ts.map +1 -1
  11. package/dest/crypto.js +88 -0
  12. package/dest/extension/provider/extension_wallet.d.ts +2 -5
  13. package/dest/extension/provider/extension_wallet.d.ts.map +1 -1
  14. package/dest/extension/provider/index.d.ts +2 -2
  15. package/dest/extension/provider/index.d.ts.map +1 -1
  16. package/dest/iframe/handlers/iframe_connection_handler.d.ts +118 -0
  17. package/dest/iframe/handlers/iframe_connection_handler.d.ts.map +1 -0
  18. package/dest/iframe/handlers/iframe_connection_handler.js +228 -0
  19. package/dest/iframe/handlers/index.d.ts +2 -0
  20. package/dest/iframe/handlers/index.d.ts.map +1 -0
  21. package/dest/iframe/handlers/index.js +1 -0
  22. package/dest/iframe/provider/iframe_discovery.d.ts +25 -0
  23. package/dest/iframe/provider/iframe_discovery.d.ts.map +1 -0
  24. package/dest/iframe/provider/iframe_discovery.js +167 -0
  25. package/dest/iframe/provider/iframe_provider.d.ts +65 -0
  26. package/dest/iframe/provider/iframe_provider.d.ts.map +1 -0
  27. package/dest/iframe/provider/iframe_provider.js +257 -0
  28. package/dest/iframe/provider/iframe_wallet.d.ts +68 -0
  29. package/dest/iframe/provider/iframe_wallet.d.ts.map +1 -0
  30. package/dest/iframe/provider/iframe_wallet.js +200 -0
  31. package/dest/iframe/provider/index.d.ts +4 -0
  32. package/dest/iframe/provider/index.d.ts.map +1 -0
  33. package/dest/iframe/provider/index.js +3 -0
  34. package/dest/manager/types.d.ts +3 -2
  35. package/dest/manager/types.d.ts.map +1 -1
  36. package/dest/manager/wallet_manager.d.ts +1 -1
  37. package/dest/manager/wallet_manager.d.ts.map +1 -1
  38. package/dest/manager/wallet_manager.js +46 -16
  39. package/dest/types.d.ts +14 -2
  40. package/dest/types.d.ts.map +1 -1
  41. package/dest/types.js +4 -0
  42. package/package.json +12 -8
  43. package/src/base-wallet/base_wallet.ts +159 -82
  44. package/src/base-wallet/index.ts +1 -1
  45. package/src/base-wallet/utils.ts +8 -0
  46. package/src/crypto.ts +104 -0
  47. package/src/extension/provider/extension_wallet.ts +1 -6
  48. package/src/extension/provider/index.ts +1 -1
  49. package/src/iframe/handlers/iframe_connection_handler.ts +328 -0
  50. package/src/iframe/handlers/index.ts +7 -0
  51. package/src/iframe/provider/iframe_discovery.ts +185 -0
  52. package/src/iframe/provider/iframe_provider.ts +331 -0
  53. package/src/iframe/provider/iframe_wallet.ts +229 -0
  54. package/src/iframe/provider/index.ts +3 -0
  55. package/src/manager/types.ts +2 -1
  56. package/src/manager/wallet_manager.ts +48 -14
  57. package/src/types.ts +13 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Web wallet discovery — creates {@link IframeWalletProvider} instances from a list of URLs.
3
+ *
4
+ * For each configured URL we probe the wallet by loading a tiny invisible iframe,
5
+ * waiting for WALLET_READY, then sending a DISCOVERY request. On a successful
6
+ * DISCOVERY_RESPONSE we emit an IframeWalletProvider to the caller.
7
+ *
8
+ * This is intentionally lightweight (no key exchange yet) — key exchange happens
9
+ * later when the user selects the wallet and calls `provider.establishSecureChannel()`.
10
+ */ import { promiseWithResolvers } from '@aztec/foundation/promise';
11
+ import { WalletMessageType } from '../../types.js';
12
+ import { IframeWalletProvider } from './iframe_provider.js';
13
+ const PROBE_TIMEOUT_MS = 10_000;
14
+ /**
15
+ * Probes a list of web wallet URLs and returns a {@link DiscoverySession} compatible
16
+ * with WalletManager's `getAvailableWallets()` interface.
17
+ *
18
+ * Discovered {@link IframeWalletProvider} instances are yielded asynchronously as each
19
+ * wallet responds to the probe.
20
+ *
21
+ * @param walletUrls - URLs of web wallets to probe
22
+ * @param chainInfo - Network information to pass during discovery
23
+ * @returns A cancellable discovery session
24
+ */ export function discoverWebWallets(walletUrls, chainInfo) {
25
+ const { promise: donePromise, resolve: resolveDone } = promiseWithResolvers();
26
+ /* eslint-enable jsdoc/require-jsdoc */ let state = {
27
+ status: 'discovering',
28
+ resolve: null
29
+ };
30
+ const pendingProviders = [];
31
+ // eslint-disable-next-line jsdoc/require-jsdoc
32
+ function emit(provider) {
33
+ if (state.status !== 'discovering') {
34
+ return;
35
+ }
36
+ if (state.resolve) {
37
+ const resolve = state.resolve;
38
+ state.resolve = null;
39
+ resolve({
40
+ value: provider,
41
+ done: false
42
+ });
43
+ } else {
44
+ pendingProviders.push(provider);
45
+ }
46
+ }
47
+ // eslint-disable-next-line jsdoc/require-jsdoc
48
+ function markComplete() {
49
+ if (state.status !== 'discovering') {
50
+ return;
51
+ }
52
+ const pendingResolve = state.resolve;
53
+ state = {
54
+ status: 'done'
55
+ };
56
+ resolveDone();
57
+ if (pendingResolve) {
58
+ pendingResolve({
59
+ value: undefined,
60
+ done: true
61
+ });
62
+ }
63
+ }
64
+ // Probe all URLs in parallel
65
+ const probes = walletUrls.map((url)=>probeWallet(url, chainInfo, PROBE_TIMEOUT_MS).then((provider)=>{
66
+ if (provider) {
67
+ emit(provider);
68
+ }
69
+ }, ()=>{
70
+ // ignore probe errors
71
+ }));
72
+ void Promise.all(probes).then(markComplete);
73
+ const wallets = {
74
+ // eslint-disable-next-line jsdoc/require-jsdoc
75
+ [Symbol.asyncIterator] () {
76
+ return {
77
+ // eslint-disable-next-line jsdoc/require-jsdoc
78
+ next () {
79
+ if (pendingProviders.length > 0) {
80
+ return Promise.resolve({
81
+ value: pendingProviders.shift(),
82
+ done: false
83
+ });
84
+ }
85
+ if (state.status === 'done') {
86
+ return Promise.resolve({
87
+ value: undefined,
88
+ done: true
89
+ });
90
+ }
91
+ return new Promise((resolve)=>{
92
+ if (state.status === 'discovering') {
93
+ state.resolve = resolve;
94
+ }
95
+ });
96
+ },
97
+ // eslint-disable-next-line jsdoc/require-jsdoc
98
+ return () {
99
+ markComplete();
100
+ return Promise.resolve({
101
+ value: undefined,
102
+ done: true
103
+ });
104
+ }
105
+ };
106
+ }
107
+ };
108
+ return {
109
+ wallets,
110
+ done: donePromise,
111
+ cancel: markComplete
112
+ };
113
+ }
114
+ /**
115
+ * Probes a single web wallet URL.
116
+ * Creates a temporary hidden iframe, waits for WALLET_READY, sends DISCOVERY_REQUEST.
117
+ * Returns an IframeWalletProvider on success, null on timeout/failure.
118
+ * @internal
119
+ */ function probeWallet(walletUrl, chainInfo, timeoutMs) {
120
+ const walletOrigin = new URL(walletUrl).origin;
121
+ const iframe = document.createElement('iframe');
122
+ iframe.src = walletUrl;
123
+ iframe.style.cssText = 'display:none;width:0;height:0;border:none;position:absolute;top:-9999px;';
124
+ iframe.allow = 'storage-access; cross-origin-isolated';
125
+ let timer;
126
+ // Register listener BEFORE appending to DOM to avoid race with WALLET_READY
127
+ const result = new Promise((resolve)=>{
128
+ const cleanup = ()=>{
129
+ if (iframe.parentNode) {
130
+ iframe.parentNode.removeChild(iframe);
131
+ }
132
+ window.removeEventListener('message', handler);
133
+ clearTimeout(timer);
134
+ };
135
+ timer = setTimeout(()=>{
136
+ cleanup();
137
+ resolve(null);
138
+ }, timeoutMs);
139
+ let step = 'waiting-ready';
140
+ const requestId = globalThis.crypto.randomUUID();
141
+ // eslint-disable-next-line jsdoc/require-jsdoc
142
+ function handler(event) {
143
+ if (event.origin !== walletOrigin) {
144
+ return;
145
+ }
146
+ const msg = event.data;
147
+ if (!msg || typeof msg !== 'object') {
148
+ return;
149
+ }
150
+ if (step === 'waiting-ready' && msg.type === WalletMessageType.WALLET_READY) {
151
+ step = 'waiting-discovery';
152
+ iframe.contentWindow?.postMessage({
153
+ type: WalletMessageType.DISCOVERY,
154
+ requestId,
155
+ appId: 'discovery-probe'
156
+ }, walletOrigin);
157
+ } else if (step === 'waiting-discovery' && msg.type === WalletMessageType.DISCOVERY_RESPONSE && msg.requestId === requestId) {
158
+ const info = msg.walletInfo;
159
+ cleanup();
160
+ resolve(new IframeWalletProvider(info.id, info.name, info.icon, walletUrl, chainInfo));
161
+ }
162
+ }
163
+ window.addEventListener('message', handler);
164
+ });
165
+ document.body.appendChild(iframe);
166
+ return result;
167
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * IframeWalletProvider — implements {@link WalletProvider} for web wallets loaded in iframes.
3
+ *
4
+ * Flow (mirrors ExtensionProvider):
5
+ * 1. Creates an `<iframe src="walletUrl">` (in app-provided container or floating panel)
6
+ * 2. Waits for WALLET_READY message from the iframe
7
+ * 3. Sends DISCOVERY → waits for DISCOVERY_RESPONSE
8
+ * 4. Sends KEY_EXCHANGE_REQUEST (ECDH public key) → waits for KEY_EXCHANGE_RESPONSE
9
+ * 5. Derives shared session keys, exposes verificationHash
10
+ * 6. On confirm(): returns IframeWallet backed by the established session
11
+ */
12
+ import type { ChainInfo } from '@aztec/aztec.js/account';
13
+ import type { PendingConnection, ProviderDisconnectionCallback, WalletProvider } from '../../manager/types.js';
14
+ /**
15
+ * Options for {@link IframeWalletProvider.establishSecureChannel}.
16
+ */
17
+ export interface IframeConnectionOptions {
18
+ /** Container element to inject the iframe into. If omitted, a floating panel is created. */
19
+ container?: HTMLElement;
20
+ }
21
+ /**
22
+ * A {@link WalletProvider} that connects to a web wallet loaded in a cross-origin iframe.
23
+ *
24
+ * Discovered via {@link discoverWebWallets} and used through the standard
25
+ * `WalletProvider.establishSecureChannel()` flow.
26
+ */
27
+ export declare class IframeWalletProvider implements WalletProvider {
28
+ /** Unique wallet identifier. */
29
+ readonly id: string;
30
+ /** Display name for the wallet. */
31
+ readonly name: string;
32
+ /** Optional wallet icon URL. */
33
+ readonly icon: string | undefined;
34
+ private readonly walletUrl;
35
+ private readonly chainInfo;
36
+ readonly type: "web";
37
+ private iframe;
38
+ private _container;
39
+ private _appOwnsContainer;
40
+ private _dragCleanup;
41
+ private wallet;
42
+ private _disconnected;
43
+ private disconnectCallbacks;
44
+ constructor(
45
+ /** Unique wallet identifier. */
46
+ id: string,
47
+ /** Display name for the wallet. */
48
+ name: string,
49
+ /** Optional wallet icon URL. */
50
+ icon: string | undefined, walletUrl: string, chainInfo: ChainInfo);
51
+ /**
52
+ * Establishes a secure encrypted channel with the iframe wallet.
53
+ *
54
+ * @param appId - Application identifier for the requesting dApp
55
+ * @param options - Optional container element for the iframe
56
+ * @returns A {@link PendingConnection} with verification hash and confirm/cancel
57
+ */
58
+ establishSecureChannel(appId: string, options?: IframeConnectionOptions): Promise<PendingConnection>;
59
+ disconnect(): Promise<void>;
60
+ onDisconnect(callback: ProviderDisconnectionCallback): () => void;
61
+ isDisconnected(): boolean;
62
+ private createFloatingPanel;
63
+ private cleanup;
64
+ }
65
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWZyYW1lX3Byb3ZpZGVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaWZyYW1lL3Byb3ZpZGVyL2lmcmFtZV9wcm92aWRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7OztHQVVHO0FBQ0gsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFVekQsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsNkJBQTZCLEVBQUUsY0FBYyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFTL0c7O0dBRUc7QUFDSCxNQUFNLFdBQVcsdUJBQXVCO0lBQ3RDLDRGQUE0RjtJQUM1RixTQUFTLENBQUMsRUFBRSxXQUFXLENBQUM7Q0FDekI7QUFFRDs7Ozs7R0FLRztBQUNILHFCQUFhLG9CQUFxQixZQUFXLGNBQWM7SUFZdkQsZ0NBQWdDO2FBQ2hCLEVBQUUsRUFBRSxNQUFNO0lBQzFCLG1DQUFtQzthQUNuQixJQUFJLEVBQUUsTUFBTTtJQUM1QixnQ0FBZ0M7YUFDaEIsSUFBSSxFQUFFLE1BQU0sR0FBRyxTQUFTO0lBQ3hDLE9BQU8sQ0FBQyxRQUFRLENBQUMsU0FBUztJQUMxQixPQUFPLENBQUMsUUFBUSxDQUFDLFNBQVM7SUFsQjVCLFFBQVEsQ0FBQyxJQUFJLFFBQWtCO0lBRS9CLE9BQU8sQ0FBQyxNQUFNLENBQWtDO0lBQ2hELE9BQU8sQ0FBQyxVQUFVLENBQStCO0lBQ2pELE9BQU8sQ0FBQyxpQkFBaUIsQ0FBUztJQUNsQyxPQUFPLENBQUMsWUFBWSxDQUE2QjtJQUNqRCxPQUFPLENBQUMsTUFBTSxDQUE2QjtJQUMzQyxPQUFPLENBQUMsYUFBYSxDQUFTO0lBQzlCLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBdUM7SUFFbEU7SUFDRSxnQ0FBZ0M7SUFDaEIsRUFBRSxFQUFFLE1BQU07SUFDMUIsbUNBQW1DO0lBQ25CLElBQUksRUFBRSxNQUFNO0lBQzVCLGdDQUFnQztJQUNoQixJQUFJLEVBQUUsTUFBTSxHQUFHLFNBQVMsRUFDdkIsU0FBUyxFQUFFLE1BQU0sRUFDakIsU0FBUyxFQUFFLFNBQVMsRUFDbkM7SUFFSjs7Ozs7O09BTUc7SUFDRyxzQkFBc0IsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxFQUFFLHVCQUF1QixHQUFHLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxDQXdGekc7SUFFRCxVQUFVLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQU0xQjtJQUVELFlBQVksQ0FBQyxRQUFRLEVBQUUsNkJBQTZCLEdBQUcsTUFBTSxJQUFJLENBUWhFO0lBRUQsY0FBYyxJQUFJLE9BQU8sQ0FFeEI7SUFJRCxPQUFPLENBQUMsbUJBQW1CO0lBZ0czQixPQUFPLENBQUMsT0FBTztDQWVoQiJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iframe_provider.d.ts","sourceRoot":"","sources":["../../../src/iframe/provider/iframe_provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAUzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,6BAA6B,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAS/G;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,4FAA4F;IAC5F,SAAS,CAAC,EAAE,WAAW,CAAC;CACzB;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,YAAW,cAAc;IAYvD,gCAAgC;aAChB,EAAE,EAAE,MAAM;IAC1B,mCAAmC;aACnB,IAAI,EAAE,MAAM;IAC5B,gCAAgC;aAChB,IAAI,EAAE,MAAM,GAAG,SAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAlB5B,QAAQ,CAAC,IAAI,QAAkB;IAE/B,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,mBAAmB,CAAuC;IAElE;IACE,gCAAgC;IAChB,EAAE,EAAE,MAAM;IAC1B,mCAAmC;IACnB,IAAI,EAAE,MAAM;IAC5B,gCAAgC;IAChB,IAAI,EAAE,MAAM,GAAG,SAAS,EACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,EACnC;IAEJ;;;;;;OAMG;IACG,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwFzG;IAED,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1B;IAED,YAAY,CAAC,QAAQ,EAAE,6BAA6B,GAAG,MAAM,IAAI,CAQhE;IAED,cAAc,IAAI,OAAO,CAExB;IAID,OAAO,CAAC,mBAAmB;IAgG3B,OAAO,CAAC,OAAO;CAehB"}
@@ -0,0 +1,257 @@
1
+ /**
2
+ * IframeWalletProvider — implements {@link WalletProvider} for web wallets loaded in iframes.
3
+ *
4
+ * Flow (mirrors ExtensionProvider):
5
+ * 1. Creates an `<iframe src="walletUrl">` (in app-provided container or floating panel)
6
+ * 2. Waits for WALLET_READY message from the iframe
7
+ * 3. Sends DISCOVERY → waits for DISCOVERY_RESPONSE
8
+ * 4. Sends KEY_EXCHANGE_REQUEST (ECDH public key) → waits for KEY_EXCHANGE_RESPONSE
9
+ * 5. Derives shared session keys, exposes verificationHash
10
+ * 6. On confirm(): returns IframeWallet backed by the established session
11
+ */ import { deriveSessionKeys, exportPublicKey, generateKeyPair, importPublicKey } from '../../crypto.js';
12
+ import { WalletMessageType } from '../../types.js';
13
+ import { IframeWallet } from './iframe_wallet.js';
14
+ const READY_TIMEOUT_MS = 15_000;
15
+ const DISCOVERY_TIMEOUT_MS = 15_000;
16
+ const KEY_EXCHANGE_TIMEOUT_MS = 15_000;
17
+ /**
18
+ * A {@link WalletProvider} that connects to a web wallet loaded in a cross-origin iframe.
19
+ *
20
+ * Discovered via {@link discoverWebWallets} and used through the standard
21
+ * `WalletProvider.establishSecureChannel()` flow.
22
+ */ export class IframeWalletProvider {
23
+ id;
24
+ name;
25
+ icon;
26
+ walletUrl;
27
+ chainInfo;
28
+ type;
29
+ iframe;
30
+ _container;
31
+ _appOwnsContainer;
32
+ _dragCleanup;
33
+ wallet;
34
+ _disconnected;
35
+ disconnectCallbacks;
36
+ constructor(/** Unique wallet identifier. */ id, /** Display name for the wallet. */ name, /** Optional wallet icon URL. */ icon, walletUrl, chainInfo){
37
+ this.id = id;
38
+ this.name = name;
39
+ this.icon = icon;
40
+ this.walletUrl = walletUrl;
41
+ this.chainInfo = chainInfo;
42
+ this.type = 'web';
43
+ this.iframe = null;
44
+ this._container = null;
45
+ this._appOwnsContainer = false;
46
+ this._dragCleanup = null;
47
+ this.wallet = null;
48
+ this._disconnected = false;
49
+ this.disconnectCallbacks = [];
50
+ }
51
+ /**
52
+ * Establishes a secure encrypted channel with the iframe wallet.
53
+ *
54
+ * @param appId - Application identifier for the requesting dApp
55
+ * @param options - Optional container element for the iframe
56
+ * @returns A {@link PendingConnection} with verification hash and confirm/cancel
57
+ */ async establishSecureChannel(appId, options) {
58
+ const iframe = document.createElement('iframe');
59
+ iframe.src = this.walletUrl;
60
+ iframe.style.cssText = 'flex:1;border:none;width:100%;height:100%;display:block;';
61
+ iframe.allow = 'storage-access; cross-origin-isolated';
62
+ this.iframe = iframe;
63
+ if (options?.container) {
64
+ this._appOwnsContainer = true;
65
+ options.container.appendChild(iframe);
66
+ } else {
67
+ this.createFloatingPanel(iframe);
68
+ }
69
+ const walletOrigin = new URL(this.walletUrl).origin;
70
+ const post = (msg)=>{
71
+ if (!iframe.contentWindow) {
72
+ throw new Error('Iframe not ready');
73
+ }
74
+ iframe.contentWindow.postMessage(msg, walletOrigin);
75
+ };
76
+ await waitForMessage((msg)=>msg.type === WalletMessageType.WALLET_READY, READY_TIMEOUT_MS, walletOrigin);
77
+ const requestId = globalThis.crypto.randomUUID();
78
+ post({
79
+ type: WalletMessageType.DISCOVERY,
80
+ requestId,
81
+ appId
82
+ });
83
+ const discoveryResp = await waitForMessage((msg)=>msg.type === WalletMessageType.DISCOVERY_RESPONSE && msg.requestId === requestId, DISCOVERY_TIMEOUT_MS, walletOrigin);
84
+ const walletInfo = discoveryResp.walletInfo;
85
+ const keyPair = await generateKeyPair();
86
+ const dAppPublicKey = await exportPublicKey(keyPair.publicKey);
87
+ post({
88
+ type: WalletMessageType.KEY_EXCHANGE_REQUEST,
89
+ requestId,
90
+ publicKey: dAppPublicKey
91
+ });
92
+ const keyExchangeResp = await waitForMessage((msg)=>msg.type === WalletMessageType.KEY_EXCHANGE_RESPONSE && msg.requestId === requestId, KEY_EXCHANGE_TIMEOUT_MS, walletOrigin);
93
+ const walletPublicKey = await importPublicKey(keyExchangeResp.publicKey);
94
+ const sessionKeys = await deriveSessionKeys(keyPair, walletPublicKey, true);
95
+ const { verificationHash, encryptionKey: sharedKey } = sessionKeys;
96
+ const iframeWallet = IframeWallet.create(walletInfo.id, requestId, iframe.contentWindow, walletOrigin, sharedKey, this.chainInfo, appId);
97
+ this.wallet = iframeWallet;
98
+ iframeWallet.onDisconnect(()=>{
99
+ this._disconnected = true;
100
+ for (const cb of this.disconnectCallbacks){
101
+ try {
102
+ cb();
103
+ } catch {
104
+ // ignore
105
+ }
106
+ }
107
+ });
108
+ let cancelled = false;
109
+ return {
110
+ verificationHash,
111
+ confirm: ()=>{
112
+ if (cancelled) {
113
+ throw new Error('Connection was cancelled');
114
+ }
115
+ return Promise.resolve(iframeWallet.asWallet());
116
+ },
117
+ cancel: ()=>{
118
+ cancelled = true;
119
+ this.cleanup();
120
+ }
121
+ };
122
+ }
123
+ disconnect() {
124
+ if (this.wallet && !this.wallet.isDisconnected()) {
125
+ this.wallet.disconnect();
126
+ }
127
+ this.cleanup();
128
+ return Promise.resolve();
129
+ }
130
+ onDisconnect(callback) {
131
+ this.disconnectCallbacks.push(callback);
132
+ return ()=>{
133
+ const i = this.disconnectCallbacks.indexOf(callback);
134
+ if (i !== -1) {
135
+ this.disconnectCallbacks.splice(i, 1);
136
+ }
137
+ };
138
+ }
139
+ isDisconnected() {
140
+ return this._disconnected;
141
+ }
142
+ // ── Floating panel creation ─────────────────────────────────────────────────
143
+ createFloatingPanel(iframe) {
144
+ const W = 420, H = 500;
145
+ const initLeft = window.innerWidth - W - 24;
146
+ const initTop = window.innerHeight - H - 24;
147
+ const container = document.createElement('div');
148
+ container.style.cssText = `
149
+ position:fixed;left:${initLeft}px;top:${initTop}px;width:${W}px;height:${H}px;
150
+ border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,0.4);z-index:999999;
151
+ overflow:hidden;display:flex;flex-direction:column;user-select:none;
152
+ `;
153
+ const dragHandle = document.createElement('div');
154
+ dragHandle.style.cssText = `
155
+ height:28px;min-height:28px;background:rgba(30,30,30,0.95);cursor:grab;
156
+ display:flex;align-items:center;justify-content:center;
157
+ border-bottom:1px solid rgba(255,255,255,0.08);flex-shrink:0;
158
+ `;
159
+ dragHandle.innerHTML = `<span style="color:rgba(255,255,255,0.3);font-size:14px;letter-spacing:4px">&#8942;&#8942;&#8942;</span>`;
160
+ const resizeHandle = document.createElement('div');
161
+ resizeHandle.style.cssText = 'position:absolute;bottom:0;right:0;width:16px;height:16px;cursor:se-resize;z-index:1;';
162
+ resizeHandle.innerHTML = `<svg width="16" height="16" style="opacity:0.3;display:block"><path d="M2 14 L14 2 M6 14 L14 6 M10 14 L14 10" stroke="white" stroke-width="1.5"/></svg>`;
163
+ container.appendChild(dragHandle);
164
+ container.appendChild(iframe);
165
+ container.appendChild(resizeHandle);
166
+ document.body.appendChild(container);
167
+ this._container = container;
168
+ let dragging = false;
169
+ let dragOffsetX = 0, dragOffsetY = 0;
170
+ dragHandle.addEventListener('mousedown', (e)=>{
171
+ dragging = true;
172
+ dragHandle.style.cursor = 'grabbing';
173
+ const rect = container.getBoundingClientRect();
174
+ dragOffsetX = e.clientX - rect.left;
175
+ dragOffsetY = e.clientY - rect.top;
176
+ iframe.style.pointerEvents = 'none';
177
+ e.preventDefault();
178
+ });
179
+ let resizing = false;
180
+ let resizeStartX = 0, resizeStartY = 0;
181
+ let resizeStartW = 0, resizeStartH = 0;
182
+ const MIN_W = 280, MIN_H = 320;
183
+ resizeHandle.addEventListener('mousedown', (e)=>{
184
+ resizing = true;
185
+ resizeStartX = e.clientX;
186
+ resizeStartY = e.clientY;
187
+ resizeStartW = container.offsetWidth;
188
+ resizeStartH = container.offsetHeight;
189
+ iframe.style.pointerEvents = 'none';
190
+ e.preventDefault();
191
+ e.stopPropagation();
192
+ });
193
+ const onMouseMove = (e)=>{
194
+ if (dragging) {
195
+ const newLeft = Math.max(0, Math.min(window.innerWidth - container.offsetWidth, e.clientX - dragOffsetX));
196
+ const newTop = Math.max(0, Math.min(window.innerHeight - container.offsetHeight, e.clientY - dragOffsetY));
197
+ container.style.left = `${newLeft}px`;
198
+ container.style.top = `${newTop}px`;
199
+ } else if (resizing) {
200
+ const newW = Math.max(MIN_W, resizeStartW + (e.clientX - resizeStartX));
201
+ const newH = Math.max(MIN_H, resizeStartH + (e.clientY - resizeStartY));
202
+ container.style.width = `${newW}px`;
203
+ container.style.height = `${newH}px`;
204
+ }
205
+ };
206
+ const onMouseUp = ()=>{
207
+ if (dragging) {
208
+ dragHandle.style.cursor = 'grab';
209
+ }
210
+ dragging = false;
211
+ resizing = false;
212
+ iframe.style.pointerEvents = '';
213
+ };
214
+ document.addEventListener('mousemove', onMouseMove);
215
+ document.addEventListener('mouseup', onMouseUp);
216
+ this._dragCleanup = ()=>{
217
+ document.removeEventListener('mousemove', onMouseMove);
218
+ document.removeEventListener('mouseup', onMouseUp);
219
+ };
220
+ }
221
+ cleanup() {
222
+ this._dragCleanup?.();
223
+ this._dragCleanup = null;
224
+ if (this._appOwnsContainer) {
225
+ if (this.iframe && this.iframe.parentNode) {
226
+ this.iframe.parentNode.removeChild(this.iframe);
227
+ }
228
+ } else if (this._container && this._container.parentNode) {
229
+ this._container.parentNode.removeChild(this._container);
230
+ }
231
+ this._container = null;
232
+ this.iframe = null;
233
+ }
234
+ }
235
+ /** @internal */ function waitForMessage(predicate, timeoutMs, expectedOrigin) {
236
+ return new Promise((resolve, reject)=>{
237
+ const timer = setTimeout(()=>{
238
+ window.removeEventListener('message', handler);
239
+ reject(new Error(`Iframe wallet: timed out waiting for message (${timeoutMs}ms)`));
240
+ }, timeoutMs);
241
+ /** Handles incoming postMessage events, filtering by origin and predicate. */ function handler(event) {
242
+ if (event.origin !== expectedOrigin) {
243
+ return;
244
+ }
245
+ const msg = event.data;
246
+ if (!msg || typeof msg !== 'object') {
247
+ return;
248
+ }
249
+ if (predicate(msg)) {
250
+ clearTimeout(timer);
251
+ window.removeEventListener('message', handler);
252
+ resolve(msg);
253
+ }
254
+ }
255
+ window.addEventListener('message', handler);
256
+ });
257
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * IframeWallet — Wallet proxy that communicates with a web wallet loaded in an iframe.
3
+ *
4
+ * This mirrors {@link ExtensionWallet} from `@aztec/wallet-sdk/extension/provider` but uses
5
+ * `window.postMessage` / `window.addEventListener('message')` instead of MessagePort.
6
+ *
7
+ * The wire protocol (encrypted {@link WalletMessage} / {@link WalletResponse}) is identical.
8
+ */
9
+ import type { ChainInfo } from '@aztec/aztec.js/account';
10
+ import { type Wallet } from '@aztec/aztec.js/wallet';
11
+ import { type DisconnectCallback } from '../../types.js';
12
+ /**
13
+ * A wallet implementation that communicates with a web wallet loaded in an iframe
14
+ * using encrypted postMessage.
15
+ *
16
+ * Uses the same Proxy pattern as {@link ExtensionWallet}: intercepts property access,
17
+ * checks if the property is a Wallet method via {@link WalletSchema}, and routes the
18
+ * call through an encrypted postMessage channel.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const wallet = IframeWallet.create(walletId, sessionId, iframeWindow, walletOrigin, sharedKey, chainInfo, appId);
23
+ * const accounts = await wallet.asWallet().getAccounts();
24
+ * ```
25
+ */
26
+ export declare class IframeWallet {
27
+ private chainInfo;
28
+ private appId;
29
+ private walletId;
30
+ private sessionId;
31
+ private iframeWindow;
32
+ private walletOrigin;
33
+ private sharedKey;
34
+ private inFlight;
35
+ private disconnected;
36
+ private disconnectCallbacks;
37
+ private messageListener;
38
+ private constructor();
39
+ /**
40
+ * Creates a proxied IframeWallet that implements the {@link Wallet} interface.
41
+ *
42
+ * All Wallet method calls are intercepted by a Proxy, encrypted with the shared
43
+ * AES-256-GCM key, sent via postMessage, and the response is decrypted and
44
+ * validated against {@link WalletSchema}.
45
+ *
46
+ * @param walletId - Unique identifier of the remote wallet
47
+ * @param sessionId - Session identifier from the key exchange
48
+ * @param iframeWindow - The iframe's contentWindow to post messages to
49
+ * @param walletOrigin - Origin of the wallet iframe (for postMessage targeting)
50
+ * @param sharedKey - AES-256-GCM key derived from ECDH key exchange
51
+ * @param chainInfo - Network information (chainId and version)
52
+ * @param appId - Application identifier for the requesting dApp
53
+ * @returns A proxied IframeWallet — call `.asWallet()` to get the typed `Wallet`
54
+ */
55
+ static create(walletId: string, sessionId: string, iframeWindow: Window, walletOrigin: string, sharedKey: CryptoKey, chainInfo: ChainInfo, appId: string): IframeWallet;
56
+ /**
57
+ * Returns this wallet as a typed {@link Wallet} interface.
58
+ * When accessed through the Proxy (via `create()`), returns the proxy itself.
59
+ */
60
+ asWallet(): Wallet;
61
+ private handleEncryptedResponse;
62
+ private postMessage;
63
+ private handleDisconnect;
64
+ onDisconnect(callback: DisconnectCallback): () => void;
65
+ isDisconnected(): boolean;
66
+ disconnect(): void;
67
+ }
68
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWZyYW1lX3dhbGxldC5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2lmcmFtZS9wcm92aWRlci9pZnJhbWVfd2FsbGV0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7O0dBT0c7QUFDSCxPQUFPLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sd0JBQXdCLENBQUM7QUFPbkUsT0FBTyxFQUFFLEtBQUssa0JBQWtCLEVBQThELE1BQU0sZ0JBQWdCLENBQUM7QUFhckg7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILHFCQUFhLFlBQVk7SUFPckIsT0FBTyxDQUFDLFNBQVM7SUFDakIsT0FBTyxDQUFDLEtBQUs7SUFDYixPQUFPLENBQUMsUUFBUTtJQUNoQixPQUFPLENBQUMsU0FBUztJQUNqQixPQUFPLENBQUMsWUFBWTtJQUNwQixPQUFPLENBQUMsWUFBWTtJQUNwQixPQUFPLENBQUMsU0FBUztJQVpuQixPQUFPLENBQUMsUUFBUSxDQUFvRDtJQUNwRSxPQUFPLENBQUMsWUFBWSxDQUFTO0lBQzdCLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBNEI7SUFDdkQsT0FBTyxDQUFDLGVBQWUsQ0FBNEM7SUFFbkUsT0FBTyxlQVFIO0lBRUo7Ozs7Ozs7Ozs7Ozs7OztPQWVHO0lBQ0gsTUFBTSxDQUFDLE1BQU0sQ0FDWCxRQUFRLEVBQUUsTUFBTSxFQUNoQixTQUFTLEVBQUUsTUFBTSxFQUNqQixZQUFZLEVBQUUsTUFBTSxFQUNwQixZQUFZLEVBQUUsTUFBTSxFQUNwQixTQUFTLEVBQUUsU0FBUyxFQUNwQixTQUFTLEVBQUUsU0FBUyxFQUNwQixLQUFLLEVBQUUsTUFBTSxHQUNaLFlBQVksQ0FxQ2Q7SUFFRDs7O09BR0c7SUFDSCxRQUFRLElBQUksTUFBTSxDQUVqQjtZQUVhLHVCQUF1QjtZQXlCdkIsV0FBVztJQTBCekIsT0FBTyxDQUFDLGdCQUFnQjtJQTBCeEIsWUFBWSxDQUFDLFFBQVEsRUFBRSxrQkFBa0IsR0FBRyxNQUFNLElBQUksQ0FRckQ7SUFFRCxjQUFjLElBQUksT0FBTyxDQUV4QjtJQUVELFVBQVUsSUFBSSxJQUFJLENBTWpCO0NBQ0YifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iframe_wallet.d.ts","sourceRoot":"","sources":["../../../src/iframe/provider/iframe_wallet.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,wBAAwB,CAAC;AAOnE,OAAO,EAAE,KAAK,kBAAkB,EAA8D,MAAM,gBAAgB,CAAC;AAarH;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAY;IAOrB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,SAAS;IAZnB,OAAO,CAAC,QAAQ,CAAoD;IACpE,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,eAAe,CAA4C;IAEnE,OAAO,eAQH;IAEJ;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,MAAM,CACX,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,MAAM,GACZ,YAAY,CAqCd;IAED;;;OAGG;IACH,QAAQ,IAAI,MAAM,CAEjB;YAEa,uBAAuB;YAyBvB,WAAW;IA0BzB,OAAO,CAAC,gBAAgB;IA0BxB,YAAY,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAQrD;IAED,cAAc,IAAI,OAAO,CAExB;IAED,UAAU,IAAI,IAAI,CAMjB;CACF"}