Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fast-cooks-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@wagmi/core": minor
---

Added `addChain` action.
5 changes: 5 additions & 0 deletions .changeset/thin-rings-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wagmi": minor
---

Added `useAddChain` hook.
10 changes: 10 additions & 0 deletions packages/core/src/actions/addChain.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { config } from '@wagmi/test'
import { avalanche } from 'viem/chains'
import { expectTypeOf, test } from 'vitest'

import { addChain } from './addChain.js'

test('return type', async () => {
const result = await addChain(config, { chain: avalanche })
expectTypeOf(result).toEqualTypeOf<void>()
})
70 changes: 70 additions & 0 deletions packages/core/src/actions/addChain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { accounts, config } from '@wagmi/test'
import { avalanche } from 'viem/chains'
import { expect, test, vi } from 'vitest'

import { mock } from '../connectors/mock.js'
import { addChain } from './addChain.js'
import { connect } from './connect.js'
import { disconnect } from './disconnect.js'

const connector = config.connectors[0]!

test('default', async () => {
await connect(config, { connector })
try {
await expect(
addChain(config, { chain: avalanche }),
).resolves.toBeUndefined()
} finally {
await disconnect(config, { connector })
}
})

test('parameters: connector and chain', async () => {
const connector_ = config._internal.connectors.setup(mock({ accounts }))
const provider = await connector_.getProvider()
const request = vi.spyOn(provider, 'request')
vi.spyOn(connector_, 'getProvider').mockResolvedValue(provider)
await connect(config, { connector: connector_ })
request.mockClear()

try {
await addChain(config, {
chain: { ...avalanche, blockExplorers: undefined },
connector: connector_,
})
expect(request).toHaveBeenCalledWith(
{
method: 'wallet_addEthereumChain',
params: [
expect.objectContaining({
blockExplorerUrls: undefined,
chainId: '0xa86a',
chainName: 'Avalanche',
}),
],
},
undefined,
)
} finally {
await disconnect(config, { connector: connector_ })
}
})

test('behavior: connector error', async () => {
const error = new Error('Failed to add chain.')
const connector_ = config._internal.connectors.setup(
mock({ accounts, features: { addChainError: error } }),
)
await connect(config, { connector: connector_ })
try {
await expect(
addChain(config, { chain: avalanche, connector: connector_ }),
).rejects.toMatchObject({
details: error.message,
name: 'UnknownRpcError',
})
} finally {
await disconnect(config, { connector: connector_ })
}
})
43 changes: 43 additions & 0 deletions packages/core/src/actions/addChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
type AddChainErrorType as viem_AddChainErrorType,
type AddChainParameters as viem_AddChainParameters,
addChain as viem_addChain,
} from 'viem/actions'

import type { Config } from '../createConfig.js'
import type { BaseErrorType, ErrorType } from '../errors/base.js'
import type { ConnectorParameter } from '../types/properties.js'
import type { Compute } from '../types/utils.js'
import { getAction } from '../utils/getAction.js'
import {
type GetConnectorClientErrorType,
getConnectorClient,
} from './getConnectorClient.js'

export type AddChainParameters = Compute<
viem_AddChainParameters & ConnectorParameter
>

export type AddChainReturnType = void

export type AddChainErrorType =
// getConnectorClient()
| GetConnectorClientErrorType
// base
| BaseErrorType
| ErrorType
// viem
| viem_AddChainErrorType

/** https://wagmi.sh/core/api/actions/addChain */
export async function addChain(
config: Config,
parameters: AddChainParameters,
): Promise<AddChainReturnType> {
const { connector, ...rest } = parameters

Check warning on line 37 in packages/core/src/actions/addChain.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/actions/addChain.ts#L37

Added line #L37 was not covered by tests

const client = await getConnectorClient(config, { connector })

Check warning on line 39 in packages/core/src/actions/addChain.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/actions/addChain.ts#L39

Added line #L39 was not covered by tests

const action = getAction(client, viem_addChain, 'addChain')
return action(rest)

Check warning on line 42 in packages/core/src/actions/addChain.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/actions/addChain.ts#L41-L42

Added lines #L41 - L42 were not covered by tests
}
13 changes: 13 additions & 0 deletions packages/core/src/connectors/mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ test('behavior: connector.getProvider request errors', async () => {
const connectorFn = mock({
accounts,
features: {
addChainError: true,
signMessageError: true,
signTypedDataError: true,
switchChainError: true,
Expand All @@ -83,6 +84,18 @@ test('behavior: connector.getProvider request errors', async () => {
) as ReturnType<typeof connectorFn>
const provider = await connector.getProvider()

await expect(
provider.request({
method: 'wallet_addEthereumChain',
params: [] as any,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[UserRejectedRequestError: User rejected the request.

Details: Failed to add chain.
Version: viem@2.55.7]
`)

await expect(
provider.request({
method: 'eth_signTypedData_v4',
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/connectors/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
| {
defaultConnected?: boolean | undefined
connectError?: boolean | Error | undefined
addChainError?: boolean | Error | undefined
switchChainError?: boolean | Error | undefined
signMessageError?: boolean | Error | undefined
signTypedDataError?: boolean | Error | undefined
Expand Down Expand Up @@ -176,6 +177,17 @@
}

// wallet methods
if (method === 'wallet_addEthereumChain') {
if (features.addChainError) {
if (typeof features.addChainError === 'boolean')
throw new UserRejectedRequestError(

Check warning on line 183 in packages/core/src/connectors/mock.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/connectors/mock.ts#L183

Added line #L183 was not covered by tests
new Error('Failed to add chain.'),
)
throw features.addChainError

Check warning on line 186 in packages/core/src/connectors/mock.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/connectors/mock.ts#L186

Added line #L186 was not covered by tests
}
return

Check warning on line 188 in packages/core/src/connectors/mock.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/connectors/mock.ts#L188

Added line #L188 was not covered by tests
}

if (method === 'wallet_switchEthereumChain') {
if (features.switchChainError) {
if (typeof features.switchChainError === 'boolean')
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/exports/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as actions from './actions.js'
test('exports', () => {
expect(Object.keys(actions)).toMatchInlineSnapshot(`
[
"addChain",
"call",
"connect",
"deployContract",
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/exports/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
////////////////////////////////////////////////////////////////////////////////

// biome-ignore lint/performance/noBarrelFile: entrypoint module
export {
type AddChainErrorType,
type AddChainParameters,
type AddChainReturnType,
addChain,
} from '../actions/addChain.js'

export {
type CallErrorType,
type CallParameters,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/exports/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as core from './index.js'
test('exports', () => {
expect(Object.keys(core)).toMatchInlineSnapshot(`
[
"addChain",
"call",
"connect",
"deployContract",
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
////////////////////////////////////////////////////////////////////////////////

// biome-ignore lint/performance/noBarrelFile: entrypoint module
export {
type AddChainErrorType,
type AddChainParameters,
type AddChainReturnType,
addChain,
} from '../actions/addChain.js'

export {
type CallErrorType,
type CallParameters,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/exports/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as query from './query.js'
test('exports', () => {
expect(Object.keys(query)).toMatchInlineSnapshot(`
[
"addChainMutationOptions",
"callQueryKey",
"callQueryOptions",
"connectMutationOptions",
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/exports/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
////////////////////////////////////////////////////////////////////////////////

// biome-ignore lint/performance/noBarrelFile: entrypoint module
export {
type AddChainData,
type AddChainMutate,
type AddChainMutateAsync,
type AddChainMutationOptions,
type AddChainOptions,
type AddChainVariables,
addChainMutationOptions,
} from '../query/addChain.js'

export {
type CallData,
type CallOptions,
Expand Down
32 changes: 32 additions & 0 deletions packages/core/src/query/addChain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { config } from '@wagmi/test'
import { avalanche } from 'viem/chains'
import { expect, test } from 'vitest'

import { connect } from '../actions/connect.js'
import { disconnect } from '../actions/disconnect.js'
import { addChainMutationOptions } from './addChain.js'

const connector = config.connectors[0]!

test('default', () => {
expect(addChainMutationOptions(config)).toMatchInlineSnapshot(`
{
"mutationFn": [Function],
"mutationKey": [
"addChain",
],
}
`)
})

test('mutationFn', async () => {
await connect(config, { connector })
try {
const options = addChainMutationOptions(config)
await expect(
options.mutationFn?.({ chain: avalanche, connector }),
).resolves.toBeUndefined()
} finally {
await disconnect(config, { connector })
}
})
56 changes: 56 additions & 0 deletions packages/core/src/query/addChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { MutationOptions } from '@tanstack/query-core'

import {
type AddChainErrorType,
type AddChainParameters,
type AddChainReturnType,
addChain,
} from '../actions/addChain.js'
import type { Config } from '../createConfig.js'
import type { MutationParameter } from '../types/query.js'
import type { Compute } from '../types/utils.js'
import type { Mutate, MutateAsync } from './types.js'

export type AddChainOptions<context = unknown> = MutationParameter<
AddChainData,
AddChainErrorType,
AddChainVariables,
context
>

export function addChainMutationOptions<context>(
config: Config,
options: AddChainOptions<context> = {},
): AddChainMutationOptions {
return {

Check warning on line 25 in packages/core/src/query/addChain.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/query/addChain.ts#L25

Added line #L25 was not covered by tests
...(options.mutation as any),
mutationFn(variables) {
return addChain(config, variables)

Check warning on line 28 in packages/core/src/query/addChain.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/query/addChain.ts#L28

Added line #L28 was not covered by tests
},
mutationKey: ['addChain'],
}
}

export type AddChainMutationOptions = MutationOptions<
AddChainData,
AddChainErrorType,
AddChainVariables
>

export type AddChainData = AddChainReturnType

export type AddChainVariables = Compute<AddChainParameters>

export type AddChainMutate<context = unknown> = Mutate<
AddChainData,
AddChainErrorType,
AddChainVariables,
context
>

export type AddChainMutateAsync<context = unknown> = MutateAsync<
AddChainData,
AddChainErrorType,
AddChainVariables,
context
>
1 change: 1 addition & 0 deletions packages/react/src/exports/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as actions from './actions.js'
test('exports', () => {
expect(Object.keys(actions)).toMatchInlineSnapshot(`
[
"addChain",
"call",
"connect",
"deployContract",
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/exports/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ test('exports', () => {
"unstable_connector",
"useAccount",
"useAccountEffect",
"useAddChain",
"useBalance",
"useBlobBaseFee",
"useBlock",
Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export {
// Hooks
////////////////////////////////////////////////////////////////////////////////

export {
type UseAddChainParameters,
type UseAddChainReturnType,
useAddChain,
} from '../hooks/useAddChain.js'

export {
type UseBalanceParameters,
type UseBalanceReturnType,
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/exports/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as query from './query.js'
test('exports', () => {
expect(Object.keys(query)).toMatchInlineSnapshot(`
[
"addChainMutationOptions",
"callQueryKey",
"callQueryOptions",
"connectMutationOptions",
Expand Down
Loading
Loading