# `X402.Signer`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/signer.ex#L1)

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: `c: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 `c:sign_eip712/3`. Solana
(SVM) payments instead sign raw transaction message bytes with Ed25519
through the optional `c: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).

Gas-paying EVM execution uses the separate optional `c:sign_transaction/3`
callback. It receives a type-2 transaction, not EIP-712 typed data. A
typed-data-only wallet is never asked to sign a raw transaction digest.

# `ed25519_signature`

```elixir
@type ed25519_signature() :: &lt;&lt;_::512&gt;&gt;
```

A raw 64-byte Ed25519 signature.

# `signature`

```elixir
@type signature() :: &lt;&lt;_::520&gt;&gt;
```

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

# `t`

```elixir
@type t() :: struct()
```

A struct whose module implements `X402.Signer`.

# `typed_data`

```elixir
@type typed_data() :: map()
```

EIP-712 typed data in its JSON representation.

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

# `address`

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

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

# `sign_ed25519`
*optional* 

```elixir
@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`
*optional* 

```elixir
@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.

# `sign_message`
*optional* 

```elixir
@callback sign_message(signer :: t(), message :: binary()) ::
  {:ok, String.t()} | {:error, term()}
```

Signs an arbitrary message with EIP-191 `personal_sign` and returns the
`0x`-prefixed hex encoding of the 65-byte `r || s || v` signature.

The signer hashes `keccak256("\x19Ethereum Signed Message:\n" <>
byte_size(message) <> message)` and signs the digest with its secp256k1
key. Used by the Sign-In-With-X extension (`X402.Extensions.SIWX.sign/3`)
for `eip155:*` chains. Optional — implement it for EVM signers that
support wallet authentication.

# `sign_transaction`
*optional* 

```elixir
@callback sign_transaction(t(), binary(), X402.Transaction.t()) ::
  {:ok, signature()} | {:error, term()}
```

Signs an EIP-1559 transaction digest without broadcasting.

Receives the locally computed digest and complete `X402.Transaction` so
hardware or remote implementations can reconstruct and review the exact
intent. Returns a raw 65-byte signature, not a serialized transaction.
Implementations must not broadcast: durable execution records the signed
bytes before granting permission to send.

# `address`
*since 0.6.0* 

```elixir
@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`
*since 0.6.0* 

```elixir
@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 `c: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`
*since 0.6.0* 

```elixir
@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}

# `sign_message`
*since 0.9.0* 

```elixir
@spec sign_message(t(), binary()) :: {:ok, String.t()} | {:error, term()}
```

Signs a message with EIP-191 `personal_sign`, dispatching on the signer's
struct module.

Returns the lowercase `0x`-prefixed hex encoding of the 65-byte
`r || s || v` signature with `v` normalized to `27`/`28`. Returns
`{:error, :unsupported_signer}` when the signer module does not implement
the optional `c:sign_message/2` callback and
`{:error, :invalid_signature_format}` when the callback returns anything
other than a 65-byte hex signature.

## Examples

    iex> X402.Signer.sign_message(:not_a_signer, "message")
    {:error, :invalid_signer}

    iex> {:ok, solana_signer} = X402.Signer.SolanaKey.new(:binary.copy(<<1>>, 32))
    iex> X402.Signer.sign_message(solana_signer, "message")
    {:error, :unsupported_signer}

# `sign_transaction`
*since 0.9.0* 

```elixir
@spec sign_transaction(t(), X402.Transaction.t()) ::
  {:ok, signature()} | {:error, term()}
```

Signs a type-2 transaction through its dedicated callback, without sending.

Computes the digest from the complete transaction and normalizes the
returned recovery byte. There is no fallback to `sign_eip712/3` or
`personal_sign`. Execution code must also verify the recovered gas-account
address before recording or broadcasting the encoded transaction.

## Examples

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
