X402.Signer behaviour (X402 v0.6.0)

Copy Markdown View Source

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.

t()

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

ed25519_signature()

@type ed25519_signature() :: <<_::512>>

A raw 64-byte Ed25519 signature.

signature()

@type signature() :: <<_::520>>

A raw 65-byte r || s || v signature.

t()

@type t() :: struct()

A struct whose module implements X402.Signer.

typed_data()

@type typed_data() :: map()

EIP-712 typed data in its JSON representation.

Contains the "domain", "types", "primaryType", and "message" keys.

Callbacks

address(signer)

@callback address(signer :: t()) :: {:ok, String.t()} | {:error, term()}

Returns the signer's payment address (for EVM, a 0x-prefixed hex address).

sign_ed25519(signer, message)

(optional)
@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.

sign_eip712(signer, digest, typed_data)

(optional)
@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

address(signer)

(since 0.6.0)
@spec address(t()) :: {:ok, String.t()} | {:error, term()}

Returns the address of a signer, dispatching on its struct module.

Examples

iex> X402.Signer.address(:not_a_signer)
{:error, :invalid_signer}

sign_ed25519(signer, message)

(since 0.6.0)
@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}

sign_eip712(signer, digest, typed_data)

(since 0.6.0)
@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}