Raxol.Payments.EIP712 (Raxol Payments v0.2.0)

Copy Markdown View Source

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(domain, types, message)

@spec hash(map(), map(), map()) :: {:ok, binary()} | {:error, term()}

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_signature(arg)

@spec pack_signature({binary(), binary(), 0..28}) :: binary()

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.