From 070e48480194c8d7f45bda1d7dd1346e6f5d7227 Mon Sep 17 00:00:00 2001 From: awkweb Date: Tue, 10 Dec 2024 01:44:31 -0500 Subject: [PATCH] feat(core,react,vue): connect parameters (#4453) * wip: make config connectors generic * feat: tweaks * chore: changeset * refactor: types --- .changeset/spotty-kangaroos-approve.md | 6 ++ .changeset/thirty-camels-exercise.md | 7 ++ packages/connectors/src/walletConnect.test.ts | 17 +++- packages/connectors/src/walletConnect.ts | 6 +- packages/core/src/actions/connect.test-d.ts | 48 +++++++++++ packages/core/src/actions/connect.ts | 33 +++++-- .../src/actions/getConnectorClient.test.ts | 6 +- packages/core/src/actions/getConnectors.ts | 7 +- packages/core/src/actions/watchConnectors.ts | 12 +-- .../core/src/connectors/createConnector.ts | 8 +- packages/core/src/connectors/mock.test.ts | 16 +++- packages/core/src/connectors/mock.ts | 12 ++- packages/core/src/createConfig.test-d.ts | 22 +++++ packages/core/src/createConfig.ts | 85 +++++++++++-------- packages/core/src/errors/config.test.ts | 2 +- packages/core/src/errors/config.ts | 2 +- packages/core/src/query/connect.ts | 65 +++++++++----- packages/react/src/hooks/useConnect.test-d.ts | 18 ++-- packages/react/src/hooks/useConnect.ts | 13 +-- packages/react/src/hooks/useConnectors.ts | 16 ++-- packages/register-tests/react/src/config.ts | 3 +- .../react/src/useConnect.test-d.ts | 13 +++ packages/register-tests/vue/src/config.ts | 3 +- .../vue/src/useConnect.test-d.ts | 13 +++ .../vue/src/composables/useConnect.test-d.ts | 11 ++- packages/vue/src/composables/useConnect.ts | 13 +-- packages/vue/src/composables/useConnectors.ts | 19 +++-- playgrounds/vite-react/src/App.tsx | 7 +- 28 files changed, 367 insertions(+), 116 deletions(-) create mode 100644 .changeset/spotty-kangaroos-approve.md create mode 100644 .changeset/thirty-camels-exercise.md create mode 100644 packages/core/src/actions/connect.test-d.ts create mode 100644 packages/register-tests/react/src/useConnect.test-d.ts create mode 100644 packages/register-tests/vue/src/useConnect.test-d.ts diff --git a/.changeset/spotty-kangaroos-approve.md b/.changeset/spotty-kangaroos-approve.md new file mode 100644 index 0000000000..bcbfb54264 --- /dev/null +++ b/.changeset/spotty-kangaroos-approve.md @@ -0,0 +1,6 @@ +--- +"wagmi": minor +"@wagmi/vue": minor +--- + +Added support to `useConnect` for custom `connector.connect` parameters. diff --git a/.changeset/thirty-camels-exercise.md b/.changeset/thirty-camels-exercise.md new file mode 100644 index 0000000000..76ac889215 --- /dev/null +++ b/.changeset/thirty-camels-exercise.md @@ -0,0 +1,7 @@ +--- +"@wagmi/connectors": minor +"@wagmi/core": minor +--- + +Added narrowing to `config.connectors`. + diff --git a/packages/connectors/src/walletConnect.test.ts b/packages/connectors/src/walletConnect.test.ts index 4f15cb10f4..4e8a74ebfe 100644 --- a/packages/connectors/src/walletConnect.test.ts +++ b/packages/connectors/src/walletConnect.test.ts @@ -1,7 +1,15 @@ import { config, walletConnectProjectId } from '@wagmi/test' import { http, HttpResponse } from 'msw' import { setupServer } from 'msw/node' -import { afterAll, afterEach, beforeAll, expect, test, vi } from 'vitest' +import { + afterAll, + afterEach, + beforeAll, + expect, + expectTypeOf, + test, + vi, +} from 'vitest' import { walletConnect } from './walletConnect.js' @@ -49,4 +57,11 @@ test('setup', () => { const connectorFn = walletConnect({ projectId: walletConnectProjectId }) const connector = config._internal.connectors.setup(connectorFn) expect(connector.name).toEqual('WalletConnect') + + type ConnectFnParameters = NonNullable< + Parameters<(typeof connector)['connect']>[0] + > + expectTypeOf().toMatchTypeOf< + string | undefined + >() }) diff --git a/packages/connectors/src/walletConnect.ts b/packages/connectors/src/walletConnect.ts index 40f8fb17b5..fc4f794c1f 100644 --- a/packages/connectors/src/walletConnect.ts +++ b/packages/connectors/src/walletConnect.ts @@ -78,7 +78,11 @@ export function walletConnect(parameters: WalletConnectParameters) { type Provider = Awaited> type Properties = { - connect(parameters?: { chainId?: number; pairingTopic?: string }): Promise<{ + connect(parameters?: { + chainId?: number | undefined + isReconnecting?: boolean | undefined + pairingTopic?: string | undefined + }): Promise<{ accounts: readonly Address[] chainId: number }> diff --git a/packages/core/src/actions/connect.test-d.ts b/packages/core/src/actions/connect.test-d.ts new file mode 100644 index 0000000000..ad790b1209 --- /dev/null +++ b/packages/core/src/actions/connect.test-d.ts @@ -0,0 +1,48 @@ +import { accounts } from '@wagmi/test' +import { http } from 'viem' +import { mainnet } from 'viem/chains' +import { expectTypeOf, test } from 'vitest' + +import type { CreateConnectorFn } from '../connectors/createConnector.js' +import { mock } from '../connectors/mock.js' +import { type Connector, createConfig } from '../createConfig.js' +import { connect } from './connect.js' + +const config = createConfig({ + chains: [mainnet], + transports: { [mainnet.id]: http() }, +}) + +test('parameters: connector (ConnectorFn)', () => { + const connectorFn = mock({ accounts }) + + connect(config, { + connector: connectorFn, + foo: 'bar', + }) + expectTypeOf< + typeof connectorFn extends CreateConnectorFn ? true : false + >().toEqualTypeOf() + + type Result = NonNullable< + Parameters>[1] + > + expectTypeOf().toEqualTypeOf() +}) + +test('parameters: connector (Connector)', () => { + const connector = config._internal.connectors.setup(mock({ accounts })) + + connect(config, { + connector, + foo: 'bar', + }) + expectTypeOf< + typeof connector extends Connector ? true : false + >().toEqualTypeOf() + + type Result = NonNullable< + Parameters>[1] + > + expectTypeOf().toEqualTypeOf() +}) diff --git a/packages/core/src/actions/connect.ts b/packages/core/src/actions/connect.ts index 9379951389..3856fa7586 100644 --- a/packages/core/src/actions/connect.ts +++ b/packages/core/src/actions/connect.ts @@ -14,11 +14,31 @@ import { import type { ChainIdParameter } from '../types/properties.js' import type { Compute } from '../types/utils.js' -export type ConnectParameters = Compute< +export type ConnectParameters< + config extends Config = Config, + connector extends Connector | CreateConnectorFn = + | Connector + | CreateConnectorFn, + /// + parameters extends unknown | undefined = + | (connector extends CreateConnectorFn + ? Omit< + NonNullable['connect']>[0]>, + 'isReconnecting' + > + : never) + | (connector extends Connector + ? Omit< + NonNullable[0]>, + 'isReconnecting' + > + : never), +> = Compute< ChainIdParameter & { - connector: Connector | CreateConnectorFn + connector: connector | CreateConnectorFn } -> +> & + parameters export type ConnectReturnType = { accounts: readonly [Address, ...Address[]] @@ -37,9 +57,12 @@ export type ConnectErrorType = | ErrorType /** https://wagmi.sh/core/api/actions/connect */ -export async function connect( +export async function connect< + config extends Config, + connector extends Connector | CreateConnectorFn, +>( config: config, - parameters: ConnectParameters, + parameters: ConnectParameters, ): Promise> { // "Register" connector if not already created let connector: Connector diff --git a/packages/core/src/actions/getConnectorClient.test.ts b/packages/core/src/actions/getConnectorClient.test.ts index dd1947b838..a9d60f5142 100644 --- a/packages/core/src/actions/getConnectorClient.test.ts +++ b/packages/core/src/actions/getConnectorClient.test.ts @@ -86,20 +86,20 @@ test('behavior: account does not exist on connector', async () => { test('behavior: reconnecting', async () => { config.setState((state) => ({ ...state, status: 'reconnecting' })) - const { id, name, type, uuid } = connector + const { id, name, type, uid } = connector await expect( getConnectorClient(config, { connector: { id, name, type, - uuid, + uid, } as unknown as Connector, }), ).rejects.toThrowErrorMatchingInlineSnapshot(` [ConnectorUnavailableReconnectingError: Connector "Mock Connector" unavailable while reconnecting. - Details: During the reconnection step, the only connector methods guaranteed to be available are: \`id\`, \`name\`, \`type\`, \`uuid\`. All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored. This error commonly occurs for connectors that asynchronously inject after reconnection has already started. + Details: During the reconnection step, the only connector methods guaranteed to be available are: \`id\`, \`name\`, \`type\`, \`uid\`. All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored. This error commonly occurs for connectors that asynchronously inject after reconnection has already started. Version: @wagmi/core@x.y.z] `) config.setState((state) => ({ ...state, status: 'disconnected' })) diff --git a/packages/core/src/actions/getConnectors.ts b/packages/core/src/actions/getConnectors.ts index 7aea301ceb..439362d3f4 100644 --- a/packages/core/src/actions/getConnectors.ts +++ b/packages/core/src/actions/getConnectors.ts @@ -1,12 +1,15 @@ import type { Config, Connector } from '../createConfig.js' import { deepEqual } from '../utils/deepEqual.js' -export type GetConnectorsReturnType = readonly Connector[] +export type GetConnectorsReturnType = + config['connectors'] let previousConnectors: readonly Connector[] = [] /** https://wagmi.sh/core/api/actions/getConnectors */ -export function getConnectors(config: Config): GetConnectorsReturnType { +export function getConnectors( + config: config, +): GetConnectorsReturnType { const connectors = config.connectors if (deepEqual(previousConnectors, connectors)) return previousConnectors previousConnectors = connectors diff --git a/packages/core/src/actions/watchConnectors.ts b/packages/core/src/actions/watchConnectors.ts index 6330c4365e..e463ab52fa 100644 --- a/packages/core/src/actions/watchConnectors.ts +++ b/packages/core/src/actions/watchConnectors.ts @@ -1,19 +1,19 @@ import type { Config } from '../createConfig.js' import type { GetConnectorsReturnType } from './getConnectors.js' -export type WatchConnectorsParameters = { +export type WatchConnectorsParameters = { onChange( - connections: GetConnectorsReturnType, - prevConnectors: GetConnectorsReturnType, + connections: GetConnectorsReturnType, + prevConnectors: GetConnectorsReturnType, ): void } export type WatchConnectorsReturnType = () => void /** https://wagmi.sh/core/api/actions/watchConnectors */ -export function watchConnectors( - config: Config, - parameters: WatchConnectorsParameters, +export function watchConnectors( + config: config, + parameters: WatchConnectorsParameters, ): WatchConnectorsReturnType { const { onChange } = parameters return config._internal.connectors.subscribe((connectors, prevConnectors) => { diff --git a/packages/core/src/connectors/createConnector.ts b/packages/core/src/connectors/createConnector.ts index b6ae1c2bad..54b4d9955c 100644 --- a/packages/core/src/connectors/createConnector.ts +++ b/packages/core/src/connectors/createConnector.ts @@ -82,6 +82,12 @@ export function createConnector< provider, properties extends Record = Record, storageItem extends Record = Record, ->(createConnectorFn: CreateConnectorFn) { + /// + createConnectorFn extends CreateConnectorFn< + provider, + properties, + storageItem + > = CreateConnectorFn, +>(createConnectorFn: createConnectorFn) { return createConnectorFn } diff --git a/packages/core/src/connectors/mock.test.ts b/packages/core/src/connectors/mock.test.ts index 6b7122f45c..516d619e64 100644 --- a/packages/core/src/connectors/mock.test.ts +++ b/packages/core/src/connectors/mock.test.ts @@ -1,12 +1,26 @@ import { accounts, config } from '@wagmi/test' -import { expect, test } from 'vitest' +import { expect, expectTypeOf, test } from 'vitest' +import type { Connector } from '../createConfig.js' +import type { CreateConnectorFn } from './createConnector.js' import { mock } from './mock.js' test('setup', () => { const connectorFn = mock({ accounts }) const connector = config._internal.connectors.setup(connectorFn) expect(connector.name).toEqual('Mock Connector') + + expectTypeOf< + typeof connectorFn extends CreateConnectorFn ? true : false + >().toEqualTypeOf() + expectTypeOf< + typeof connector extends Connector ? true : false + >().toEqualTypeOf() + + type ConnectFnParameters = NonNullable< + Parameters<(typeof connector)['connect']>[0] + > + expectTypeOf().toMatchTypeOf() }) test('behavior: features.connectError', () => { diff --git a/packages/core/src/connectors/mock.ts b/packages/core/src/connectors/mock.ts index 2598c72b62..8a2dd8a6e6 100644 --- a/packages/core/src/connectors/mock.ts +++ b/packages/core/src/connectors/mock.ts @@ -48,10 +48,20 @@ export function mock(parameters: MockParameters) { type Provider = ReturnType< Transport<'custom', unknown, EIP1193RequestFn> > + type Properties = { + connect(parameters?: { + chainId?: number | undefined + isReconnecting?: boolean | undefined + foo?: string | undefined + }): Promise<{ + accounts: readonly Address[] + chainId: number + }> + } let connected = features.defaultConnected let connectedChainId: number - return createConnector((config) => ({ + return createConnector((config) => ({ id: 'mock', name: 'Mock Connector', type: mock.type, diff --git a/packages/core/src/createConfig.test-d.ts b/packages/core/src/createConfig.test-d.ts index 32f12eb829..3daa57bd5f 100644 --- a/packages/core/src/createConfig.test-d.ts +++ b/packages/core/src/createConfig.test-d.ts @@ -86,3 +86,25 @@ test('behavior: parameters should not include certain client config properties', 'transport' extends Result ? true : false >().toEqualTypeOf() }) + +test('infer connectors', () => { + const connectorFn = mock({ accounts }) + const config = createConfig({ + chains: [mainnet, sepolia], + connectors: [connectorFn], + transports: { + [mainnet.id]: webSocket(), + [sepolia.id]: http(), + }, + }) + + const connector = config.connectors[0]! + expectTypeOf(connector).toEqualTypeOf( + config._internal.connectors.setup(connectorFn), + ) + + type ConnectFnParameters = NonNullable< + Parameters<(typeof connector)['connect']>[0] + > + expectTypeOf().toMatchTypeOf() +}) diff --git a/packages/core/src/createConfig.ts b/packages/core/src/createConfig.ts index 1ada1da8c0..b8be9ce108 100644 --- a/packages/core/src/createConfig.ts +++ b/packages/core/src/createConfig.ts @@ -33,42 +33,13 @@ import type { import { uid } from './utils/uid.js' import { version } from './version.js' -export type CreateConfigParameters< - chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]], - transports extends Record = Record< - chains[number]['id'], - Transport - >, -> = Compute< - { - chains: chains - connectors?: CreateConnectorFn[] | undefined - multiInjectedProviderDiscovery?: boolean | undefined - storage?: Storage | null | undefined - ssr?: boolean | undefined - syncConnectedChain?: boolean | undefined - } & OneOf< - | ({ transports: transports } & { - [key in keyof ClientConfig]?: - | ClientConfig[key] - | { [_ in chains[number]['id']]?: ClientConfig[key] | undefined } - | undefined - }) - | { - client(parameters: { chain: chains[number] }): Client< - transports[chains[number]['id']], - chains[number] - > - } - > -> - export function createConfig< const chains extends readonly [Chain, ...Chain[]], transports extends Record, + const connectorFns extends readonly CreateConnectorFn[], >( - parameters: CreateConfigParameters, -): Config { + parameters: CreateConfigParameters, +): Config { const { multiInjectedProviderDiscovery = true, storage = createStorage({ @@ -442,7 +413,7 @@ export function createConfig< return chains.getState() as chains }, get connectors() { - return connectors.getState() + return connectors.getState() as Connector[] }, storage, @@ -497,7 +468,9 @@ export function createConfig< }, connectors: { providerDetailToConnector, - setup, + setup: setup as ( + connectorFn: connectorFn, + ) => Connector, setState(value) { return connectors.setState( typeof value === 'function' ? value(connectors.getState()) : value, @@ -517,15 +490,49 @@ export function createConfig< // Types ///////////////////////////////////////////////////////////////////////////////////////////////// +export type CreateConfigParameters< + chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]], + transports extends Record = Record< + chains[number]['id'], + Transport + >, + connectorFns extends + readonly CreateConnectorFn[] = readonly CreateConnectorFn[], +> = Compute< + { + chains: chains + connectors?: connectorFns | undefined + multiInjectedProviderDiscovery?: boolean | undefined + storage?: Storage | null | undefined + ssr?: boolean | undefined + syncConnectedChain?: boolean | undefined + } & OneOf< + | ({ transports: transports } & { + [key in keyof ClientConfig]?: + | ClientConfig[key] + | { [_ in chains[number]['id']]?: ClientConfig[key] | undefined } + | undefined + }) + | { + client(parameters: { chain: chains[number] }): Client< + transports[chains[number]['id']], + chains[number] + > + } + > +> + export type Config< chains extends readonly [Chain, ...Chain[]] = readonly [Chain, ...Chain[]], transports extends Record = Record< chains[number]['id'], Transport >, + connectorFns extends + readonly CreateConnectorFn[] = readonly CreateConnectorFn[], > = { readonly chains: chains - readonly connectors: readonly Connector[] + readonly connectors: readonly Connector[] readonly storage: Storage | null readonly state: State @@ -586,7 +593,9 @@ type Internal< providerDetailToConnector( providerDetail: EIP6963ProviderDetail, ): CreateConnectorFn - setup(connectorFn: CreateConnectorFn): Connector + setup( + connectorFn: connectorFn, + ): Connector setState(value: Connector[] | ((state: Connector[]) => Connector[])): void subscribe( listener: (state: Connector[], prevState: Connector[]) => void, @@ -618,7 +627,9 @@ export type Connection = { connector: Connector } -export type Connector = ReturnType & { +export type Connector< + createConnectorFn extends CreateConnectorFn = CreateConnectorFn, +> = ReturnType & { emitter: Emitter uid: string } diff --git a/packages/core/src/errors/config.test.ts b/packages/core/src/errors/config.test.ts index 9a569290b9..1ec07fd107 100644 --- a/packages/core/src/errors/config.test.ts +++ b/packages/core/src/errors/config.test.ts @@ -62,7 +62,7 @@ test('constructors', () => { ).toMatchInlineSnapshot(` [ConnectorUnavailableReconnectingError: Connector "Rabby Wallet" unavailable while reconnecting. - Details: During the reconnection step, the only connector methods guaranteed to be available are: \`id\`, \`name\`, \`type\`, \`uuid\`. All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored. This error commonly occurs for connectors that asynchronously inject after reconnection has already started. + Details: During the reconnection step, the only connector methods guaranteed to be available are: \`id\`, \`name\`, \`type\`, \`uid\`. All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored. This error commonly occurs for connectors that asynchronously inject after reconnection has already started. Version: @wagmi/core@x.y.z] `) }) diff --git a/packages/core/src/errors/config.ts b/packages/core/src/errors/config.ts index 46cd2cb18e..2956f0132c 100644 --- a/packages/core/src/errors/config.ts +++ b/packages/core/src/errors/config.ts @@ -94,7 +94,7 @@ export class ConnectorUnavailableReconnectingError extends BaseError { constructor({ connector }: { connector: { name: string } }) { super(`Connector "${connector.name}" unavailable while reconnecting.`, { details: [ - 'During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uuid`.', + 'During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uid`.', 'All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored.', 'This error commonly occurs for connectors that asynchronously inject after reconnection has already started.', ].join(' '), diff --git a/packages/core/src/query/connect.ts b/packages/core/src/query/connect.ts index 4b8907d490..f521512789 100644 --- a/packages/core/src/query/connect.ts +++ b/packages/core/src/query/connect.ts @@ -1,4 +1,4 @@ -import type { MutationOptions } from '@tanstack/query-core' +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' import { type ConnectErrorType, @@ -6,9 +6,10 @@ import { type ConnectReturnType, connect, } from '../actions/connect.js' -import type { Config } from '../createConfig.js' +import type { Config, Connector } from '../createConfig.js' -import type { Mutate, MutateAsync } from './types.js' +import type { CreateConnectorFn } from '../connectors/createConnector.js' +import type { Compute } from '../types/utils.js' export function connectMutationOptions(config: config) { return { @@ -19,27 +20,51 @@ export function connectMutationOptions(config: config) { } as const satisfies MutationOptions< ConnectData, ConnectErrorType, - ConnectVariables + ConnectVariables > } export type ConnectData = ConnectReturnType -export type ConnectVariables = ConnectParameters +export type ConnectVariables< + config extends Config, + connector extends Connector | CreateConnectorFn, +> = ConnectParameters -export type ConnectMutate = Mutate< - ConnectData, - ConnectErrorType, - ConnectVariables, - context -> +export type ConnectMutate = < + connector extends + | config['connectors'][number] + | Connector + | CreateConnectorFn, +>( + variables: ConnectVariables, + options?: + | Compute< + MutateOptions< + ConnectData, + ConnectErrorType, + Compute>, + context + > + > + | undefined, +) => void -export type ConnectMutateAsync< - config extends Config, - context = unknown, -> = MutateAsync< - ConnectData, - ConnectErrorType, - ConnectVariables, - context -> +export type ConnectMutateAsync = < + connector extends + | config['connectors'][number] + | Connector + | CreateConnectorFn, +>( + variables: ConnectVariables, + options?: + | Compute< + MutateOptions< + ConnectData, + ConnectErrorType, + Compute>, + context + > + > + | undefined, +) => Promise> diff --git a/packages/react/src/hooks/useConnect.test-d.ts b/packages/react/src/hooks/useConnect.test-d.ts index f2287945d7..e678d2edd8 100644 --- a/packages/react/src/hooks/useConnect.test-d.ts +++ b/packages/react/src/hooks/useConnect.test-d.ts @@ -67,22 +67,26 @@ test('context', () => { | undefined >() expectTypeOf(error).toEqualTypeOf() - expectTypeOf(variables).toEqualTypeOf< + expectTypeOf(variables).toMatchTypeOf< | { chainId?: number | undefined - connector: Connector | CreateConnectorFn + connector: CreateConnectorFn | Connector } | undefined >() expectTypeOf(context).toEqualTypeOf() connect( - { connector }, + { + connector, + foo: 'bar', + }, { onError(error, variables, context) { expectTypeOf(variables).toEqualTypeOf<{ chainId?: number | undefined - connector: Connector | CreateConnectorFn + connector: typeof connector | CreateConnectorFn + foo?: string | undefined }>() expectTypeOf(error).toEqualTypeOf() expectTypeOf(context).toEqualTypeOf() @@ -90,7 +94,8 @@ test('context', () => { onSuccess(data, variables, context) { expectTypeOf(variables).toEqualTypeOf<{ chainId?: number | undefined - connector: Connector | CreateConnectorFn + connector: typeof connector | CreateConnectorFn + foo?: string | undefined }>() expectTypeOf(data).toEqualTypeOf<{ accounts: readonly [Address, ...Address[]] @@ -109,7 +114,8 @@ test('context', () => { expectTypeOf(error).toEqualTypeOf() expectTypeOf(variables).toEqualTypeOf<{ chainId?: number | undefined - connector: Connector | CreateConnectorFn + connector: typeof connector | CreateConnectorFn + foo?: string | undefined }>() expectTypeOf(context).toEqualTypeOf() }, diff --git a/packages/react/src/hooks/useConnect.ts b/packages/react/src/hooks/useConnect.ts index eec190c93c..acfe69234d 100644 --- a/packages/react/src/hooks/useConnect.ts +++ b/packages/react/src/hooks/useConnect.ts @@ -29,7 +29,7 @@ export type UseConnectParameters< | UseMutationParameters< ConnectData, ConnectErrorType, - ConnectVariables, + ConnectVariables, context > | undefined @@ -43,12 +43,12 @@ export type UseConnectReturnType< UseMutationReturnType< ConnectData, ConnectErrorType, - ConnectVariables, + ConnectVariables, context > & { connect: ConnectMutate connectAsync: ConnectMutateAsync - connectors: Compute + connectors: Compute | config['connectors'] } > @@ -80,10 +80,11 @@ export function useConnect< ) }, [config, result.reset]) + type Return = UseConnectReturnType return { - ...result, - connect: mutate, - connectAsync: mutateAsync, + ...(result as Return), + connect: mutate as Return['connect'], + connectAsync: mutateAsync as Return['connectAsync'], connectors: useConnectors({ config }), } } diff --git a/packages/react/src/hooks/useConnectors.ts b/packages/react/src/hooks/useConnectors.ts index 71e70e709f..40a681689d 100644 --- a/packages/react/src/hooks/useConnectors.ts +++ b/packages/react/src/hooks/useConnectors.ts @@ -1,7 +1,9 @@ 'use client' import { + type Config, type GetConnectorsReturnType, + type ResolvedRegister, getConnectors, watchConnectors, } from '@wagmi/core' @@ -10,14 +12,18 @@ import { useSyncExternalStore } from 'react' import type { ConfigParameter } from '../types/properties.js' import { useConfig } from './useConfig.js' -export type UseConnectorsParameters = ConfigParameter +export type UseConnectorsParameters = + ConfigParameter -export type UseConnectorsReturnType = GetConnectorsReturnType +export type UseConnectorsReturnType = + GetConnectorsReturnType /** https://wagmi.sh/react/api/hooks/useConnectors */ -export function useConnectors( - parameters: UseConnectorsParameters = {}, -): UseConnectorsReturnType { +export function useConnectors< + config extends Config = ResolvedRegister['config'], +>( + parameters: UseConnectorsParameters = {}, +): UseConnectorsReturnType { const config = useConfig(parameters) return useSyncExternalStore( diff --git a/packages/register-tests/react/src/config.ts b/packages/register-tests/react/src/config.ts index a80ad6ce73..5772212c1b 100644 --- a/packages/register-tests/react/src/config.ts +++ b/packages/register-tests/react/src/config.ts @@ -1,9 +1,10 @@ import { http } from 'viem' -import { createConfig } from 'wagmi' +import { createConfig, mock } from 'wagmi' import { celo, mainnet, optimism, zkSync } from 'wagmi/chains' export const config = createConfig({ chains: [celo, mainnet, optimism, zkSync], + connectors: [mock({ accounts: ['0x'] })], transports: { [celo.id]: http(), [mainnet.id]: http(), diff --git a/packages/register-tests/react/src/useConnect.test-d.ts b/packages/register-tests/react/src/useConnect.test-d.ts new file mode 100644 index 0000000000..2386dca7e9 --- /dev/null +++ b/packages/register-tests/react/src/useConnect.test-d.ts @@ -0,0 +1,13 @@ +import { expectTypeOf, test } from 'vitest' +import { useConnect } from 'wagmi' + +test('infers connect parameters', () => { + const { connect, connectors, variables } = useConnect() + const connector = connectors[0]! + + expectTypeOf(variables?.foo).toEqualTypeOf() + connect({ + connector, + foo: 'bar', + }) +}) diff --git a/packages/register-tests/vue/src/config.ts b/packages/register-tests/vue/src/config.ts index 735bcff0ed..fd13c5a6be 100644 --- a/packages/register-tests/vue/src/config.ts +++ b/packages/register-tests/vue/src/config.ts @@ -1,9 +1,10 @@ -import { createConfig } from '@wagmi/vue' +import { createConfig, mock } from '@wagmi/vue' import { celo, mainnet, optimism, zkSync } from '@wagmi/vue/chains' import { http } from 'viem' export const config = createConfig({ chains: [celo, mainnet, optimism, zkSync], + connectors: [mock({ accounts: ['0x'] })], transports: { [celo.id]: http(), [mainnet.id]: http(), diff --git a/packages/register-tests/vue/src/useConnect.test-d.ts b/packages/register-tests/vue/src/useConnect.test-d.ts new file mode 100644 index 0000000000..128834ff6d --- /dev/null +++ b/packages/register-tests/vue/src/useConnect.test-d.ts @@ -0,0 +1,13 @@ +import { useConnect } from '@wagmi/vue' +import { expectTypeOf, test } from 'vitest' + +test('infers connect parameters', () => { + const { connect, connectors, variables } = useConnect() + const connector = connectors[0]! + + expectTypeOf(variables.value?.foo).toEqualTypeOf() + connect({ + connector, + foo: 'bar', + }) +}) diff --git a/packages/vue/src/composables/useConnect.test-d.ts b/packages/vue/src/composables/useConnect.test-d.ts index da859eeba6..44c7b6fdac 100644 --- a/packages/vue/src/composables/useConnect.test-d.ts +++ b/packages/vue/src/composables/useConnect.test-d.ts @@ -67,7 +67,7 @@ test('context', () => { | undefined >() expectTypeOf(error.value).toEqualTypeOf() - expectTypeOf(variables.value).toEqualTypeOf< + expectTypeOf(variables.value).toMatchTypeOf< | { chainId?: number | undefined connector: Connector | CreateConnectorFn @@ -82,7 +82,8 @@ test('context', () => { onError(error, variables, context) { expectTypeOf(variables).toEqualTypeOf<{ chainId?: number | undefined - connector: Connector | CreateConnectorFn + connector: typeof connector | CreateConnectorFn + foo?: string | undefined }>() expectTypeOf(error).toEqualTypeOf() expectTypeOf(context).toEqualTypeOf() @@ -90,7 +91,8 @@ test('context', () => { onSuccess(data, variables, context) { expectTypeOf(variables).toEqualTypeOf<{ chainId?: number | undefined - connector: Connector | CreateConnectorFn + connector: typeof connector | CreateConnectorFn + foo?: string | undefined }>() expectTypeOf(data).toEqualTypeOf<{ accounts: readonly [Address, ...Address[]] @@ -109,7 +111,8 @@ test('context', () => { expectTypeOf(error).toEqualTypeOf() expectTypeOf(variables).toEqualTypeOf<{ chainId?: number | undefined - connector: Connector | CreateConnectorFn + connector: typeof connector | CreateConnectorFn + foo?: string | undefined }>() expectTypeOf(context).toEqualTypeOf() }, diff --git a/packages/vue/src/composables/useConnect.ts b/packages/vue/src/composables/useConnect.ts index 4851f3b580..7659dc3411 100644 --- a/packages/vue/src/composables/useConnect.ts +++ b/packages/vue/src/composables/useConnect.ts @@ -32,7 +32,7 @@ export type UseConnectParameters< | UseMutationParameters< ConnectData, ConnectErrorType, - ConnectVariables, + ConnectVariables, context > | undefined @@ -46,12 +46,12 @@ export type UseConnectReturnType< UseMutationReturnType< ConnectData, ConnectErrorType, - ConnectVariables, + ConnectVariables, context > & { connect: ConnectMutate connectAsync: ConnectMutateAsync - connectors: Compute + connectors: Compute | config['connectors'] } > @@ -82,10 +82,11 @@ export function useConnect< ) onScopeDispose(() => unsubscribe()) + type Return = UseConnectReturnType return { - ...result, - connect: mutate, - connectAsync: mutateAsync, + ...(result as Return), + connect: mutate as Return['connect'], + connectAsync: mutateAsync as Return['connectAsync'], connectors: useConnectors({ config }).value, } } diff --git a/packages/vue/src/composables/useConnectors.ts b/packages/vue/src/composables/useConnectors.ts index 4660159b42..4d12d81806 100644 --- a/packages/vue/src/composables/useConnectors.ts +++ b/packages/vue/src/composables/useConnectors.ts @@ -1,5 +1,7 @@ import { + type Config, type GetConnectorsReturnType, + type ResolvedRegister, getConnectors, watchConnectors, } from '@wagmi/core' @@ -8,20 +10,25 @@ import { type Ref, onScopeDispose, ref } from 'vue' import type { ConfigParameter } from '../types/properties.js' import { useConfig } from './useConfig.js' -export type UseConnectorsParameters = ConfigParameter +export type UseConnectorsParameters = + ConfigParameter -export type UseConnectorsReturnType = Ref +export type UseConnectorsReturnType = Ref< + GetConnectorsReturnType +> /** https://wagmi.sh/vue/api/composables/useConnectors */ -export function useConnectors( - parameters: UseConnectorsParameters = {}, -): UseConnectorsReturnType { +export function useConnectors< + config extends Config = ResolvedRegister['config'], +>( + parameters: UseConnectorsParameters = {}, +): UseConnectorsReturnType { const config = useConfig(parameters) const connectors = ref(getConnectors(config)) const unsubscribe = watchConnectors(config, { onChange(data) { - connectors.value = data + connectors.value = data as never }, }) onScopeDispose(() => unsubscribe()) diff --git a/playgrounds/vite-react/src/App.tsx b/playgrounds/vite-react/src/App.tsx index cc613d58ed..ad2e36b06f 100644 --- a/playgrounds/vite-react/src/App.tsx +++ b/playgrounds/vite-react/src/App.tsx @@ -92,7 +92,12 @@ function Connect() { {connectors.map((connector) => (