UNPKG

viem

Version:

TypeScript Interface for Ethereum

122 lines 4.18 kB
// Implementation forked and adapted from https://github.com/MetaMask/eth-sig-util/blob/main/src/sign-typed-data.ts import { encodeAbiParameters, } from '../abi/encodeAbiParameters.js'; import { concat } from '../data/concat.js'; import { toHex } from '../encoding/toHex.js'; import { keccak256 } from '../hash/keccak256.js'; import { getTypesForEIP712Domain, validateTypedData, } from '../typedData.js'; export function hashTypedData(parameters) { const { domain = {}, message, primaryType, } = parameters; const types = { EIP712Domain: getTypesForEIP712Domain({ domain }), ...parameters.types, }; // Need to do a runtime validation check on addresses, byte ranges, integer ranges, etc // as we can't statically check this with TypeScript. validateTypedData({ domain, message, primaryType, types, }); const parts = ['0x1901']; if (domain) parts.push(hashDomain({ domain, types: types, })); if (primaryType !== 'EIP712Domain') parts.push(hashStruct({ data: message, primaryType, types: types, })); return keccak256(concat(parts)); } export function hashDomain({ domain, types, }) { return hashStruct({ data: domain, primaryType: 'EIP712Domain', types, }); } export function hashStruct({ data, primaryType, types, }) { const encoded = encodeData({ data, primaryType, types, }); return keccak256(encoded); } function encodeData({ data, primaryType, types, }) { const encodedTypes = [{ type: 'bytes32' }]; const encodedValues = [hashType({ primaryType, types })]; for (const field of types[primaryType]) { const [type, value] = encodeField({ types, name: field.name, type: field.type, value: data[field.name], }); encodedTypes.push(type); encodedValues.push(value); } return encodeAbiParameters(encodedTypes, encodedValues); } function hashType({ primaryType, types, }) { const encodedHashType = toHex(encodeType({ primaryType, types })); return keccak256(encodedHashType); } export function encodeType({ primaryType, types, }) { let result = ''; const unsortedDeps = findTypeDependencies({ primaryType, types }); unsortedDeps.delete(primaryType); const deps = [primaryType, ...Array.from(unsortedDeps).sort()]; for (const type of deps) { result += `${type}(${types[type] .map(({ name, type: t }) => `${t} ${name}`) .join(',')})`; } return result; } function findTypeDependencies({ primaryType: primaryType_, types, }, results = new Set()) { const match = primaryType_.match(/^\w*/u); const primaryType = match?.[0]; if (results.has(primaryType) || types[primaryType] === undefined) { return results; } results.add(primaryType); for (const field of types[primaryType]) { findTypeDependencies({ primaryType: field.type, types }, results); } return results; } function encodeField({ types, name, type, value, }) { if (types[type] !== undefined) { return [ { type: 'bytes32' }, keccak256(encodeData({ data: value, primaryType: type, types })), ]; } if (type === 'bytes') { const prepend = value.length % 2 ? '0' : ''; value = `0x${prepend + value.slice(2)}`; return [{ type: 'bytes32' }, keccak256(value)]; } if (type === 'string') return [{ type: 'bytes32' }, keccak256(toHex(value))]; if (type.lastIndexOf(']') === type.length - 1) { const parsedType = type.slice(0, type.lastIndexOf('[')); const typeValuePairs = value.map((item) => encodeField({ name, type: parsedType, types, value: item, })); return [ { type: 'bytes32' }, keccak256(encodeAbiParameters(typeValuePairs.map(([t]) => t), typeValuePairs.map(([, v]) => v))), ]; } return [{ type }, value]; } //# sourceMappingURL=hashTypedData.js.map