From 9198db052d84b185095cb145e5b0e37ab1b36eb4 Mon Sep 17 00:00:00 2001 From: Tarrence van As Date: Mon, 14 Mar 2022 09:01:58 -0400 Subject: [PATCH] feat(connectors): modularize connectors --- .changeset/tiny-crews-deliver.md | 5 ++ .../src/components/ConnectWallet.tsx | 6 +- packages/core/src/connectors/base.ts | 19 +++++++ packages/core/src/connectors/index.ts | 2 + packages/core/src/connectors/injected.ts | 56 +++++++++++++++++++ packages/core/src/errors.ts | 19 +++++++ packages/core/src/index.ts | 1 + .../core/src/providers/starknet/manager.ts | 40 +++++-------- packages/core/src/providers/starknet/model.ts | 7 +-- .../core/test/providers/starknet.test.tsx | 3 +- website/docs/hooks/starknet.md | 5 +- website/docs/intro.md | 17 +++--- website/src/components/Demo.tsx | 7 ++- yarn.lock | 5 -- 14 files changed, 138 insertions(+), 54 deletions(-) create mode 100644 .changeset/tiny-crews-deliver.md create mode 100644 packages/core/src/connectors/base.ts 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/.changeset/tiny-crews-deliver.md b/.changeset/tiny-crews-deliver.md new file mode 100644 index 00000000..78f57658 --- /dev/null +++ b/.changeset/tiny-crews-deliver.md @@ -0,0 +1,5 @@ +--- +'@starknet-react/core': minor +--- + +Modularize connectors 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/base.ts b/packages/core/src/connectors/base.ts new file mode 100644 index 00000000..2082611e --- /dev/null +++ b/packages/core/src/connectors/base.ts @@ -0,0 +1,19 @@ +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 */ + static readonly ready: boolean + /** Options to use with connector */ + readonly options: Options + + constructor({ options }: { options: Options }) { + this.options = options + } + + abstract connect(): Promise + abstract account(): Promise +} diff --git a/packages/core/src/connectors/index.ts b/packages/core/src/connectors/index.ts new file mode 100644 index 00000000..1d442872 --- /dev/null +++ b/packages/core/src/connectors/index.ts @@ -0,0 +1,2 @@ +export { Connector } from './base' +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..63bad8be --- /dev/null +++ b/packages/core/src/connectors/injected.ts @@ -0,0 +1,56 @@ +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' + static readonly ready = typeof window != 'undefined' && !!window.starknet + + private starknet = getStarknet() + + constructor(options?: InjectedConnectorOptions) { + super({ options }) + } + + async connect() { + if (!InjectedConnector.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 (!InjectedConnector.ready) { + throw new ConnectorNotFoundError() + } + + return this.starknet.account + ? Promise.resolve(this.starknet.account) + : Promise.reject(new ConnectorNotConnectedError()) + } +} + +export type EventHandler = (accounts: string[]) => void 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..c7cb4932 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,24 @@ 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((connector: Connector) => { + 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/packages/core/test/providers/starknet.test.tsx b/packages/core/test/providers/starknet.test.tsx index 982ce6c8..e3a97a3a 100644 --- a/packages/core/test/providers/starknet.test.tsx +++ b/packages/core/test/providers/starknet.test.tsx @@ -7,8 +7,7 @@ describe('useStarknet', () => { const wrapper = ({ children }) => {children} const { result } = renderHook(() => useStarknet(), { wrapper }) - const { account, hasStarknet } = result.current + const { account } = result.current expect(account).toBeUndefined() - expect(hasStarknet).toBeFalsy() }) }) diff --git a/website/docs/hooks/starknet.md b/website/docs/hooks/starknet.md index df73b1a4..5498c278 100644 --- a/website/docs/hooks/starknet.md +++ b/website/docs/hooks/starknet.md @@ -9,7 +9,7 @@ Hook to access the current instance of the underlying StarkNet library. ```typescript import { useStarknet } from '@starknet-react/core' -const { account, hasStarknet, connectBrowserWallet, library, error } = useStarknet() +const { account, connect, library, error } = useStarknet() ``` ## Return Values @@ -17,8 +17,7 @@ const { account, hasStarknet, connectBrowserWallet, library, error } = useStarkn ```typescript { account?: string - hasStarknet: boolean - connectBrowserWallet: () => void + connect: (Connector) => Promise library: ProviderInterface error?: string } diff --git a/website/docs/intro.md b/website/docs/intro.md index 5624fe99..da3ee016 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -17,6 +17,7 @@ yarn add @starknet-react/core @argent/get-starknet starknet ``` Or with npm: + ``` npm install @starknet-react/core @argent/get-starknet starknet ``` @@ -36,21 +37,23 @@ function App() { ``` 3. Connect the wallet (needs Argent X StartkNet Wallet extension installed) + ```typescript -import { useStarknet } from '@starknet-react/core'; +import { useStarknet, InjectedConnector } from '@starknet-react/core' function YourComponent() { - const { connectBrowserWallet } = useStarknet() + const { connect } = useStarknet() - return ( - - ) + if (!InjectedConnector.ready) { + ;Injected connector not found + } + + return } ``` 4. Retrieve the account address + ```typescript import { useStarknet } from '@starknet-react/core' diff --git a/website/src/components/Demo.tsx b/website/src/components/Demo.tsx index 300ce42a..c6cd5d92 100644 --- a/website/src/components/Demo.tsx +++ b/website/src/components/Demo.tsx @@ -11,6 +11,7 @@ import { useStarknetTransactionManager, Transaction, useStarknet, + InjectedConnector, } from '@starknet-react/core' import CounterAbi from '../abi/counter.json' @@ -48,16 +49,16 @@ function useCounterContract() { } function DemoAccount() { - const { account, connectBrowserWallet, hasStarknet } = useStarknet() + const { account, connect } = useStarknet() return (
Account

Connected Account: {account}

- {hasStarknet ? ( + {InjectedConnector.ready ? ( - + ) : (
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"