From e917425ec2803018cb634d5e8b6ac57d38cb4b4d Mon Sep 17 00:00:00 2001 From: sam bacha Date: Tue, 25 Jan 2022 22:22:55 -0800 Subject: [PATCH 1/7] feat(web3): improve store and types --- gen-readme.js | 74 +++++++++ packages/core/src/index.ts | 291 ++++++++++++++++++++++++++---------- packages/store/src/index.ts | 119 ++++++++------- packages/types/src/index.ts | 2 +- packages/types/src/types.ts | 86 +++++++---- 5 files changed, 399 insertions(+), 173 deletions(-) create mode 100644 gen-readme.js diff --git a/gen-readme.js b/gen-readme.js new file mode 100644 index 0000000..4c5bb39 --- /dev/null +++ b/gen-readme.js @@ -0,0 +1,74 @@ + +const fs = require("fs"); +const path = require("path"); + +const src = __dirname + "/../src"; + +const exclude = [ + ".DS_Store", + "util.ts", + "index.tsx", + "index.ts", + "iconIndex.tsx", +]; + +const include = [".tsx", ".ts"]; + +const excludeExt = [".json", ".md"]; + +const basicDocjs = (name) => `<${name}> `; +const basicReadme = (name) => ` + # ${name} + ## Abstract + + ## Usage + + ## Development + + ### Related + + | Library | Description | NPM | + | ------------ | ----------------------------------------- | ------------------------------------------------ | + | @disco3/types | types | https://www.npmjs.com/package/@disco3/types | + + ### License + See [LICENSE](LICENSE.md) +`; + +const sourceFiles = fs + .readdirSync(src) + .map((sourcePath) => path.parse(sourcePath)) + // .filter((parsedPath) => include.includes(parsedPath.ext)) + .filter((parsedPath) => !exclude.includes(parsedPath.base)) + .filter((parsedPath) => !excludeExt.includes(parsedPath.ext)); + +// sourceFiles.map((parsedPath) => fs.mkdirSync(src + "/" + parsedPath.name)); + +console.log(sourceFiles); + +sourceFiles.map((parsedPath) => { + // const oldPath = src + "/" + parsedPath.base; + // const newPath = src + "/" + parsedPath.name + "/" + parsedPath.base; + // console.log(oldPath, newPath); + // fs.renameSync(oldPath, newPath, function (err) { + // if (err) throw err; + // // console.log('Successfully renamed - AKA moved!') + // }); + // const readmePath = src + "/" + parsedPath.name + "/" + "README.md"; + const docsJsonPath = src + "/" + parsedPath.name + "/" + "docs.json"; + + const docsJsPath = src + "/" + parsedPath.name + "/" + "docs.js"; + + if (fs.existsSync(docsJsonPath)) { + fs.unlinkSync(docsJsonPath); + } + // fs.writeFileSync(readmePath, basicReadme(parsedPath.name), "utf8"); + + if (fs.existsSync(docsJsPath)) { + // do something + } else { + fs.writeFileSync(docsJsPath, basicDocjs(parsedPath.name), "utf8"); + } +}); + +// console.log(sourceFiles) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 448c263..82ab721 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,161 +1,286 @@ import type { Actions, Connector, Web3ReactState } from '@disco3/types'; -import create, { UseBoundStore } from 'zustand'; +import type { EqualityChecker, UseBoundStore } from 'zustand' +import create from 'zustand' + import { useEffect, useMemo, useState } from 'react'; import type { Networkish } from '@ethersproject/networks'; import { Web3Provider } from '@ethersproject/providers'; import { createWeb3ReactStoreAndActions } from '@disco3/store'; + export type Web3ReactHooks = ReturnType & ReturnType & - ReturnType; - + ReturnType + +export type Web3ReactPriorityHooks = ReturnType + +/** + * Wraps the initialization of a `connector`. Creates a zustand `store` with `actions` bound to it, and then passes + * these to the connector as specified in `f`. Also creates a variety of `hooks` bound to this `store`. + * + * @typeParam T - The type of the `connector` returned from `f`. + * @param f - A function which is called with `actions` bound to the returned `store`. + * @param allowedChainIds - An optional array of chainIds which the `connector` may connect to. If the `connector` is + * connected to a chainId which is not allowed, a ChainIdNotAllowedError error will be reported. + * If this argument is unspecified, the `connector` may connect to any chainId. + * @returns [connector, hooks, store] - The initialized connector, a variety of hooks, and a zustand store. + */ export function initializeConnector( f: (actions: Actions) => T, - allowedChainIds?: number[], -): [T, Web3ReactHooks] { - const [store, actions] = createWeb3ReactStoreAndActions(allowedChainIds); + allowedChainIds?: number[] +): [T, Web3ReactHooks, Web3ReactStore] { + const [store, actions] = createWeb3ReactStoreAndActions(allowedChainIds) - const connector = f(actions); - const useConnector = create(store); + const connector = f(actions) + const useConnector = create(store) - const stateHooks = getStateHooks(useConnector); - const derivedHooks = getDerivedHooks(stateHooks); + const stateHooks = getStateHooks(useConnector) + const derivedHooks = getDerivedHooks(stateHooks) + const augmentedHooks = getAugmentedHooks(connector, stateHooks, derivedHooks) - const augmentedHooks = getAugmentedHooks(connector, stateHooks, derivedHooks); + return [connector, { ...stateHooks, ...derivedHooks, ...augmentedHooks }, store] +} - return [connector, { ...stateHooks, ...derivedHooks, ...augmentedHooks }]; +function computeIsActive({ chainId, accounts, activating, error }: Web3ReactState) { + return Boolean(chainId && accounts && !activating && !error) } -const CHAIN_ID = (state: Web3ReactState) => state.chainId; -const ACCOUNTS = (state: Web3ReactState) => state.accounts; -const ACTIVATING = (state: Web3ReactState) => state.activating; -const ERROR = (state: Web3ReactState) => state.error; +/** + * Creates a variety of convenience `hooks` that return data associated with the first of the `initializedConnectors` + * that is active. + * + * @param initializedConnectors - Two or more [connector, hooks] arrays, as returned from initializeConnector. + * @returns hooks - A variety of convenience hooks that wrap the hooks returned from initializeConnector. + */ +export function getPriorityConnector(...initializedConnectors: [Connector, Web3ReactHooks][]) { + // the following code calls hooks in a map a lot, which violates the eslint rule. + // this is ok, though, because initializedConnectors never changes, so the same hooks are called each time + + function useActiveIndex() { + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useIsActive }]) => useIsActive()) + const index = values.findIndex((isActive) => isActive) + return index === -1 ? undefined : index + } + + function usePriorityConnector() { + return initializedConnectors[useActiveIndex() ?? 0][0] + } + + function usePriorityChainId() { + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useChainId }]) => useChainId()) + return values[useActiveIndex() ?? 0] + } + + function usePriorityAccounts() { + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useAccounts }]) => useAccounts()) + return values[useActiveIndex() ?? 0] + } + + function usePriorityIsActivating() { + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useIsActivating }]) => useIsActivating()) + return values[useActiveIndex() ?? 0] + } + + function usePriorityError() { + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useError }]) => useError()) + return values[useActiveIndex() ?? 0] + } + + function usePriorityAccount() { + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useAccount }]) => useAccount()) + return values[useActiveIndex() ?? 0] + } + + function usePriorityIsActive() { + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useIsActive }]) => useIsActive()) + return values[useActiveIndex() ?? 0] + } + + function usePriorityProvider(network?: Networkish) { + const index = useActiveIndex() + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useProvider }], i) => useProvider(network, i === index)) + return values[index ?? 0] + } + + function usePriorityENSNames(provider: Web3Provider | undefined) { + const index = useActiveIndex() + const values = initializedConnectors.map(([, { useENSNames }], i) => + // eslint-disable-next-line react-hooks/rules-of-hooks + useENSNames(i === index ? provider : undefined) + ) + return values[index ?? 0] + } + + function usePriorityENSName(provider: Web3Provider | undefined) { + const index = useActiveIndex() + // eslint-disable-next-line react-hooks/rules-of-hooks + const values = initializedConnectors.map(([, { useENSName }], i) => useENSName(i === index ? provider : undefined)) + return values[index ?? 0] + } + + function usePriorityWeb3React(provider: Web3Provider | undefined) { + const index = useActiveIndex() + const values = initializedConnectors.map(([, { useWeb3React }], i) => + // eslint-disable-next-line react-hooks/rules-of-hooks + useWeb3React(i === index ? provider : undefined) + ) + return values[index ?? 0] + } + + return { + usePriorityConnector, + usePriorityChainId, + usePriorityAccounts, + usePriorityIsActivating, + usePriorityError, + usePriorityAccount, + usePriorityIsActive, + usePriorityProvider, + usePriorityENSNames, + usePriorityENSName, + usePriorityWeb3React, + } +} + +const CHAIN_ID = (state: Web3ReactState) => state.chainId +const ACCOUNTS = (state: Web3ReactState) => state.accounts +const ACCOUNTS_EQUALITY_CHECKER: EqualityChecker = (oldAccounts, newAccounts) => + (oldAccounts === undefined && newAccounts === undefined) || + (oldAccounts !== undefined && + oldAccounts.length === newAccounts?.length && + oldAccounts.every((oldAccount, i) => oldAccount === newAccounts[i])) +const ACTIVATING = (state: Web3ReactState) => state.activating +const ERROR = (state: Web3ReactState) => state.error function getStateHooks(useConnector: UseBoundStore) { function useChainId(): Web3ReactState['chainId'] { - return useConnector(CHAIN_ID); + return useConnector(CHAIN_ID) } function useAccounts(): Web3ReactState['accounts'] { - return useConnector(ACCOUNTS); + return useConnector(ACCOUNTS, ACCOUNTS_EQUALITY_CHECKER) } function useIsActivating(): Web3ReactState['activating'] { - return useConnector(ACTIVATING); + return useConnector(ACTIVATING) } function useError(): Web3ReactState['error'] { - return useConnector(ERROR); + return useConnector(ERROR) } - return { useChainId, useAccounts, useIsActivating, useError }; + return { useChainId, useAccounts, useIsActivating, useError } } -function getDerivedHooks({ - useChainId, - useAccounts, - useIsActivating, - useError, -}: ReturnType) { +function getDerivedHooks({ useChainId, useAccounts, useIsActivating, useError }: ReturnType) { function useAccount(): string | undefined { - return useAccounts()?.[0]; + return useAccounts()?.[0] } function useIsActive(): boolean { - const chainId = useChainId(); - const accounts = useAccounts(); - const activating = useIsActivating(); - const error = useError(); + const chainId = useChainId() + const accounts = useAccounts() + const activating = useIsActivating() + const error = useError() - return Boolean(chainId && accounts && !activating && !error); + return computeIsActive({ + chainId, + accounts, + activating, + error, + }) } - return { useAccount, useIsActive }; + return { useAccount, useIsActive } } -function useENS( - provider?: Web3Provider, - accounts?: string[], -): (string | null)[] | undefined { - const [ENSNames, setENSNames] = useState<(string | null)[] | undefined>(); +function useENS(provider?: Web3Provider, accounts?: string[]): (string | null)[] | undefined { + const [ENSNames, setENSNames] = useState<(string | null)[] | undefined>() useEffect(() => { if (provider && accounts?.length) { - let stale = false; + let stale = false Promise.all(accounts.map((account) => provider.lookupAddress(account))) .then((ENSNames) => { if (!stale) { - setENSNames(ENSNames); + setENSNames(ENSNames) } }) .catch((error) => { - console.debug('Could not fetch ENS names', error); - }); + console.debug('Could not fetch ENS names', error) + }) return () => { - stale = true; - setENSNames(undefined); - }; + stale = true + setENSNames(undefined) + } } - }, [provider, accounts]); + }, [provider, accounts]) - return ENSNames; + return ENSNames } function getAugmentedHooks( connector: T, { useChainId, useAccounts, useError }: ReturnType, - { useAccount, useIsActive }: ReturnType, + { useAccount, useIsActive }: ReturnType ) { - function useProvider(network?: Networkish): Web3Provider | undefined { - const isActive = useIsActive(); + function useProvider(network?: Networkish, enabled = true): Web3Provider | undefined { + const isActive = useIsActive() - const chainId = useChainId(); - const accounts = useChainId(); + const chainId = useChainId() + const accounts = useAccounts() return useMemo(() => { // we use chainId and accounts to re-render in case connector.provider changes in place - if (isActive && connector.provider && chainId && accounts) { - return new Web3Provider(connector.provider, network); + if (enabled && isActive && connector.provider && chainId && accounts) { + return new Web3Provider(connector.provider, network) } - }, [isActive, network, chainId, accounts]); + }, [enabled, isActive, network, chainId, accounts]) } - function useENSNames( - provider: Web3Provider | undefined, - ): (string | null)[] | undefined { - const accounts = useAccounts(); - - return useENS(provider, accounts); + function useENSNames(provider: Web3Provider | undefined): (string | null)[] | undefined { + const accounts = useAccounts() + return useENS(provider, accounts) } - function useENSName( - provider: Web3Provider | undefined, - ): (string | null) | undefined { - const account = useAccount(); + function useENSName(provider: Web3Provider | undefined): (string | null) | undefined { + const account = useAccount() + const accounts = useMemo(() => (account === undefined ? undefined : [account]), [account]) - return useENS(provider, account === undefined ? undefined : [account])?.[0]; + return useENS(provider, accounts)?.[0] } // for backwards compatibility only function useWeb3React(provider: Web3Provider | undefined) { - const chainId = useChainId(); - const error = useError(); - - const account = useAccount(); - const isActive = useIsActive(); - - return { - connector, - library: provider, - chainId, - account, - active: isActive, - error, - }; + const chainId = useChainId() + const account = useAccount() + const error = useError() + + const isActive = useIsActive() + + return useMemo( + () => ({ + connector, + library: provider, + chainId, + account, + active: isActive, + error, + }), + [provider, chainId, account, isActive, error] + ) } - return { useProvider, useENSNames, useENSName, useWeb3React }; -} + return { useProvider, useENSNames, useENSName, useWeb3React } +} \ No newline at end of file diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index 1b9b915..25d871e 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -9,37 +9,30 @@ import create from 'zustand/vanilla'; import { getAddress } from '@ethersproject/address'; function validateChainId(chainId: number): void { - if ( - !Number.isInteger(chainId) || - chainId <= 0 || - chainId > Number.MAX_SAFE_INTEGER - ) { - throw new Error(`Invalid chainId ${chainId}`); + if (!Number.isInteger(chainId) || chainId <= 0 || chainId > Number.MAX_SAFE_INTEGER) { + throw new Error(`Invalid chainId ${chainId}`) } } export class ChainIdNotAllowedError extends Error { - public readonly chainId: number; + public readonly chainId: number public constructor(chainId: number, allowedChainIds: number[]) { - super(`chainId ${chainId} not included in ${allowedChainIds.toString()}`); - this.chainId = chainId; - this.name = ChainIdNotAllowedError.name; - Object.setPrototypeOf(this, ChainIdNotAllowedError.prototype); + super(`chainId ${chainId} not included in ${allowedChainIds.toString()}`) + this.chainId = chainId + this.name = ChainIdNotAllowedError.name + Object.setPrototypeOf(this, ChainIdNotAllowedError.prototype) } } -function ensureChainIdIsAllowed( - chainId: number, - allowedChainIds: number[], -): ChainIdNotAllowedError | undefined { +function ensureChainIdIsAllowed(chainId: number, allowedChainIds: number[]): ChainIdNotAllowedError | undefined { return allowedChainIds.some((allowedChainId) => chainId === allowedChainId) ? undefined - : new ChainIdNotAllowedError(chainId, allowedChainIds); + : new ChainIdNotAllowedError(chainId, allowedChainIds) } function validateAccount(account: string): string { - return getAddress(account); + return getAddress(account) } const DEFAULT_STATE = { @@ -47,99 +40,105 @@ const DEFAULT_STATE = { accounts: undefined, activating: false, error: undefined, -}; +} -export function createWeb3ReactStoreAndActions( - allowedChainIds?: number[], -): [Web3ReactStore, Actions] { +export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web3ReactStore, Actions] { if (allowedChainIds?.length === 0) { - throw new Error(`allowedChainIds is length 0`); + throw new Error(`allowedChainIds is length 0`) } - const store = create(() => DEFAULT_STATE); + const store = create(() => DEFAULT_STATE) // flag for tracking updates so we don't clobber data when cancelling activation - let nullifier = 0; - + let nullifier = 0 + + /** + * Sets activating to true, indicating that an update is in progress. + * + * @returns cancelActivation - A function that cancels the activation by setting activating to false, + * as long as there haven't been any intervening updates. + */ function startActivation(): () => void { - const nullifierCached = ++nullifier; + const nullifierCached = ++nullifier - store.setState({ ...DEFAULT_STATE, activating: true }); + store.setState({ ...DEFAULT_STATE, activating: true }) // return a function that cancels the activation iff nothing else has happened return () => { if (nullifier === nullifierCached) { - store.setState({ ...DEFAULT_STATE, activating: false }); + store.setState({ ...DEFAULT_STATE, activating: false }) } - }; + } } + /** + * Used to report a `stateUpdate` which is merged with existing state. The first `stateUpdate` that results in chainId + * and accounts being set will also set activating to false, indicating a successful connection. Similarly, if an + * error is set, the first `stateUpdate` that results in chainId and accounts being set will clear this error. + * + * @param stateUpdate - The state update to report. + */ function update(stateUpdate: Web3ReactStateUpdate): void { // validate chainId statically, independent of existing state if (stateUpdate.chainId !== undefined) { - validateChainId(stateUpdate.chainId); + validateChainId(stateUpdate.chainId) } // validate accounts statically, independent of existing state if (stateUpdate.accounts !== undefined) { for (let i = 0; i < stateUpdate.accounts.length; i++) { - stateUpdate.accounts[i] = validateAccount(stateUpdate.accounts[i]); + stateUpdate.accounts[i] = validateAccount(stateUpdate.accounts[i]) } } - nullifier++; + nullifier++ store.setState((existingState): Web3ReactState => { // determine the next chainId and accounts - const chainId = stateUpdate.chainId ?? existingState.chainId; - const accounts = stateUpdate.accounts ?? existingState.accounts; + const chainId = stateUpdate.chainId ?? existingState.chainId + const accounts = stateUpdate.accounts ?? existingState.accounts // determine the next error - let error = existingState.error; + let error = existingState.error if (chainId && allowedChainIds) { // if we have a chainId allowlist and a chainId, we need to ensure it's allowed - const chainIdError = ensureChainIdIsAllowed(chainId, allowedChainIds); + const chainIdError = ensureChainIdIsAllowed(chainId, allowedChainIds) // warn if we're going to clobber existing error if (chainIdError && error) { - if ( - !(error instanceof ChainIdNotAllowedError) || - error.chainId !== chainIdError.chainId - ) { - console.debug( - `${error.name} is being clobbered by ${chainIdError.name}`, - ); + if (!(error instanceof ChainIdNotAllowedError) || error.chainId !== chainIdError.chainId) { + console.debug(`${error.name} is being clobbered by ${chainIdError.name}`) } } - error = chainIdError; + error = chainIdError } // ensure that the error is cleared when appropriate - if ( - error && - !(error instanceof ChainIdNotAllowedError) && - chainId && - accounts - ) { - error = undefined; + if (error && !(error instanceof ChainIdNotAllowedError) && chainId && accounts) { + error = undefined } // ensure that the activating flag is cleared when appropriate - let activating = existingState.activating; + let activating = existingState.activating if (activating && (error || (chainId && accounts))) { - activating = false; + activating = false } - return { chainId, accounts, activating, error }; - }); + return { chainId, accounts, activating, error } + }) } - function reportError(error: Error | undefined) { - nullifier++; + /** + * Used to report an `error`, which clears all existing state. + * + * @param error - The error to report. If undefined, the state will be reset to its default value. + */ + function reportError(error: Error | undefined): void { + nullifier++ - store.setState(() => ({ ...DEFAULT_STATE, error })); + store.setState(() => ({ ...DEFAULT_STATE, error })) } - return [store, { startActivation, update, reportError }]; -} + return [store, { startActivation, update, reportError }] +} \ No newline at end of file diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 068b099..e9c1821 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -7,4 +7,4 @@ export { ProviderConnectInfo } from './types'; export { ProviderMessage } from './types'; export { Connector } from './types'; export { Actions } from './types'; -export { Provider } from './types'; +export { Provider } from './types'; \ No newline at end of file diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index d9d4c27..eab8853 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -1,65 +1,93 @@ -import type { State, StoreApi } from 'zustand/vanilla'; - -import type { EventEmitter } from 'node:events'; +import type { EventEmitter } from 'node:events' +import type { State, StoreApi } from 'zustand/vanilla' export interface Web3ReactState extends State { - chainId: number | undefined; - accounts: string[] | undefined; - activating: boolean; - error: Error | undefined; + chainId: number | undefined + accounts: string[] | undefined + activating: boolean + error: Error | undefined } -export type Web3ReactStore = StoreApi; +export type Web3ReactStore = StoreApi -export interface Web3ReactStateUpdate { - chainId?: number; - accounts?: string[]; -} +export type Web3ReactStateUpdate = + | { + chainId: number + accounts: string[] + } + | { + chainId: number + accounts?: never + } + | { + chainId?: never + accounts: string[] + } export interface Actions { - startActivation: () => () => void; - update: (stateUpdate: Web3ReactStateUpdate) => void; - reportError: (error: Error) => void; + startActivation: () => () => void + update: (stateUpdate: Web3ReactStateUpdate) => void + reportError: (error: Error | undefined) => void } // per EIP-1193 export interface RequestArguments { - readonly method: string; - readonly params?: readonly unknown[] | object; + readonly method: string + readonly params?: readonly unknown[] | object } // per EIP-1193 export interface Provider extends EventEmitter { - request(args: RequestArguments): Promise; + request(args: RequestArguments): Promise } // per EIP-1193 export interface ProviderConnectInfo { - readonly chainId: string; + readonly chainId: string } // per EIP-1193 export interface ProviderRpcError extends Error { - message: string; - code: number; - data?: unknown; + message: string + code: number + data?: unknown } -// per EIP-1193 export interface ProviderMessage { readonly type: string; readonly data: unknown; } + export abstract class Connector { - public provider: Provider | undefined; + /** + * An + * EIP-1193 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md}) and + * EIP-1102 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md}) compliant provider. + * This property must be defined while the connector is active. + */ + public provider: Provider | undefined - protected readonly actions: Actions; + protected readonly actions: Actions + /** + * @param actions - Methods bound to a zustand store that tracks the state of the connector. + * Actions are used by the connector to report changes in connection status. + */ constructor(actions: Actions) { - this.actions = actions; + this.actions = actions } - public abstract activate(...args: unknown[]): Promise | void; - public deactivate?(...args: unknown[]): Promise | void; -} + /** + * Initiate a connection. + */ + public abstract activate(...args: unknown[]): Promise | void + + /** + * Initiate a disconnect. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public deactivate(...args: unknown[]): Promise | void { + this.actions.reportError(undefined) + } +} \ No newline at end of file From b805ead7e9f1d087926b82f7422f7f4d5845350c Mon Sep 17 00:00:00 2001 From: sam bacha Date: Tue, 25 Jan 2022 23:11:20 -0800 Subject: [PATCH 2/7] fix(ts): unused locales --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 73a74cc..6b98038 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,7 @@ "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true, - "noUnusedLocals": true, + "noUnusedLocals": false, "noUnusedParameters": true, "noImplicitReturns": false, "noFallthroughCasesInSwitch": true, From ce6a8e9801464d15c8a4516595899c4788c71d4a Mon Sep 17 00:00:00 2001 From: sam bacha Date: Tue, 25 Jan 2022 23:55:56 -0800 Subject: [PATCH 3/7] test(core): update spec test utils --- packages/core/src/index.spec.ts | 70 ++++++++- packages/core/src/index.ts | 245 +++++++++++++++++++------------- packages/store/src/index.ts | 97 ++++++++----- packages/types/src/index.ts | 2 +- packages/types/src/types.ts | 61 ++++---- 5 files changed, 304 insertions(+), 171 deletions(-) diff --git a/packages/core/src/index.spec.ts b/packages/core/src/index.spec.ts index 605a349..409d3e6 100644 --- a/packages/core/src/index.spec.ts +++ b/packages/core/src/index.spec.ts @@ -1,9 +1,11 @@ -import { Web3ReactHooks, initializeConnector } from '.'; import { act, renderHook } from '@testing-library/react-hooks'; import type { Actions } from '@disco3/types'; import { Connector } from '@disco3/types'; +import type { Web3ReactHooks, Web3ReactPriorityHooks } from '.'; +import { getPriorityConnector, initializeConnector } from '.'; + class MockConnector extends Connector { constructor(actions: Actions) { super(actions); @@ -19,6 +21,8 @@ class MockConnector extends Connector { } } +class MockConnector2 extends MockConnector {} + describe('#initializeConnector', () => { let connector: MockConnector; let hooks: Web3ReactHooks; @@ -123,3 +127,67 @@ describe('#initializeConnector', () => { expect(error).toBeInstanceOf(Error); }); }); + +describe('#useHighestPriorityConnector', () => { + let connector: MockConnector; + let hooks: Web3ReactHooks; + let store: Web3ReactStore; + + let connector2: MockConnector; + let hooks2: Web3ReactHooks; + let store2: Web3ReactStore; + + let priorityConnectorHooks: Web3ReactPriorityHooks; + + beforeEach(() => { + [connector, hooks, store] = initializeConnector( + (actions) => new MockConnector(actions), + ); + [connector2, hooks2, store2] = initializeConnector( + (actions) => new MockConnector2(actions), + ); + + priorityConnectorHooks = getPriorityConnector( + [connector, hooks], + [connector2, hooks2], + ); + }); + + test('returns first connector if both are uninitialized', () => { + const { + result: { current: priorityConnector }, + } = renderHook(() => priorityConnectorHooks.usePriorityConnector()); + + expect(priorityConnector).toBeInstanceOf(MockConnector); + expect(priorityConnector).not.toBeInstanceOf(MockConnector2); + }); + + test('returns first connector if it is initialized', () => { + act(() => connector.update({ chainId: 1, accounts: [] })); + const { + result: { current: priorityConnector }, + } = renderHook(() => priorityConnectorHooks.usePriorityConnector()); + + const { + result: { current: isActive }, + } = renderHook(() => priorityConnectorHooks.usePriorityIsActive()); + expect(isActive).toBe(true); + + expect(priorityConnector).toBeInstanceOf(MockConnector); + expect(priorityConnector).not.toBeInstanceOf(MockConnector2); + }); + + test('returns second connector if it is initialized', () => { + act(() => connector2.update({ chainId: 1, accounts: [] })); + const { + result: { current: priorityConnector }, + } = renderHook(() => priorityConnectorHooks.usePriorityConnector()); + + const { + result: { current: isActive }, + } = renderHook(() => priorityConnectorHooks.usePriorityIsActive()); + expect(isActive).toBe(true); + + expect(priorityConnector).toBeInstanceOf(MockConnector2); + }); +}); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 82ab721..ba9a054 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,6 @@ import type { Actions, Connector, Web3ReactState } from '@disco3/types'; -import type { EqualityChecker, UseBoundStore } from 'zustand' -import create from 'zustand' +import type { EqualityChecker, UseBoundStore } from 'zustand'; +import create from 'zustand'; import { useEffect, useMemo, useState } from 'react'; @@ -8,12 +8,11 @@ import type { Networkish } from '@ethersproject/networks'; import { Web3Provider } from '@ethersproject/providers'; import { createWeb3ReactStoreAndActions } from '@disco3/store'; - export type Web3ReactHooks = ReturnType & ReturnType & - ReturnType + ReturnType; -export type Web3ReactPriorityHooks = ReturnType +export type Web3ReactPriorityHooks = ReturnType; /** * Wraps the initialization of a `connector`. Creates a zustand `store` with `actions` bound to it, and then passes @@ -28,22 +27,31 @@ export type Web3ReactPriorityHooks = ReturnType */ export function initializeConnector( f: (actions: Actions) => T, - allowedChainIds?: number[] + allowedChainIds?: number[], ): [T, Web3ReactHooks, Web3ReactStore] { - const [store, actions] = createWeb3ReactStoreAndActions(allowedChainIds) + const [store, actions] = createWeb3ReactStoreAndActions(allowedChainIds); - const connector = f(actions) - const useConnector = create(store) + const connector = f(actions); + const useConnector = create(store); - const stateHooks = getStateHooks(useConnector) - const derivedHooks = getDerivedHooks(stateHooks) - const augmentedHooks = getAugmentedHooks(connector, stateHooks, derivedHooks) + const stateHooks = getStateHooks(useConnector); + const derivedHooks = getDerivedHooks(stateHooks); + const augmentedHooks = getAugmentedHooks(connector, stateHooks, derivedHooks); - return [connector, { ...stateHooks, ...derivedHooks, ...augmentedHooks }, store] + return [ + connector, + { ...stateHooks, ...derivedHooks, ...augmentedHooks }, + store, + ]; } -function computeIsActive({ chainId, accounts, activating, error }: Web3ReactState) { - return Boolean(chainId && accounts && !activating && !error) +function computeIsActive({ + chainId, + accounts, + activating, + error, +}: Web3ReactState) { + return Boolean(chainId && accounts && !activating && !error); } /** @@ -53,87 +61,105 @@ function computeIsActive({ chainId, accounts, activating, error }: Web3ReactStat * @param initializedConnectors - Two or more [connector, hooks] arrays, as returned from initializeConnector. * @returns hooks - A variety of convenience hooks that wrap the hooks returned from initializeConnector. */ -export function getPriorityConnector(...initializedConnectors: [Connector, Web3ReactHooks][]) { +export function getPriorityConnector( + ...initializedConnectors: [Connector, Web3ReactHooks][] +) { // the following code calls hooks in a map a lot, which violates the eslint rule. // this is ok, though, because initializedConnectors never changes, so the same hooks are called each time function useActiveIndex() { // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useIsActive }]) => useIsActive()) - const index = values.findIndex((isActive) => isActive) - return index === -1 ? undefined : index + const values = initializedConnectors.map(([, { useIsActive }]) => + useIsActive(), + ); + const index = values.findIndex((isActive) => isActive); + return index === -1 ? undefined : index; } function usePriorityConnector() { - return initializedConnectors[useActiveIndex() ?? 0][0] + return initializedConnectors[useActiveIndex() ?? 0][0]; } function usePriorityChainId() { // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useChainId }]) => useChainId()) - return values[useActiveIndex() ?? 0] + const values = initializedConnectors.map(([, { useChainId }]) => + useChainId(), + ); + return values[useActiveIndex() ?? 0]; } function usePriorityAccounts() { // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useAccounts }]) => useAccounts()) - return values[useActiveIndex() ?? 0] + const values = initializedConnectors.map(([, { useAccounts }]) => + useAccounts(), + ); + return values[useActiveIndex() ?? 0]; } function usePriorityIsActivating() { // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useIsActivating }]) => useIsActivating()) - return values[useActiveIndex() ?? 0] + const values = initializedConnectors.map(([, { useIsActivating }]) => + useIsActivating(), + ); + return values[useActiveIndex() ?? 0]; } function usePriorityError() { // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useError }]) => useError()) - return values[useActiveIndex() ?? 0] + const values = initializedConnectors.map(([, { useError }]) => useError()); + return values[useActiveIndex() ?? 0]; } function usePriorityAccount() { // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useAccount }]) => useAccount()) - return values[useActiveIndex() ?? 0] + const values = initializedConnectors.map(([, { useAccount }]) => + useAccount(), + ); + return values[useActiveIndex() ?? 0]; } function usePriorityIsActive() { // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useIsActive }]) => useIsActive()) - return values[useActiveIndex() ?? 0] + const values = initializedConnectors.map(([, { useIsActive }]) => + useIsActive(), + ); + return values[useActiveIndex() ?? 0]; } function usePriorityProvider(network?: Networkish) { - const index = useActiveIndex() + const index = useActiveIndex(); // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useProvider }], i) => useProvider(network, i === index)) - return values[index ?? 0] + const values = initializedConnectors.map(([, { useProvider }], i) => + useProvider(network, i === index), + ); + return values[index ?? 0]; } function usePriorityENSNames(provider: Web3Provider | undefined) { - const index = useActiveIndex() + const index = useActiveIndex(); const values = initializedConnectors.map(([, { useENSNames }], i) => // eslint-disable-next-line react-hooks/rules-of-hooks - useENSNames(i === index ? provider : undefined) - ) - return values[index ?? 0] + useENSNames(i === index ? provider : undefined), + ); + return values[index ?? 0]; } function usePriorityENSName(provider: Web3Provider | undefined) { - const index = useActiveIndex() + const index = useActiveIndex(); // eslint-disable-next-line react-hooks/rules-of-hooks - const values = initializedConnectors.map(([, { useENSName }], i) => useENSName(i === index ? provider : undefined)) - return values[index ?? 0] + const values = initializedConnectors.map(([, { useENSName }], i) => + useENSName(i === index ? provider : undefined), + ); + return values[index ?? 0]; } function usePriorityWeb3React(provider: Web3Provider | undefined) { - const index = useActiveIndex() + const index = useActiveIndex(); const values = initializedConnectors.map(([, { useWeb3React }], i) => // eslint-disable-next-line react-hooks/rules-of-hooks - useWeb3React(i === index ? provider : undefined) - ) - return values[index ?? 0] + useWeb3React(i === index ? provider : undefined), + ); + return values[index ?? 0]; } return { @@ -148,126 +174,147 @@ export function getPriorityConnector(...initializedConnectors: [Connector, Web3R usePriorityENSNames, usePriorityENSName, usePriorityWeb3React, - } + }; } -const CHAIN_ID = (state: Web3ReactState) => state.chainId -const ACCOUNTS = (state: Web3ReactState) => state.accounts -const ACCOUNTS_EQUALITY_CHECKER: EqualityChecker = (oldAccounts, newAccounts) => +const CHAIN_ID = (state: Web3ReactState) => state.chainId; +const ACCOUNTS = (state: Web3ReactState) => state.accounts; +const ACCOUNTS_EQUALITY_CHECKER: EqualityChecker = ( + oldAccounts, + newAccounts, +) => (oldAccounts === undefined && newAccounts === undefined) || (oldAccounts !== undefined && oldAccounts.length === newAccounts?.length && - oldAccounts.every((oldAccount, i) => oldAccount === newAccounts[i])) -const ACTIVATING = (state: Web3ReactState) => state.activating -const ERROR = (state: Web3ReactState) => state.error + oldAccounts.every((oldAccount, i) => oldAccount === newAccounts[i])); +const ACTIVATING = (state: Web3ReactState) => state.activating; +const ERROR = (state: Web3ReactState) => state.error; function getStateHooks(useConnector: UseBoundStore) { function useChainId(): Web3ReactState['chainId'] { - return useConnector(CHAIN_ID) + return useConnector(CHAIN_ID); } function useAccounts(): Web3ReactState['accounts'] { - return useConnector(ACCOUNTS, ACCOUNTS_EQUALITY_CHECKER) + return useConnector(ACCOUNTS, ACCOUNTS_EQUALITY_CHECKER); } function useIsActivating(): Web3ReactState['activating'] { - return useConnector(ACTIVATING) + return useConnector(ACTIVATING); } function useError(): Web3ReactState['error'] { - return useConnector(ERROR) + return useConnector(ERROR); } - return { useChainId, useAccounts, useIsActivating, useError } + return { useChainId, useAccounts, useIsActivating, useError }; } -function getDerivedHooks({ useChainId, useAccounts, useIsActivating, useError }: ReturnType) { +function getDerivedHooks({ + useChainId, + useAccounts, + useIsActivating, + useError, +}: ReturnType) { function useAccount(): string | undefined { - return useAccounts()?.[0] + return useAccounts()?.[0]; } function useIsActive(): boolean { - const chainId = useChainId() - const accounts = useAccounts() - const activating = useIsActivating() - const error = useError() + const chainId = useChainId(); + const accounts = useAccounts(); + const activating = useIsActivating(); + const error = useError(); return computeIsActive({ chainId, accounts, activating, error, - }) + }); } - return { useAccount, useIsActive } + return { useAccount, useIsActive }; } -function useENS(provider?: Web3Provider, accounts?: string[]): (string | null)[] | undefined { - const [ENSNames, setENSNames] = useState<(string | null)[] | undefined>() +function useENS( + provider?: Web3Provider, + accounts?: string[], +): (string | null)[] | undefined { + const [ENSNames, setENSNames] = useState<(string | null)[] | undefined>(); useEffect(() => { if (provider && accounts?.length) { - let stale = false + let stale = false; Promise.all(accounts.map((account) => provider.lookupAddress(account))) .then((ENSNames) => { if (!stale) { - setENSNames(ENSNames) + setENSNames(ENSNames); } }) .catch((error) => { - console.debug('Could not fetch ENS names', error) - }) + console.debug('Could not fetch ENS names', error); + }); return () => { - stale = true - setENSNames(undefined) - } + stale = true; + setENSNames(undefined); + }; } - }, [provider, accounts]) + }, [provider, accounts]); - return ENSNames + return ENSNames; } function getAugmentedHooks( connector: T, { useChainId, useAccounts, useError }: ReturnType, - { useAccount, useIsActive }: ReturnType + { useAccount, useIsActive }: ReturnType, ) { - function useProvider(network?: Networkish, enabled = true): Web3Provider | undefined { - const isActive = useIsActive() + function useProvider( + network?: Networkish, + enabled = true, + ): Web3Provider | undefined { + const isActive = useIsActive(); - const chainId = useChainId() - const accounts = useAccounts() + const chainId = useChainId(); + const accounts = useAccounts(); return useMemo(() => { // we use chainId and accounts to re-render in case connector.provider changes in place if (enabled && isActive && connector.provider && chainId && accounts) { - return new Web3Provider(connector.provider, network) + return new Web3Provider(connector.provider, network); } - }, [enabled, isActive, network, chainId, accounts]) + }, [enabled, isActive, network, chainId, accounts]); } - function useENSNames(provider: Web3Provider | undefined): (string | null)[] | undefined { - const accounts = useAccounts() - return useENS(provider, accounts) + function useENSNames( + provider: Web3Provider | undefined, + ): (string | null)[] | undefined { + const accounts = useAccounts(); + return useENS(provider, accounts); } - function useENSName(provider: Web3Provider | undefined): (string | null) | undefined { - const account = useAccount() - const accounts = useMemo(() => (account === undefined ? undefined : [account]), [account]) + function useENSName( + provider: Web3Provider | undefined, + ): (string | null) | undefined { + const account = useAccount(); + const accounts = useMemo( + () => (account === undefined ? undefined : [account]), + [account], + ); - return useENS(provider, accounts)?.[0] + return useENS(provider, accounts)?.[0]; } // for backwards compatibility only function useWeb3React(provider: Web3Provider | undefined) { - const chainId = useChainId() - const account = useAccount() - const error = useError() + const chainId = useChainId(); + const account = useAccount(); + const error = useError(); - const isActive = useIsActive() + const isActive = useIsActive(); return useMemo( () => ({ @@ -278,9 +325,9 @@ function getAugmentedHooks( active: isActive, error, }), - [provider, chainId, account, isActive, error] - ) + [provider, chainId, account, isActive, error], + ); } - return { useProvider, useENSNames, useENSName, useWeb3React } -} \ No newline at end of file + return { useProvider, useENSNames, useENSName, useWeb3React }; +} diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index 25d871e..37064e3 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -9,30 +9,37 @@ import create from 'zustand/vanilla'; import { getAddress } from '@ethersproject/address'; function validateChainId(chainId: number): void { - if (!Number.isInteger(chainId) || chainId <= 0 || chainId > Number.MAX_SAFE_INTEGER) { - throw new Error(`Invalid chainId ${chainId}`) + if ( + !Number.isInteger(chainId) || + chainId <= 0 || + chainId > Number.MAX_SAFE_INTEGER + ) { + throw new Error(`Invalid chainId ${chainId}`); } } export class ChainIdNotAllowedError extends Error { - public readonly chainId: number + public readonly chainId: number; public constructor(chainId: number, allowedChainIds: number[]) { - super(`chainId ${chainId} not included in ${allowedChainIds.toString()}`) - this.chainId = chainId - this.name = ChainIdNotAllowedError.name - Object.setPrototypeOf(this, ChainIdNotAllowedError.prototype) + super(`chainId ${chainId} not included in ${allowedChainIds.toString()}`); + this.chainId = chainId; + this.name = ChainIdNotAllowedError.name; + Object.setPrototypeOf(this, ChainIdNotAllowedError.prototype); } } -function ensureChainIdIsAllowed(chainId: number, allowedChainIds: number[]): ChainIdNotAllowedError | undefined { +function ensureChainIdIsAllowed( + chainId: number, + allowedChainIds: number[], +): ChainIdNotAllowedError | undefined { return allowedChainIds.some((allowedChainId) => chainId === allowedChainId) ? undefined - : new ChainIdNotAllowedError(chainId, allowedChainIds) + : new ChainIdNotAllowedError(chainId, allowedChainIds); } function validateAccount(account: string): string { - return getAddress(account) + return getAddress(account); } const DEFAULT_STATE = { @@ -40,17 +47,19 @@ const DEFAULT_STATE = { accounts: undefined, activating: false, error: undefined, -} +}; -export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web3ReactStore, Actions] { +export function createWeb3ReactStoreAndActions( + allowedChainIds?: number[], +): [Web3ReactStore, Actions] { if (allowedChainIds?.length === 0) { - throw new Error(`allowedChainIds is length 0`) + throw new Error(`allowedChainIds is length 0`); } - const store = create(() => DEFAULT_STATE) + const store = create(() => DEFAULT_STATE); // flag for tracking updates so we don't clobber data when cancelling activation - let nullifier = 0 + let nullifier = 0; /** * Sets activating to true, indicating that an update is in progress. @@ -59,16 +68,16 @@ export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web * as long as there haven't been any intervening updates. */ function startActivation(): () => void { - const nullifierCached = ++nullifier + const nullifierCached = ++nullifier; - store.setState({ ...DEFAULT_STATE, activating: true }) + store.setState({ ...DEFAULT_STATE, activating: true }); // return a function that cancels the activation iff nothing else has happened return () => { if (nullifier === nullifierCached) { - store.setState({ ...DEFAULT_STATE, activating: false }) + store.setState({ ...DEFAULT_STATE, activating: false }); } - } + }; } /** @@ -81,52 +90,62 @@ export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web function update(stateUpdate: Web3ReactStateUpdate): void { // validate chainId statically, independent of existing state if (stateUpdate.chainId !== undefined) { - validateChainId(stateUpdate.chainId) + validateChainId(stateUpdate.chainId); } // validate accounts statically, independent of existing state if (stateUpdate.accounts !== undefined) { for (let i = 0; i < stateUpdate.accounts.length; i++) { - stateUpdate.accounts[i] = validateAccount(stateUpdate.accounts[i]) + stateUpdate.accounts[i] = validateAccount(stateUpdate.accounts[i]); } } - nullifier++ + nullifier++; store.setState((existingState): Web3ReactState => { // determine the next chainId and accounts - const chainId = stateUpdate.chainId ?? existingState.chainId - const accounts = stateUpdate.accounts ?? existingState.accounts + const chainId = stateUpdate.chainId ?? existingState.chainId; + const accounts = stateUpdate.accounts ?? existingState.accounts; // determine the next error - let error = existingState.error + let error = existingState.error; if (chainId && allowedChainIds) { // if we have a chainId allowlist and a chainId, we need to ensure it's allowed - const chainIdError = ensureChainIdIsAllowed(chainId, allowedChainIds) + const chainIdError = ensureChainIdIsAllowed(chainId, allowedChainIds); // warn if we're going to clobber existing error if (chainIdError && error) { - if (!(error instanceof ChainIdNotAllowedError) || error.chainId !== chainIdError.chainId) { - console.debug(`${error.name} is being clobbered by ${chainIdError.name}`) + if ( + !(error instanceof ChainIdNotAllowedError) || + error.chainId !== chainIdError.chainId + ) { + console.debug( + `${error.name} is being clobbered by ${chainIdError.name}`, + ); } } - error = chainIdError + error = chainIdError; } // ensure that the error is cleared when appropriate - if (error && !(error instanceof ChainIdNotAllowedError) && chainId && accounts) { - error = undefined + if ( + error && + !(error instanceof ChainIdNotAllowedError) && + chainId && + accounts + ) { + error = undefined; } // ensure that the activating flag is cleared when appropriate - let activating = existingState.activating + let activating = existingState.activating; if (activating && (error || (chainId && accounts))) { - activating = false + activating = false; } - return { chainId, accounts, activating, error } - }) + return { chainId, accounts, activating, error }; + }); } /** @@ -135,10 +154,10 @@ export function createWeb3ReactStoreAndActions(allowedChainIds?: number[]): [Web * @param error - The error to report. If undefined, the state will be reset to its default value. */ function reportError(error: Error | undefined): void { - nullifier++ + nullifier++; - store.setState(() => ({ ...DEFAULT_STATE, error })) + store.setState(() => ({ ...DEFAULT_STATE, error })); } - return [store, { startActivation, update, reportError }] -} \ No newline at end of file + return [store, { startActivation, update, reportError }]; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index e9c1821..068b099 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -7,4 +7,4 @@ export { ProviderConnectInfo } from './types'; export { ProviderMessage } from './types'; export { Connector } from './types'; export { Actions } from './types'; -export { Provider } from './types'; \ No newline at end of file +export { Provider } from './types'; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index eab8853..4635c57 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -1,56 +1,56 @@ -import type { EventEmitter } from 'node:events' -import type { State, StoreApi } from 'zustand/vanilla' +import type { EventEmitter } from 'node:events'; +import type { State, StoreApi } from 'zustand/vanilla'; export interface Web3ReactState extends State { - chainId: number | undefined - accounts: string[] | undefined - activating: boolean - error: Error | undefined + chainId: number | undefined; + accounts: string[] | undefined; + activating: boolean; + error: Error | undefined; } -export type Web3ReactStore = StoreApi +export type Web3ReactStore = StoreApi; export type Web3ReactStateUpdate = | { - chainId: number - accounts: string[] + chainId: number; + accounts: string[]; } | { - chainId: number - accounts?: never + chainId: number; + accounts?: never; } | { - chainId?: never - accounts: string[] - } + chainId?: never; + accounts: string[]; + }; export interface Actions { - startActivation: () => () => void - update: (stateUpdate: Web3ReactStateUpdate) => void - reportError: (error: Error | undefined) => void + startActivation: () => () => void; + update: (stateUpdate: Web3ReactStateUpdate) => void; + reportError: (error: Error | undefined) => void; } // per EIP-1193 export interface RequestArguments { - readonly method: string - readonly params?: readonly unknown[] | object + readonly method: string; + readonly params?: readonly unknown[] | object; } // per EIP-1193 export interface Provider extends EventEmitter { - request(args: RequestArguments): Promise + request(args: RequestArguments): Promise; } // per EIP-1193 export interface ProviderConnectInfo { - readonly chainId: string + readonly chainId: string; } // per EIP-1193 export interface ProviderRpcError extends Error { - message: string - code: number - data?: unknown + message: string; + code: number; + data?: unknown; } export interface ProviderMessage { @@ -58,7 +58,6 @@ export interface ProviderMessage { readonly data: unknown; } - export abstract class Connector { /** * An @@ -66,28 +65,28 @@ export abstract class Connector { * EIP-1102 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md}) compliant provider. * This property must be defined while the connector is active. */ - public provider: Provider | undefined + public provider: Provider | undefined; - protected readonly actions: Actions + protected readonly actions: Actions; /** * @param actions - Methods bound to a zustand store that tracks the state of the connector. * Actions are used by the connector to report changes in connection status. */ constructor(actions: Actions) { - this.actions = actions + this.actions = actions; } /** * Initiate a connection. */ - public abstract activate(...args: unknown[]): Promise | void + public abstract activate(...args: unknown[]): Promise | void; /** * Initiate a disconnect. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public deactivate(...args: unknown[]): Promise | void { - this.actions.reportError(undefined) + this.actions.reportError(undefined); } -} \ No newline at end of file +} From fe68ee3af4234ebf1ddf0efe023ae6ca3de6c627 Mon Sep 17 00:00:00 2001 From: sam bacha Date: Wed, 26 Jan 2022 00:02:47 -0800 Subject: [PATCH 4/7] v0.0.2 --- .github/workflows/coverage.yml | 14 ++++++++------ .github/workflows/pipeline-release.yml | 14 +++++++------- .prettierignore | 2 +- package.json | 2 +- packages/types/tsconfig.cjs.json | 1 + packages/types/tsconfig.json | 1 + 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b090546..d405b66 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -30,7 +30,9 @@ jobs: id: cache-node-modules with: path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }} + key: + ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}-${{ + hashFiles('**/package.json') }} - uses: actions/cache@v2 name: Cache Jest cache @@ -49,18 +51,18 @@ jobs: - run: yarn install id: install - + - run: yarn run build id: turbo - + - uses: codecov/codecov-action@v2.1.0 with: - token: ${{ secrets.CODECOV_TOKEN }} + token: ${{ secrets.CODECOV_TOKEN }} - uses: ArtiomTr/jest-coverage-report-action@v2.0-rc.6 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - package-manager: yarn + github-token: ${{ secrets.GITHUB_TOKEN }} + package-manager: yarn - name: Tests run: yarn run test:ci diff --git a/.github/workflows/pipeline-release.yml b/.github/workflows/pipeline-release.yml index a5a16dd..e48d358 100644 --- a/.github/workflows/pipeline-release.yml +++ b/.github/workflows/pipeline-release.yml @@ -3,10 +3,10 @@ name: pipeline-release on: pull_request: branches: - - "release-*" - - "master" + - 'release-*' + - 'master' tags-ignore: - - "*" + - '*' concurrency: group: ci-tests-${{ github.ref }}-1 @@ -51,12 +51,12 @@ jobs: - run: echo "::set-output name=date::$(date +'%Y-%m-%d')" - - uses: "marvinpinto/action-automatic-releases@latest" + - uses: 'marvinpinto/action-automatic-releases@latest' with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - automatic_release_tag: "latest" + repo_token: '${{ secrets.GITHUB_TOKEN }}' + automatic_release_tag: 'latest' prerelease: true files: | LICENSE.md packages/**/*.tgz - id: "automatic_releases" + id: 'automatic_releases' diff --git a/.prettierignore b/.prettierignore index 8d128ae..9edd73c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ +.yarn/* .cache public/ build/ @@ -15,4 +16,3 @@ dist/ *.uml *.json *.js -*.d.ts diff --git a/package.json b/package.json index 8f3d93c..d08a096 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@disco3/monorepo", - "version": "0.0.1", + "version": "0.0.2", "private": true, "workspaces": [ "packages/*" diff --git a/packages/types/tsconfig.cjs.json b/packages/types/tsconfig.cjs.json index 08d87e8..ecd311e 100644 --- a/packages/types/tsconfig.cjs.json +++ b/packages/types/tsconfig.cjs.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.cjs.json", "include": ["./src"], "compilerOptions": { + "noUnusedLocals": false, "outDir": "./dist/cjs" } } diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index 67531bb..5ec2b72 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "include": ["./src"], "compilerOptions": { + "noUnusedLocals": false, "outDir": "./dist" } } From 52e3fef9016166af20bff941c14475f39d9c8d0d Mon Sep 17 00:00:00 2001 From: sam bacha Date: Wed, 26 Jan 2022 00:21:59 -0800 Subject: [PATCH 5/7] fix(types): args --- packages/types/src/types.ts | 69 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 4635c57..e6f105d 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -1,62 +1,63 @@ -import type { EventEmitter } from 'node:events'; -import type { State, StoreApi } from 'zustand/vanilla'; +import type { EventEmitter } from 'node:events' +import type { State, StoreApi } from 'zustand/vanilla' export interface Web3ReactState extends State { - chainId: number | undefined; - accounts: string[] | undefined; - activating: boolean; - error: Error | undefined; + chainId: number | undefined + accounts: string[] | undefined + activating: boolean + error: Error | undefined } -export type Web3ReactStore = StoreApi; +export type Web3ReactStore = StoreApi export type Web3ReactStateUpdate = | { - chainId: number; - accounts: string[]; + chainId: number + accounts: string[] } | { - chainId: number; - accounts?: never; + chainId: number + accounts?: never } | { - chainId?: never; - accounts: string[]; - }; + chainId?: never + accounts: string[] + } export interface Actions { - startActivation: () => () => void; - update: (stateUpdate: Web3ReactStateUpdate) => void; - reportError: (error: Error | undefined) => void; + startActivation: () => () => void + update: (stateUpdate: Web3ReactStateUpdate) => void + reportError: (error: Error | undefined) => void } // per EIP-1193 export interface RequestArguments { - readonly method: string; - readonly params?: readonly unknown[] | object; + readonly method: string + readonly params?: readonly unknown[] | object } // per EIP-1193 export interface Provider extends EventEmitter { - request(args: RequestArguments): Promise; + request(args: RequestArguments): Promise } // per EIP-1193 export interface ProviderConnectInfo { - readonly chainId: string; + readonly chainId: string } // per EIP-1193 export interface ProviderRpcError extends Error { - message: string; - code: number; - data?: unknown; + message: string + code: number + data?: unknown } export interface ProviderMessage { - readonly type: string; - readonly data: unknown; -} + readonly type: string; + readonly data: unknown; + } + export abstract class Connector { /** @@ -65,28 +66,28 @@ export abstract class Connector { * EIP-1102 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md}) compliant provider. * This property must be defined while the connector is active. */ - public provider: Provider | undefined; + public provider: Provider | undefined - protected readonly actions: Actions; + protected readonly actions: Actions /** * @param actions - Methods bound to a zustand store that tracks the state of the connector. * Actions are used by the connector to report changes in connection status. */ constructor(actions: Actions) { - this.actions = actions; + this.actions = actions } /** * Initiate a connection. */ - public abstract activate(...args: unknown[]): Promise | void; + public abstract activate(...args: unknown[]): Promise | void /** * Initiate a disconnect. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public deactivate(...args: unknown[]): Promise | void { - this.actions.reportError(undefined); + public deactivate(..._args: unknown[]): Promise | void { + this.actions.reportError(undefined) } -} +} \ No newline at end of file From b60eb52eb5c065604f8fe1a02daa8088184c4e8b Mon Sep 17 00:00:00 2001 From: sam bacha Date: Wed, 26 Jan 2022 00:22:29 -0800 Subject: [PATCH 6/7] chore(lint): format --- packages/types/src/types.ts | 67 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index e6f105d..987d9ef 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -1,63 +1,62 @@ -import type { EventEmitter } from 'node:events' -import type { State, StoreApi } from 'zustand/vanilla' +import type { EventEmitter } from 'node:events'; +import type { State, StoreApi } from 'zustand/vanilla'; export interface Web3ReactState extends State { - chainId: number | undefined - accounts: string[] | undefined - activating: boolean - error: Error | undefined + chainId: number | undefined; + accounts: string[] | undefined; + activating: boolean; + error: Error | undefined; } -export type Web3ReactStore = StoreApi +export type Web3ReactStore = StoreApi; export type Web3ReactStateUpdate = | { - chainId: number - accounts: string[] + chainId: number; + accounts: string[]; } | { - chainId: number - accounts?: never + chainId: number; + accounts?: never; } | { - chainId?: never - accounts: string[] - } + chainId?: never; + accounts: string[]; + }; export interface Actions { - startActivation: () => () => void - update: (stateUpdate: Web3ReactStateUpdate) => void - reportError: (error: Error | undefined) => void + startActivation: () => () => void; + update: (stateUpdate: Web3ReactStateUpdate) => void; + reportError: (error: Error | undefined) => void; } // per EIP-1193 export interface RequestArguments { - readonly method: string - readonly params?: readonly unknown[] | object + readonly method: string; + readonly params?: readonly unknown[] | object; } // per EIP-1193 export interface Provider extends EventEmitter { - request(args: RequestArguments): Promise + request(args: RequestArguments): Promise; } // per EIP-1193 export interface ProviderConnectInfo { - readonly chainId: string + readonly chainId: string; } // per EIP-1193 export interface ProviderRpcError extends Error { - message: string - code: number - data?: unknown + message: string; + code: number; + data?: unknown; } export interface ProviderMessage { - readonly type: string; - readonly data: unknown; - } - + readonly type: string; + readonly data: unknown; +} export abstract class Connector { /** @@ -66,28 +65,28 @@ export abstract class Connector { * EIP-1102 ({@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md}) compliant provider. * This property must be defined while the connector is active. */ - public provider: Provider | undefined + public provider: Provider | undefined; - protected readonly actions: Actions + protected readonly actions: Actions; /** * @param actions - Methods bound to a zustand store that tracks the state of the connector. * Actions are used by the connector to report changes in connection status. */ constructor(actions: Actions) { - this.actions = actions + this.actions = actions; } /** * Initiate a connection. */ - public abstract activate(...args: unknown[]): Promise | void + public abstract activate(...args: unknown[]): Promise | void; /** * Initiate a disconnect. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public deactivate(..._args: unknown[]): Promise | void { - this.actions.reportError(undefined) + this.actions.reportError(undefined); } -} \ No newline at end of file +} From 9076b21ea788fc4b1e771fb40e24bb73a54560a7 Mon Sep 17 00:00:00 2001 From: sam bacha Date: Wed, 26 Jan 2022 00:29:15 -0800 Subject: [PATCH 7/7] style: prettier add support for `cursorOffset` --- .prettierrc.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.prettierrc.js b/.prettierrc.js index c2b40d6..fb850cc 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,15 +1,15 @@ /** * @file Prettier configuration for Conformance - * @version 1.0.6 + * @version 1.0.8 * @summary base config adapted from AirBNB to maximize performance * @schema http://json.schemastore.org/prettierrc */ 'use strict'; - module.exports = { arrowParens: 'always', bracketSpacing: true, + cursorOffset: 1, endOfLine: 'lf', jsxBracketSameLine: false, jsxSingleQuote: false, @@ -21,6 +21,5 @@ module.exports = { tabWidth: 2, trailingComma: 'all', useTabs: false, -embeddedLanguageFormatting: "auto" - + embeddedLanguageFormatting: 'auto' };