From bf871d27e0522a9c9c583b80d2023110085524a6 Mon Sep 17 00:00:00 2001 From: bluecco Date: Mon, 1 Jul 2024 12:12:49 +0200 Subject: [PATCH] fix: update starknetkit to be compatible with starknet-react v3 --- package.json | 8 +- pnpm-lock.yaml | 39 +++ renovate.json | 7 +- src/connectors/argentMobile/index.ts | 48 +++- src/connectors/connector.ts | 31 ++- src/connectors/injected/index.ts | 260 +++++++++--------- .../webwallet/helpers/openWebwallet.ts | 1 - src/connectors/webwallet/index.ts | 59 ++-- src/helpers/defaultConnectors.ts | 6 +- src/helpers/mapModalWallets.ts | 10 +- src/main.ts | 9 +- src/modal/ConnectorButton.svelte | 15 +- src/modal/Modal.svelte | 8 +- src/window/modal.ts | 10 +- src/window/window.ts | 4 +- 15 files changed, 316 insertions(+), 199 deletions(-) diff --git a/package.json b/package.json index 513b112..3489fa0 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,8 @@ "dev": "vite build --watch" }, "dependencies": { + "@starknet-io/get-starknet": "^4.0.0", + "@starknet-io/types-js": "^0.7.7", "@trpc/client": "^10.38.1", "@trpc/server": "^10.38.1", "@walletconnect/sign-client": "^2.11.0", @@ -99,6 +101,8 @@ "prettier-plugin-import-sort": "^0.0.7", "semantic-release": "^21.1.1", "starknet-types": "^0.7.2", + "starknet4": "npm:starknet@4.22.0", + "starknet5": "npm:starknet@5.29.0", "svelte": "^4.0.0", "svelte-check": "^3.5.1", "svelte-preprocess": "^5.0.4", @@ -109,9 +113,7 @@ "vite-plugin-dts": "^3.0.0", "vitest": "^1.6.0", "ws": "^8.8.1", - "zod": "^3.20.6", - "starknet4": "npm:starknet@4.22.0", - "starknet5": "npm:starknet@5.29.0" + "zod": "^3.20.6" }, "peerDependencies": { "starknet": "^6.9.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aaeedc2..eda5ec9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@starknet-io/get-starknet': + specifier: ^4.0.0 + version: 4.0.0 + '@starknet-io/types-js': + specifier: ^0.7.7 + version: 0.7.7 '@trpc/client': specifier: ^10.38.1 version: 10.45.2(@trpc/server@10.45.2) @@ -696,6 +702,12 @@ packages: '@microsoft/tsdoc@0.14.2': resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + '@module-federation/runtime@0.1.21': + resolution: {integrity: sha512-/p4BhZ0SnjJuiL0wwu+FebFgIUJ9vM+oCY7CyprUHImyi/Y23ulI61WNWMVrKQGgdMoXQDQCL8RH4EnrVP2ZFw==} + + '@module-federation/sdk@0.1.21': + resolution: {integrity: sha512-r7xPiAm+O4e+8Zvw+8b4ToeD0D0VJD004nHmt+Y8r/l98J2eA6di72Vn1FeyjtQbCrFtiMw3ts/dlqtcmIBipw==} + '@motionone/animation@10.18.0': resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==} @@ -1163,6 +1175,15 @@ packages: '@stablelib/x25519@1.0.3': resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} + '@starknet-io/get-starknet-core@4.0.0': + resolution: {integrity: sha512-M++JTbMxZJ5wCkw1f4vAXCY3BTlRMdxFScqsIgZonLXD3GKHPyM/pFi/JqorPO1o4RKHLnFX6M7r0izZ/NWpvA==} + + '@starknet-io/get-starknet@4.0.0': + resolution: {integrity: sha512-SmnRzBewS0BVjtKzViSrWXi+SvOnSrj9hnvlx8B3ZnCq9A2NuX8pNI550lDBLl/ilIr587FH2VNAj6jdgsyhJQ==} + + '@starknet-io/types-js@0.7.7': + resolution: {integrity: sha512-WLrpK7LIaIb8Ymxu6KF/6JkGW1sso988DweWu7p5QY/3y7waBIiPvzh27D9bX5KIJNRDyOoOVoHVEKYUYWZ/RQ==} + '@sveltejs/vite-plugin-svelte-inspector@1.0.4': resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==} engines: {node: ^14.18.0 || >= 16} @@ -5427,6 +5448,12 @@ snapshots: '@microsoft/tsdoc@0.14.2': {} + '@module-federation/runtime@0.1.21': + dependencies: + '@module-federation/sdk': 0.1.21 + + '@module-federation/sdk@0.1.21': {} + '@motionone/animation@10.18.0': dependencies: '@motionone/easing': 10.18.0 @@ -5942,6 +5969,18 @@ snapshots: '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 + '@starknet-io/get-starknet-core@4.0.0': + dependencies: + '@module-federation/runtime': 0.1.21 + '@starknet-io/types-js': 0.7.7 + + '@starknet-io/get-starknet@4.0.0': + dependencies: + '@starknet-io/get-starknet-core': 4.0.0 + bowser: 2.11.0 + + '@starknet-io/types-js@0.7.7': {} + '@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.5.3(svelte@4.2.18)(vite@4.5.3(@types/node@20.14.2)))(svelte@4.2.18)(vite@4.5.3(@types/node@20.14.2))': dependencies: '@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.2.18)(vite@4.5.3(@types/node@20.14.2)) diff --git a/renovate.json b/renovate.json index e0f7b7b..b3d4141 100644 --- a/renovate.json +++ b/renovate.json @@ -5,12 +5,15 @@ "timezone": "Europe/London", "rebaseWhen": "never", "ignoreDeps": [ - "starknet-types", + "starknet", "starknet4-deprecated", "starknet4", "starknet5", "get-starknet-core", - "get-starknet-coreV3" + "get-starknet-coreV3", + "starknet-types", + "@starknet-io/types-js", + "@starknet-io/get-starknet" ], "packageRules": [ { diff --git a/src/connectors/argentMobile/index.ts b/src/connectors/argentMobile/index.ts index 073e2ff..7bbc5d0 100644 --- a/src/connectors/argentMobile/index.ts +++ b/src/connectors/argentMobile/index.ts @@ -1,6 +1,18 @@ import { type AccountChangeEventHandler } from "get-starknet-core" -import { constants } from "starknet" -import { Permission, type StarknetWindowObject } from "starknet-types" +import { + AccountInterface, + ProviderInterface, + ProviderOptions, + WalletAccount, + constants, +} from "starknet" +import { + Permission, + RequestFnCall, + RpcMessage, + RpcTypeToMessageMap, + type StarknetWindowObject, +} from "starknet-types" import { ConnectorNotConnectedError, ConnectorNotFoundError, @@ -110,20 +122,17 @@ export class ArgentMobileConnector extends Connector { this._wallet = null } - async account(): Promise { + async account( + provider: ProviderOptions | ProviderInterface, + ): Promise { if (!this._wallet) { throw new ConnectorNotConnectedError() } - const [account] = await this._wallet.request({ - type: "wallet_requestAccounts", - params: { silent_mode: true }, - }) - - return account ?? null + return new WalletAccount(provider, this._wallet) } - async chainId(): Promise { + async chainId(): Promise { if (!this._wallet) { throw new ConnectorNotConnectedError() } @@ -132,7 +141,24 @@ export class ArgentMobileConnector extends Connector { type: "wallet_requestChainId", }) - return getStarknetChainId(chainId) + const hexChainId = getStarknetChainId(chainId) + return BigInt(hexChainId) + } + + async request( + call: RequestFnCall, + ): Promise { + this.ensureWallet() + + if (!this._wallet) { + throw new ConnectorNotConnectedError() + } + + try { + return await this._wallet.request(call) + } catch { + throw new UserRejectedRequestError() + } } // needed, methods required by starknet-react. Otherwise an exception is throwd diff --git a/src/connectors/connector.ts b/src/connectors/connector.ts index fa38719..7fd8e9a 100644 --- a/src/connectors/connector.ts +++ b/src/connectors/connector.ts @@ -1,21 +1,21 @@ import EventEmitter from "eventemitter3" -import { constants } from "starknet" -import type { StarknetWindowObject } from "starknet-types" +import { AccountInterface, ProviderInterface, ProviderOptions } from "starknet" +import type { + RequestFnCall, + RpcMessage, + RpcTypeToMessageMap, + StarknetWindowObject, +} from "starknet-types" /** Connector icons, as base64 encoded svg. */ -export type ConnectorIcons = { - /** Dark-mode icon. */ - dark?: string - /** Light-mode icon. */ - light?: string -} +export type ConnectorIcons = StarknetWindowObject["icon"] /** Connector data. */ export type ConnectorData = { /** Connector account. */ account?: string /** Connector network. */ - chainId?: constants.StarknetChainId + chainId?: bigint } /** Connector events. */ @@ -45,9 +45,18 @@ export abstract class Connector extends EventEmitter { /** Disconnect wallet. */ abstract disconnect(): Promise /** Get current account silently. Return null if the account is not authorized */ - abstract account(): Promise + abstract account( + provider: ProviderOptions | ProviderInterface, + ): Promise /** Get current chain id. */ - abstract chainId(): Promise + abstract chainId(): Promise + /** Create request call to wallet */ + abstract request( + call: RequestFnCall, + ): Promise +} + +export abstract class StarknetkitConnector extends Connector { /** Connector StarknetWindowObject */ abstract get wallet(): StarknetWindowObject } diff --git a/src/connectors/injected/index.ts b/src/connectors/injected/index.ts index 567c3b9..004199a 100644 --- a/src/connectors/injected/index.ts +++ b/src/connectors/injected/index.ts @@ -1,5 +1,16 @@ -import { Permission, type StarknetWindowObject } from "starknet-types" -import { constants } from "starknet" +import { + AccountInterface, + ProviderInterface, + ProviderOptions, + WalletAccount, +} from "starknet" +import { + Permission, + RequestFnCall, + RpcMessage, + RpcTypeToMessageMap, + type StarknetWindowObject, +} from "starknet-types" import { ConnectorNotConnectedError, ConnectorNotFoundError, @@ -15,8 +26,6 @@ import { WALLET_NOT_FOUND_ICON_DARK, WALLET_NOT_FOUND_ICON_LIGHT, } from "./constants" -import { isString } from "lodash-es" -import { getStarknetChainId } from "../../helpers/getStarknetChainId" /** Injected connector options. */ export interface InjectedConnectorOptions { /** The wallet id. */ @@ -27,22 +36,76 @@ export interface InjectedConnectorOptions { icon?: ConnectorIcons } +export interface InjectedConnectorOptions { + /** The wallet id. */ + id: string + /** Wallet human readable name. */ + name?: string + /** Wallet icons. */ + icon?: ConnectorIcons +} + +// Icons used when the injected wallet is not installed +// Icons from media kits +const walletIcons = { + argentX: + "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA2NS4xOTUwOCA1Ny43MzU2MiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNjUuMTk1MDggNTcuNzM1NjI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkY4NzVCO30KPC9zdHlsZT4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQwLjk4NTkyLDBIMjQuMjA4ODhjLTAuNTYsMC0xLjAxMDAxLDAuNDUxMDItMS4wMjE5NywxLjAxMjAyCgljLTAuMzM4OTksMTUuNzU1LTguNTgyMDMsMzAuNzA4OTgtMjIuNzcwMDIsNDEuMzAwOTljLTAuNDUwMDEsMC4zMzcwMS0wLjU1Mjk4LDAuOTY3OTktMC4yMjQsMS40MjNsOS44MTU5OCwxMy41NzMKCWMwLjMzNDA1LDAuNDYyMDEsMC45ODUwNSwwLjU2NTk4LDEuNDQyOTksMC4yMjY5OWM4Ljg3MTAzLTYuNTc5MDEsMTYuMDA3MDItMTQuNTE3LDIxLjE0NjA2LTIzLjMxNQoJYzUuMTM4LDguNzk4LDEyLjI3Mzk5LDE2LjczNTk5LDIxLjE0NiwyMy4zMTVjMC40NTY5NywwLjMzODk5LDEuMTA3OTcsMC4yMzUwMiwxLjQ0MTk2LTAuMjI2OTlsOS44MTYwNC0xMy41NzMKCWMwLjMyODk4LTAuNDU1MDIsMC4yMjY5OS0xLjA4Ni0wLjIyNC0xLjQyM0M1MC41ODk4NiwzMS43MjEwMSw0Mi4zNDY4OCwxNi43NjcwMyw0Mi4wMDc4OSwxLjAxMjAyCglDNDEuOTk1ODcsMC40NTEwMiw0MS41NDY4OSwwLDQwLjk4NTkyLDAiLz4KPC9zdmc+Cg==", + braavos: + "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAwIiBoZWlnaHQ9IjUwMCIgdmlld0JveD0iMCAwIDUwMCA1MDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0zMjMuNDQgNDEuMzg4NkMzMjQuMTk4IDQyLjY3MjggMzIzLjE5NSA0NC4yNjAzIDMyMS43MDQgNDQuMjYwM0MyOTEuNTEgNDQuMjYwMyAyNjYuOTY1IDY4LjE2NTYgMjY2LjM4OSA5Ny44NzFDMjU2LjA1IDk1Ljk0MDcgMjQ1LjMzNyA5NS43OTU2IDIzNC43NTQgOTcuNTc4N0MyMzQuMDIzIDY4LjAwOSAyMDkuNTQgNDQuMjYwMyAxNzkuNDQ1IDQ0LjI2MDNDMTc3Ljk1MyA0NC4yNjAzIDE3Ni45NDkgNDIuNjcxNiAxNzcuNzA3IDQxLjM4NjVDMTkyLjMyMyAxNi42MzMgMjE5LjQ4MyAwIDI1MC41NzMgMEMyODEuNjY0IDAgMzA4LjgyNCAxNi42MzM5IDMyMy40NCA0MS4zODg2WiIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzIzMjRfNjE4NjkpIi8+CjxwYXRoIGQ9Ik00MTguNzU2IDIyNi44OTRDNDI2LjM3IDIyOS4yIDQzMy41ODEgMjIyLjUxNyA0MzEuMDM2IDIxNC45NzlDNDA0LjUwNyAxMzYuNDAxIDMxNi41MzUgMTA0LjM1OCAyNTAuMTU5IDEwNC4zNThDMTgzLjY3NCAxMDQuMzU4IDkzLjczOTEgMTM3LjQxOCA2OS4zMDUxIDIxNS4zMzFDNjYuOTU3NCAyMjIuODE4IDc0LjE0NjUgMjI5LjI3NSA4MS42NDc5IDIyNi45NzdMMjQ0LjI1IDE3Ny4xNTFDMjQ3LjU2OSAxNzYuMTM0IDI1MS4xMTYgMTc2LjEyOCAyNTQuNDM5IDE3Ny4xMzVMNDE4Ljc1NiAyMjYuODk0WiIgZmlsbD0idXJsKCNwYWludDFfbGluZWFyXzIzMjRfNjE4NjkpIi8+CjxwYXRoIGQ9Ik02OS43MTY1IDIzOS40MjZMMjQ0LjM3IDE4Ni40NTZDMjQ3LjY2OSAxODUuNDU2IDI1MS4xOTEgMTg1LjQ1MyAyNTQuNDkyIDE4Ni40NDhMNDMwLjIzMiAyMzkuNDUyQzQ0NC43NiAyNDMuODMzIDQ1NC43MDEgMjU3LjIxNiA0NTQuNzAxIDI3Mi4zOVY0MzAuNDgxQzQ1NC4wMjggNDY5LjA3IDQxOS4zNjIgNTAwIDM4MC43ODYgNTAwSDMxNi43MTJDMzEwLjM3OSA1MDAgMzA1LjI1IDQ5NC44NzcgMzA1LjI1IDQ4OC41NDNWNDMzLjExNUMzMDUuMjUgNDExLjI4OSAzMTguMTY3IDM5MS41MzUgMzM4LjE1NSAzODIuNzkyQzM2NC45NDkgMzcxLjA3MSAzOTYuNjQ2IDM1NS4yMTggNDAyLjYwOCAzMjMuNDA2QzQwNC41MzIgMzEzLjEzOCAzOTcuODM3IDMwMy4yMzQgMzg3LjU5NSAzMDEuMTk4QzM2MS42OTkgMjk2LjA1MSAzMzIuOTg5IDI5OC4wMzkgMzA4LjcxMSAzMDguODk4QzI4MS4xNSAzMjEuMjI1IDI3My45NCAzNDEuNzMxIDI3MS4yNzEgMzY5LjI3TDI2OC4wMzYgMzk4LjkzOEMyNjcuMDQ3IDQwOC4wMDUgMjU4LjU0NiA0MTQuOTUyIDI0OS40MjkgNDE0Ljk1MkMyMzkuOTk4IDQxNC45NTIgMjMyLjkyNiA0MDcuNzY5IDIzMS45MDMgMzk4LjM4OEwyMjguNzI4IDM2OS4yN0MyMjYuNDQyIDM0NS42ODEgMjIyLjI5OCAzMjIuNzY3IDE5Ny45MTIgMzExLjg2QzE3MC4wOTUgMjk5LjQxOSAxNDIuMTQxIDI5NS4yODcgMTEyLjQwNCAzMDEuMTk4QzEwMi4xNjIgMzAzLjIzNCA5NS40NjcgMzEzLjEzOCA5Ny4zOTEzIDMyMy40MDZDMTAzLjQwNSAzNTUuNDk1IDEzNC44NTQgMzcwLjk4NSAxNjEuODQ0IDM4Mi43OTJDMTgxLjgzMyAzOTEuNTM1IDE5NC43NSA0MTEuMjg5IDE5NC43NSA0MzMuMTE1VjQ4OC41MzNDMTk0Ljc1IDQ5NC44NjcgMTg5LjYyMiA1MDAgMTgzLjI4OSA1MDBIMTE5LjIxNEM4MC42Mzc0IDUwMCA0NS45NzE2IDQ2OS4wNyA0NS4yOTc5IDQzMC40ODFWMjcyLjM0OUM0NS4yOTc5IDI1Ny4xOTQgNTUuMjE0MiAyNDMuODI0IDY5LjcxNjUgMjM5LjQyNloiIGZpbGw9InVybCgjcGFpbnQyX2xpbmVhcl8yMzI0XzYxODY5KSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzIzMjRfNjE4NjkiIHgxPSIyNDUuOTg2IiB5MT0iLTI3IiB4Mj0iNDI1LjQ5NiIgeTI9IjUwMi4zNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0Y1RDQ1RSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRjk2MDAiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDFfbGluZWFyXzIzMjRfNjE4NjkiIHgxPSIyNDUuOTg2IiB5MT0iLTI3IiB4Mj0iNDI1LjQ5NiIgeTI9IjUwMi4zNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0Y1RDQ1RSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRjk2MDAiLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDJfbGluZWFyXzIzMjRfNjE4NjkiIHgxPSIyNDUuOTg2IiB5MT0iLTI3IiB4Mj0iNDI1LjQ5NiIgeTI9IjUwMi4zNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0Y1RDQ1RSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRjk2MDAiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4=", +} + export class InjectedConnector extends Connector { private _wallet?: StarknetWindowObject - private _options: InjectedConnectorOptions + private readonly _options: InjectedConnectorOptions constructor({ options }: { options: InjectedConnectorOptions }) { super() this._options = options } + get id(): string { + return this._options.id + } + + get name(): string { + this.ensureWallet() + return this._options.name ?? this._wallet?.name ?? this._options.id + } + + get icon(): ConnectorIcons { + this.ensureWallet() + const deafultIcon = { + dark: + walletIcons[this.id as keyof typeof walletIcons] || + WALLET_NOT_FOUND_ICON_DARK, + light: + walletIcons[this.id as keyof typeof walletIcons] || + WALLET_NOT_FOUND_ICON_LIGHT, + } + return this._options.icon || this._wallet?.icon || deafultIcon + } + 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 chainId(): Promise { + this.ensureWallet() + const locked = await this.isLocked() + + if (!this._wallet || locked) { + throw new ConnectorNotConnectedError() + } + + try { + const chainIdHex = await this.request({ type: "wallet_requestChainId" }) + return BigInt(chainIdHex) + } catch { + throw new ConnectorNotFoundError() + } + } + async ready(): Promise { this.ensureWallet() @@ -50,47 +113,24 @@ export class InjectedConnector extends Connector { return false } - const permissions = await this._wallet.request({ + const permissions: Permission[] = await this.request({ type: "wallet_getPermissions", }) - return (permissions as Permission[]).includes(Permission.Accounts) + return permissions?.includes(Permission.Accounts) } - async chainId(): Promise { + async account( + provider: ProviderOptions | ProviderInterface, + ): Promise { this.ensureWallet() + const locked = await this.isLocked() - if (!this._wallet) { + if (locked || !this._wallet) { throw new ConnectorNotConnectedError() } - const chainId = await this._wallet.request({ - type: "wallet_requestChainId", - }) - return getStarknetChainId(chainId) - } - - private async onAccountsChanged(accounts?: string[]): Promise { - if (!accounts) { - return void this.emit("disconnect") - } - const account = accounts[0] - const chainId = await this.chainId() - this.emit("change", { account, chainId }) - } - - private onNetworkChanged(chainId?: string, accounts?: string[]): void { - const { SN_MAIN, SN_SEPOLIA } = constants.StarknetChainId - const account = accounts?.[0] - switch (chainId) { - case SN_MAIN: - case SN_SEPOLIA: - this.emit("change", { chainId, account }) - break - default: - this.emit("change", {}) - break - } + return new WalletAccount(provider, this._wallet) } async connect(): Promise { @@ -100,31 +140,32 @@ export class InjectedConnector extends Connector { throw new ConnectorNotFoundError() } - let accounts: string[] | null + let accounts: string[] try { - accounts = await this._wallet.request({ + accounts = await this.request({ type: "wallet_requestAccounts", - params: { silent_mode: false }, // explicit to show the modal }) } catch { - // NOTE: Argent v3.0.0 swallows the `.enable` call on reject, so this won't get hit. throw new UserRejectedRequestError() } if (!accounts) { - // NOTE: Argent v3.0.0 swallows the `.enable` call on reject, so this won't get hit. throw new UserRejectedRequestError() } - this._wallet.on("accountsChanged", this.onAccountsChanged.bind(this)) + this._wallet.on("accountsChanged", async (accounts) => { + await this.onAccountsChanged(accounts) + }) - this._wallet.on("networkChanged", this.onNetworkChanged.bind(this)) + this._wallet.on("networkChanged", (chainId, accounts) => { + this.onNetworkChanged(chainId, accounts) + }) await this.onAccountsChanged(accounts) - const account = accounts[0] - const chainId = await this.chainId() + const [account] = accounts + const chainId = await this.chainId() this.emit("connect", { account, chainId }) return { @@ -136,56 +177,74 @@ export class InjectedConnector extends Connector { async disconnect(): Promise { this.ensureWallet() removeStarknetLastConnectedWallet() - if (!this.available()) { + + if (!this._wallet) { throw new ConnectorNotFoundError() } + + this.emit("disconnect") } - async account(): Promise { + async request( + call: RequestFnCall, + ): Promise { this.ensureWallet() if (!this._wallet) { throw new ConnectorNotConnectedError() } - return this._wallet - .request({ - type: "wallet_requestAccounts", - params: { silent_mode: true }, - }) - .then((accounts) => accounts[0]) + try { + return await this._wallet.request(call) + } catch { + throw new UserRejectedRequestError() + } } - get id(): string { - return this._options.id - } + private async isLocked() { + const accounts = await this.request({ + type: "wallet_requestAccounts", + params: { silent_mode: true }, + }) - get name(): string { - if (!this._wallet) { - throw new ConnectorNotConnectedError() - } - return this._wallet.name + return accounts.length === 0 } - get icon(): ConnectorIcons { - const defaultIcons = { - dark: WALLET_NOT_FOUND_ICON_DARK, - light: WALLET_NOT_FOUND_ICON_LIGHT, - } + private ensureWallet() { + // biome-ignore lint/suspicious/noExplicitAny: any + const global_object: Record = globalThis - if (this._options.icon) { - return this._options.icon - } + const wallet: StarknetWindowObject = + global_object?.[`starknet_${this._options.id}`] - const walletIcon = this._wallet?.icon - if (walletIcon) { - const darkIcon = isString(walletIcon) ? walletIcon : walletIcon.dark - const lightIcon = isString(walletIcon) ? walletIcon : walletIcon.light + if (wallet) { + this._wallet = wallet + } + } - return { dark: darkIcon, light: lightIcon } + private async onAccountsChanged(accounts?: string[]): Promise { + if (!accounts) { + this.emit("disconnect") + } else { + const [account] = accounts + + if (account) { + const chainId = await this.chainId() + this.emit("change", { account, chainId }) + } else { + this.emit("disconnect") + } } + } - return defaultIcons + private onNetworkChanged(chainIdHex?: string, accounts?: string[]): void { + if (chainIdHex) { + const chainId = BigInt(chainIdHex) + const [account] = accounts || [] + this.emit("change", { chainId, account }) + } else { + this.emit("change", {}) + } } get wallet(): StarknetWindowObject { @@ -194,51 +253,4 @@ export class InjectedConnector extends Connector { } return this._wallet } - - 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 StarknetWindowObject - "request", - "on", - "off", - "version", - "id", - "name", - "icon", - ].every((key) => key in wallet) - ) - } catch (err) { - /* empty */ - } - return false } diff --git a/src/connectors/webwallet/helpers/openWebwallet.ts b/src/connectors/webwallet/helpers/openWebwallet.ts index 135bcba..f49103b 100644 --- a/src/connectors/webwallet/helpers/openWebwallet.ts +++ b/src/connectors/webwallet/helpers/openWebwallet.ts @@ -83,7 +83,6 @@ export const openWebwallet = async ( { modal, iframe }, ) return starknetWindowObject - /* } */ } else { const windowProxyClient = trpcProxyClient({}) return await getWebWalletStarknetObject( diff --git a/src/connectors/webwallet/index.ts b/src/connectors/webwallet/index.ts index 67df2dc..5b2c78a 100644 --- a/src/connectors/webwallet/index.ts +++ b/src/connectors/webwallet/index.ts @@ -1,25 +1,32 @@ +import { + AccountInterface, + ProviderInterface, + ProviderOptions, + WalletAccount, +} from "starknet" import { Permission, + RequestFnCall, + RpcMessage, + RpcTypeToMessageMap, type AccountChangeEventHandler, type StarknetWindowObject, } from "starknet-types" -import { - Connector, - type ConnectorData, - type ConnectorIcons, -} from "../connector" -import { setPopupOptions } from "./helpers/trpc" - import { ConnectorNotConnectedError, ConnectorNotFoundError, UserRejectedRequestError, } from "../../errors" +import { getStarknetChainId } from "../../helpers/getStarknetChainId" +import { removeStarknetLastConnectedWallet } from "../../helpers/lastConnected" +import { + Connector, + type ConnectorData, + type ConnectorIcons, +} from "../connector" import { DEFAULT_WEBWALLET_ICON, DEFAULT_WEBWALLET_URL } from "./constants" import { openWebwallet } from "./helpers/openWebwallet" -import { removeStarknetLastConnectedWallet } from "../../helpers/lastConnected" -import { constants } from "starknet" -import { getStarknetChainId } from "../../helpers/getStarknetChainId" +import { setPopupOptions } from "./helpers/trpc" let _wallet: StarknetWindowObject | null = null @@ -107,7 +114,7 @@ export class WebWalletConnector extends Connector { // Prevent trpc from throwing an error (closed prematurely) // this happens when 2 requests to webwallet are made in a row (trpc-browser is closing the first popup and requesting a new one right after) // won't be needed with chrome iframes will be enabled again (but still needed for other browsers) - await new Promise((r) => setTimeout(r, 100)) + await new Promise((r) => setTimeout(r, 200)) const chainId = await this.chainId() return { @@ -116,6 +123,20 @@ export class WebWalletConnector extends Connector { } } + async request( + call: RequestFnCall, + ): Promise { + if (!this._wallet) { + throw new ConnectorNotConnectedError() + } + try { + return await this._wallet.request(call) + } catch (e) { + console.error(e) + throw new UserRejectedRequestError() + } + } + async disconnect(): Promise { if (!this.available() && !this._wallet) { throw new ConnectorNotFoundError() @@ -126,22 +147,19 @@ export class WebWalletConnector extends Connector { removeStarknetLastConnectedWallet() } - async account(): Promise { + async account( + provider: ProviderOptions | ProviderInterface, + ): Promise { this._wallet = _wallet if (!this._wallet) { throw new ConnectorNotConnectedError() } - const [account] = await this._wallet.request({ - type: "wallet_requestAccounts", - params: { silent_mode: true }, - }) - - return account ?? null + return new WalletAccount(provider, this._wallet) } - async chainId(): Promise { + async chainId(): Promise { if (!this._wallet) { throw new ConnectorNotConnectedError() } @@ -150,7 +168,8 @@ export class WebWalletConnector extends Connector { type: "wallet_requestChainId", }) - return getStarknetChainId(chainId) + const hexChainId = getStarknetChainId(chainId) + return BigInt(hexChainId) } async initEventListener(accountChangeCb: AccountChangeEventHandler) { diff --git a/src/helpers/defaultConnectors.ts b/src/helpers/defaultConnectors.ts index e468e3b..85429d4 100644 --- a/src/helpers/defaultConnectors.ts +++ b/src/helpers/defaultConnectors.ts @@ -1,4 +1,4 @@ -import { type Connector } from "../connectors" +import { StarknetkitConnector } from "../connectors" import { ArgentMobileConnector, type ArgentMobileConnectorOptions, @@ -12,13 +12,13 @@ export const defaultConnectors = ({ }: { argentMobileOptions?: ArgentMobileConnectorOptions webWalletUrl?: string -}): Connector[] => { +}): StarknetkitConnector[] => { const isSafari = typeof window !== "undefined" ? /^((?!chrome|android).)*safari/i.test(navigator.userAgent) : false - const defaultConnectors: Connector[] = [] + const defaultConnectors: StarknetkitConnector[] = [] if (!isSafari) { defaultConnectors.push( diff --git a/src/helpers/mapModalWallets.ts b/src/helpers/mapModalWallets.ts index 51963a0..216423d 100644 --- a/src/helpers/mapModalWallets.ts +++ b/src/helpers/mapModalWallets.ts @@ -1,12 +1,12 @@ -import { Connector } from "../connectors/connector" -import { ARGENT_X_ICON } from "../connectors/injected/constants" -import type { ModalWallet, StoreVersion } from "../window/modal" -import { isString } from "lodash-es" import { WalletProvider } from "get-starknet-core" +import { isString } from "lodash-es" import type { StarknetWindowObject } from "starknet-types" +import { StarknetkitConnector } from "../connectors/connector" +import { ARGENT_X_ICON } from "../connectors/injected/constants" +import type { ModalWallet, StoreVersion } from "../window/modal" interface SetConnectorsExpandedParams { - availableConnectors: Connector[] + availableConnectors: StarknetkitConnector[] installedWallets: StarknetWindowObject[] discoveryWallets: WalletProvider[] storeVersion: StoreVersion | null diff --git a/src/main.ts b/src/main.ts index 8b1e9df..35e5022 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ import type { DisconnectOptions } from "get-starknet-core" import sn from "get-starknet-core" import snV3 from "get-starknet-coreV3" import type { StarknetWindowObject } from "starknet-types" -import { ConnectorData, type Connector } from "./connectors" +import { Connector, ConnectorData, StarknetkitConnector } from "./connectors" import { DEFAULT_WEBWALLET_URL } from "./connectors/webwallet/constants" import { defaultConnectors } from "./helpers/defaultConnectors" import { getStoreVersionFromBrowser } from "./helpers/getStoreVersionFromBrowser" @@ -15,7 +15,7 @@ import Modal from "./modal/Modal.svelte" import css from "./theme.css?inline" import type { ConnectOptions, ModalResult, ModalWallet } from "./window/modal" -let selectedConnector: Connector | null = null +let selectedConnector: StarknetkitConnector | null = null export const connect = async ({ modalMode = "canAsk", @@ -129,7 +129,7 @@ export const connect = async ({ target: getTarget(), props: { dappName, - callback: async (connector: Connector | null) => { + callback: async (connector: StarknetkitConnector | null) => { try { selectedConnector = connector const connectorData = (await connector?.connect()) ?? null @@ -183,10 +183,11 @@ export type { ConnectorData, DisconnectOptions, StarknetWindowObject, + StarknetkitConnector, defaultConnectors as starknetkitDefaultConnectors, } -export type * from "./window/modal" export * from "./window" +export type * from "./window/modal" export { useStarknetkitConnectModal } from "./hooks/useStarknetkitConnectModal" diff --git a/src/modal/ConnectorButton.svelte b/src/modal/ConnectorButton.svelte index 81cb1f4..9f2fe0b 100644 --- a/src/modal/ConnectorButton.svelte +++ b/src/modal/ConnectorButton.svelte @@ -1,12 +1,19 @@ diff --git a/src/modal/Modal.svelte b/src/modal/Modal.svelte index 5bb035b..d1d85b3 100644 --- a/src/modal/Modal.svelte +++ b/src/modal/Modal.svelte @@ -2,14 +2,14 @@ import { onMount } from "svelte" import type { StarknetWindowObject } from "starknet-types" import ConnectorButton from "./ConnectorButton.svelte" - import type { ModalWallet } from "../types/modal" - import type { Connector } from "../connectors/connector" + import type { StarknetkitConnector } from "../connectors/connector" import { InjectedConnector } from "../connectors/injected" + import { ModalWallet } from "../window" export let dappName: string = window?.document.title ?? "" export let modalWallets: ModalWallet[] export let callback: ( - value: Connector | null, + value: StarknetkitConnector | null, ) => Promise = async () => {} export let theme: "light" | "dark" | null = null @@ -25,7 +25,7 @@ loadingItem = item } - let cb = async (connector: Connector | null) => { + let cb = async (connector: StarknetkitConnector | null) => { setLoadingItem(connector?.id ?? false) try { await callback(connector ?? null) diff --git a/src/window/modal.ts b/src/window/modal.ts index 202e6db..c2a3086 100644 --- a/src/window/modal.ts +++ b/src/window/modal.ts @@ -1,18 +1,18 @@ import type { GetWalletOptions } from "get-starknet-core" import { StarknetWindowObject } from "starknet-types" +import type { ArgentMobileConnectorOptions } from "../connectors/argentMobile" import type { - Connector, ConnectorData, ConnectorIcons, + StarknetkitConnector, } from "../connectors/connector" -import type { ArgentMobileConnectorOptions } from "../connectors/argentMobile" export type StoreVersion = "chrome" | "firefox" | "edge" export interface ConnectOptions extends GetWalletOptions { argentMobileOptions?: ArgentMobileConnectorOptions dappName?: string - connectors?: Connector[] + connectors?: StarknetkitConnector[] modalMode?: "alwaysAsk" | "canAsk" | "neverAsk" modalTheme?: "light" | "dark" | "system" storeVersion?: StoreVersion | null @@ -27,11 +27,11 @@ export type ModalWallet = { download?: string subtitle?: string title?: string - connector: Connector + connector: StarknetkitConnector } export type ModalResult = { - connector: Connector | null + connector: StarknetkitConnector | null connectorData: ConnectorData | null wallet?: StarknetWindowObject | null } diff --git a/src/window/window.ts b/src/window/window.ts index e26bb54..69879ec 100644 --- a/src/window/window.ts +++ b/src/window/window.ts @@ -6,7 +6,7 @@ const DECIMAL_REGEX = /^\d+$/ const shortStringSchema = z .string() - .nonempty("The short string cannot be empty") + .min(1, "The short string cannot be empty") .max(31, "The short string cannot exceed 31 characters") .refine( (value) => !HEX_REGEX.test(value), @@ -116,7 +116,7 @@ export const StarknetMethodArgumentsSchemas = { z .object({ starknetVersion: z - .union([z.literal("v3"), z.literal("v4")]) + .union([z.literal("v3"), z.literal("v4"), z.literal("v5")]) .optional(), }) .optional(),