From c76bce621f3b11e2a548731db6d0d0c459a7e8b1 Mon Sep 17 00:00:00 2001 From: Francesco Ceccon Date: Fri, 11 Mar 2022 15:26:27 +0000 Subject: [PATCH] feat: add connectors --- .../src/components/ConnectWallet.tsx | 6 +- packages/core/src/connectors/index.ts | 21 +++++ packages/core/src/connectors/injected.ts | 80 +++++++++++++++++++ packages/core/src/errors.ts | 19 +++++ packages/core/src/index.ts | 1 + .../core/src/providers/starknet/manager.ts | 42 ++++------ packages/core/src/providers/starknet/model.ts | 7 +- yarn.lock | 5 -- 8 files changed, 142 insertions(+), 39 deletions(-) create mode 100644 packages/core/src/connectors/index.ts create mode 100644 packages/core/src/connectors/injected.ts create mode 100644 packages/core/src/errors.ts diff --git a/examples/starknet-react-next/src/components/ConnectWallet.tsx b/examples/starknet-react-next/src/components/ConnectWallet.tsx index f027af77..7a5ad65e 100644 --- a/examples/starknet-react-next/src/components/ConnectWallet.tsx +++ b/examples/starknet-react-next/src/components/ConnectWallet.tsx @@ -1,11 +1,11 @@ -import { useStarknet } from '@starknet-react/core' +import { useStarknet, InjectedConnector } from '@starknet-react/core' export function ConnectWallet() { - const { account, connectBrowserWallet } = useStarknet() + const { account, connect } = useStarknet() if (account) { return

Account: {account}

} - return + return } diff --git a/packages/core/src/connectors/index.ts b/packages/core/src/connectors/index.ts new file mode 100644 index 00000000..3c5785cd --- /dev/null +++ b/packages/core/src/connectors/index.ts @@ -0,0 +1,21 @@ +import { AccountInterface } from 'starknet' + +export abstract class Connector { + /** Unique connector id */ + abstract readonly id: string + /** Connector name */ + abstract readonly name: string + /** Whether connector is usable */ + abstract readonly ready: boolean + /** Options to use with connector */ + readonly options: Options + + constructor({ options }: { options: Options }) { + this.options = options + } + + abstract connect(): Promise + abstract account(): Promise +} + +export { InjectedConnector } from './injected' diff --git a/packages/core/src/connectors/injected.ts b/packages/core/src/connectors/injected.ts new file mode 100644 index 00000000..0f68287f --- /dev/null +++ b/packages/core/src/connectors/injected.ts @@ -0,0 +1,80 @@ +import { AccountInterface, Provider } from 'starknet' +import { getStarknet } from '@argent/get-starknet' + +import { Connector } from './index' +import { + ConnectorNotConnectedError, + ConnectorNotFoundError, + UserRejectedRequestError, +} from '../errors' + +type InjectedConnectorOptions = { + showModal?: boolean +} + +export class InjectedConnector extends Connector { + readonly id = 'injected' + readonly name = 'argent' + readonly ready = typeof window != 'undefined' && !!window.starknet + + private starknet = getStarknet() + + constructor(options?: InjectedConnectorOptions) { + super({ options }) + } + + async connect() { + if (!this.ready) { + throw new ConnectorNotFoundError() + } + + try { + await this.starknet.enable(this.options) + } catch { + // NOTE: Argent v3.0.0 swallows the `.enable` call on reject, so this won't get hit. + throw new UserRejectedRequestError() + } + + if (!this.starknet.isConnected) { + // NOTE: Argent v3.0.0 swallows the `.enable` call on reject, so this won't get hit. + throw new UserRejectedRequestError() + } + + return this.starknet.account + } + + account() { + if (!this.ready) { + throw new ConnectorNotFoundError() + } + + return this.starknet.account + ? Promise.resolve(this.starknet.account) + : Promise.reject(new ConnectorNotConnectedError()) + } +} + +export type EventHandler = (accounts: string[]) => void + +interface IStarknetWindowObject { + enable: (options?: { showModal?: boolean }) => Promise + isPreauthorized: () => Promise + on: (event: 'accountsChanged', handleEvent: EventHandler) => void + off: (event: 'accountsChanged', handleEvent: EventHandler) => void + account?: AccountInterface + provider: Provider + selectedAddress?: string + version: string +} + +interface ConnectedStarknetWindowObject extends IStarknetWindowObject { + isConnected: true + account: AccountInterface + selectedAddress: string +} + +interface DisconnectedStarknetWindowObject extends IStarknetWindowObject { + isConnected: false +} + +export type StarknetWindowObject = ConnectedStarknetWindowObject | DisconnectedStarknetWindowObject diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts new file mode 100644 index 00000000..6891a31a --- /dev/null +++ b/packages/core/src/errors.ts @@ -0,0 +1,19 @@ +export class ConnectorAlreadyConnectedError extends Error { + name = 'ConnectorAlreadyConnectedError' + message = 'Connector already connected' +} + +export class ConnectorNotConnectedError extends Error { + name = 'ConnectorNotConnectedError' + message = 'Connector not connected' +} + +export class ConnectorNotFoundError extends Error { + name = 'ConnectorNotFoundError' + message = 'Connector not found' +} + +export class UserRejectedRequestError extends Error { + name = 'UserRejectedRequestError' + message = 'User rejected request' +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a8032fba..45e2b959 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,3 +4,4 @@ export { useStarknetTransactionManager } from './providers/transaction' export type { Transaction } from './providers/transaction' export { StarknetProvider } from './providers' export * from './hooks' +export * from './connectors' diff --git a/packages/core/src/providers/starknet/manager.ts b/packages/core/src/providers/starknet/manager.ts index 78ce3ab5..1df78100 100644 --- a/packages/core/src/providers/starknet/manager.ts +++ b/packages/core/src/providers/starknet/manager.ts @@ -1,8 +1,8 @@ -import { getStarknet } from '@argent/get-starknet' -import { useCallback, useEffect, useReducer, useState } from 'react' +import { useCallback, useReducer } from 'react' import { defaultProvider, ProviderInterface } from 'starknet' import { StarknetState } from './model' +import { Connector } from '../../connectors' interface StarknetManagerState { account?: string @@ -45,38 +45,26 @@ function reducer(state: StarknetManagerState, action: Action): StarknetManagerSt } export function useStarknetManager(): StarknetState { - const [hasStarknet, setHasStarknet] = useState(false) const [state, dispatch] = useReducer(reducer, { library: defaultProvider, }) const { account, library, error } = state - useEffect(() => { - if (typeof window !== undefined) { - // calling getStarknet here makes the detection more reliable - const starknet = getStarknet() - if (starknet.version !== 'uninstalled') { - setHasStarknet(true) - } + const connect = useCallback(async (connector: Connector) => { + if (connector.ready) { + connector.connect().then( + (account) => { + dispatch({ type: 'set_account', account: account.address }) + dispatch({ type: 'set_provider', provider: account }) + }, + (err) => { + console.error(err) + dispatch({ type: 'set_error', error: 'could not activate StarkNet' }) + } + ) } }, []) - const connectBrowserWallet = useCallback(async () => { - try { - if (typeof window === undefined) return - if (window.starknet === undefined) return - const [account] = await window.starknet.enable() - dispatch({ type: 'set_account', account }) - const starknet = getStarknet() - if (starknet.account) { - dispatch({ type: 'set_provider', provider: starknet.account }) - } - } catch (err) { - console.error(err) - dispatch({ type: 'set_error', error: 'could not activate StarkNet' }) - } - }, []) - - return { account, hasStarknet, connectBrowserWallet, library, error } + return { account, connect, library, error } } diff --git a/packages/core/src/providers/starknet/model.ts b/packages/core/src/providers/starknet/model.ts index 80f8d74f..4b8d33f8 100644 --- a/packages/core/src/providers/starknet/model.ts +++ b/packages/core/src/providers/starknet/model.ts @@ -1,16 +1,15 @@ import { defaultProvider, ProviderInterface } from 'starknet' +import { Connector } from '../../connectors' export interface StarknetState { account?: string - hasStarknet: boolean - connectBrowserWallet: () => void + connect: (connector: Connector) => void library: ProviderInterface error?: string } export const STARKNET_INITIAL_STATE: StarknetState = { account: undefined, - hasStarknet: false, - connectBrowserWallet: () => undefined, + connect: () => undefined, library: defaultProvider, } diff --git a/yarn.lock b/yarn.lock index 263d89fc..a158df71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -138,11 +138,6 @@ "@jridgewell/trace-mapping" "^0.2.2" sourcemap-codec "1.4.8" -"@argent/get-starknet@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@argent/get-starknet/-/get-starknet-2.1.1.tgz#cc44b02ad11d427d0c6300c3c8e6d8eb9f2366cc" - integrity sha512-CqW/bXrvcTnLlhCpxc51uhRjVb7UUbUgkckhvIWBG3Ma9wKFWaJyJyZizRjh+I47NL5VL3PpJwzJC1tEKeaZBQ== - "@argent/get-starknet@^3.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@argent/get-starknet/-/get-starknet-3.0.0.tgz#a1c581b8b3777e7dfb4d8da1a1cbab84dfbf28b1"