EIP-712 typed-structured-data hashing.
Produces the 32-byte digest defined by EIP-712:
keccak256(0x19 || 0x01 || domainSeparator || hashStruct(message))The digest can then be passed to ExSecp256k1.sign/2 (or any other
secp256k1 signer) to produce the EIP-712 signature.
Used by wallet implementations (Raxol.Payments.Wallets.Env,
Raxol.Payments.Wallets.Op) and by ACP memo signing in raxol_acp.
Example
domain = %{
name: "USD Coin",
version: "2",
chainId: 8453,
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
}
types = %{
"TransferWithAuthorization" => [
{"from", "address"},
{"to", "address"},
{"value", "uint256"},
{"validAfter", "uint256"},
{"validBefore", "uint256"},
{"nonce", "bytes32"}
]
}
message = %{
from: "0xabc...",
to: "0xdef...",
value: 1_000_000,
validAfter: 0,
validBefore: 9_999_999_999,
nonce: "0x" <> String.duplicate("00", 32)
}
{:ok, digest} = Raxol.Payments.EIP712.hash(domain, types, message)
Summary
Functions
Hash an EIP-712 typed message into a 32-byte digest.
Pack an ExSecp256k1.sign/2 result into Ethereum's canonical 65-byte
signature r || s || v.
Functions
Hash an EIP-712 typed message into a 32-byte digest.
domain— EIP-712 domain separator fields. Recognized keys::name,:version,:chainId(or:chain_id),:verifyingContract(or:verifying_contract), and:salt(bytes32). Only the keys present in the map are included in the domain type, matching reference implementations.types— map of struct type names to field definitions. The first key in the map is treated as the primary type.message— map of field values for the primary type.
Returns {:ok, digest} on success or {:error, reason} if a value
cannot be encoded under its declared type.
Pack an ExSecp256k1.sign/2 result into Ethereum's canonical 65-byte
signature r || s || v.
ExSecp256k1.sign/2 returns the recovery id as 0 or 1. Ethereum's
ecrecover -- and the ERC-3009 / Permit2 on-chain authorizations the Xochi
origin pull and x402 settlement are verified against -- require the canonical
27 / 28, returning address(0) for v < 27. Normalizing here keeps every
wallet's output verifiable both on-chain and off-chain (viem accepts either
form). Idempotent: a signature already carrying 27 / 28 is unchanged.