diff --git a/src/connectors/argentMobile/index.ts b/src/connectors/argentMobile/index.ts index efb672d..f97206c 100644 --- a/src/connectors/argentMobile/index.ts +++ b/src/connectors/argentMobile/index.ts @@ -103,26 +103,16 @@ export class ArgentMobileConnector extends Connector { this._wallet = null } - async account(): Promise { - /* - Don't throw an exception if the wallet is not connected. - This is needed because when argentMobile and webwallet connectors are used together with starknet-react, - it would always try to retrieve the account since the connectors are always available (and throw an exception since the value is null) - - https://github.com/apibara/starknet-react/blob/226e4cb1d8e9b478dc57d45a98a59a57733572bb/packages/core/src/hooks/useAccount.ts#L92 - - */ + async account(): Promise { if (!this._wallet || !this._wallet.account) { - return null + throw new ConnectorNotConnectedError() } return this._wallet.account as AccountInterface } async chainId(): Promise { - this.ensureWallet() - - if (!this._wallet) { + if (!this._wallet || !this.wallet.account || !this._wallet.provider) { throw new ConnectorNotConnectedError() } diff --git a/src/connectors/argentMobile/modal/adapter.ts b/src/connectors/argentMobile/modal/adapter.ts index 637f46c..c0be7c3 100644 --- a/src/connectors/argentMobile/modal/adapter.ts +++ b/src/connectors/argentMobile/modal/adapter.ts @@ -63,7 +63,7 @@ export abstract class NamespaceAdapter { }: SessionTypes.Struct) => { const chain = this.formatChainId(this.chainId) if (requiredNamespaces) { - return !!requiredNamespaces[this.namespace]?.chains.includes(chain) + return !!requiredNamespaces[this.namespace]?.chains?.includes(chain) } return !!namespaces?.[this.namespace]?.accounts.some((account) => account.startsWith(chain), diff --git a/src/connectors/argentMobile/modal/index.ts b/src/connectors/argentMobile/modal/index.ts index 35231f9..1c799a5 100644 --- a/src/connectors/argentMobile/modal/index.ts +++ b/src/connectors/argentMobile/modal/index.ts @@ -11,4 +11,5 @@ export type { StarknetWindowObject, IArgentLoginOptions } export const getStarknetWindowObject = async ( options: IArgentLoginOptions, -): Promise => login(options, StarknetAdapter) +): Promise => + login(options, StarknetAdapter) diff --git a/src/connectors/argentMobile/modal/login.ts b/src/connectors/argentMobile/modal/login.ts index 46e1a1d..8db7d64 100644 --- a/src/connectors/argentMobile/modal/login.ts +++ b/src/connectors/argentMobile/modal/login.ts @@ -37,7 +37,14 @@ export const login = async ( walletConnect, }: IArgentLoginOptions, Adapter: new (options: NamespaceAdapterOptions) => TAdapter, -): Promise => { +): Promise => { + if (!bridgeUrl) { + throw new Error("bridgeUrl is required") + } + + if (!mobileUrl) { + throw new Error("mobileUrl is required") + } argentModal.bridgeUrl = bridgeUrl argentModal.mobileUrl = mobileUrl argentModal.type = modalType @@ -101,6 +108,7 @@ export const login = async ( } catch (error) { console.error("@argent/login::error") argentModal.closeModal() + return null } } diff --git a/src/connectors/injected/constants.ts b/src/connectors/injected/constants.ts index 296d2cb..36d0e95 100644 --- a/src/connectors/injected/constants.ts +++ b/src/connectors/injected/constants.ts @@ -2,7 +2,5 @@ export const ARGENT_X_ICON = `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhl // Icons used when the injected wallet is not found and no icon is provided. // question-mark-circle from heroicons with color changed to black/white. -export const WALLET_NOT_FOUND_ICON_LIGHT = - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iYmxhY2siPgogIDxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTkuODc5IDcuNTE5YzEuMTcxLTEuMDI1IDMuMDcxLTEuMDI1IDQuMjQyIDAgMS4xNzIgMS4wMjUgMS4xNzIgMi42ODcgMCAzLjcxMi0uMjAzLjE3OS0uNDMuMzI2LS42Ny40NDItLjc0NS4zNjEtMS40NS45OTktMS40NSAxLjgyN3YuNzVNMjEgMTJhOSA5IDAgMTEtMTggMCA5IDkgMCAwMTE4IDB6bS05IDUuMjVoLjAwOHYuMDA4SDEydi0uMDA4eiIgLz4KPC9zdmc+" -export const WALLET_NOT_FOUND_ICON_DARK = - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0id2hpdGUiPgogIDxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTkuODc5IDcuNTE5YzEuMTcxLTEuMDI1IDMuMDcxLTEuMDI1IDQuMjQyIDAgMS4xNzIgMS4wMjUgMS4xNzIgMi42ODcgMCAzLjcxMi0uMjAzLjE3OS0uNDMuMzI2LS42Ny40NDItLjc0NS4zNjEtMS40NS45OTktMS40NSAxLjgyN3YuNzVNMjEgMTJhOSA5IDAgMTEtMTggMCA5IDkgMCAwMTE4IDB6bS05IDUuMjVoLjAwOHYuMDA4SDEydi0uMDA4eiIgLz4KPC9zdmc+Cg==" +export const WALLET_NOT_FOUND_ICON_LIGHT = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iYmxhY2siPgogIDxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTkuODc5IDcuNTE5YzEuMTcxLTEuMDI1IDMuMDcxLTEuMDI1IDQuMjQyIDAgMS4xNzIgMS4wMjUgMS4xNzIgMi42ODcgMCAzLjcxMi0uMjAzLjE3OS0uNDMuMzI2LS42Ny40NDItLjc0NS4zNjEtMS40NS45OTktMS40NSAxLjgyN3YuNzVNMjEgMTJhOSA5IDAgMTEtMTggMCA5IDkgMCAwMTE4IDB6bS05IDUuMjVoLjAwOHYuMDA4SDEydi0uMDA4eiIgLz4KPC9zdmc+` +export const WALLET_NOT_FOUND_ICON_DARK = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0id2hpdGUiPgogIDxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTkuODc5IDcuNTE5YzEuMTcxLTEuMDI1IDMuMDcxLTEuMDI1IDQuMjQyIDAgMS4xNzIgMS4wMjUgMS4xNzIgMi42ODcgMCAzLjcxMi0uMjAzLjE3OS0uNDMuMzI2LS42Ny40NDItLjc0NS4zNjEtMS40NS45OTktMS40NSAxLjgyN3YuNzVNMjEgMTJhOSA5IDAgMTEtMTggMCA5IDkgMCAwMTE4IDB6bS05IDUuMjVoLjAwOHYuMDA4SDEydi0uMDA4eiIgLz4KPC9zdmc+Cg==` diff --git a/src/connectors/injected/index.ts b/src/connectors/injected/index.ts index 183e997..fb29653 100644 --- a/src/connectors/injected/index.ts +++ b/src/connectors/injected/index.ts @@ -1,5 +1,4 @@ import type { StarknetWindowObject } from "get-starknet-core" -import { getStarknet } from "get-starknet-core" import { AccountInterface, constants } from "starknet" import { ConnectorNotConnectedError, @@ -36,12 +35,14 @@ export class InjectedConnector extends Connector { } available(): boolean { + // This should be awaited ideally but it would break compatibility with + // starknet-react. Do we need to make this async? Is ensureWallet needed? this.ensureWallet() return this._wallet !== undefined } async ready(): Promise { - await this.ensureWallet() + this.ensureWallet() if (!this._wallet) { return false @@ -149,7 +150,7 @@ export class InjectedConnector extends Connector { } async disconnect(): Promise { - await this.ensureWallet() + this.ensureWallet() if (!this.available()) { throw new ConnectorNotFoundError() @@ -160,17 +161,13 @@ export class InjectedConnector extends Connector { } } - async account(): Promise { - await this.ensureWallet() + async account(): Promise { + this.ensureWallet() - if (!this._wallet) { + if (!this._wallet || !this._wallet.account) { throw new ConnectorNotConnectedError() } - if (!this._wallet.account) { - return null - } - return this._wallet.account } @@ -210,12 +207,54 @@ export class InjectedConnector extends Connector { return this._wallet } - private async ensureWallet() { - const starknet = getStarknet() - const installed = await starknet.getAvailableWallets() + private ensureWallet() { + const installed = getAvailableWallets(globalThis) const wallet = installed.filter((w) => w.id === this._options.id)[0] if (wallet) { this._wallet = wallet } } } + +function getAvailableWallets(obj: Record): StarknetWindowObject[] { + return Object.values( + Object.getOwnPropertyNames(obj).reduce< + Record + >((wallets, key) => { + if (key.startsWith("starknet")) { + const wallet = obj[key] + + if (isWalletObject(wallet) && !wallets[wallet.id]) { + wallets[wallet.id] = wallet as StarknetWindowObject + } + } + return wallets + }, {}), + ) +} + +// biome-ignore lint: wallet could be anything +function isWalletObject(wallet: any): boolean { + try { + return ( + wallet && + [ + // wallet's must have methods/members, see IStarknetWindowObject + "request", + "isConnected", + "provider", + "enable", + "isPreauthorized", + "on", + "off", + "version", + "id", + "name", + "icon", + ].every((key) => key in wallet) + ) + } catch (err) { + /* empty */ + } + return false +} diff --git a/src/connectors/webwallet/helpers/trpc.ts b/src/connectors/webwallet/helpers/trpc.ts index ce50a01..b1b5f72 100644 --- a/src/connectors/webwallet/helpers/trpc.ts +++ b/src/connectors/webwallet/helpers/trpc.ts @@ -43,7 +43,7 @@ export const setPopupOptions = ({ : parentTop + parentHeight / 2 - height / 2 popupOrigin = origin ?? popupOrigin - popupLocation = location ?? location + popupLocation = location ?? popupLocation popupParams = `width=${width},height=${height},top=${y},left=${x},toolbar=no,menubar=no,scrollbars=no,location=no,status=no,popup=1` } diff --git a/src/connectors/webwallet/index.ts b/src/connectors/webwallet/index.ts index bd30153..18ddfb8 100644 --- a/src/connectors/webwallet/index.ts +++ b/src/connectors/webwallet/index.ts @@ -16,7 +16,7 @@ import { UserNotConnectedError, UserRejectedRequestError, } from "../../errors" -import { DEFAULT_WEBWALLET_URL } from "./constants" +import { DEFAULT_WEBWALLET_ICON, DEFAULT_WEBWALLET_URL } from "./constants" import { getWebWalletStarknetObject } from "./starknetWindowObject/getWebWalletStarknetObject" let _wallet: StarknetWindowObject | null = null @@ -60,8 +60,8 @@ export class WebWalletConnector extends Connector { get icon(): ConnectorIcons { return { - light: DEFAULT_WEBWALLET_URL, - dark: DEFAULT_WEBWALLET_URL, + light: DEFAULT_WEBWALLET_ICON, + dark: DEFAULT_WEBWALLET_ICON, } } @@ -120,28 +120,18 @@ export class WebWalletConnector extends Connector { this._wallet = _wallet } - /* - Don't throw an exception if the wallet is not connected. - This is needed because when argentMobile and webwallet connectors are used together with starknet-react, - it would always try to retrieve the account since the connectors are always available (and throw an exception since the value is null) - - https://github.com/apibara/starknet-react/blob/226e4cb1d8e9b478dc57d45a98a59a57733572bb/packages/core/src/hooks/useAccount.ts#L92 - - */ - async account(): Promise { + async account(): Promise { this._wallet = _wallet if (!this._wallet || !this._wallet.account) { - return null + throw new ConnectorNotConnectedError() } return this._wallet.account as unknown as AccountInterface } async chainId(): Promise { - this.ensureWallet() - - if (!this._wallet) { + if (!this._wallet || !this.wallet.account || !this._wallet.provider) { throw new ConnectorNotConnectedError() } diff --git a/src/connectors/webwallet/starknetWindowObject/account.ts b/src/connectors/webwallet/starknetWindowObject/account.ts index 2fde840..5a3a525 100644 --- a/src/connectors/webwallet/starknetWindowObject/account.ts +++ b/src/connectors/webwallet/starknetWindowObject/account.ts @@ -61,7 +61,11 @@ export class MessageAccount extends Account implements AccountInterface { height: EXECUTE_POPUP_HEIGHT, location: "/review", }) - if (calls[0] && calls[0].entrypoint === "use_offchain_session") { + if ( + Array.isArray(calls) && + calls[0] && + calls[0].entrypoint === "use_offchain_session" + ) { setPopupOptions({ width: 1, height: 1, diff --git a/src/helpers/mapModalWallets.ts b/src/helpers/mapModalWallets.ts index 9aeb4c0..5b53336 100644 --- a/src/helpers/mapModalWallets.ts +++ b/src/helpers/mapModalWallets.ts @@ -2,6 +2,7 @@ import type { StarknetWindowObject, WalletProvider } from "get-starknet-core" import { Connector } from "../connectors/connector" import { ARGENT_X_ICON } from "../connectors/injected/constants" import type { ModalWallet, StoreVersion } from "../types/modal" +import { isString } from "lodash-es" interface SetConnectorsExpandedParams { availableConnectors: Connector[] @@ -25,13 +26,15 @@ export const mapModalWallets = ({ } return availableConnectors - .map((c) => { + .map((c) => { const installed = installedWallets.find((w) => w.id === c.id) if (installed) { + const installedIcon = + installed.id === "argentX" ? ARGENT_X_ICON : installed.icon return { name: installed.name, id: installed.id, - icon: installed.id === "argentX" ? ARGENT_X_ICON : installed.icon, + icon: { light: installedIcon, dark: installedIcon }, connector: c, } } @@ -44,10 +47,13 @@ export const mapModalWallets = ({ if (discovery) { const { downloads } = discovery + + const discoveryIcon = + discovery.id === "argentX" ? ARGENT_X_ICON : discovery.icon return { name: discovery.name, id: discovery.id, - icon: discovery.id === "argentX" ? ARGENT_X_ICON : discovery.icon, + icon: { light: discoveryIcon, dark: discoveryIcon }, connector: c, download: downloads[storeVersion as keyof typeof downloads], } @@ -62,9 +68,10 @@ export const mapModalWallets = ({ id: c.id, icon: c.icon, connector: c, - title: "title" in c ? c.title : undefined, - subtitle: "subtitle" in c ? c.subtitle : undefined, + title: "title" in c && isString(c.title) ? c.title : undefined, + subtitle: + "subtitle" in c && isString(c.subtitle) ? c.subtitle : undefined, } }) - .filter((c) => c !== null) as ModalWallet[] + .filter((c): c is ModalWallet => c !== null) } diff --git a/src/main.ts b/src/main.ts index dea60ac..ddbb659 100644 --- a/src/main.ts +++ b/src/main.ts @@ -68,7 +68,9 @@ export const connect = async ({ if (wallet) { const connector = availableConnectors.find((c) => c.id === lastWalletId) await connector?.connect() - selectedConnector = connector + if (connector) { + selectedConnector = connector + } return wallet } // otherwise fallback to modal } @@ -93,11 +95,13 @@ export const connect = async ({ dappName, callback: async (value: StarknetWindowObject | null) => { try { - if (value.id !== "argentWebWallet") { + if (value !== null && value.id !== "argentWebWallet") { setStarknetLastConnectedWallet(value.id) } selectedConnector = - availableConnectors.find((c) => c.id === value.id) ?? null + availableConnectors.find( + (c) => value !== null && c.id === value.id, + ) ?? null resolve(value) } finally { setTimeout(() => modal.$destroy()) @@ -116,7 +120,9 @@ export const getSelectedConnectorWallet = () => export const disconnect = async (options: DisconnectOptions = {}) => { removeStarknetLastConnectedWallet() - await selectedConnector.disconnect() + if (selectedConnector) { + await selectedConnector.disconnect() + } selectedConnector = null return sn.disconnect(options) diff --git a/src/modal/ConnectorButton.svelte b/src/modal/ConnectorButton.svelte index 121c9f2..81cb1f4 100644 --- a/src/modal/ConnectorButton.svelte +++ b/src/modal/ConnectorButton.svelte @@ -3,9 +3,11 @@ import type { Connector } from "../connectors/connector" export let wallet: ModalWallet + export let theme: "light" | "dark" | null = null export let cb: (value: Connector | null) => Promise = async () => {} export let loadingItem: string | false = false - const isSvg = wallet.icon.startsWith(" {#if wallet.download} @@ -36,7 +38,7 @@

Install {wallet.name}

- {wallet.name} + {wallet.name} {:else} @@ -91,9 +93,9 @@ Loading... {:else if isSvg} -
{@html wallet.icon}
+
{@html icon}
{:else} - {wallet?.name} + {wallet?.name} {/if} {/if} diff --git a/src/modal/Modal.svelte b/src/modal/Modal.svelte index 5cc346c..c80d88e 100644 --- a/src/modal/Modal.svelte +++ b/src/modal/Modal.svelte @@ -51,7 +51,7 @@ darkModeControlClass = "" } - if (isInAppBrowser) { + if (isInAppBrowser && window?.starknet_argentX) { try { const enabledValue = await sn.enable(window?.starknet_argentX) callback(enabledValue ?? window?.starknet_argentX) @@ -140,7 +140,7 @@
    {#each modalWallets as wallet} - + {/each}
diff --git a/src/types/modal.ts b/src/types/modal.ts index cef77eb..91608dd 100644 --- a/src/types/modal.ts +++ b/src/types/modal.ts @@ -1,5 +1,5 @@ import type { GetWalletOptions } from "get-starknet-core" -import type { Connector } from "../connectors/connector" +import type { Connector, ConnectorIcons } from "../connectors/connector" import type { ArgentMobileConnectorOptions } from "../connectors/argentMobile" export type StoreVersion = "chrome" | "firefox" | "edge" @@ -17,7 +17,7 @@ export interface ConnectOptions extends GetWalletOptions { export type ModalWallet = { name: string id: string - icon: string + icon: ConnectorIcons download?: string subtitle?: string title?: string diff --git a/tsconfig.json b/tsconfig.json index 4a1139e..b458ac4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,9 @@ */ "allowJs": true, "checkJs": true, - "declaration": true + "declaration": true, + "verbatimModuleSyntax": false }, - "include": ["src"] + "include": ["src"], + "exclude": ["node_modules"] }