diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index 242f11a1..1eef1e90 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -2,8 +2,14 @@ import discovery, { type WalletProvider } from "./discovery" import { LocalStorageWrapper } from "./localStorageStore" import type { GetStarknetOptions, GetStarknetResult } from "./types" import { pipe, ssrSafeWindow } from "./utils" +import { + EVMWalletInfo, + EVMWalletProvider, + detectEVMSupport, +} from "./wallet/EVMWalletBridge" import { filterBy, filterByAuthorized } from "./wallet/filter" import { + isEvmWallet, isFullWallet, isVirtualWallet, isWalletObject, @@ -72,12 +78,38 @@ export function getStarknet( initiateVirtualWallets(windowObject) + let evmWallets: { + provider: EVMWalletProvider | null + info: EVMWalletInfo | null + }[] + + async function isEVMAvailable() { + evmWallets = await detectEVMSupport(windowObject) + } + + isEVMAvailable() + return { getAvailableWallets: async (options = {}) => { const availableWallets = scanObjectForWallets( windowObject, isWalletObject, ) + + evmWallets.forEach((evmWallet) => { + if (evmWallet.provider && evmWallet.info) { + availableWallets.push({ + ...evmWallet.provider, + id: evmWallet.info.name, + name: evmWallet.info.name, + icon: evmWallet.info.icon, + version: evmWallet.info.icon, + on: evmWallet.provider.on, + off: evmWallet.provider.off, + }) + } + }) + return pipe( (_) => filterBy(_, options), (_) => sortBy(_, options.sort), @@ -144,18 +176,36 @@ export function getStarknet( let wallet: StarknetWindowObject if (isVirtualWallet(inputWallet)) { wallet = await resolveVirtualWallet(windowObject, inputWallet) + } else if (isEvmWallet(inputWallet)) { + // Get all detected EVM wallets + const evmWallets = await detectEVMSupport(windowObject) + + // Find the matching wallet in the detected list + const selectedWallet = evmWallets.find( + ({ info }) => info && info.name === inputWallet.name, + ) + + if (selectedWallet && selectedWallet.provider) { + wallet = selectedWallet.provider + } else { + throw new Error("Failed to connect to the selected EVM wallet") + } } else if (isFullWallet(inputWallet)) { wallet = inputWallet } else { throw new Error("Invalid wallet object") } - await wallet.request({ - type: "wallet_requestAccounts", - params: { - silent_mode: options?.silent_mode, - }, - }) + if (isEvmWallet(wallet)) { + await wallet.request({ method: "eth_requestAccounts" }) + } else { + await wallet.request({ + type: "wallet_requestAccounts", + params: { + silent_mode: options?.silent_mode, + }, + }) + } // check for permissions const permissions: Permission[] = await wallet.request({ @@ -165,6 +215,7 @@ export function getStarknet( throw new Error("Failed to connect to wallet") } lastConnectedStore.set(wallet.id) + return wallet }, disconnect: async ({ clearLastWallet } = {}) => { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 49e81bfb..8a825c44 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -41,6 +41,21 @@ export interface VirtualWallet { hasSupport: (windowObject: Record) => Promise } +export interface EvmWallet { + sendAsync?: ( + request: { method: string; params?: Array }, + callback: (error: Error | null, response: unknown) => void, + ) => void + send?: ( + request: { method: string; params?: Array }, + callback: (error: Error | null, response: unknown) => void, + ) => void + request: (request: { + method: string + params?: Array + }) => Promise +} + export const virtualWalletKeys = ensureKeysArray({ id: true, name: true, @@ -60,6 +75,12 @@ export const fullWalletKeys = ensureKeysArray({ off: true, }) +export const evmWalletKeys = ensureKeysArray({ + sendAsync: true, + send: true, + request: true, +}) + export interface GetStarknetResult { getAvailableWallets: ( options?: GetWalletOptions, diff --git a/packages/core/src/wallet/EVMWalletBridge.ts b/packages/core/src/wallet/EVMWalletBridge.ts new file mode 100644 index 00000000..fc3d8a50 --- /dev/null +++ b/packages/core/src/wallet/EVMWalletBridge.ts @@ -0,0 +1,122 @@ +import { WalletEventListener } from "@starknet-io/types-js" +import { RequestFn } from "@starknet-io/types-js" + +interface Request extends RequestFn { + (request: { method: string; params?: Array }): Promise +} + +export interface EVMWalletProvider { + request: Request + on: WalletEventListener + off: WalletEventListener + id: string + name: string + icon: string + version: "1.0.0" +} + +export interface EVMWalletInfo { + icon: string + name: string + rdns: string + uuid: string + version: "1.0.0" +} + +function isEVMProvider(obj: unknown): obj is EVMWalletProvider { + return ( + obj !== null && + typeof obj === "object" && + obj.hasOwnProperty("sendAsync") && + obj.hasOwnProperty("request") + ) +} + +async function detectEVMProvider( + windowObject: Record, + { timeout = 3000 } = {}, +): Promise< + { provider: EVMWalletProvider | null; info: EVMWalletInfo | null }[] +> { + let handled = false + let wallets: { provider: EVMWalletProvider; info: EVMWalletInfo }[] = [] + + return new Promise((resolve) => { + const handleEIP6963Provider = (event: CustomEvent) => { + let { info, provider } = event.detail + + // Rename specific wallet names + if (info.rdns === "com.bitget.web3") { + info = { ...info, name: "Bitget Wallet via Rosettanet" } + } else if (info.rdns === "com.okex.wallet") { + info = { ...info, name: "OKX Wallet via Rosettanet" } + } + + // Avoid duplicates based on unique info.rdns + if (!wallets.some((wallet) => wallet.info.rdns === info.rdns)) { + wallets.push({ info, provider }) + } + + if (isEVMProvider(provider)) { + resolve(wallets) + handled = true + } + } + + if (typeof windowObject.addEventListener === "function") { + windowObject.addEventListener( + "eip6963:announceProvider", + handleEIP6963Provider, + ) + } + + setTimeout(() => { + if (!handled) { + resolve([{ provider: null, info: null }]) + } + }, timeout) + + if (typeof windowObject.dispatchEvent === "function") { + windowObject.dispatchEvent(new Event("eip6963:requestProvider")) + } + }) +} + +async function waitForEVMProvider( + windowObject: Record, + options: { timeout?: number; retries?: number } = {}, +): Promise< + { provider: EVMWalletProvider | null; info: EVMWalletInfo | null }[] +> { + const { timeout = 3000, retries = 0 } = options + + try { + const result = await detectEVMProvider(windowObject, { timeout }) + if (result[0].provider) { + return result + } + } catch { + // Silent error + } + + if (retries === 0) { + return [{ provider: null, info: null }] + } + + return waitForEVMProvider(windowObject, { + timeout, + retries: retries - 1, + }) +} + +export async function detectEVMSupport( + windowObject: Record, +): Promise< + { provider: EVMWalletProvider | null; info: EVMWalletInfo | null }[] +> { + const result = await waitForEVMProvider(windowObject, { + retries: 3, + }) + + return result // Returns provider or null +} diff --git a/packages/core/src/wallet/isWalletObject.ts b/packages/core/src/wallet/isWalletObject.ts index af2b21cf..55d6f228 100644 --- a/packages/core/src/wallet/isWalletObject.ts +++ b/packages/core/src/wallet/isWalletObject.ts @@ -1,4 +1,4 @@ -import { fullWalletKeys, virtualWalletKeys } from "../types" +import { evmWalletKeys, fullWalletKeys, virtualWalletKeys } from "../types" function createWalletGuard(keys: (keyof T)[]) { return function hasKeys(obj: unknown): obj is T { @@ -12,11 +12,15 @@ const isFullWallet = createWalletGuard(fullWalletKeys) const isVirtualWallet = createWalletGuard(virtualWalletKeys) +const isEvmWallet = createWalletGuard(evmWalletKeys) + function isWalletObject(wallet: unknown): boolean { try { - return isFullWallet(wallet) || isVirtualWallet(wallet) + return ( + isFullWallet(wallet) || isVirtualWallet(wallet) || isEvmWallet(wallet) + ) } catch (err) {} return false } -export { isVirtualWallet, isFullWallet, isWalletObject } +export { isVirtualWallet, isFullWallet, isWalletObject, isEvmWallet } diff --git a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts index f1067651..c91af657 100644 --- a/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts +++ b/packages/core/src/wallet/virtualWallets/metaMaskVirtualWallet.ts @@ -95,9 +95,9 @@ export type Eip6963SupportedWallet = { class MetaMaskVirtualWallet implements VirtualWallet, Eip6963SupportedWallet, StarknetWindowObject { - id: string = "metamask" - name: string = "MetaMask" - icon: string = `data:image/svg+xml;utf8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMTIiIGhlaWdodD0iMTg5IiB2aWV3Qm94PSIwIDAgMjEyIDE4OSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cG9seWdvbiBmaWxsPSIjQ0RCREIyIiBwb2ludHM9IjYwLjc1IDE3My4yNSA4OC4zMTMgMTgwLjU2MyA4OC4zMTMgMTcxIDkwLjU2MyAxNjguNzUgMTA2LjMxMyAxNjguNzUgMTA2LjMxMyAxODAgMTA2LjMxMyAxODcuODc1IDg5LjQzOCAxODcuODc1IDY4LjYyNSAxNzguODc1Ii8+PHBvbHlnb24gZmlsbD0iI0NEQkRCMiIgcG9pbnRzPSIxMDUuNzUgMTczLjI1IDEzMi43NSAxODAuNTYzIDEzMi43NSAxNzEgMTM1IDE2OC43NSAxNTAuNzUgMTY4Ljc1IDE1MC43NSAxODAgMTUwLjc1IDE4Ny44NzUgMTMzLjg3NSAxODcuODc1IDExMy4wNjMgMTc4Ljg3NSIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMjU2LjUgMCkiLz48cG9seWdvbiBmaWxsPSIjMzkzOTM5IiBwb2ludHM9IjkwLjU2MyAxNTIuNDM4IDg4LjMxMyAxNzEgOTEuMTI1IDE2OC43NSAxMjAuMzc1IDE2OC43NSAxMjMuNzUgMTcxIDEyMS41IDE1Mi40MzggMTE3IDE0OS42MjUgOTQuNSAxNTAuMTg4Ii8+PHBvbHlnb24gZmlsbD0iI0Y4OUMzNSIgcG9pbnRzPSI3NS4zNzUgMjcgODguODc1IDU4LjUgOTUuMDYzIDE1MC4xODggMTE3IDE1MC4xODggMTIzLjc1IDU4LjUgMTM2LjEyNSAyNyIvPjxwb2x5Z29uIGZpbGw9IiNGODlEMzUiIHBvaW50cz0iMTYuMzEzIDk2LjE4OCAuNTYzIDE0MS43NSAzOS45MzggMTM5LjUgNjUuMjUgMTM5LjUgNjUuMjUgMTE5LjgxMyA2NC4xMjUgNzkuMzEzIDU4LjUgODMuODEzIi8+PHBvbHlnb24gZmlsbD0iI0Q4N0MzMCIgcG9pbnRzPSI0Ni4xMjUgMTAxLjI1IDkyLjI1IDEwMi4zNzUgODcuMTg4IDEyNiA2NS4yNSAxMjAuMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VBOEQzQSIgcG9pbnRzPSI0Ni4xMjUgMTAxLjgxMyA2NS4yNSAxMTkuODEzIDY1LjI1IDEzNy44MTMiLz48cG9seWdvbiBmaWxsPSIjRjg5RDM1IiBwb2ludHM9IjY1LjI1IDEyMC4zNzUgODcuNzUgMTI2IDk1LjA2MyAxNTAuMTg4IDkwIDE1MyA2NS4yNSAxMzguMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VCOEYzNSIgcG9pbnRzPSI2NS4yNSAxMzguMzc1IDYwLjc1IDE3My4yNSA5MC41NjMgMTUyLjQzOCIvPjxwb2x5Z29uIGZpbGw9IiNFQThFM0EiIHBvaW50cz0iOTIuMjUgMTAyLjM3NSA5NS4wNjMgMTUwLjE4OCA4Ni42MjUgMTI1LjcxOSIvPjxwb2x5Z29uIGZpbGw9IiNEODdDMzAiIHBvaW50cz0iMzkuMzc1IDEzOC45MzggNjUuMjUgMTM4LjM3NSA2MC43NSAxNzMuMjUiLz48cG9seWdvbiBmaWxsPSIjRUI4RjM1IiBwb2ludHM9IjEyLjkzOCAxODguNDM4IDYwLjc1IDE3My4yNSAzOS4zNzUgMTM4LjkzOCAuNTYzIDE0MS43NSIvPjxwb2x5Z29uIGZpbGw9IiNFODgyMUUiIHBvaW50cz0iODguODc1IDU4LjUgNjQuNjg4IDc4Ljc1IDQ2LjEyNSAxMDEuMjUgOTIuMjUgMTAyLjkzOCIvPjxwb2x5Z29uIGZpbGw9IiNERkNFQzMiIHBvaW50cz0iNjAuNzUgMTczLjI1IDkwLjU2MyAxNTIuNDM4IDg4LjMxMyAxNzAuNDM4IDg4LjMxMyAxODAuNTYzIDY4LjA2MyAxNzYuNjI1Ii8+PHBvbHlnb24gZmlsbD0iI0RGQ0VDMyIgcG9pbnRzPSIxMjEuNSAxNzMuMjUgMTUwLjc1IDE1Mi40MzggMTQ4LjUgMTcwLjQzOCAxNDguNSAxODAuNTYzIDEyOC4yNSAxNzYuNjI1IiB0cmFuc2Zvcm09Im1hdHJpeCgtMSAwIDAgMSAyNzIuMjUgMCkiLz48cG9seWdvbiBmaWxsPSIjMzkzOTM5IiBwb2ludHM9IjcwLjMxMyAxMTIuNSA2NC4xMjUgMTI1LjQzOCA4Ni4wNjMgMTE5LjgxMyIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMTUwLjE4OCAwKSIvPjxwb2x5Z29uIGZpbGw9IiNFODhGMzUiIHBvaW50cz0iMTIuMzc1IC41NjMgODguODc1IDU4LjUgNzUuOTM4IDI3Ii8+PHBhdGggZmlsbD0iIzhFNUEzMCIgZD0iTTEyLjM3NTAwMDIsMC41NjI1MDAwMDggTDIuMjUwMDAwMDMsMzEuNTAwMDAwNSBMNy44NzUwMDAxMiw2NS4yNTAwMDEgTDMuOTM3NTAwMDYsNjcuNTAwMDAxIEw5LjU2MjUwMDE0LDcyLjU2MjUgTDUuMDYyNTAwMDgsNzYuNTAwMDAxMSBMMTEuMjUsODIuMTI1MDAxMiBMNy4zMTI1MDAxMSw4NS41MDAwMDEzIEwxNi4zMTI1MDAyLDk2Ljc1MDAwMTQgTDU4LjUwMDAwMDksODMuODEyNTAxMiBDNzkuMTI1MDAxMiw2Ny4zMTI1MDA0IDg5LjI1MDAwMTMsNTguODc1MDAwMyA4OC44NzUwMDEzLDU4LjUwMDAwMDkgQzg4LjUwMDAwMTMsNTguMTI1MDAwOSA2My4wMDAwMDA5LDM4LjgxMjUwMDYgMTIuMzc1MDAwMiwwLjU2MjUwMDAwOCBaIi8+PGcgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMjExLjUgMCkiPjxwb2x5Z29uIGZpbGw9IiNGODlEMzUiIHBvaW50cz0iMTYuMzEzIDk2LjE4OCAuNTYzIDE0MS43NSAzOS45MzggMTM5LjUgNjUuMjUgMTM5LjUgNjUuMjUgMTE5LjgxMyA2NC4xMjUgNzkuMzEzIDU4LjUgODMuODEzIi8+PHBvbHlnb24gZmlsbD0iI0Q4N0MzMCIgcG9pbnRzPSI0Ni4xMjUgMTAxLjI1IDkyLjI1IDEwMi4zNzUgODcuMTg4IDEyNiA2NS4yNSAxMjAuMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VBOEQzQSIgcG9pbnRzPSI0Ni4xMjUgMTAxLjgxMyA2NS4yNSAxMTkuODEzIDY1LjI1IDEzNy44MTMiLz48cG9seWdvbiBmaWxsPSIjRjg5RDM1IiBwb2ludHM9IjY1LjI1IDEyMC4zNzUgODcuNzUgMTI2IDk1LjA2MyAxNTAuMTg4IDkwIDE1MyA2NS4yNSAxMzguMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VCOEYzNSIgcG9pbnRzPSI2NS4yNSAxMzguMzc1IDYwLjc1IDE3My4yNSA5MCAxNTMiLz48cG9seWdvbiBmaWxsPSIjRUE4RTNBIiBwb2ludHM9IjkyLjI1IDEwMi4zNzUgOTUuMDYzIDE1MC4xODggODYuNjI1IDEyNS43MTkiLz48cG9seWdvbiBmaWxsPSIjRDg3QzMwIiBwb2ludHM9IjM5LjM3NSAxMzguOTM4IDY1LjI1IDEzOC4zNzUgNjAuNzUgMTczLjI1Ii8+PHBvbHlnb24gZmlsbD0iI0VCOEYzNSIgcG9pbnRzPSIxMi45MzggMTg4LjQzOCA2MC43NSAxNzMuMjUgMzkuMzc1IDEzOC45MzggLjU2MyAxNDEuNzUiLz48cG9seWdvbiBmaWxsPSIjRTg4MjFFIiBwb2ludHM9Ijg4Ljg3NSA1OC41IDY0LjY4OCA3OC43NSA0Ni4xMjUgMTAxLjI1IDkyLjI1IDEwMi45MzgiLz48cG9seWdvbiBmaWxsPSIjMzkzOTM5IiBwb2ludHM9IjcwLjMxMyAxMTIuNSA2NC4xMjUgMTI1LjQzOCA4Ni4wNjMgMTE5LjgxMyIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMTUwLjE4OCAwKSIvPjxwb2x5Z29uIGZpbGw9IiNFODhGMzUiIHBvaW50cz0iMTIuMzc1IC41NjMgODguODc1IDU4LjUgNzUuOTM4IDI3Ii8+PHBhdGggZmlsbD0iIzhFNUEzMCIgZD0iTTEyLjM3NTAwMDIsMC41NjI1MDAwMDggTDIuMjUwMDAwMDMsMzEuNTAwMDAwNSBMNy44NzUwMDAxMiw2NS4yNTAwMDEgTDMuOTM3NTAwMDYsNjcuNTAwMDAxIEw5LjU2MjUwMDE0LDcyLjU2MjUgTDUuMDYyNTAwMDgsNzYuNTAwMDAxMSBMMTEuMjUsODIuMTI1MDAxMiBMNy4zMTI1MDAxMSw4NS41MDAwMDEzIEwxNi4zMTI1MDAyLDk2Ljc1MDAwMTQgTDU4LjUwMDAwMDksODMuODEyNTAxMiBDNzkuMTI1MDAxMiw2Ny4zMTI1MDA0IDg5LjI1MDAwMTMsNTguODc1MDAwMyA4OC44NzUwMDEzLDU4LjUwMDAwMDkgQzg4LjUwMDAwMTMsNTguMTI1MDAwOSA2My4wMDAwMDA5LDM4LjgxMjUwMDYgMTIuMzc1MDAwMiwwLjU2MjUwMDAwOCBaIi8+PC9nPjwvZz48L3N2Zz4=` + id: string = "metamask-snaps" + name: string = "MetaMask Snaps" + icon: string = `data:image/svg+xml;utf8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAxNDIgMTM3Ij4KICA8cGF0aCBmaWxsPSIjMEEwQTBBIiBkPSJtMTMxLjIxNSAxMzAuNzI3LTI5Ljk3Ni04Ljg4My0yMi42MDQgMTMuNDQ5SDYyLjg2MWwtMjIuNjE5LTEzLjQ0OS0yOS45NiA4Ljg4My05LjExLTMwLjYzIDkuMTE3LTMzLjk5Mi05LjExNy0yOC43NDIgOS4xMS0zNS42MTggNDYuODE3IDI3Ljg0N2gyNy4yOThsNDYuODE4LTI3Ljg0NyA5LjExNyAzNS42MTgtOS4xMTcgMjguNzQyIDkuMTE3IDMzLjk5Mi05LjExNyAzMC42M1oiLz4KICA8cGF0aCBmaWxsPSIjODlCMEZGIiBkPSJtMTM4LjgyOCAxMDEuMjE5LTguMzY0IDI4LjEwMy0yOC4wODgtOC4zMzUtMi4yNTctLjY2OS0zLjIxOS0uOTU2LTEzLjc4LTQuMDkyLTEuMjA0LjE1OC0uNDY2IDEuNyAxNy4wMTUgNS4wNDgtMjAuMTQ1IDExLjk5SDYzLjE5M2wtMjAuMTQ0LTExLjk5IDE3LjAwOC01LjA0LS40NjctMS43MDgtMS4xOTYtLjE1OC0xNy4wMDcgNS4wNDgtMi4yNTcuNjY5LTI4LjA4IDguMzM1LTguMzY1LTI4LjEwM0wwIDEwMC4xMjFsOS41MyAzMi4wMDYgMzAuNTctOS4wNzkgMjIuNDY5IDEzLjM3NGgxNi4zNzZsMjIuNDY4LTEzLjM3NCAzMC41NyA5LjA3OSA5LjUyMy0zMi4wMDYtMi42NzggMS4wOThaIi8+CiAgPHBhdGggZmlsbD0iI0QwNzVGRiIgZD0iTTM5LjEzIDEwMS4yMTh2MTkuNzY4bDIuMjU3LS42Njl2LTE3Ljk0OGwxNy4wMDcgMTIuOSAxLjE5Ni4xNTggMS4xMTMtMS4yNDEtMjAuMDc2LTE1LjIyNUgyLjY0N2w4LjUwOC0zMS43MjgtMi4wMzgtMS4xMDZMMCAxMDAuMTJsMi42ODUgMS4wOThIMzkuMTNabTcwLjEyOC0xNy44MjctNy4yMjEgMS43ODN2Mi4zMzJsMTAuNjM2LTIuNjMzLjA2OC0xNy42NGgtMS40OTdsLS43Ni0uNTE4LS4wNiAxNC42Ni04LjcxOC04LjIyOUg4My42MTVsLS4zNDYgMi4yNjRoMTcuNTQybDguNDQ3IDcuOTgxWiIvPgogIDxwYXRoIGZpbGw9IiNEMDc1RkYiIGQ9Ik0zOS40NzUgODcuNTA2di0yLjMzMmwtNy4yMjItMS43ODMgOC40NDgtNy45OGgxNy41MzRsLS4zNDYtMi4yNjVINDAuMjQybC0uNzc1LjMwOS04LjM4IDcuOTItLjA2LTE0LjY2LS43Ni41MTloLTEuNTA0bC4wNjggMTcuNjQgMTAuNjQ0IDIuNjMyWm05MC44NzctMjAuMjczIDguNTA4IDMxLjcyOGgtMzcuOTc5bC0yMC4wNzcgMTUuMjI1IDEuMTE0IDEuMjQxIDEuMjAzLS4xNTggMTctMTIuOXYxNy45NDhsMi4yNTcuNjY5di0xOS43NjhoMzYuNDUybDIuNjc4LTEuMDk4LTkuMTEtMzMuOTkzLTIuMDQ2IDEuMTA2WiIvPgogIDxwYXRoIGZpbGw9IiNGRjVDMTYiIGQ9Ik0yOC43NjUgNjcuMjMzaDEuNTA0bC43Ni0uNTIgMjMuMzg2LTE2LjAyMSAzLjQ4MyAyMi40Ni4zNDYgMi4yNjUgNS40OTEgMzUuNDIyIDEuOTU2LS43OWguMjAzbC05LjUwOC02MS4zNSAxLjc1Mi0xNy45NzFoMjUuMjM3TDg1LjEyIDQ4LjcybC05LjUwOCA2MS4zMjhoLjIwNGwxLjk1NS43OSA1LjQ5MS0zNS40MjIuMzQ2LTIuMjY0aC4wMDhsMy40ODMtMjIuNDYxIDIzLjM3OCAxNi4wMjIuNzYuNTI2aDE5LjExNGwyLjAzOC0xLjEwNSA5LjExLTI4LjczNUwxMzEuOTM4IDAgODQuMTIgMjguNDY0SDU3LjM5NEw5LjU2OCAwIDAgMzcuNGw5LjExIDI4LjczNSAyLjAzOCAxLjEwNWgxNy42MWwuMDA3LS4wMDdabTExMC4zOTQtMjkuOS04Ljc3IDI3LjY0M2gtMTguNDIybC0yMy45NzMtMTYuNDIgNDIuNjM1LTQ0LjU2MiA4LjUzIDMzLjMzOFpNMTI0LjY3MiA2Ljk1NyA4Ny4xNTIgNDYuMTdsLTEuNTU4LTE1Ljk1NSAzOS4wNzgtMjMuMjU4Wm0tNjguNzYgMjMuMjUtMS41NSAxNS45NjMtMzcuNTItMzkuMjIgMzkuMDcgMjMuMjV2LjAwOFpNMi4zNDcgMzcuMzMzbDguNTMtMzMuMzM4IDQyLjYzNSA0NC41NjEtMjMuOTcyIDE2LjQySDExLjExOEwyLjM0NyAzNy4zMzJaIi8+CiAgPHBhdGggZmlsbD0iI0JBRjI0QSIgZD0iTTc3LjA3IDExMC4wNDlINjQuNDQybC00Ljg1MiA1LjM3OSAyLjQxNSA4LjgwOGgxNy40ODlsMi40MTUtOC44MDgtNC44NTItNS4zNzloLjAxNVptLjcgMTEuOTNINjMuNzVsLTEuNjQtNS45NzIgMy4zMTctMy42NzloMTAuNjY2bDMuMzE3IDMuNjc5LTEuNjQgNS45NzJaTTU4LjI2IDkwLjgwN2wtLjIxMS0uNTV2LS4wMTRsLTMuNzM5LTkuNjg5SDQ0LjJsLTQuNzIzIDQuNjE5djIuMzI0bDE2LjY3NiA0LjEyMiAyLjEwNi0uODEyWm0tMTMuMTQyLTcuOTg5aDcuNjQzbDIuNCA2LjIxNC0xMy4xMDQtMy4yMzUgMy4wNTQtMi45NzhoLjAwN1ptNDAuMjI4IDguODAyIDE2LjY3Ny00LjEyMXYtMi4zMjVsLTQuNzI0LTQuNjFoLTEwLjExbC0zLjczOCA5LjY4di4wMTVsLS4yMTEuNTUgMi4xMDYuODEyWm0xNC4wOS01LjgyMi0xMy4xMDQgMy4yMzUgMi40LTYuMjJoNy42NDJsMy4wNTQgMi45ODZoLjAwN1oiLz4KPC9zdmc+Cg==` windowKey: string = "starknet_metamask" provider: MetaMaskProvider | null = null swo: StarknetWindowObject | null = null