"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvalidStructTypeError = exports.InvalidPrimaryTypeError = exports.InvalidDomainError = exports.BytesSizeMismatchError = void 0;
exports.assert = assert;
exports.domainSeparator = domainSeparator;
exports.encode = encode;
exports.encodeType = encodeType;
exports.extractEip712DomainTypes = extractEip712DomainTypes;
exports.getSignPayload = getSignPayload;
exports.hashDomain = hashDomain;
exports.hashStruct = hashStruct;
exports.serialize = serialize;
exports.validate = validate;
exports.encodeData = encodeData;
exports.hashType = hashType;
exports.encodeField = encodeField;
exports.findTypeDependencies = findTypeDependencies;
const AbiParameters = require("./AbiParameters.js");
const Address = require("./Address.js");
const Bytes = require("./Bytes.js");
const Errors = require("./Errors.js");
const Hash = require("./Hash.js");
const Hex = require("./Hex.js");
const Json = require("./Json.js");
const Solidity = require("./Solidity.js");
function assert(value) {
    const { domain, message, primaryType, types } = value;
    const validateData = (struct, data) => {
        for (const param of struct) {
            const { name, type } = param;
            const value = data[name];
            const integerMatch = type.match(Solidity.integerRegex);
            if (integerMatch &&
                (typeof value === 'number' || typeof value === 'bigint')) {
                const [, base, size_] = integerMatch;
                Hex.fromNumber(value, {
                    signed: base === 'int',
                    size: Number.parseInt(size_ ?? '', 10) / 8,
                });
            }
            if (type === 'address' &&
                typeof value === 'string' &&
                !Address.validate(value))
                throw new Address.InvalidAddressError({
                    address: value,
                    cause: new Address.InvalidInputError(),
                });
            const bytesMatch = type.match(Solidity.bytesRegex);
            if (bytesMatch) {
                const [, size] = bytesMatch;
                if (size && Hex.size(value) !== Number.parseInt(size, 10))
                    throw new BytesSizeMismatchError({
                        expectedSize: Number.parseInt(size, 10),
                        givenSize: Hex.size(value),
                    });
            }
            const struct = types[type];
            if (struct) {
                validateReference(type);
                validateData(struct, value);
            }
        }
    };
    if (types.EIP712Domain && domain) {
        if (typeof domain !== 'object')
            throw new InvalidDomainError({ domain });
        validateData(types.EIP712Domain, domain);
    }
    if (primaryType !== 'EIP712Domain') {
        if (types[primaryType])
            validateData(types[primaryType], message);
        else
            throw new InvalidPrimaryTypeError({ primaryType, types });
    }
}
function domainSeparator(domain) {
    return hashDomain({
        domain,
    });
}
function encode(value) {
    const { domain = {}, message, primaryType } = value;
    const types = {
        EIP712Domain: extractEip712DomainTypes(domain),
        ...value.types,
    };
    assert({
        domain,
        message,
        primaryType,
        types,
    });
    const parts = ['0x19', '0x01'];
    if (domain)
        parts.push(hashDomain({
            domain,
            types,
        }));
    if (primaryType !== 'EIP712Domain')
        parts.push(hashStruct({
            data: message,
            primaryType,
            types,
        }));
    return Hex.concat(...parts);
}
function encodeType(value) {
    const { primaryType, types } = value;
    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 extractEip712DomainTypes(domain) {
    return [
        typeof domain?.name === 'string' && { name: 'name', type: 'string' },
        domain?.version && { name: 'version', type: 'string' },
        (typeof domain?.chainId === 'number' ||
            typeof domain?.chainId === 'bigint') && {
            name: 'chainId',
            type: 'uint256',
        },
        domain?.verifyingContract && {
            name: 'verifyingContract',
            type: 'address',
        },
        domain?.salt && { name: 'salt', type: 'bytes32' },
    ].filter(Boolean);
}
function getSignPayload(value) {
    return Hash.keccak256(encode(value));
}
function hashDomain(value) {
    const { domain, types } = value;
    return hashStruct({
        data: domain,
        primaryType: 'EIP712Domain',
        types: {
            ...types,
            EIP712Domain: types?.EIP712Domain || extractEip712DomainTypes(domain),
        },
    });
}
function hashStruct(value) {
    const { data, primaryType, types } = value;
    const encoded = encodeData({
        data,
        primaryType,
        types,
    });
    return Hash.keccak256(encoded);
}
function serialize(value) {
    const { domain: domain_, message: message_, primaryType, types, } = value;
    const normalizeData = (struct, value) => {
        const data = { ...value };
        for (const param of struct) {
            const { name, type } = param;
            if (type === 'address')
                data[name] = data[name].toLowerCase();
        }
        return data;
    };
    const domain = (() => {
        if (!domain_)
            return {};
        const type = types.EIP712Domain ?? extractEip712DomainTypes(domain_);
        return normalizeData(type, domain_);
    })();
    const message = (() => {
        if (primaryType === 'EIP712Domain')
            return undefined;
        if (!types[primaryType])
            return {};
        return normalizeData(types[primaryType], message_);
    })();
    return Json.stringify({ domain, message, primaryType, types }, (_, value) => {
        if (typeof value === 'bigint')
            return value.toString();
        return value;
    });
}
function validate(value) {
    try {
        assert(value);
        return true;
    }
    catch {
        return false;
    }
}
class BytesSizeMismatchError extends Errors.BaseError {
    constructor({ expectedSize, givenSize, }) {
        super(`Expected bytes${expectedSize}, got bytes${givenSize}.`);
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'TypedData.BytesSizeMismatchError'
        });
    }
}
exports.BytesSizeMismatchError = BytesSizeMismatchError;
class InvalidDomainError extends Errors.BaseError {
    constructor({ domain }) {
        super(`Invalid domain "${Json.stringify(domain)}".`, {
            metaMessages: ['Must be a valid EIP-712 domain.'],
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'TypedData.InvalidDomainError'
        });
    }
}
exports.InvalidDomainError = InvalidDomainError;
class InvalidPrimaryTypeError extends Errors.BaseError {
    constructor({ primaryType, types, }) {
        super(`Invalid primary type \`${primaryType}\` must be one of \`${JSON.stringify(Object.keys(types))}\`.`, {
            metaMessages: ['Check that the primary type is a key in `types`.'],
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'TypedData.InvalidPrimaryTypeError'
        });
    }
}
exports.InvalidPrimaryTypeError = InvalidPrimaryTypeError;
class InvalidStructTypeError extends Errors.BaseError {
    constructor({ type }) {
        super(`Struct type "${type}" is invalid.`, {
            metaMessages: ['Struct type must not be a Solidity type.'],
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'TypedData.InvalidStructTypeError'
        });
    }
}
exports.InvalidStructTypeError = InvalidStructTypeError;
function encodeData(value) {
    const { data, primaryType, types } = value;
    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 AbiParameters.encode(encodedTypes, encodedValues);
}
function hashType(value) {
    const { primaryType, types } = value;
    const encodedHashType = Hex.fromString(encodeType({ primaryType, types }));
    return Hash.keccak256(encodedHashType);
}
function encodeField(properties) {
    let { types, name, type, value } = properties;
    if (types[type] !== undefined)
        return [
            { type: 'bytes32' },
            Hash.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' }, Hash.keccak256(value, { as: 'Hex' })];
    }
    if (type === 'string')
        return [
            { type: 'bytes32' },
            Hash.keccak256(Bytes.fromString(value), { as: 'Hex' }),
        ];
    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' },
            Hash.keccak256(AbiParameters.encode(typeValuePairs.map(([t]) => t), typeValuePairs.map(([, v]) => v))),
        ];
    }
    return [{ type }, value];
}
function findTypeDependencies(value, results = new Set()) {
    const { primaryType: primaryType_, types } = value;
    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 validateReference(type) {
    if (type === 'address' ||
        type === 'bool' ||
        type === 'string' ||
        type.startsWith('bytes') ||
        type.startsWith('uint') ||
        type.startsWith('int'))
        throw new InvalidStructTypeError({ type });
}
//# sourceMappingURL=TypedData.js.map