Behaviour for client-side payment signers.
A signer produces the cryptographic signatures a payer client needs to authorize x402 payments. Implementations are structs whose module implements this behaviour; the library dispatches on the struct's module, so custom signers (KMS-backed, hardware wallets, remote signing services) can be supplied anywhere the library takes a signer.
Callback design
The reference SDKs expose two shapes: the TypeScript ClientEvmSigner
signs full EIP-712 typed data (signTypedData), because wallet-backed and
remote signers refuse raw digests, while the Go client signer signs the
precomputed EIP-712 digest from a local private key. This behaviour
supports both: sign_eip712/3 receives the precomputed 32-byte digest
(sufficient for local keys and raw-signing KMS APIs) and the full typed
data map (domain/types/primaryType/message, mirroring the EIP-712 JSON
representation) for implementations that must reconstruct the message.
Implementations return the raw 65-byte r || s || v signature. v may be
0/1 or 27/28; the dispatcher normalizes it to 27/28 as expected
by EIP-3009 contracts.
Chain families
EVM payments sign EIP-712 typed data through sign_eip712/3. Solana
(SVM) payments instead sign raw transaction message bytes with Ed25519
through the optional sign_ed25519/2 callback — a signer implements
the callbacks for the chain families it supports, and schemes report a
signer without the needed callback as {:error, :unsupported_signer}.
Built-in implementations
X402.Signer.LocalKey signs with a raw secp256k1 private key and requires
the optional ex_secp256k1 and ex_keccak dependencies.
X402.Signer.SolanaKey signs with an Ed25519 key through OTP's :crypto
(no extra dependencies).
Summary
Types
A raw 64-byte Ed25519 signature.
A raw 65-byte r || s || v signature.
A struct whose module implements X402.Signer.
EIP-712 typed data in its JSON representation.
Callbacks
Returns the signer's payment address (for EVM, a 0x-prefixed hex address).
Signs a message with Ed25519 and returns the raw 64-byte signature.
Signs an EIP-712 digest and returns the 65-byte r || s || v signature.
Functions
Returns the address of a signer, dispatching on its struct module.
Signs a message with Ed25519, dispatching on the signer's struct module.
Signs an EIP-712 digest with a signer, dispatching on its struct module.
Types
@type ed25519_signature() :: <<_::512>>
A raw 64-byte Ed25519 signature.
@type signature() :: <<_::520>>
A raw 65-byte r || s || v signature.
@type t() :: struct()
A struct whose module implements X402.Signer.
@type typed_data() :: map()
EIP-712 typed data in its JSON representation.
Contains the "domain", "types", "primaryType", and "message" keys.
Callbacks
Returns the signer's payment address (for EVM, a 0x-prefixed hex address).
@callback sign_ed25519(signer :: t(), message :: binary()) :: {:ok, ed25519_signature()} | {:error, term()}
Signs a message with Ed25519 and returns the raw 64-byte signature.
Used by SVM (Solana) schemes, where message is the serialized
transaction message bytes (including the version prefix). Optional —
implement it for signers that support Solana payments.
@callback sign_eip712(signer :: t(), digest :: binary(), typed_data :: typed_data()) :: {:ok, signature()} | {:error, term()}
Signs an EIP-712 digest and returns the 65-byte r || s || v signature.
digest is the precomputed 32-byte EIP-712 digest
(keccak256(0x19 0x01 || domainSeparator || structHash)). typed_data is
the full EIP-712 typed data for implementations that cannot sign raw
digests. Optional — implement it for signers that support EVM payments.
Functions
Returns the address of a signer, dispatching on its struct module.
Examples
iex> X402.Signer.address(:not_a_signer)
{:error, :invalid_signer}
@spec sign_ed25519(t(), binary()) :: {:ok, ed25519_signature()} | {:error, term()}
Signs a message with Ed25519, dispatching on the signer's struct module.
Returns {:error, :unsupported_signer} when the signer module does not
implement the optional sign_ed25519/2 callback, and
{:error, :invalid_signature_format} for signatures that are not
64 bytes.
Examples
iex> X402.Signer.sign_ed25519(:not_a_signer, "message")
{:error, :invalid_signer}
iex> {:ok, evm_signer} = X402.Signer.LocalKey.new("0x" <> String.duplicate("11", 32))
iex> X402.Signer.sign_ed25519(evm_signer, "message")
{:error, :unsupported_signer}
@spec sign_eip712(t(), binary(), typed_data()) :: {:ok, signature()} | {:error, term()}
Signs an EIP-712 digest with a signer, dispatching on its struct module.
Normalizes the recovery byte to 27/28 and rejects signatures that are
not 65 bytes with {:error, :invalid_signature_format}.
Examples
iex> X402.Signer.sign_eip712(:not_a_signer, <<0::256>>, %{})
{:error, :invalid_signer}