From 1818adf171bc464b901058b24b52114cf9659d84 Mon Sep 17 00:00:00 2001 From: stanleyyuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:11:53 +0800 Subject: [PATCH 1/6] feat: add event support on MM --- .../virtualWallets/metaMaskVirtualWallet.ts | 146 ++++++++++++++---- 1 file changed, 112 insertions(+), 34 deletions(-) diff --git a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts index 5ef1d63..900de71 100644 --- a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts +++ b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts @@ -1,6 +1,11 @@ import { VirtualWallet } from "../../types" import { init, loadRemote } from "@module-federation/runtime" -import { RpcMessage, StarknetWindowObject } from "@starknet-io/types-js" +import { + RequestFnCall, + RpcMessage, + StarknetWindowObject, + WalletEventHandlers, +} from "@starknet-io/types-js" import { Mutex } from "async-mutex" interface MetaMaskProvider { @@ -87,17 +92,8 @@ export type Eip6963SupportedWallet = { provider: MetaMaskProvider | null } -export type EmptyVirtualWallet = { - swo: StarknetWindowObject | null - on(): void - off(): void - request( - call: Omit, - ): Promise -} - class MetaMaskVirtualWallet - implements VirtualWallet, Eip6963SupportedWallet, EmptyVirtualWallet + implements VirtualWallet, Eip6963SupportedWallet, StarknetWindowObject { id: string = "metamask" name: string = "MetaMask" @@ -106,13 +102,39 @@ class MetaMaskVirtualWallet provider: MetaMaskProvider | null = null swo: StarknetWindowObject | null = null lock: Mutex + version: string = "v2.0.0" constructor() { this.lock = new Mutex() } + /** + * Load and resolve the `StarknetWindowObject`. + * + * @param windowObject The window object. + * @returns A promise to resolve a `StarknetWindowObject`. + */ async loadWallet( windowObject: Record, + ): Promise { + // Using `this.#loadSwoSafe` to prevent the wallet is loading in a racing condition + + await this.#loadSwoSafe(windowObject) + // Whenever trgger function call to `request` / `on` / `off`, + // it will load the wallet into the `this.swo` object and forward the function call to the `this.swo` object. + // Therefore the `MetaMaskVirtualWallet` object actually act as a proxy to the `this.swo` object. + // Thus, to standardize the behavior, we should return the `MetaMaskVirtualWallet` object here, instead of the `this.swo` object. + return this + } + + /** + * Load the remote `StarknetWindowObject` with module federation. + * + * @param windowObject The window object. + * @returns A promise to resolve a `StarknetWindowObject`. + */ + async #loadSwo( + windowObject: Record, ): Promise { if (!this.provider) { this.provider = await detectMetamaskSupport(windowObject) @@ -125,7 +147,7 @@ class MetaMaskVirtualWallet name: "MetaMaskStarknetSnapWallet", alias: "MetaMaskStarknetSnapWallet", entry: - "https://snaps.consensys.io/starknet/get-starknet/v1/remoteEntry.js", //"http://localhost:8082/remoteEntry.js", + "https://snaps.consensys.io/starknet/get-starknet/v1/remoteEntry.js", }, ], }) @@ -149,44 +171,100 @@ class MetaMaskVirtualWallet ) } + /** + * Verify if the hosting machine is support the Wallet or not without loading the wallet itself. + * + * @param windowObject The window object. + * @returns A promise to resolve a boolean value to indicate the support status. + */ async hasSupport(windowObject: Record) { this.provider = await detectMetamaskSupport(windowObject) return this.provider !== null } + /** + * Proxy the RPC request to the `this.swo` object. + * Load the `this.swo` if not loaded. + * + * @param call The RPC API arguments. + * @returns A promise to resolve a response of the proxy RPC API. + */ async request( - arg: Omit, + call: Omit, ): Promise { - const { type } = arg - // `wallet_supportedWalletApi` and `wallet_supportedSpecs` should enabled even if the wallet is not loaded/connected - switch (type) { - case "wallet_supportedWalletApi": - return ["0.7"] as unknown as Data["result"] - case "wallet_supportedSpecs": - return ["0.7"] as unknown as Data["result"] - default: - return this.#handleRequest(arg) - } + return this.#loadSwoSafe().then((swo: StarknetWindowObject) => { + // Forward the request to the `this.swo` object. + // Except RPC `wallet_supportedSpecs` and `wallet_getPermissions`, others API will trigger the Snap to install if not installed + return swo.request( + call as unknown as RequestFnCall, + ) as unknown as Data["result"] + }) } - async #handleRequest( - arg: Omit, - ): Promise { - // Using lock to ensure the load wallet operation is not fall into a racing condirtion + /** + * Subscribe the `accountsChanged` or `networkChanged` event. + * Proxy the subscription to the `this.swo` object. + * Load the `this.swo` if not loaded. + * + * @param event - The event name. + * @param handleEvent - The event handler function. + */ + on( + event: Event, + handleEvent: WalletEventHandlers[Event], + ): void { + this.#loadSwoSafe().then((swo: StarknetWindowObject) => + swo.on(event, handleEvent), + ) + } + + /** + * Un-subscribe the `accountsChanged` or `networkChanged` event for a given handler. + * Proxy the un-subscribe request to the `this.swo` object. + * Load the `this.swo` if not loaded. + * + * @param event - The event name. + * @param handleEvent - The event handler function. + */ + off( + event: Event, + handleEvent: WalletEventHandlers[Event], + ): void { + this.#loadSwoSafe().then((swo: StarknetWindowObject) => + swo.off(event, handleEvent), + ) + } + + /** + * Load the `StarknetWindowObject` safely with lock. + * And prevent the loading operation fall into a racing condirtion. + * + * @returns A promise to resolve a `StarknetWindowObject`. + */ + async #loadSwoSafe( + windowObject: Record = window, + ): Promise { return this.lock.runExclusive(async () => { // Using `this.swo` to prevent the wallet is loaded multiple times if (!this.swo) { - this.swo = await this.loadWallet(window) + this.swo = await this.#loadSwo(windowObject) + this.#bindSwoProperties() } - // forward the request to the actual connect wallet object - // it will also trigger the Snap to install if not installed - return this.swo.request(arg) as unknown as Data["result"] + return this.swo }) } - // MetaMask Snap Wallet does not support `on` and `off` method - on() {} - off() {} + /** + * Bind properties to `MetaMaskVirtualWallet` from `this.swo`. + */ + #bindSwoProperties(): void { + if (this.swo) { + this.version = this.swo.version + this.name = this.swo.name + this.id = this.swo.id + this.icon = this.swo.icon as string + } + } } const metaMaskVirtualWallet = new MetaMaskVirtualWallet() From d35f27c806b398bdfd0718793810eea642652536 Mon Sep 17 00:00:00 2001 From: stanleyyuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:25:28 +0800 Subject: [PATCH 2/6] chore: update comment text --- .../core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts index 900de71..f792268 100644 --- a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts +++ b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts @@ -117,8 +117,7 @@ class MetaMaskVirtualWallet async loadWallet( windowObject: Record, ): Promise { - // Using `this.#loadSwoSafe` to prevent the wallet is loading in a racing condition - + // Using `this.#loadSwoSafe` to prevent the wallet is loading in a racing condition. await this.#loadSwoSafe(windowObject) // Whenever trgger function call to `request` / `on` / `off`, // it will load the wallet into the `this.swo` object and forward the function call to the `this.swo` object. From 5887ee629bd08047a9e5fe0d757569c0fea4c18b Mon Sep 17 00:00:00 2001 From: stanleyyuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:52:29 +0800 Subject: [PATCH 3/6] chore: fix comment text --- .../core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts index f792268..e7feb10 100644 --- a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts +++ b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts @@ -193,7 +193,7 @@ class MetaMaskVirtualWallet ): Promise { return this.#loadSwoSafe().then((swo: StarknetWindowObject) => { // Forward the request to the `this.swo` object. - // Except RPC `wallet_supportedSpecs` and `wallet_getPermissions`, others API will trigger the Snap to install if not installed + // Except RPCs `wallet_supportedSpecs` and `wallet_getPermissions`, other RPCs will trigger the Snap to install if not installed. return swo.request( call as unknown as RequestFnCall, ) as unknown as Data["result"] From 3f030eb3e9c93aa1898d3866ce8f6e4c8e426869 Mon Sep 17 00:00:00 2001 From: stanleyyuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:01:08 +0800 Subject: [PATCH 4/6] chore: resolve comment --- .../virtualWallets/metaMaskVirtualWallet.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts index e7feb10..15558e5 100644 --- a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts +++ b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts @@ -117,12 +117,13 @@ class MetaMaskVirtualWallet async loadWallet( windowObject: Record, ): Promise { - // Using `this.#loadSwoSafe` to prevent the wallet is loading in a racing condition. + // Using `this.#loadSwoSafe` to prevent race condition when the wallet is loading. await this.#loadSwoSafe(windowObject) - // Whenever trgger function call to `request` / `on` / `off`, - // it will load the wallet into the `this.swo` object and forward the function call to the `this.swo` object. - // Therefore the `MetaMaskVirtualWallet` object actually act as a proxy to the `this.swo` object. - // Thus, to standardize the behavior, we should return the `MetaMaskVirtualWallet` object here, instead of the `this.swo` object. + // The `MetaMaskVirtualWallet` object acts as a proxy for the `this.swo` object. + // When `request`, `on`, or `off` is called, the wallet is loaded into `this.swo`, + // and the function call is forwarded to it. + // To maintain consistent behaviour, the `MetaMaskVirtualWallet` + // object (`this`) is returned instead of `this.swo`. return this } @@ -171,10 +172,10 @@ class MetaMaskVirtualWallet } /** - * Verify if the hosting machine is support the Wallet or not without loading the wallet itself. + * Verify if the hosting machine supports the Wallet or not without loading the wallet itself. * * @param windowObject The window object. - * @returns A promise to resolve a boolean value to indicate the support status. + * @returns A promise that resolves to a boolean value to indicate the support status. */ async hasSupport(windowObject: Record) { this.provider = await detectMetamaskSupport(windowObject) @@ -236,7 +237,7 @@ class MetaMaskVirtualWallet /** * Load the `StarknetWindowObject` safely with lock. - * And prevent the loading operation fall into a racing condirtion. + * And prevent the loading operation fall into a racing condition. * * @returns A promise to resolve a `StarknetWindowObject`. */ From 1ee5da9bbdaf4669464cc1e3f94af229f4326433 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Tue, 7 Jan 2025 13:30:03 +0100 Subject: [PATCH 5/6] feat: add cache busting timestmap to remoteEntry.js --- .../core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts index 15558e5..f106765 100644 --- a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts +++ b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts @@ -146,8 +146,7 @@ class MetaMaskVirtualWallet { name: "MetaMaskStarknetSnapWallet", alias: "MetaMaskStarknetSnapWallet", - entry: - "https://snaps.consensys.io/starknet/get-starknet/v1/remoteEntry.js", + entry: `https://snaps.consensys.io/starknet/get-starknet/v1/remoteEntry.js?ts=${Date.now()}`, }, ], }) From bcd4294559f53edb18f40489b297ba19a89ad5a4 Mon Sep 17 00:00:00 2001 From: Florin Dzeladini Date: Tue, 7 Jan 2025 13:40:53 +0100 Subject: [PATCH 6/6] chore: added changeset --- .changeset/long-kangaroos-talk.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/long-kangaroos-talk.md diff --git a/.changeset/long-kangaroos-talk.md b/.changeset/long-kangaroos-talk.md new file mode 100644 index 0000000..63bab2a --- /dev/null +++ b/.changeset/long-kangaroos-talk.md @@ -0,0 +1,6 @@ +--- +"@starknet-io/get-starknet-core": patch +--- + +refactor MetaMask Virtual Wallet to improve event handling, RPC requests, and +wallet initialization logic