diff --git a/CHANGELOG.md b/CHANGELOG.md
index 956f5c62f64..7db914cfc86 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1783,6 +1783,24 @@ If there are any bugs, improvements, optimizations or any new feature proposal f
- `RpcErrorMessages` that contains mapping for standard RPC Errors and their messages. (#6230)
+#### web3-eth
+
+- A `rpc_method_wrapper` (`signTypedData`) for the rpc calls `eth_signTypedData` and `eth_signTypedData_v4` (#6286)
+- A `signTypedData` method to the `Web3Eth` class (#6286)
+
+#### web3-eth-abi
+
+- A `getEncodedEip712Data` method that takes an EIP-712 typed data object and returns the encoded data with the option to also keccak256 hash it (#6286)
+
+#### web3-rpc-methods
+
+- A `signTypedData` method to `eth_rpc_methods` for the rpc calls `eth_signTypedData` and `eth_signTypedData_v4` (#6286)
+
+#### web3-types
+
+- `eth_signTypedData` and `eth_signTypedData_v4` to `web3_eth_execution_api` (#6286)
+- `Eip712TypeDetails` and `Eip712TypedData` to `eth_types` (#6286)
+
#### web3-validator
- Added `json-schema` as a main json schema type (#6264)
diff --git a/packages/web3-eth-abi/CHANGELOG.md b/packages/web3-eth-abi/CHANGELOG.md
index 20d0819bccd..4cc31c0f92c 100644
--- a/packages/web3-eth-abi/CHANGELOG.md
+++ b/packages/web3-eth-abi/CHANGELOG.md
@@ -125,3 +125,7 @@ Documentation:
- Dependencies updated
## [Unreleased]
+
+### Added
+
+- A `getEncodedEip712Data` method that takes an EIP-712 typed data object and returns the encoded data with the option to also keccak256 hash it (#6286)
diff --git a/packages/web3-eth-abi/src/eip_712.ts b/packages/web3-eth-abi/src/eip_712.ts
new file mode 100644
index 00000000000..948591bb474
--- /dev/null
+++ b/packages/web3-eth-abi/src/eip_712.ts
@@ -0,0 +1,201 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+
+/**
+ * @note This code was taken from: https://github.com/Mrtenz/eip-712/tree/master
+ */
+
+import { Eip712TypedData } from 'web3-types';
+import { isNullish, keccak256 } from 'web3-utils';
+
+import ethersAbiCoder from './ethers_abi_coder.js';
+
+const TYPE_REGEX = /^\w+/;
+const ARRAY_REGEX = /^(.*)\[([0-9]*?)]$/;
+
+/**
+ * Get the dependencies of a struct type. If a struct has the same dependency multiple times, it's only included once
+ * in the resulting array.
+ */
+const getDependencies = (
+ typedData: Eip712TypedData,
+ type: string,
+ dependencies: string[] = [],
+): string[] => {
+ const match = type.match(TYPE_REGEX)!;
+ const actualType = match[0];
+ if (dependencies.includes(actualType)) {
+ return dependencies;
+ }
+
+ if (!typedData.types[actualType]) {
+ return dependencies;
+ }
+
+ return [
+ actualType,
+ ...typedData.types[actualType].reduce(
+ (previous, _type) => [
+ ...previous,
+ ...getDependencies(typedData, _type.type, previous).filter(
+ dependency => !previous.includes(dependency),
+ ),
+ ],
+ [],
+ ),
+ ];
+};
+
+/**
+ * Encode a type to a string. All dependant types are alphabetically sorted.
+ *
+ * @param {TypedData} typedData
+ * @param {string} type
+ * @param {Options} [options]
+ * @return {string}
+ */
+const encodeType = (typedData: Eip712TypedData, type: string): string => {
+ const [primary, ...dependencies] = getDependencies(typedData, type);
+ // eslint-disable-next-line @typescript-eslint/require-array-sort-compare
+ const types = [primary, ...dependencies.sort()];
+
+ return types
+ .map(
+ dependency =>
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ `${dependency}(${typedData.types[dependency].map(
+ _type => `${_type.type} ${_type.name}`,
+ )})`,
+ )
+ .join('');
+};
+
+/**
+ * Get a type string as hash.
+ */
+const getTypeHash = (typedData: Eip712TypedData, type: string) =>
+ keccak256(encodeType(typedData, type));
+
+/**
+ * Get encoded data as a hash. The data should be a key -> value object with all the required values. All dependant
+ * types are automatically encoded.
+ */
+const getStructHash = (
+ typedData: Eip712TypedData,
+ type: string,
+ data: Record,
+ // eslint-disable-next-line no-use-before-define
+): string => keccak256(encodeData(typedData, type, data));
+
+/**
+ * Get the EIP-191 encoded message to sign, from the typedData object. If `hash` is enabled, the message will be hashed
+ * with Keccak256.
+ */
+export const getMessage = (typedData: Eip712TypedData, hash?: boolean): string => {
+ const EIP_191_PREFIX = '1901';
+ const message = `0x${EIP_191_PREFIX}${getStructHash(
+ typedData,
+ 'EIP712Domain',
+ typedData.domain as Record,
+ ).substring(2)}${getStructHash(typedData, typedData.primaryType, typedData.message).substring(
+ 2,
+ )}`;
+
+ if (hash) {
+ return keccak256(message);
+ }
+
+ return message;
+};
+
+/**
+ * Encodes a single value to an ABI serialisable string, number or Buffer. Returns the data as tuple, which consists of
+ * an array of ABI compatible types, and an array of corresponding values.
+ */
+const encodeValue = (
+ typedData: Eip712TypedData,
+ type: string,
+ data: unknown,
+): [string, string | Uint8Array | number] => {
+ const match = type.match(ARRAY_REGEX);
+
+ // Checks for array types
+ if (match) {
+ const arrayType = match[1];
+ const length = Number(match[2]) || undefined;
+
+ if (!Array.isArray(data)) {
+ throw new Error('Cannot encode data: value is not of array type');
+ }
+
+ if (length && data.length !== length) {
+ throw new Error(
+ `Cannot encode data: expected length of ${length}, but got ${data.length}`,
+ );
+ }
+
+ const encodedData = data.map(item => encodeValue(typedData, arrayType, item));
+ const types = encodedData.map(item => item[0]);
+ const values = encodedData.map(item => item[1]);
+
+ return ['bytes32', keccak256(ethersAbiCoder.encode(types, values))];
+ }
+
+ if (typedData.types[type]) {
+ return ['bytes32', getStructHash(typedData, type, data as Record)];
+ }
+
+ // Strings and arbitrary byte arrays are hashed to bytes32
+ if (type === 'string') {
+ return ['bytes32', keccak256(data as string)];
+ }
+
+ if (type === 'bytes') {
+ return ['bytes32', keccak256(data as string)];
+ }
+
+ return [type, data as string];
+};
+
+/**
+ * Encode the data to an ABI encoded Buffer. The data should be a key -> value object with all the required values. All
+ * dependant types are automatically encoded.
+ */
+const encodeData = (
+ typedData: Eip712TypedData,
+ type: string,
+ data: Record,
+): string => {
+ const [types, values] = typedData.types[type].reduce<[string[], unknown[]]>(
+ ([_types, _values], field) => {
+ if (isNullish(data[field.name]) || isNullish(data[field.name])) {
+ throw new Error(`Cannot encode data: missing data for '${field.name}'`);
+ }
+
+ const value = data[field.name];
+ const [_type, encodedValue] = encodeValue(typedData, field.type, value);
+
+ return [
+ [..._types, _type],
+ [..._values, encodedValue],
+ ];
+ },
+ [['bytes32'], [getTypeHash(typedData, type)]],
+ );
+
+ return ethersAbiCoder.encode(types, values);
+};
diff --git a/packages/web3-eth-abi/src/index.ts b/packages/web3-eth-abi/src/index.ts
index 6a8754e486a..baa57947847 100644
--- a/packages/web3-eth-abi/src/index.ts
+++ b/packages/web3-eth-abi/src/index.ts
@@ -25,3 +25,4 @@ export * from './api/logs_api.js';
export * from './api/parameters_api.js';
export * from './utils.js';
export * from './decode_contract_error_data.js';
+export { getMessage as getEncodedEip712Data } from './eip_712.js';
diff --git a/packages/web3-eth-abi/test/fixtures/get_encoded_eip712_data.ts b/packages/web3-eth-abi/test/fixtures/get_encoded_eip712_data.ts
new file mode 100644
index 00000000000..ff7474aa33f
--- /dev/null
+++ b/packages/web3-eth-abi/test/fixtures/get_encoded_eip712_data.ts
@@ -0,0 +1,758 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { Eip712TypedData } from 'web3-types';
+
+/**
+ * string is the test title
+ * Eip712TypedData is the entire EIP-712 typed data object
+ * boolean is whether the EIP-712 encoded data is keccak256 hashed
+ * string is the encoded data expected to be returned by getEncodedEip712Data
+ */
+export const testData: [string, Eip712TypedData, boolean | undefined, string][] = [
+ [
+ 'should get encoded message without hashing, hash = undefined',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Person: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'wallet',
+ type: 'address',
+ },
+ ],
+ Mail: [
+ {
+ name: 'from',
+ type: 'Person',
+ },
+ {
+ name: 'to',
+ type: 'Person',
+ },
+ {
+ name: 'contents',
+ type: 'string',
+ },
+ ],
+ },
+ primaryType: 'Mail',
+ domain: {
+ name: 'Ether Mail',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ from: {
+ name: 'Cow',
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ },
+ to: {
+ name: 'Bob',
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ },
+ contents: 'Hello, Bob!',
+ },
+ },
+ undefined,
+ '0x1901f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e',
+ ],
+ [
+ 'should get encoded message without hashing, hash = false',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Person: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'wallet',
+ type: 'address',
+ },
+ ],
+ Mail: [
+ {
+ name: 'from',
+ type: 'Person',
+ },
+ {
+ name: 'to',
+ type: 'Person',
+ },
+ {
+ name: 'contents',
+ type: 'string',
+ },
+ ],
+ },
+ primaryType: 'Mail',
+ domain: {
+ name: 'Ether Mail',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ from: {
+ name: 'Cow',
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ },
+ to: {
+ name: 'Bob',
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ },
+ contents: 'Hello, Bob!',
+ },
+ },
+ false,
+ '0x1901f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e',
+ ],
+ [
+ 'should get the hashed encoded message, hash = true',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Person: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'wallet',
+ type: 'address',
+ },
+ ],
+ Mail: [
+ {
+ name: 'from',
+ type: 'Person',
+ },
+ {
+ name: 'to',
+ type: 'Person',
+ },
+ {
+ name: 'contents',
+ type: 'string',
+ },
+ ],
+ },
+ primaryType: 'Mail',
+ domain: {
+ name: 'Ether Mail',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ from: {
+ name: 'Cow',
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ },
+ to: {
+ name: 'Bob',
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ },
+ contents: 'Hello, Bob!',
+ },
+ },
+ true,
+ '0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2',
+ ],
+ [
+ 'should get encoded message with array types',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'address[]',
+ },
+ {
+ name: 'array3',
+ type: 'uint256[]',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ array1: ['string', 'string2', 'string3'],
+ array2: [
+ '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ ],
+ array3: [123456, 654321, 42],
+ },
+ },
+ false,
+ '0x1901928e4773f1f7243172cd0dd213906be49eb9d275e09c8bd0575921c51ba00058596a0bafab67b5b49cfe99456c50dd5b6294b1383e4f17c6e5c3c14afee96ac3',
+ ],
+ [
+ 'should get encoded message with array types',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'address[]',
+ },
+ {
+ name: 'array3',
+ type: 'uint256[]',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ array1: ['string', 'string2', 'string3'],
+ array2: [
+ '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ ],
+ array3: [123456, 654321, 42],
+ },
+ },
+ true,
+ '0x3e4d581a408c8c2fa8775017c26e0127df030593d83a8202e6c19b3380bde3da',
+ ],
+ [
+ 'should get encoded message with fixed array',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'address[3]',
+ },
+ {
+ name: 'array3',
+ type: 'uint256[]',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ array1: ['string', 'string2', 'string3'],
+ array2: [
+ '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ ],
+ array3: [123456, 654321, 42],
+ },
+ },
+ false,
+ '0x1901928e4773f1f7243172cd0dd213906be49eb9d275e09c8bd0575921c51ba00058b068b45d685c16bc9ef637106b4fd3a4fb9aa259f53218491a3d9eb65b1b574c',
+ ],
+ [
+ 'should get encoded message with fixed array',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'address[3]',
+ },
+ {
+ name: 'array3',
+ type: 'uint256[]',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ array1: ['string', 'string2', 'string3'],
+ array2: [
+ '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ ],
+ array3: [123456, 654321, 42],
+ },
+ },
+ true,
+ '0x133d00e67f2390ce846a631aeb6718a674a3923f5320b79b6d3e2f5bf146319e',
+ ],
+ [
+ 'should get encoded message with bytes32',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'bytes32',
+ type: 'bytes32',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ bytes32: '0x133d00e67f2390ce846a631aeb6718a674a3923f5320b79b6d3e2f5bf146319e',
+ },
+ },
+ false,
+ '0x1901928e4773f1f7243172cd0dd213906be49eb9d275e09c8bd0575921c51ba000587c9d26380d51aac5dc2ff6f794d1c043ea4259bb42068f70f79d2e4849133ac3',
+ ],
+ [
+ 'should get encoded message with bytes32',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'bytes32',
+ type: 'bytes',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ bytes32: '0x133d00e67f2390ce846a631aeb6718a674a3923f5320b79b6d3e2f5bf146319e',
+ },
+ },
+ false,
+ '0x1901928e4773f1f7243172cd0dd213906be49eb9d275e09c8bd0575921c51ba00058353ed034fd1df0cd409a19133f4a89f5e99ddc735ad3fbb767d0bb72c97ef175',
+ ],
+ [
+ 'should get encoded message with bytes32',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'bytes32',
+ type: 'bytes32',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ bytes32: '0x133d00e67f2390ce846a631aeb6718a674a3923f5320b79b6d3e2f5bf146319e',
+ },
+ },
+ true,
+ '0xa6cd048c02ef3cb70feee1bd9795decbbc8b431b976dfc86e3b09e55e0d2a3f3',
+ ],
+];
+
+/**
+ * string is the test title
+ * Eip712TypedData is the entire EIP-712 typed data object
+ * boolean is whether the EIP-712 encoded data is keccak256 hashed
+ * string is the encoded data expected to be returned by getEncodedEip712Data
+ */
+export const erroneousTestData: [string, Eip712TypedData, boolean | undefined, Error][] = [
+ [
+ 'should throw error: Cannot encode data: value is not of array type',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'address[]',
+ },
+ {
+ name: 'array3',
+ type: 'uint256[]',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ array1: ['string', 'string2', 'string3'],
+ array2: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ array3: [123456, 654321, 42],
+ },
+ },
+ false,
+ new Error('Cannot encode data: value is not of array type'),
+ ],
+ [
+ 'should throw error: Cannot encode data: expected length of 3, but got 1',
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'address[3]',
+ },
+ {
+ name: 'array3',
+ type: 'uint256[]',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ array1: ['string', 'string2', 'string3'],
+ array2: ['0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'],
+ array3: [123456, 654321, 42],
+ },
+ },
+ false,
+ new Error('Cannot encode data: expected length of 3, but got 1'),
+ ],
+ [
+ "should throw error: Cannot encode data: missing data for 'array3'",
+ {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ ArrayData: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'address[]',
+ },
+ {
+ name: 'array3',
+ type: 'uint256[]',
+ },
+ ],
+ },
+ primaryType: 'ArrayData',
+ domain: {
+ name: 'Array Data',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ array1: ['string', 'string2', 'string3'],
+ array2: ['0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'],
+ array3: undefined,
+ },
+ },
+ false,
+ new Error("Cannot encode data: missing data for 'array3'"),
+ ],
+];
diff --git a/packages/web3-eth-abi/test/unit/get_encoded_eip712_data.test.ts b/packages/web3-eth-abi/test/unit/get_encoded_eip712_data.test.ts
new file mode 100644
index 00000000000..d40c5f25511
--- /dev/null
+++ b/packages/web3-eth-abi/test/unit/get_encoded_eip712_data.test.ts
@@ -0,0 +1,29 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { getEncodedEip712Data } from '../../src/index';
+import { erroneousTestData, testData } from '../fixtures/get_encoded_eip712_data';
+
+describe('getEncodedEip712Data', () => {
+ it.each(testData)('%s', (_, typedData, hashEncodedData, expectedResponse) => {
+ const encodedMessage = getEncodedEip712Data(typedData, hashEncodedData);
+ expect(encodedMessage).toBe(expectedResponse);
+ });
+
+ it.each(erroneousTestData)('%s', (_, typedData, hashEncodedData, expectedError) => {
+ expect(() => getEncodedEip712Data(typedData, hashEncodedData)).toThrowError(expectedError);
+ });
+});
diff --git a/packages/web3-eth/CHANGELOG.md b/packages/web3-eth/CHANGELOG.md
index 37151b39dd6..abefb0b7e65 100644
--- a/packages/web3-eth/CHANGELOG.md
+++ b/packages/web3-eth/CHANGELOG.md
@@ -163,6 +163,11 @@ Documentation:
## [Unreleased]
+### Added
+
+- A `rpc_method_wrapper` (`signTypedData`) for the rpc calls `eth_signTypedData` and `eth_signTypedData_v4` (#6286)
+- A `signTypedData` method to the `Web3Eth` class (#6286)
+
### Fixed
- Missing `blockHeaderSchema` properties causing some properties to not appear in response of `newHeads` subscription (#6243)
diff --git a/packages/web3-eth/src/rpc_method_wrappers.ts b/packages/web3-eth/src/rpc_method_wrappers.ts
index 246cbf67ca0..1d7d1915405 100644
--- a/packages/web3-eth/src/rpc_method_wrappers.ts
+++ b/packages/web3-eth/src/rpc_method_wrappers.ts
@@ -47,6 +47,7 @@ import {
TransactionWithFromAndToLocalWalletIndex,
TransactionForAccessList,
AccessListResult,
+ Eip712TypedData,
} from 'web3-types';
import { Web3Context, Web3PromiEvent } from 'web3-core';
import { format, hexToBytes, bytesToUint8Array, numberToHex } from 'web3-utils';
@@ -1125,3 +1126,24 @@ export async function createAccessList(
return format(accessListResultSchema, response, returnFormat);
}
+
+/**
+ * View additional documentations here: {@link Web3Eth.signTypedData}
+ * @param web3Context ({@link Web3Context}) Web3 configuration object that contains things such as the provider, request manager, wallet, etc.
+ */
+export async function signTypedData(
+ web3Context: Web3Context,
+ address: Address,
+ typedData: Eip712TypedData,
+ useLegacy: boolean,
+ returnFormat: ReturnFormat,
+) {
+ const response = await ethRpcMethods.signTypedData(
+ web3Context.requestManager,
+ address,
+ typedData,
+ useLegacy,
+ );
+
+ return format({ format: 'bytes' }, response, returnFormat);
+}
diff --git a/packages/web3-eth/src/web3_eth.ts b/packages/web3-eth/src/web3_eth.ts
index cd1c57bcc2b..c4b3b6a92f8 100644
--- a/packages/web3-eth/src/web3_eth.ts
+++ b/packages/web3-eth/src/web3_eth.ts
@@ -37,6 +37,7 @@ import {
TransactionForAccessList,
DataFormat,
DEFAULT_RETURN_FORMAT,
+ Eip712TypedData,
} from 'web3-types';
import { isSupportedProvider, Web3Context, Web3ContextInitOptions } from 'web3-core';
import { TransactionNotFound } from 'web3-errors';
@@ -1534,6 +1535,24 @@ export class Web3Eth extends Web3Context(
+ address: Address,
+ typedData: Eip712TypedData,
+ useLegacy = false,
+ returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
+ ) {
+ return rpcMethodsWrappers.signTypedData(this, address, typedData, useLegacy, returnFormat);
+ }
+
/**
* Lets you subscribe to specific events in the blockchain.
*
diff --git a/packages/web3-eth/test/integration/web3_eth/sign_typed_data.test.ts b/packages/web3-eth/test/integration/web3_eth/sign_typed_data.test.ts
new file mode 100644
index 00000000000..741690bc47f
--- /dev/null
+++ b/packages/web3-eth/test/integration/web3_eth/sign_typed_data.test.ts
@@ -0,0 +1,206 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { getEncodedEip712Data } from 'web3-eth-abi';
+import { ecrecover, toUint8Array } from 'web3-eth-accounts';
+import { bytesToHex, hexToNumber, keccak256 } from 'web3-utils';
+
+import Web3Eth from '../../../src';
+import {
+ closeOpenConnection,
+ createTempAccount,
+ getSystemTestBackend,
+ getSystemTestProvider,
+ itIf,
+} from '../../fixtures/system_test_utils';
+
+describe('Web3Eth.signTypedData', () => {
+ let web3Eth: Web3Eth;
+ let tempAcc: { address: string; privateKey: string };
+
+ beforeAll(async () => {
+ web3Eth = new Web3Eth(getSystemTestProvider());
+ tempAcc = await createTempAccount();
+ });
+
+ afterAll(async () => {
+ await closeOpenConnection(web3Eth);
+ });
+
+ itIf(getSystemTestBackend() === 'ganache')(
+ 'should sign the typed data, return the signature, and recover the correct ETH address',
+ async () => {
+ const typedData = {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Person: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'wallet',
+ type: 'address',
+ },
+ ],
+ Mail: [
+ {
+ name: 'from',
+ type: 'Person',
+ },
+ {
+ name: 'to',
+ type: 'Person',
+ },
+ {
+ name: 'contents',
+ type: 'string',
+ },
+ ],
+ },
+ primaryType: 'Mail',
+ domain: {
+ name: 'Ether Mail',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ from: {
+ name: 'Cow',
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ },
+ to: {
+ name: 'Bob',
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ },
+ contents: 'Hello, Bob!',
+ },
+ };
+ const encodedTypedDataHash = getEncodedEip712Data(typedData, true);
+ const signature = await web3Eth.signTypedData(tempAcc.address, typedData);
+ const r = toUint8Array(signature.slice(0, 66));
+ const s = toUint8Array(`0x${signature.slice(66, 130)}`);
+ const v = BigInt(hexToNumber(`0x${signature.slice(130, 132)}`));
+ const recoveredPublicKey = bytesToHex(
+ ecrecover(toUint8Array(encodedTypedDataHash), v, r, s),
+ );
+
+ const recoveredAddress = `0x${keccak256(bytesToHex(recoveredPublicKey)).slice(-40)}`;
+ // eslint-disable-next-line jest/no-standalone-expect
+ expect(recoveredAddress).toBe(tempAcc.address);
+ },
+ );
+
+ itIf(getSystemTestBackend() === 'ganache')(
+ 'should sign the typed data (using legacy RPC method), return the signature, and recover the correct ETH address',
+ async () => {
+ const typedData = {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Person: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'wallet',
+ type: 'address',
+ },
+ ],
+ Mail: [
+ {
+ name: 'from',
+ type: 'Person',
+ },
+ {
+ name: 'to',
+ type: 'Person',
+ },
+ {
+ name: 'contents',
+ type: 'string',
+ },
+ ],
+ },
+ primaryType: 'Mail',
+ domain: {
+ name: 'Ether Mail',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ from: {
+ name: 'Cow',
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ },
+ to: {
+ name: 'Bob',
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ },
+ contents: 'Hello, Bob!',
+ },
+ };
+ const encodedTypedDataHash = getEncodedEip712Data(typedData, true);
+ const signature = await web3Eth.signTypedData(tempAcc.address, typedData, true);
+ const r = toUint8Array(signature.slice(0, 66));
+ const s = toUint8Array(`0x${signature.slice(66, 130)}`);
+ const v = BigInt(hexToNumber(`0x${signature.slice(130, 132)}`));
+ const recoveredPublicKey = bytesToHex(
+ ecrecover(toUint8Array(encodedTypedDataHash), v, r, s),
+ );
+
+ const recoveredAddress = `0x${keccak256(bytesToHex(recoveredPublicKey)).slice(-40)}`;
+ // eslint-disable-next-line jest/no-standalone-expect
+ expect(recoveredAddress).toBe(tempAcc.address);
+ },
+ );
+});
diff --git a/packages/web3-eth/test/unit/rpc_method_wrappers/fixtures/sign_typed_data.ts b/packages/web3-eth/test/unit/rpc_method_wrappers/fixtures/sign_typed_data.ts
new file mode 100644
index 00000000000..b01f33f698e
--- /dev/null
+++ b/packages/web3-eth/test/unit/rpc_method_wrappers/fixtures/sign_typed_data.ts
@@ -0,0 +1,100 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { Address, Eip712TypedData } from 'web3-types';
+
+const address = '0x407d73d8a49eeb85d32cf465507dd71d507100c1';
+
+const typedData = {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Person: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'wallet',
+ type: 'address',
+ },
+ ],
+ Mail: [
+ {
+ name: 'from',
+ type: 'Person',
+ },
+ {
+ name: 'to',
+ type: 'Person',
+ },
+ {
+ name: 'contents',
+ type: 'string',
+ },
+ ],
+ },
+ primaryType: 'Mail',
+ domain: {
+ name: 'Ether Mail',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ from: {
+ name: 'Cow',
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ },
+ to: {
+ name: 'Bob',
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ },
+ contents: 'Hello, Bob!',
+ },
+};
+
+export const mockRpcResponse =
+ '0xf326421b6b34e1e59a8a34c986861e8790a9402a9e51e012718872cd51dad4e23c590bd170be23c51cff4b44d8d4eba54120431ca6a04940098dae62d97677da1c';
+
+/**
+ * Array consists of:
+ * - Test title
+ * - Input parameters:
+ * - address
+ * - message
+ */
+type TestData = [string, [Address, Eip712TypedData, boolean]];
+export const testData: TestData[] = [
+ ['useLegacy = false', [address, typedData, false]],
+ ['useLegacy = true', [address, typedData, true]],
+];
diff --git a/packages/web3-eth/test/unit/rpc_method_wrappers/sign_typed_data.test.ts b/packages/web3-eth/test/unit/rpc_method_wrappers/sign_typed_data.test.ts
new file mode 100644
index 00000000000..8256beb7f78
--- /dev/null
+++ b/packages/web3-eth/test/unit/rpc_method_wrappers/sign_typed_data.test.ts
@@ -0,0 +1,64 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { Web3Context } from 'web3-core';
+import { DEFAULT_RETURN_FORMAT, FMT_BYTES, FMT_NUMBER, Web3EthExecutionAPI } from 'web3-types';
+import { ethRpcMethods } from 'web3-rpc-methods';
+import { format } from 'web3-utils';
+
+import { signTypedData } from '../../../src/rpc_method_wrappers';
+import { testData, mockRpcResponse } from './fixtures/sign_typed_data';
+
+jest.mock('web3-rpc-methods');
+
+describe('signTypedData', () => {
+ let web3Context: Web3Context;
+
+ beforeAll(() => {
+ web3Context = new Web3Context('http://127.0.0.1:8545');
+ });
+
+ it.each(testData)(
+ `should call rpcMethods.signTypedData with expected parameters\nTitle: %s\nInput parameters: %s\n`,
+ async (_, inputParameters) => {
+ await signTypedData(web3Context, ...inputParameters, DEFAULT_RETURN_FORMAT);
+ expect(ethRpcMethods.signTypedData).toHaveBeenCalledWith(
+ web3Context.requestManager,
+ ...inputParameters,
+ );
+ },
+ );
+
+ it.each(testData)(
+ `should format mockRpcResponse using provided return format\nTitle: %s\nInput parameters: %s\n`,
+ async (_, inputParameters) => {
+ const expectedReturnFormat = { number: FMT_NUMBER.STR, bytes: FMT_BYTES.UINT8ARRAY };
+ const expectedFormattedResult = format(
+ { format: 'bytes' },
+ mockRpcResponse,
+ expectedReturnFormat,
+ );
+ (ethRpcMethods.signTypedData as jest.Mock).mockResolvedValueOnce(mockRpcResponse);
+
+ const result = await signTypedData(
+ web3Context,
+ ...inputParameters,
+ expectedReturnFormat,
+ );
+ expect(result).toStrictEqual(expectedFormattedResult);
+ },
+ );
+});
diff --git a/packages/web3-rpc-methods/CHANGELOG.md b/packages/web3-rpc-methods/CHANGELOG.md
index 56772ed7164..e30d8e79193 100644
--- a/packages/web3-rpc-methods/CHANGELOG.md
+++ b/packages/web3-rpc-methods/CHANGELOG.md
@@ -103,3 +103,7 @@ Documentation:
- Rpc method `getPastLogs` accept blockHash as a parameter https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs (#6181)
## [Unreleased]
+
+### Added
+
+- A `signTypedData` method to `eth_rpc_methods` for the rpc calls `eth_signTypedData` and `eth_signTypedData_v4` (#6286)
diff --git a/packages/web3-rpc-methods/src/eth_rpc_methods.ts b/packages/web3-rpc-methods/src/eth_rpc_methods.ts
index 5209302903a..2a027733dfb 100644
--- a/packages/web3-rpc-methods/src/eth_rpc_methods.ts
+++ b/packages/web3-rpc-methods/src/eth_rpc_methods.ts
@@ -28,6 +28,7 @@ import {
Uint256,
Web3EthExecutionAPI,
} from 'web3-types';
+import { Eip712TypedData } from 'web3-types/src/eth_types';
import { validator } from 'web3-validator';
export async function getProtocolVersion(requestManager: Web3RequestManager) {
@@ -575,3 +576,18 @@ export async function createAccessList(
params: [transaction, blockNumber],
});
}
+
+export async function signTypedData(
+ requestManager: Web3RequestManager,
+ address: Address,
+ typedData: Eip712TypedData,
+ useLegacy = false,
+): Promise {
+ // TODO Add validation for typedData
+ validator.validate(['address'], [address]);
+
+ return requestManager.send({
+ method: `eth_signTypedData${useLegacy ? '' : '_v4'}`,
+ params: [address, typedData],
+ });
+}
diff --git a/packages/web3-rpc-methods/test/unit/eth_rpc_methods/fixtures/sign_typed_data.ts b/packages/web3-rpc-methods/test/unit/eth_rpc_methods/fixtures/sign_typed_data.ts
new file mode 100644
index 00000000000..37a5ee3171e
--- /dev/null
+++ b/packages/web3-rpc-methods/test/unit/eth_rpc_methods/fixtures/sign_typed_data.ts
@@ -0,0 +1,98 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { Address, Eip712TypedData } from 'web3-types';
+
+const address = '0x407d73d8a49eeb85d32cf465507dd71d507100c1';
+
+const typedData = {
+ types: {
+ EIP712Domain: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'version',
+ type: 'string',
+ },
+ {
+ name: 'chainId',
+ type: 'uint256',
+ },
+ {
+ name: 'verifyingContract',
+ type: 'address',
+ },
+ ],
+ Person: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'wallet',
+ type: 'address',
+ },
+ ],
+ Mail: [
+ {
+ name: 'from',
+ type: 'Person',
+ },
+ {
+ name: 'to',
+ type: 'Person',
+ },
+ {
+ name: 'contents',
+ type: 'string',
+ },
+ ],
+ },
+ primaryType: 'Mail',
+ domain: {
+ name: 'Ether Mail',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
+ },
+ message: {
+ from: {
+ name: 'Cow',
+ wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
+ },
+ to: {
+ name: 'Bob',
+ wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ },
+ contents: 'Hello, Bob!',
+ },
+};
+
+/**
+ * Array consists of:
+ * - Test title
+ * - Input parameters:
+ * - address
+ * - message
+ */
+type TestData = [string, [Address, Eip712TypedData, boolean | undefined]];
+export const testData: TestData[] = [
+ ['useLegacy = undefined', [address, typedData, undefined]],
+ ['useLegacy = false', [address, typedData, false]],
+ ['useLegacy = true', [address, typedData, true]],
+];
diff --git a/packages/web3-rpc-methods/test/unit/eth_rpc_methods/sign_typed_data.test.ts b/packages/web3-rpc-methods/test/unit/eth_rpc_methods/sign_typed_data.test.ts
new file mode 100644
index 00000000000..a9674a8d381
--- /dev/null
+++ b/packages/web3-rpc-methods/test/unit/eth_rpc_methods/sign_typed_data.test.ts
@@ -0,0 +1,54 @@
+/*
+This file is part of web3.js.
+
+web3.js is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+web3.js is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with web3.js. If not, see .
+*/
+import { Web3RequestManager } from 'web3-core';
+import { validator } from 'web3-validator';
+
+import { ethRpcMethods } from '../../../src/index';
+import { testData } from './fixtures/sign_typed_data';
+
+jest.mock('web3-validator');
+
+describe('signTypedData', () => {
+ let requestManagerSendSpy: jest.Mock;
+ let requestManager: Web3RequestManager;
+
+ beforeAll(() => {
+ requestManager = new Web3RequestManager('http://127.0.0.1:8545');
+ requestManagerSendSpy = jest.fn();
+ requestManager.send = requestManagerSendSpy;
+ });
+
+ it.each(testData)(
+ 'should call requestManager.send with signTypedData method and expect parameters\n Title: %s\n Input parameters: %s',
+ async (_, inputParameters) => {
+ await ethRpcMethods.signTypedData(requestManager, ...inputParameters);
+ expect(requestManagerSendSpy).toHaveBeenCalledWith({
+ method: `eth_signTypedData${inputParameters[2] ? '' : '_v4'}`,
+ params: [inputParameters[0], inputParameters[1]],
+ });
+ },
+ );
+
+ it.each(testData)(
+ 'should call validator.validate with expected params\n Title: %s\n Input parameters: %s',
+ async (_, inputParameters) => {
+ const validatorSpy = jest.spyOn(validator, 'validate');
+ await ethRpcMethods.signTypedData(requestManager, ...inputParameters);
+ expect(validatorSpy).toHaveBeenCalledWith(['address'], [inputParameters[0]]);
+ },
+ );
+});
diff --git a/packages/web3-types/CHANGELOG.md b/packages/web3-types/CHANGELOG.md
index 3607abfae24..da55982895e 100644
--- a/packages/web3-types/CHANGELOG.md
+++ b/packages/web3-types/CHANGELOG.md
@@ -141,3 +141,8 @@ Documentation:
- type `Filter` includes `blockHash` (#6206)
## [Unreleased]
+
+### Added
+
+- `eth_signTypedData` and `eth_signTypedData_v4` to `web3_eth_execution_api` (#6286)
+- `Eip712TypeDetails` and `Eip712TypedData` to `eth_types` (#6286)
diff --git a/packages/web3-types/src/apis/web3_eth_execution_api.ts b/packages/web3-types/src/apis/web3_eth_execution_api.ts
index 161e73f8d39..fde1250b4a3 100644
--- a/packages/web3-types/src/apis/web3_eth_execution_api.ts
+++ b/packages/web3-types/src/apis/web3_eth_execution_api.ts
@@ -19,6 +19,8 @@ import {
AccountObject,
Address,
BlockNumberOrTag,
+ Eip712TypedData,
+ HexString256Bytes,
HexString32Bytes,
TransactionInfo,
Uint,
@@ -41,4 +43,18 @@ export type Web3EthExecutionAPI = EthExecutionAPI & {
storageKeys: HexString32Bytes[],
blockNumber: BlockNumberOrTag,
) => AccountObject;
+
+ // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md
+ eth_signTypedData: (
+ address: Address,
+ typedData: Eip712TypedData,
+ useLegacy: true,
+ ) => HexString256Bytes;
+
+ // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md
+ eth_signTypedData_v4: (
+ address: Address,
+ typedData: Eip712TypedData,
+ useLegacy: false | undefined,
+ ) => HexString256Bytes;
};
diff --git a/packages/web3-types/src/eth_types.ts b/packages/web3-types/src/eth_types.ts
index 6a9a4aa8e88..2865428b092 100644
--- a/packages/web3-types/src/eth_types.ts
+++ b/packages/web3-types/src/eth_types.ts
@@ -496,3 +496,17 @@ export interface AccountObject {
readonly accountProof: Bytes[];
readonly storageProof: StorageProof[];
}
+
+export interface Eip712TypeDetails {
+ name: string;
+ type: string;
+}
+export interface Eip712TypedData {
+ readonly types: {
+ EIP712Domain: Eip712TypeDetails[];
+ [key: string]: Eip712TypeDetails[];
+ };
+ readonly primaryType: string;
+ readonly domain: Record;
+ readonly message: Record;
+}