viem
Version:
122 lines • 4.18 kB
JavaScript
// 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