Skip to content

Commit

Permalink
feat: getEip712Domain (#2399)
Browse files Browse the repository at this point in the history
feat: `getEip712Domain` action
  • Loading branch information
jxom authored Jun 13, 2024
1 parent 6578102 commit a61a90c
Show file tree
Hide file tree
Showing 14 changed files with 422 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-suits-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `getEip712Domain` Action.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "test/contracts/lib/solady"]
path = test/contracts/lib/solady
url = [email protected]:Vectorized/solady.git
53 changes: 53 additions & 0 deletions site/pages/docs/actions/public/getEip712Domain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
description: Reads the EIP-712 domain from a contract.
---

# getEip712Domain

Reads the EIP-712 domain from a contract, based on the [ERC-5267 specification](https://eips.ethereum.org/EIPS/eip-5267).

## Usage

:::code-group

```ts twoslash [example.ts]
import { publicClient } from './client'

const { domain, extensions, fields } = await publicClient.getEip712Domain({
address: '0x57ba3ec8df619d4d243ce439551cce713bb17411',
})
```

```ts [client.ts] filename="client.ts"
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

export const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
```

:::

## Returns

`GetEip712DomainReturnType`

The EIP-712 domain (`domain`) for the contract, with `fields` and `extensions`, as per [ERC-5267](https://eips.ethereum.org/EIPS/eip-5267).

## Parameters

### address

- **Type:** `string`

The address of the contract to read the EIP-712 domain from.

```ts twoslash
// [!include ~/snippets/publicClient.ts]
// ---cut---
const result = await publicClient.getEip712Domain({
address: '0x57ba3ec8df619d4d243ce439551cce713bb17411', // [!code focus]
})
```
9 changes: 9 additions & 0 deletions site/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ export const sidebar = {
{ text: 'getChainId', link: '/docs/actions/public/getChainId' },
],
},
{
text: 'EIP-712',
items: [
{
text: 'getEip712Domain',
link: '/docs/actions/public/getEip712Domain',
},
],
},
{
text: 'Fee',
items: [
Expand Down
89 changes: 89 additions & 0 deletions src/actions/public/getEip712Domain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { expect, test } from 'vitest'
import { Mock4337AccountFactory } from '../../../test/contracts/generated.js'
import { anvilMainnet } from '../../../test/src/anvil.js'
import { accounts } from '../../../test/src/constants.js'
import { deployMock4337Account } from '../../../test/src/utils.js'
import { pad } from '../../utils/index.js'
import { mine } from '../test/mine.js'
import { writeContract } from '../wallet/writeContract.js'
import { getEip712Domain } from './getEip712Domain.js'
import { simulateContract } from './simulateContract.js'

const client = anvilMainnet.getClient()

test('default', async () => {
const { factoryAddress } = await deployMock4337Account()

const { result: address, request } = await simulateContract(client, {
account: accounts[0].address,
abi: Mock4337AccountFactory.abi,
address: factoryAddress,
functionName: 'createAccount',
args: [accounts[0].address, pad('0x0')],
})
await writeContract(client, request)
await mine(client, { blocks: 1 })

expect(
await getEip712Domain(client, {
address,
}),
).toMatchInlineSnapshot(`
{
"domain": {
"chainId": 1,
"name": "Mock4337Account",
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
"verifyingContract": "0x8a00708a83D977494139D21D618C6C2A71fA8ed1",
"version": "1",
},
"extensions": [],
"fields": "0x0f",
}
`)
})

test('error: non-existent', async () => {
await expect(() =>
getEip712Domain(client, {
address: '0x0000000000000000000000000000000000000000',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`
[Eip712DomainNotFoundError: No EIP-712 domain found on contract "0x0000000000000000000000000000000000000000".
Ensure that:
- The contract is deployed at the address "0x0000000000000000000000000000000000000000".
- \`eip712Domain()\` function exists on the contract.
- \`eip712Domain()\` function matches signature to ERC-5267 specification.
Version: [email protected]]
`,
)
})

test('error: default', async () => {
await expect(() =>
getEip712Domain(client, {
address: '0x00000000000000000000000000000000000000000',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`
[ContractFunctionExecutionError: Address "0x00000000000000000000000000000000000000000" is invalid.
- Address must be a hex value of 20 bytes (40 hex characters).
- Address must match its checksum counterpart.
Raw Call Arguments:
to: 0x00000000000000000000000000000000000000000
data: 0x84b0196e
Contract Call:
address: 0x0000000000000000000000000000000000000000
function: eip712Domain()
Docs: https://viem.sh/docs/contract/readContract
Version: [email protected]]
`,
)
})
124 changes: 124 additions & 0 deletions src/actions/public/getEip712Domain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { Address, TypedDataDomain } from 'abitype'
import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import {
Eip712DomainNotFoundError,
type Eip712DomainNotFoundErrorType,
} from '../../errors/eip712.js'
import type { ErrorType } from '../../errors/utils.js'
import type { Hex } from '../../types/misc.js'
import { getAction } from '../../utils/getAction.js'
import { type ReadContractErrorType, readContract } from './readContract.js'

export type GetEip712DomainParameters = {
address: Address
}

export type GetEip712DomainReturnType = {
domain: TypedDataDomain
fields: Hex
extensions: readonly bigint[]
}

export type GetEip712DomainErrorType =
| Eip712DomainNotFoundErrorType
| ReadContractErrorType
| ErrorType

/**
* Reads the EIP-712 domain from a contract, based on the ERC-5267 specification.
*
* @param client - A {@link Client} instance.
* @param parameters - The parameters of the action. {@link GetEip712DomainParameters}
* @returns The EIP-712 domain, fields, and extensions. {@link GetEip712DomainReturnType}
*
* @example
* ```ts
* import { createPublicClient, http, getEip712Domain } from 'viem'
* import { mainnet } from 'viem/chains'
*
* const client = createPublicClient({
* chain: mainnet,
* transport: http(),
* })
*
* const domain = await getEip712Domain(client, {
* address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
* })
* // {
* // domain: {
* // name: 'ExampleContract',
* // version: '1',
* // chainId: 1,
* // verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
* // },
* // fields: '0x0f',
* // extensions: [],
* // }
* ```
*/
export async function getEip712Domain(
client: Client<Transport>,
parameters: GetEip712DomainParameters,
): Promise<GetEip712DomainReturnType> {
const { address } = parameters

try {
const [
fields,
name,
version,
chainId,
verifyingContract,
salt,
extensions,
] = await getAction(
client,
readContract,
'readContract',
)({
abi,
address,
functionName: 'eip712Domain',
})

return {
domain: {
name,
version,
chainId: Number(chainId),
verifyingContract,
salt,
},
extensions,
fields,
}
} catch (e) {
const error = e as ReadContractErrorType
if (
error.name === 'ContractFunctionExecutionError' &&
error.cause.name === 'ContractFunctionZeroDataError'
) {
throw new Eip712DomainNotFoundError({ address })
}
throw error
}
}

const abi = [
{
inputs: [],
name: 'eip712Domain',
outputs: [
{ name: 'fields', type: 'bytes1' },
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
{ name: 'salt', type: 'bytes32' },
{ name: 'extensions', type: 'uint256[]' },
],
stateMutability: 'view',
type: 'function',
},
] as const
34 changes: 34 additions & 0 deletions src/clients/decorators/public.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ import { accounts, address, typedData } from '~test/src/constants.js'
import { getBlockNumber } from '../../actions/public/getBlockNumber.js'
import { parseEther } from '../../utils/unit/parseEther.js'

import { Mock4337AccountFactory } from '../../../test/contracts/generated.js'
import { anvilMainnet } from '../../../test/src/anvil.js'
import { deployMock4337Account } from '../../../test/src/utils.js'
import { privateKeyToAccount } from '../../accounts/privateKeyToAccount.js'
import { signMessage } from '../../accounts/utils/signMessage.js'
import {
mine,
reset,
sendTransaction,
signTransaction,
simulateContract,
writeContract,
} from '../../actions/index.js'
import { base } from '../../chains/index.js'
import { pad } from '../../utils/index.js'
import { createSiweMessage } from '../../utils/siwe/createSiweMessage.js'
import { wait } from '../../utils/wait.js'
import { createPublicClient } from '../createPublicClient.js'
Expand Down Expand Up @@ -47,6 +52,7 @@ test('default', async () => {
"getBytecode": [Function],
"getChainId": [Function],
"getContractEvents": [Function],
"getEip712Domain": [Function],
"getEnsAddress": [Function],
"getEnsAvatar": [Function],
"getEnsName": [Function],
Expand Down Expand Up @@ -177,6 +183,34 @@ describe('smoke test', () => {
).toBeDefined()
})

test('getEip712Domain', async () => {
const { factoryAddress } = await deployMock4337Account()

const { result: address, request } = await simulateContract(client, {
account: accounts[0].address,
abi: Mock4337AccountFactory.abi,
address: factoryAddress,
functionName: 'createAccount',
args: [accounts[0].address, pad('0x0')],
})
await writeContract(client, request)
await mine(client, { blocks: 1 })

expect(await client.getEip712Domain({ address })).toMatchInlineSnapshot(`
{
"domain": {
"chainId": 1,
"name": "Mock4337Account",
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
"verifyingContract": "0x8a00708a83D977494139D21D618C6C2A71fA8ed1",
"version": "1",
},
"extensions": [],
"fields": "0x0f",
}
`)
})

test(
'getEnsAddress',
async () => {
Expand Down
Loading

0 comments on commit a61a90c

Please sign in to comment.