Skip to content
Open
63 changes: 57 additions & 6 deletions packages/core/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<StarknetWindowObject[]>(
(_) => filterBy(_, options),
(_) => sortBy(_, options.sort),
Expand Down Expand Up @@ -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({
Expand All @@ -165,6 +215,7 @@ export function getStarknet(
throw new Error("Failed to connect to wallet")
}
lastConnectedStore.set(wallet.id)

return wallet
},
disconnect: async ({ clearLastWallet } = {}) => {
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ export interface VirtualWallet {
hasSupport: (windowObject: Record<string, unknown>) => Promise<boolean>
}

export interface EvmWallet {
sendAsync?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void,
) => void
send?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void,
) => void
request: (request: {
method: string
params?: Array<unknown>
}) => Promise<unknown>
}

export const virtualWalletKeys = ensureKeysArray<VirtualWallet>({
id: true,
name: true,
Expand All @@ -60,6 +75,12 @@ export const fullWalletKeys = ensureKeysArray<StarknetWindowObject>({
off: true,
})

export const evmWalletKeys = ensureKeysArray<EvmWallet>({
sendAsync: true,
send: true,
request: true,
})

export interface GetStarknetResult {
getAvailableWallets: (
options?: GetWalletOptions,
Expand Down
122 changes: 122 additions & 0 deletions packages/core/src/wallet/EVMWalletBridge.ts
Original file line number Diff line number Diff line change
@@ -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<unknown> }): Promise<unknown>
}

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<string, unknown>,
{ 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<string, unknown>,
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<string, unknown>,
): Promise<
{ provider: EVMWalletProvider | null; info: EVMWalletInfo | null }[]
> {
const result = await waitForEVMProvider(windowObject, {
retries: 3,
})

return result // Returns provider or null
}
10 changes: 7 additions & 3 deletions packages/core/src/wallet/isWalletObject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fullWalletKeys, virtualWalletKeys } from "../types"
import { evmWalletKeys, fullWalletKeys, virtualWalletKeys } from "../types"

function createWalletGuard<T>(keys: (keyof T)[]) {
return function hasKeys(obj: unknown): obj is T {
Expand All @@ -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 }
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down