EIPs
EIP-712 — Typed structured data hashing and signing
EIP-712 lets a dapp ask the user to sign structured data — domain-bound, schema-defined, human-readable in the wallet popup. Where personal_sign (EIP-191) signs an opaque byte string, EIP-712 signs a typed object whose hash is computed from declared field types.
import { hash_typed_data, type TypedData } from "@ethernauta/eip/712";
import { AddressSchema, Uint256Schema } from "@ethernauta/core";
import { parse } from "valibot";
const owner = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const spender = parse(AddressSchema, "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC");
const value = parse(Uint256Schema, "0x3e8");
const nonce = parse(Uint256Schema, "0x0");
const deadline = parse(Uint256Schema, "0xffffffff");
const typed: TypedData = {
types: {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
},
primaryType: "Permit",
domain: {
name: "USD Coin",
version: "2",
chainId: 1,
verifyingContract: parse(AddressSchema, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
},
message: { owner, spender, value, nonce, deadline },
};
const digest = hash_typed_data(typed); Surface
| Export | Type | Purpose |
|---|---|---|
TypedDataSchema | Valibot schema | Validate a typed-data payload. |
TypedData, TypedDataDomain, TypedDataField | types | Inferred shapes. |
hash_typed_data | (td: TypedData) => Hash32 | EIP-712 digest. |
eth_signTypedData_v4 | Signable<Bytes> | Wallet-side signing method binding. |
Signing via the wallet
import { eth_signTypedData_v4, type TypedData } from "@ethernauta/eip/712";
import type { Provider } from "@ethernauta/eip/1193";
import { create_provider, encode_chain_id } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
import { AddressSchema } from "@ethernauta/core";
import { parse } from "valibot";
const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
declare const provider: Provider; // see /eips/6963 for discovery
const { signer } = create_provider(provider);
const account = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const typed_data: TypedData = {
domain: { name: "Example", version: "1", chainId: 1 },
types: { Mail: [{ name: "from", type: "address" }] },
primaryType: "Mail",
message: { from: account },
};
const signature = await eth_signTypedData_v4([account, typed_data])(
signer({ chain_id: CHAIN_ID }),
); The signer wraps a 1193 provider; the wallet opens its sign-typed-data view, displays the field-by-field breakdown, and returns the 65-byte signature.
Verification
import { verify_typed_data } from "@ethernauta/crypto";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import type { TypedData } from "@ethernauta/eip/712";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
import { AddressSchema, BytesSchema } from "@ethernauta/core";
import { parse } from "valibot";
const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
{ chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);
const address = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const signature = parse(BytesSchema, "0x");
const typedData: TypedData = {
domain: { name: "Example", version: "1", chainId: 1 },
types: { Mail: [{ name: "from", type: "address" }] },
primaryType: "Mail",
message: { from: address },
};
const ok = await verify_typed_data({
address,
typedData,
signature,
})(reader({ chain_id: CHAIN_ID })); Same EOA → 1271 fallback as verify_message. Add _universal for counterfactual.
Common consumers
- ERC-2612 permit — gasless ERC-20 approvals.
- EIP-4361 SIWE — sign-in with Ethereum.
- ERC-7683 — cross-chain order signing.
- EIP-7702 authorizations — set-code delegations.
Each of those packages composes the hash_typed_data primitive rather than reimplementing the digest.