Skip to content

Commit

Permalink
feat: signTypedData
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Jul 20, 2023
1 parent 4f74d82 commit 267ac40
Show file tree
Hide file tree
Showing 22 changed files with 566 additions and 57 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"rome": "^12.1.0",
"simple-git-hooks": "^2.8.1",
"typescript": "^5.0.4",
"viem": "1.2.14",
"viem": "0.0.0-w-20230720004551",
"vite": "^4.3.2",
"vitest": "^0.31.0"
},
Expand All @@ -56,7 +56,7 @@
"@wagmi/core": "workspace:*",
"remark-shiki-twoslash>shiki": "^0.14.1",
"shiki-twoslash>shiki": "^0.14.1",
"viem": "1.2.14"
"viem": "0.0.0-w-20230720004551"
},
"patchedDependencies": {
"@coinbase/wallet-sdk@3.7.1": "patches/@coinbase__wallet-sdk@3.7.1.patch",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/actions/connect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ test('behavior: user rejected request', async () => {
[UserRejectedRequestError: User rejected the request.
Details: Failed to connect.
Version: viem@1.2.14]
Version: viem@1.2.15]
`)
})

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/actions/getToken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ test('behavior: bogus token', async () => {
function: decimals()
Docs: https://viem.sh/docs/contract/multicall.html
Version: viem@1.2.14"
Version: viem@1.2.15"
`)
})
14 changes: 9 additions & 5 deletions packages/core/src/actions/signMessage.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { accounts, config, testConnector } from '@wagmi/test'
import { recoverMessageAddress } from 'viem'
import { expect, test } from 'vitest'

import { connect } from './connect.js'
import { disconnect } from './disconnect.js'
import { getAccount } from './getAccount.js'
import { signMessage } from './signMessage.js'

const connector = config.connectors[0]!

test('default', async () => {
await connect(config, { connector })
const signature = await signMessage(config, { message: 'foo bar baz' })
await expect(
signMessage(config, { message: 'foo bar baz' }),
).resolves.toMatchInlineSnapshot(
'"0xbbfaf80b48f1067feb22abdff88464ae345ac1aa54b275c82847eb07c2185dab7d72051bcbd739a44daaac019f99ce38300ece87c7da4eb3da863179672b9acc1b"',
)
recoverMessageAddress({
message: 'foo bar baz',
signature,
}),
).resolves.toEqual(getAccount(config).address)
await disconnect(config, { connector })
})

Expand All @@ -31,7 +35,7 @@ test('behavior: user rejected request', async () => {
[UserRejectedRequestError: User rejected the request.
Details: Failed to sign message.
Version: viem@1.2.14]
Version: viem@1.2.15]
`)
await disconnect(config, { connector: connector_ })
})
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/actions/signTypedData.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { config, typedData } from '@wagmi/test'
import { test } from 'vitest'

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

test('default', async () => {
signTypedData(config, {
types: typedData.basic.types,
primaryType: 'Mail',
message: typedData.basic.message,
})
})

test('domain', async () => {
signTypedData(config, {
primaryType: 'EIP712Domain',
domain: {},
})
})

test('custom domain', async () => {
signTypedData(config, {
types: {
EIP712Domain: [{ type: 'uint256', name: 'chainId' }],
},
primaryType: 'EIP712Domain',
domain: {
chainId: 123n,
},
})
})
65 changes: 65 additions & 0 deletions packages/core/src/actions/signTypedData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { accounts, config, testConnector, typedData } from '@wagmi/test'
import { recoverTypedDataAddress } from 'viem'
import { expect, test } from 'vitest'

import { connect } from './connect.js'
import { disconnect } from './disconnect.js'
import { getAccount } from './getAccount.js'
import { signTypedData } from './signTypedData.js'

const connector = config.connectors[0]!

test('default', async () => {
await connect(config, { connector })
const signature = await signTypedData(config, {
types: typedData.basic.types,
primaryType: 'Mail',
message: typedData.basic.message,
})
await expect(
recoverTypedDataAddress({
types: typedData.basic.types,
primaryType: 'Mail',
message: typedData.basic.message,
signature,
}),
).resolves.toBe(getAccount(config).address)
await disconnect(config, { connector })
})

test('behavior: user rejected request', async () => {
const connector_ = config._internal.setup(
testConnector({
accounts,
features: { signTypedDataError: true },
}),
)
await connect(config, { connector: connector_ })
await expect(
signTypedData(config, {
types: typedData.basic.types,
primaryType: 'Mail',
message: typedData.basic.message,
}),
).rejects.toMatchInlineSnapshot(`
[UserRejectedRequestError: User rejected the request.
Details: Failed to sign typed data.
Version: viem@1.2.15]
`)
await disconnect(config, { connector: connector_ })
})

test('behavior: not connected', async () => {
await expect(
signTypedData(config, {
types: typedData.basic.types,
primaryType: 'Mail',
message: typedData.basic.message,
}),
).rejects.toMatchInlineSnapshot(`
[ConnectorNotFoundError: Connector not found.
Version: @wagmi/core@x.y.z]
`)
})
39 changes: 39 additions & 0 deletions packages/core/src/actions/signTypedData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { TypedData, UserRejectedRequestError } from 'viem'
import {
type SignTypedDataParameters as viem_SignTypedDataParameters,
type SignTypedDataReturnType as viem_SignTypedDataReturnType,
signTypedData as viem_signTypedData,
} from 'viem/actions'

import { type Config } from '../config.js'
import { ConnectorNotFoundError } from '../errors/config.js'
import { getConnectorClient } from './getConnectorClient.js'

export type SignTypedDataParameters<
typedData extends TypedData | Record<string, unknown> = TypedData,
primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData,
> = viem_SignTypedDataParameters<typedData, primaryType, never>

export type SignTypedDataReturnType = viem_SignTypedDataReturnType

export type SignTypedDataError =
| ConnectorNotFoundError
| UserRejectedRequestError
// base
| Error

/** https://wagmi.sh/core/actions/signTypedData */
export async function signTypedData<
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | 'EIP712Domain',
>(
config: Config,
parameters: SignTypedDataParameters<typedData, primaryType>,
): Promise<SignTypedDataReturnType> {
const client = await getConnectorClient(config)
if (!client) throw new ConnectorNotFoundError()
return viem_signTypedData(
client,
parameters as unknown as viem_SignTypedDataParameters,
)
}
2 changes: 1 addition & 1 deletion packages/core/src/actions/switchChain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ test('behavior: user rejected request', async () => {
[UserRejectedRequestError: User rejected the request.
Details: Failed to switch chain.
Version: viem@1.2.14]
Version: viem@1.2.15]
`)
await disconnect(config, { connector: connector_ })
})
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ export {
signMessage,
} from './actions/signMessage.js'

export {
type SignTypedDataError,
type SignTypedDataParameters,
type SignTypedDataReturnType,
signTypedData,
} from './actions/signTypedData.js'

export {
type SwitchAccountError,
type SwitchAccountParameters,
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ export {
signMessageMutationOptions,
} from './query/signMessage.js'

export {
type SignTypedDataData,
type SignTypedDataVariables,
type SignTypedDataMutate,
type SignTypedDataMutateAsync,
signTypedDataMutationOptions,
} from './query/signTypedData.js'

export {
type SwitchAccountData,
type SwitchAccountVariables,
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/query/signTypedData.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { config } from '@wagmi/test'
import { expect, test } from 'vitest'

import { signTypedDataMutationOptions } from './signTypedData.js'

test('default', () => {
expect(signTypedDataMutationOptions(config)).toMatchInlineSnapshot(`
{
"mutationFn": [Function],
"mutationKey": [
"signTypedData",
],
}
`)
})
59 changes: 59 additions & 0 deletions packages/core/src/query/signTypedData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { MutateOptions, MutationOptions } from '@tanstack/query-core'

import {
type SignTypedDataError,
type SignTypedDataParameters,
type SignTypedDataReturnType,
signTypedData,
} from '../actions/signTypedData.js'
import { type Config } from '../config.js'
import type { Evaluate } from '../types/utils.js'
import type { TypedData } from 'viem'

export function signTypedDataMutationOptions<config extends Config>(
config: config,
) {
return {
mutationFn(variables) {
return signTypedData(config, variables)
},
mutationKey: ['signTypedData'],
} as const satisfies MutationOptions<
SignTypedDataData,
SignTypedDataError,
SignTypedDataVariables
>
}

export type SignTypedDataData = Evaluate<SignTypedDataReturnType>

export type SignTypedDataVariables<
typedData extends TypedData | Record<string, unknown> = TypedData,
primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData,
> = SignTypedDataParameters<typedData, primaryType>

export type SignTypedDataMutate<context = unknown> = <
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | 'EIP712Domain',
>(
variables: SignTypedDataVariables<typedData, primaryType>,
options?: MutateOptions<
SignTypedDataData,
SignTypedDataError,
SignTypedDataVariables<typedData, primaryType>,
context
>,
) => void

export type SignTypedDataMutateAsync<context = unknown,> = <
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | 'EIP712Domain',
>(
variables: SignTypedDataVariables<typedData, primaryType>,
options?: MutateOptions<
SignTypedDataData,
SignTypedDataError,
SignTypedDataVariables<typedData, primaryType>,
context
>,
) => Promise<SignTypedDataData>
36 changes: 18 additions & 18 deletions packages/react/src/hooks/useSendTransaction.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,25 @@ test('context', () => {
return contextValue
},
onError(error, variables, context) {
expectTypeOf(variables).toMatchTypeOf<
{ chainId?: number | undefined } | undefined
>()
expectTypeOf(variables).toMatchTypeOf<{
chainId?: number | undefined
}>()
expectTypeOf(error).toEqualTypeOf<SendTransactionError>()
expectTypeOf(context).toEqualTypeOf<typeof contextValue | undefined>()
},
onSuccess(data, variables, context) {
expectTypeOf(variables).toMatchTypeOf<
{ chainId?: number | undefined } | undefined
>()
expectTypeOf(variables).toMatchTypeOf<{
chainId?: number | undefined
}>()
expectTypeOf(data).toEqualTypeOf<{ hash: Hash }>()
expectTypeOf(context).toEqualTypeOf<typeof contextValue | undefined>()
},
onSettled(data, error, variables, context) {
expectTypeOf(data).toEqualTypeOf<{ hash: Hash } | undefined>()
expectTypeOf(error).toEqualTypeOf<SendTransactionError | null>()
expectTypeOf(variables).toMatchTypeOf<
{ chainId?: number | undefined } | undefined
>()
expectTypeOf(variables).toMatchTypeOf<{
chainId?: number | undefined
}>()
expectTypeOf(context).toEqualTypeOf<typeof contextValue | undefined>()
},
})
Expand All @@ -51,25 +51,25 @@ test('context', () => {
{ to: '0x' },
{
onError(error, variables, context) {
expectTypeOf(variables).toMatchTypeOf<
{ chainId?: number | undefined } | undefined
>()
expectTypeOf(variables).toMatchTypeOf<{
chainId?: number | undefined
}>()
expectTypeOf(error).toEqualTypeOf<SendTransactionError>()
expectTypeOf(context).toEqualTypeOf<typeof contextValue | undefined>()
},
onSuccess(data, variables, context) {
expectTypeOf(variables).toMatchTypeOf<
{ chainId?: number | undefined } | undefined
>()
expectTypeOf(variables).toMatchTypeOf<{
chainId?: number | undefined
}>()
expectTypeOf(data).toEqualTypeOf<{ hash: Hash }>()
expectTypeOf(context).toEqualTypeOf<typeof contextValue>()
},
onSettled(data, error, variables, context) {
expectTypeOf(data).toEqualTypeOf<{ hash: Hash } | undefined>()
expectTypeOf(error).toEqualTypeOf<SendTransactionError | null>()
expectTypeOf(variables).toMatchTypeOf<
{ chainId?: number | undefined } | undefined
>()
expectTypeOf(variables).toMatchTypeOf<{
chainId?: number | undefined
}>()
expectTypeOf(context).toEqualTypeOf<typeof contextValue | undefined>()
},
},
Expand Down
Loading

0 comments on commit 267ac40

Please sign in to comment.