/*
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 '@theqrl/web3-types';
import { isNullish } from './object.js';
const isPlainObject = (value: unknown): value is Record =>
!isNullish(value) && typeof value === 'object' && !Array.isArray(value);
/**
* Checks that a value satisfies the structural contract required to encode EIP-712 typed data.
*
* The contract mirrors `getEncodedEip712Data` (`@theqrl/web3-qrl-abi`) — the encoder this library
* and the QRL wallet share — rather than any node-side implementation. Typed-data signing is
* answered by a wallet provider, which encodes with that same function, so these are precisely
* the preconditions that must hold before a request is worth sending.
*
* Each check below corresponds to a value the encoder dereferences unconditionally; without them
* a malformed request fails deep inside the encoder with an opaque `TypeError` (for example
* "Cannot read properties of undefined (reading 'reduce')") instead of a typed validation error.
*
* Field-level data (that every member declared in a type is present in `domain`/`message`) is
* deliberately NOT checked here: the encoder already reports that precisely
* ("Cannot encode data: missing data for 'x'"), and duplicating it would mean re-implementing
* type resolution.
*/
export const isEip712TypedData = (value: unknown): boolean => {
if (!isPlainObject(value)) return false;
const { types, primaryType, domain, message } = value as Partial;
// `types` is indexed for every type encountered, and `types.EIP712Domain` is read
// unconditionally by `getMessage` to build the domain separator.
if (!isPlainObject(types)) return false;
if (!Array.isArray((types as Record).EIP712Domain)) return false;
// Every declared type is a list of `{ name, type }` members; `encodeType` reads both fields
// of every member of every type it depends on.
for (const members of Object.values(types as Record)) {
if (!Array.isArray(members)) return false;
for (const member of members) {
if (!isPlainObject(member)) return false;
if (typeof member.name !== 'string' || member.name.length === 0) return false;
if (typeof member.type !== 'string' || member.type.length === 0) return false;
}
}
// `primaryType` must name a declared type: `encodeData` does `types[primaryType].reduce(...)`.
if (typeof primaryType !== 'string' || primaryType.length === 0) return false;
if (!Array.isArray((types as Record)[primaryType])) return false;
// Both are indexed per declared member by `encodeData`.
if (!isPlainObject(domain)) return false;
if (!isPlainObject(message)) return false;
return true;
};