FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

View File

@@ -0,0 +1,149 @@
import type { Address, TypedDataDomain } from 'abitype'
import type { Account } from '../../../accounts/types.js'
import { parseAccount } from '../../../accounts/utils/parseAccount.js'
import {
type GetEip712DomainParameters,
getEip712Domain,
} from '../../../actions/public/getEip712Domain.js'
import { signTypedData } from '../../../actions/wallet/signTypedData.js'
import type { Client } from '../../../clients/createClient.js'
import type { Transport } from '../../../clients/transports/createTransport.js'
import { AccountNotFoundError } from '../../../errors/account.js'
import type { ErrorType } from '../../../errors/utils.js'
import type { GetAccountParameter } from '../../../types/account.js'
import type { Chain } from '../../../types/chain.js'
import type { Hex, SignableMessage } from '../../../types/misc.js'
import type { OneOf, RequiredBy } from '../../../types/utils.js'
import { getAction } from '../../../utils/getAction.js'
import { toPrefixedMessage } from '../../../utils/signature/toPrefixedMessage.js'
import type { GetVerifierParameter } from '../types.js'
export type SignMessageParameters<
account extends Account | undefined = Account | undefined,
accountOverride extends Account | undefined = Account | undefined,
verifier extends Address | undefined = Address | undefined,
> = Pick<GetEip712DomainParameters, 'factory' | 'factoryData'> &
GetAccountParameter<account, accountOverride> & {
message: SignableMessage
} & OneOf<
| {
verifierDomain: RequiredBy<
TypedDataDomain,
'chainId' | 'name' | 'verifyingContract' | 'version'
>
verifier?: undefined
}
| (GetVerifierParameter<verifier> & {
verifierDomain?:
| RequiredBy<
TypedDataDomain,
'chainId' | 'name' | 'verifyingContract' | 'version'
>
| undefined
})
>
export type SignMessageReturnType = Hex
export type SignMessageErrorType = ErrorType
/**
* Signs a [EIP-191](https://eips.ethereum.org/EIPS/eip-191) personal sign message via [ERC-7739 `PersonalSign` format](https://eips.ethereum.org/EIPS/eip-7702).
*
* This Action is suitable to sign messages for Smart Accounts that implement (or conform to) [ERC-7739](https://eips.ethereum.org/EIPS/eip-7702) (e.g. Solady's [ERC1271.sol](https://github.com/Vectorized/solady/blob/main/src/accounts/ERC1271.sol)).
*
* - Docs: https://viem.sh/experimental/erc7739/signMessage
*
* With the calculated signature, you can:
* - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage) to verify the signature,
*
* @param client - Client to use
* @param parameters - {@link SignMessageParameters}
* @returns The signed message. {@link SignMessageReturnType}
*
* @example
* import { createWalletClient, custom } from 'viem'
* import { mainnet } from 'viem/chains'
* import { signMessage } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* chain: mainnet,
* transport: custom(window.ethereum),
* })
*
* const signature = await signMessage(client, {
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb',
* message: 'hello world',
* verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
*
* @example
* // Account Hoisting
* import { createWalletClient, custom } from 'viem'
* import { privateKeyToAccount } from 'viem/accounts'
* import { mainnet } from 'viem/chains'
* import { signMessage } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb',
* chain: mainnet,
* transport: custom(window.ethereum),
* })
*
* const signature = await signMessage(client, {
* message: 'hello world',
* verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
*/
export async function signMessage<
chain extends Chain | undefined,
account extends Account | undefined,
accountOverride extends Account | undefined = undefined,
>(
client: Client<Transport, chain, account>,
parameters: SignMessageParameters<account, accountOverride>,
): Promise<SignMessageReturnType> {
const {
account: account_ = client.account,
factory,
factoryData,
message,
verifier,
} = parameters
if (!account_)
throw new AccountNotFoundError({
docsPath: '/experimental/erc7739/signMessage',
})
const account = parseAccount(account_)
const { salt: _, ...domain } = await (async () => {
if (parameters.verifierDomain) return parameters.verifierDomain
const { domain } = await getAction(
client,
getEip712Domain,
'getEip712Domain',
)({
address: verifier!,
factory,
factoryData,
})
return domain
})()
return getAction(
client,
signTypedData,
'signTypedData',
)({
account,
domain,
types: {
PersonalSign: [{ name: 'prefixed', type: 'bytes' }],
},
primaryType: 'PersonalSign',
message: {
prefixed: toPrefixedMessage(message),
},
})
}

View File

@@ -0,0 +1,237 @@
import type { Address, TypedData, TypedDataDomain } from 'abitype'
import type { Account } from '../../../accounts/types.js'
import { parseAccount } from '../../../accounts/utils/parseAccount.js'
import {
type GetEip712DomainParameters,
getEip712Domain,
} from '../../../actions/public/getEip712Domain.js'
import { signTypedData as signTypedData_ } from '../../../actions/wallet/signTypedData.js'
import type { Client } from '../../../clients/createClient.js'
import type { Transport } from '../../../clients/transports/createTransport.js'
import { AccountNotFoundError } from '../../../errors/account.js'
import type { ErrorType } from '../../../errors/utils.js'
import type { GetAccountParameter } from '../../../types/account.js'
import type { Chain } from '../../../types/chain.js'
import type { Hex } from '../../../types/misc.js'
import type { TypedDataDefinition } from '../../../types/typedData.js'
import type { OneOf, RequiredBy } from '../../../types/utils.js'
import { getAction } from '../../../utils/getAction.js'
import type { GetVerifierParameter } from '../types.js'
import { wrapTypedDataSignature } from '../utils/wrapTypedDataSignature.js'
export type SignTypedDataParameters<
typedData extends TypedData | Record<string, unknown> = TypedData,
primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData,
account extends Account | undefined = undefined,
accountOverride extends Account | undefined = undefined,
verifier extends Address | undefined = Address | undefined,
///
primaryTypes = typedData extends TypedData ? keyof typedData : string,
> = TypedDataDefinition<typedData, primaryType, primaryTypes> &
Pick<GetEip712DomainParameters, 'factory' | 'factoryData'> &
GetAccountParameter<account, accountOverride> &
OneOf<
| {
verifierDomain: RequiredBy<
TypedDataDomain,
'chainId' | 'name' | 'verifyingContract' | 'salt' | 'version'
>
verifier?: undefined
}
| (GetVerifierParameter<verifier> & {
verifierDomain?:
| RequiredBy<
TypedDataDomain,
'chainId' | 'name' | 'verifyingContract' | 'salt' | 'version'
>
| undefined
})
>
export type SignTypedDataReturnType = Hex
export type SignTypedDataErrorType = ErrorType
/**
* Signs an [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data message via [ERC-7739 `TypedDataSign` format](https://eips.ethereum.org/EIPS/eip-7702).
*
* This Action is suitable to sign messages for Smart Accounts that implement (or conform to) [ERC-7739](https://eips.ethereum.org/EIPS/eip-7702) (e.g. Solady's [ERC1271.sol](https://github.com/Vectorized/solady/blob/main/src/accounts/ERC1271.sol)).
*
* - Docs: https://viem.sh/experimental/erc7739/signTypedData
*
* @param client - Client to use
* @param parameters - {@link SignTypedDataParameters}
* @returns The signed data. {@link SignTypedDataReturnType}
*
* @example
* import { createWalletClient, custom } from 'viem'
* import { mainnet } from 'viem/chains'
* import { signTypedData } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* chain: mainnet,
* transport: custom(window.ethereum),
* })
* const signature = await signTypedData(client, {
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb',
* domain: {
* name: 'Ether Mail',
* version: '1',
* chainId: 1,
* verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
* },
* types: {
* Person: [
* { name: 'name', type: 'string' },
* { name: 'wallet', type: 'address' },
* ],
* Mail: [
* { name: 'from', type: 'Person' },
* { name: 'to', type: 'Person' },
* { name: 'contents', type: 'string' },
* ],
* },
* primaryType: 'Mail',
* message: {
* from: {
* name: 'Cow',
* wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
* },
* to: {
* name: 'Bob',
* wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
* },
* contents: 'Hello, Bob!',
* },
* verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
*
* @example
* // Account Hoisting
* import { createWalletClient, http } from 'viem'
* import { privateKeyToAccount } from 'viem/accounts'
* import { mainnet } from 'viem/chains'
* import { signTypedData } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb'
* chain: mainnet,
* transport: http(),
* })
* const signature = await signTypedData(client, {
* domain: {
* name: 'Ether Mail',
* version: '1',
* chainId: 1,
* verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
* },
* types: {
* Person: [
* { name: 'name', type: 'string' },
* { name: 'wallet', type: 'address' },
* ],
* Mail: [
* { name: 'from', type: 'Person' },
* { name: 'to', type: 'Person' },
* { name: 'contents', type: 'string' },
* ],
* },
* primaryType: 'Mail',
* message: {
* from: {
* name: 'Cow',
* wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
* },
* to: {
* name: 'Bob',
* wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
* },
* contents: 'Hello, Bob!',
* },
* verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
*/
export async function signTypedData<
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | 'EIP712Domain',
chain extends Chain | undefined,
account extends Account | undefined,
accountOverride extends Account | undefined = undefined,
>(
client: Client<Transport, chain, account>,
parameters: SignTypedDataParameters<
typedData,
primaryType,
account,
accountOverride
>,
): Promise<SignTypedDataReturnType> {
const {
account: account_ = client.account,
domain,
factory,
factoryData,
message,
primaryType,
types,
verifier,
} = parameters as unknown as SignTypedDataParameters
if (!account_)
throw new AccountNotFoundError({
docsPath: '/experimental/erc7739/signTypedData',
})
const account = parseAccount(account_!)
// Retrieve account EIP712 domain.
const { domain: verifierDomain } = await (async () => {
if (parameters.verifierDomain)
return {
domain: parameters.verifierDomain,
}
return getAction(
client,
getEip712Domain,
'getEip712Domain',
)({
address: verifier!,
factory,
factoryData,
})
})()
// Sign with typed data wrapper.
const signature = await getAction(
client,
signTypedData_,
'signTypedData',
)({
account,
domain,
types: {
...types,
TypedDataSign: [
{ name: 'contents', type: primaryType },
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
{ name: 'salt', type: 'bytes32' },
],
},
primaryType: 'TypedDataSign',
message: {
contents: message as any,
...(verifierDomain as any),
},
})
return wrapTypedDataSignature({
domain,
message,
primaryType,
signature,
types,
})
}

View File

@@ -0,0 +1,232 @@
import type { Address, TypedData } from 'abitype'
import type { Client } from '../../../clients/createClient.js'
import type { Transport } from '../../../clients/transports/createTransport.js'
import type { Account } from '../../../types/account.js'
import type { Chain } from '../../../types/chain.js'
import {
type SignMessageParameters,
type SignMessageReturnType,
signMessage,
} from '../actions/signMessage.js'
import {
type SignTypedDataParameters,
type SignTypedDataReturnType,
signTypedData,
} from '../actions/signTypedData.js'
export type Erc7739Actions<
account extends Account | undefined = Account | undefined,
verifier extends Address | undefined = Address | undefined,
> = {
/**
* Signs a [EIP-191](https://eips.ethereum.org/EIPS/eip-191) personal sign message via [ERC-7739 `PersonalSign` format](https://eips.ethereum.org/EIPS/eip-7702).
*
* This Action is suitable to sign messages for Smart Accounts that implement (or conform to) [ERC-7739](https://eips.ethereum.org/EIPS/eip-7702) (e.g. Solady's [ERC1271.sol](https://github.com/Vectorized/solady/blob/main/src/accounts/ERC1271.sol)).
*
* - Docs: https://viem.sh/experimental/erc7739/signMessage
*
* With the calculated signature, you can:
* - use [`verifyMessage`](https://viem.sh/docs/utilities/verifyMessage) to verify the signature,
*
* @param client - Client to use
* @param parameters - {@link SignMessageParameters}
* @returns The signed message. {@link SignMessageReturnType}
*
* @example
* ```ts
* import { createWalletClient, custom } from 'viem'
* import { mainnet } from 'viem/chains'
* import { erc7739Actions } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* chain: mainnet,
* transport: custom(window.ethereum),
* }).extend(erc7739Actions())
*
* const signature = await client.signMessage({
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb',
* message: 'hello world',
* verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
* ```
*
* @example Account & Signer Hoisting
* ```ts
* import { createWalletClient, custom } from 'viem'
* import { mainnet } from 'viem/chains'
* import { erc7739Actions } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb',
* chain: mainnet,
* transport: custom(window.ethereum),
* }).extend(erc7739Actions({ verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e' }))
*
* const signature = await client.signMessage({
* message: 'hello world',
* })
* ```
*/
signMessage: <accountOverride extends Account | undefined = undefined>(
parameters: SignMessageParameters<account, accountOverride, verifier>,
) => Promise<SignMessageReturnType>
/**
* Signs an [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data message via [ERC-7739 `TypedDataSign` format](https://eips.ethereum.org/EIPS/eip-7702).
*
* This Action is suitable to sign messages for Smart Accounts that implement (or conform to) [ERC-7739](https://eips.ethereum.org/EIPS/eip-7702) (e.g. Solady's [ERC1271.sol](https://github.com/Vectorized/solady/blob/main/src/accounts/ERC1271.sol)).
*
* - Docs: https://viem.sh/experimental/erc7739/signTypedData
*
* @param client - Client to use
* @param parameters - {@link SignTypedDataParameters}
* @returns The signed data. {@link SignTypedDataReturnType}
*
* @example
* ```ts
* import { createWalletClient, custom } from 'viem'
* import { mainnet } from 'viem/chains'
* import { erc7739Actions } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* chain: mainnet,
* transport: custom(window.ethereum),
* }).extend(erc7739Actions())
*
* const signature = await client.signTypedData({
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb',
* domain: {
* name: 'Ether Mail',
* version: '1',
* chainId: 1,
* verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
* },
* types: {
* Person: [
* { name: 'name', type: 'string' },
* { name: 'wallet', type: 'address' },
* ],
* Mail: [
* { name: 'from', type: 'Person' },
* { name: 'to', type: 'Person' },
* { name: 'contents', type: 'string' },
* ],
* },
* primaryType: 'Mail',
* message: {
* from: {
* name: 'Cow',
* wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
* },
* to: {
* name: 'Bob',
* wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
* },
* contents: 'Hello, Bob!',
* },
* verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* })
* ```
*
* @example Account & Signer Hoisting
* ```ts
* import { createWalletClient, http } from 'viem'
* import { privateKeyToAccount } from 'viem/accounts'
* import { mainnet } from 'viem/chains'
* import { erc7739Actions } from 'viem/experimental/erc7739'
*
* const client = createWalletClient({
* account: '0xE8Df82fA4E10e6A12a9Dab552bceA2acd26De9bb',
* chain: mainnet,
* transport: http(),
* }).extend(erc7739Actions({ verifier: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e' }))
*
* const signature = await client.signTypedData({
* domain: {
* name: 'Ether Mail',
* version: '1',
* chainId: 1,
* verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
* },
* types: {
* Person: [
* { name: 'name', type: 'string' },
* { name: 'wallet', type: 'address' },
* ],
* Mail: [
* { name: 'from', type: 'Person' },
* { name: 'to', type: 'Person' },
* { name: 'contents', type: 'string' },
* ],
* },
* primaryType: 'Mail',
* message: {
* from: {
* name: 'Cow',
* wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
* },
* to: {
* name: 'Bob',
* wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
* },
* contents: 'Hello, Bob!',
* },
* })
* ```
*/
signTypedData: <
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | 'EIP712Domain',
accountOverride extends Account | undefined = undefined,
>(
parameters: SignTypedDataParameters<
typedData,
primaryType,
account,
accountOverride
>,
) => Promise<SignTypedDataReturnType>
}
export type Erc7739ActionsParameters<
verifier extends Account | Address | undefined =
| Account
| Address
| undefined,
> = {
verifier?: verifier | undefined
}
/**
* A suite of Actions based on [Solady contracts](https://github.com/Vectorized/solady).
*
* @example
* import { createPublicClient, createWalletClient, http } from 'viem'
* import { mainnet } from 'viem/chains'
* import { erc7739Actions } from 'viem/experimental'
*
* const walletClient = createWalletClient({
* chain: mainnet,
* transport: http(),
* }).extend(erc7739Actions())
*
* const result = await walletClient.signMessage({...})
*/
export function erc7739Actions<
verifier extends Address | undefined = undefined,
>(parameters: Erc7739ActionsParameters<verifier> = {}) {
const { verifier } = parameters
return <
transport extends Transport,
chain extends Chain | undefined = Chain | undefined,
account extends Account | undefined = Account | undefined,
>(
client: Client<transport, chain, account>,
): Erc7739Actions<account, verifier> => {
return {
signMessage: (parameters) =>
signMessage(client, { verifier, ...parameters }),
signTypedData: (parameters) =>
signTypedData(client, { verifier, ...(parameters as any) }),
}
}
}

36
node_modules/viem/experimental/erc7739/index.ts generated vendored Normal file
View File

@@ -0,0 +1,36 @@
// biome-ignore lint/performance/noBarrelFile: entrypoint
export {
type SignMessageErrorType,
type SignMessageParameters,
type SignMessageReturnType,
signMessage,
} from './actions/signMessage.js'
export {
type SignTypedDataErrorType,
type SignTypedDataParameters,
type SignTypedDataReturnType,
signTypedData,
} from './actions/signTypedData.js'
export {
type Erc7739Actions,
type Erc7739ActionsParameters,
erc7739Actions,
} from './decorators/erc7739.js'
export {
type HashMessageErrorType,
type HashMessageParameters,
type HashMessageReturnType,
hashMessage,
} from './utils/hashMessage.js'
export {
type HashTypedDataErrorType,
type HashTypedDataParameters,
type HashTypedDataReturnType,
hashTypedData,
} from './utils/hashTypedData.js'
export {
type WrapTypedDataSignatureErrorType,
type WrapTypedDataSignatureParameters,
type WrapTypedDataSignatureReturnType,
wrapTypedDataSignature,
} from './utils/wrapTypedDataSignature.js'

6
node_modules/viem/experimental/erc7739/package.json generated vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"type": "module",
"types": "../../_types/experimental/erc7739/index.d.ts",
"module": "../../_esm/experimental/erc7739/index.js",
"main": "../../_cjs/experimental/erc7739/index.js"
}

7
node_modules/viem/experimental/erc7739/types.ts generated vendored Normal file
View File

@@ -0,0 +1,7 @@
import type { Address } from 'abitype'
export type GetVerifierParameter<
verifier extends Address | undefined = Address,
> = verifier extends Address
? { verifier?: Address | undefined }
: { verifier: Address }

View File

@@ -0,0 +1,60 @@
import type { TypedDataDomain } from 'abitype'
import type { ErrorType } from '../../../errors/utils.js'
import type { Hex, SignableMessage } from '../../../types/misc.js'
import type { RequiredBy } from '../../../types/utils.js'
import { hashTypedData } from '../../../utils/index.js'
import type { HashTypedDataErrorType } from '../../../utils/signature/hashTypedData.js'
import {
type ToPrefixedMessageErrorType,
toPrefixedMessage,
} from '../../../utils/signature/toPrefixedMessage.js'
export type HashMessageParameters = {
message: SignableMessage
verifierDomain: RequiredBy<
TypedDataDomain,
'chainId' | 'name' | 'verifyingContract' | 'version'
>
}
export type HashMessageReturnType = Hex
export type HashMessageErrorType =
| HashTypedDataErrorType
| ToPrefixedMessageErrorType
| ErrorType
/**
* Generates a signable hash for a ERC-7739 personal sign message.
*
* @example
* ```ts
* const hash = hashMessage({
* message: 'hello world',
* verifierDomain: {
* name: 'Smart Account',
* version: '1',
* verifyingContract: '0x1234567890abcdef1234567890abcdef12345678',
* chainId: 1,
* },
* })
* ```
*/
export function hashMessage(
parameters: HashMessageParameters,
): HashMessageReturnType {
const {
message,
verifierDomain: { salt: _, ...domain },
} = parameters
return hashTypedData({
domain,
types: {
PersonalSign: [{ name: 'prefixed', type: 'bytes' }],
},
primaryType: 'PersonalSign',
message: {
prefixed: toPrefixedMessage(message),
},
})
}

View File

@@ -0,0 +1,98 @@
import type { TypedData, TypedDataDomain } from 'abitype'
import type { ErrorType } from '../../../errors/utils.js'
import type { Hex } from '../../../types/misc.js'
import type { TypedDataDefinition } from '../../../types/typedData.js'
import type { RequiredBy } from '../../../types/utils.js'
import {
type HashTypedDataErrorType as HashTypedDataErrorType_,
hashTypedData as hashTypedData_,
} from '../../../utils/signature/hashTypedData.js'
export type HashTypedDataParameters<
typedData extends TypedData | Record<string, unknown> = TypedData,
primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData,
///
primaryTypes = typedData extends TypedData ? keyof typedData : string,
> = TypedDataDefinition<typedData, primaryType, primaryTypes> & {
verifierDomain: RequiredBy<
TypedDataDomain,
'chainId' | 'name' | 'verifyingContract' | 'salt' | 'version'
>
}
export type HashTypedDataReturnType = Hex
export type HashTypedDataErrorType = HashTypedDataErrorType_ | ErrorType
/**
* Generates a signable hash for ERC-7739 typed data.
*
* @example
* ```ts
* const hash = hashTypedData({
* domain: {
* name: 'Ether Mail',
* version: '1',
* chainId: 1,
* verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
* },
* types: {
* Person: [
* { name: 'name', type: 'string' },
* { name: 'wallet', type: 'address' },
* ],
* Mail: [
* { name: 'from', type: 'Person' },
* { name: 'to', type: 'Person' },
* { name: 'contents', type: 'string' },
* ],
* },
* primaryType: 'Mail',
* message: {
* from: {
* name: 'Cow',
* wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
* },
* to: {
* name: 'Bob',
* wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
* },
* contents: 'Hello, Bob!',
* },
* verifierDomain: {
* name: 'Smart Account',
* version: '1',
* verifyingContract: '0x1234567890abcdef1234567890abcdef12345678',
* chainId: 1,
* },
* })
* ```
*/
export function hashTypedData<
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | 'EIP712Domain',
>(
parameters: HashTypedDataParameters<typedData, primaryType>,
): HashTypedDataReturnType {
const { domain, message, primaryType, types, verifierDomain } = parameters
return hashTypedData_({
domain: domain as any,
types: {
...types,
TypedDataSign: [
{ name: 'contents', type: primaryType },
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
{ name: 'salt', type: 'bytes32' },
],
},
primaryType: 'TypedDataSign',
message: {
contents: message as any,
...(verifierDomain as any),
},
})
}

View File

@@ -0,0 +1,108 @@
import type { TypedData } from 'abitype'
import { serializeSignature } from '../../../accounts/index.js'
import type { ByteArray, Hex, Signature } from '../../../types/misc.js'
import type { TypedDataDefinition } from '../../../types/typedData.js'
import { encodePacked } from '../../../utils/abi/encodePacked.js'
import { type IsHexErrorType, isHex } from '../../../utils/data/isHex.js'
import { size } from '../../../utils/data/size.js'
import { bytesToHex, stringToHex } from '../../../utils/encoding/toHex.js'
import {
encodeType,
hashStruct,
} from '../../../utils/signature/hashTypedData.js'
import { getTypesForEIP712Domain } from '../../../utils/typedData.js'
export type WrapTypedDataSignatureParameters<
typedData extends TypedData | Record<string, unknown> = TypedData,
primaryType extends keyof typedData | 'EIP712Domain' = keyof typedData,
///
primaryTypes = typedData extends TypedData ? keyof typedData : string,
> = TypedDataDefinition<typedData, primaryType, primaryTypes> & {
signature: Hex | ByteArray | Signature
}
export type WrapTypedDataSignatureReturnType = Hex
export type WrapTypedDataSignatureErrorType = IsHexErrorType
/**
* Wraps a typed data signature for ERC-7739.
*
* @example
* ```ts
* const signature = wrapTypedDataSignature({
* domain: {
* name: 'Ether Mail',
* version: '1',
* chainId: 1,
* verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
* },
* types: {
* Person: [
* { name: 'name', type: 'string' },
* { name: 'wallet', type: 'address' },
* ],
* Mail: [
* { name: 'from', type: 'Person' },
* { name: 'to', type: 'Person' },
* { name: 'contents', type: 'string' },
* ],
* },
* primaryType: 'Mail',
* message: {
* from: {
* name: 'Cow',
* wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
* },
* to: {
* name: 'Bob',
* wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
* },
* contents: 'Hello, Bob!',
* },
* signature: '0x...',
* })
* ```
*/
export function wrapTypedDataSignature(
parameters: WrapTypedDataSignatureParameters,
): WrapTypedDataSignatureReturnType {
const { domain, message, primaryType, signature, types } = parameters
const signatureHex = (() => {
if (isHex(signature)) return signature
if (typeof signature === 'object' && 'r' in signature && 's' in signature)
return serializeSignature(signature)
return bytesToHex(signature)
})()
// Compute dependencies for wrapped signature.
const hashedDomain = hashStruct({
data: domain ?? {},
types: {
EIP712Domain: getTypesForEIP712Domain({ domain }),
},
primaryType: 'EIP712Domain',
})
const hashedContents = hashStruct({
data: message,
types: types as any,
primaryType,
})
const encodedType = encodeType({
primaryType,
types: types as any,
})
// Construct wrapped signature.
return encodePacked(
['bytes', 'bytes32', 'bytes32', 'bytes', 'uint16'],
[
signatureHex,
hashedDomain,
hashedContents,
stringToHex(encodedType),
size(stringToHex(encodedType)),
],
)
}