@zerodev/sdk
Version:
A utility library for working with ERC-4337
160 lines (153 loc) • 4.68 kB
text/typescript
// Copied from: https://github.com/pimlicolabs/permissionless.js/blob/main/packages/permissionless/actions/smartAccount/signTypedData.ts
import {
type Chain,
type Client,
type SignTypedDataParameters,
type SignTypedDataReturnType,
type Transport,
type TypedData,
type TypedDataDefinition,
type TypedDataDomain,
getTypesForEIP712Domain,
validateTypedData
} from "viem"
import type { SmartAccount } from "viem/account-abstraction"
import { parseAccount } from "viem/utils"
import { AccountNotFoundError } from "../../errors/index.js"
/**
* Signs typed data and calculates an Ethereum-specific signature in [https://eips.ethereum.org/EIPS/eip-712](https://eips.ethereum.org/EIPS/eip-712): `sign(keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message)))`
*
* - Docs: https://viem.sh/docs/actions/wallet/signTypedData.html
* - JSON-RPC Methods:
* - JSON-RPC Accounts: [`eth_signTypedData_v4`](https://docs.metamask.io/guide/signing-data.html#signtypeddata-v4)
* - Local Accounts: Signs locally. No JSON-RPC request.
*
* @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/wallet'
*
* const client = createWalletClient({
* chain: mainnet,
* transport: custom(window.ethereum),
* })
* const signature = await signTypedData(client, {
* account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
* 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!',
* },
* })
*
* @example
* // Account Hoisting
* import { createWalletClient, http } from 'viem'
* import { privateKeyToAccount } from 'viem/accounts'
* import { mainnet } from 'viem/chains'
* import { signTypedData } from 'viem/wallet'
*
* const client = createWalletClient({
* account: privateKeyToAccount('0x…'),
* 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!',
* },
* })
*/
export async function signTypedData<
const TTypedData extends TypedData | { [key: string]: unknown },
TPrimaryType extends string,
TAccount extends SmartAccount | undefined = SmartAccount | undefined
>(
client: Client<Transport, Chain | undefined, TAccount>,
{
account: account_ = client.account,
domain,
message,
primaryType,
types: types_
}: SignTypedDataParameters<TTypedData, TPrimaryType, TAccount>
): Promise<SignTypedDataReturnType> {
if (!account_) {
throw new AccountNotFoundError({
docsPath: "/docs/actions/wallet/signMessage"
})
}
const account = parseAccount(account_) as SmartAccount
const types = {
EIP712Domain: getTypesForEIP712Domain({ domain } as {
domain: TypedDataDomain
}),
...(types_ as TTypedData)
}
validateTypedData({
domain,
message,
primaryType,
types
} as TypedDataDefinition)
return account.signTypedData({
domain,
primaryType,
types,
message
} as TypedDataDefinition)
}