# `X402.Client.SIWX`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/client/siwx.ex#L1)

Client side of the `sign-in-with-x` extension: answering a challenge.

When a server advertises a Sign-In-With-X challenge under
`PaymentRequired.extensions["sign-in-with-x"]`, a client that has already
paid for the resource can prove control of its wallet instead of paying
again. `authenticate/4` picks the chain to sign for, refuses challenges
that are not bound to the resource's origin, signs the CAIP-122 message
with `X402.Extensions.SIWX.sign/3`, and returns the `SIGN-IN-WITH-X`
header value. `X402.Client.Finch` and `X402.MCP.Client` drive the full
flow through their `:siwx` option; use this module directly with any
other transport.

## Options

The `:siwx` option of the drivers is a keyword list:

* `:chain_id` - Required. CAIP-2 chain to sign for, or `:auto` to pick the first advertised
  `supportedChains` entry the signer can sign (EVM signers map to
  `eip155:*`, Solana signers to `solana:*`).

* `:address` - Address placed in the proof; the signer's address when omitted. The default value is `nil`.

* `:signature_scheme` - Optional `signatureScheme` hint copied into the proof. The default value is `nil`.

* `:domain` - Expected challenge `domain`. Required for transports without a
  resource URL (MCP); for HTTP it defaults to the resource URL's host. The default value is `nil`.

* `:uri` - Exact expected challenge URI, including path and query. Required for
  automatic MCP proofs, alongside `:domain`. Configure it independently
  of the challenge and bind the transport to that trusted server. The default value is `nil`.

# `opts`

```elixir
@type opts() :: [
  chain_id: String.t() | :auto,
  address: String.t() | nil,
  signature_scheme: String.t() | nil,
  domain: String.t() | nil,
  uri: String.t() | nil
]
```

Validated `:siwx` driver options.

# `proof`

```elixir
@type proof() :: %{header: String.t(), chain_id: String.t(), address: String.t()}
```

A signed challenge, ready to send.

# `reason`

```elixir
@type reason() ::
  :invalid_challenge
  | :domain_mismatch
  | :uri_mismatch
  | :unsupported_chain
  | :payment_cancelled
  | X402.Extensions.SIWX.sign_error()
  | X402.Extensions.SIWX.signed_encode_error()
```

Errors returned by `authenticate/4`, wrapped as `{:siwx, reason}`.

# `authenticate`
*since 0.9.0* 

```elixir
@spec authenticate(map(), X402.Signer.t(), keyword(), keyword()) ::
  {:ok, proof()} | :none | {:error, {:siwx, reason()}}
```

Signs the challenge advertised in a `PaymentRequired` map.

Returns `:none` when the server advertised no `sign-in-with-x`
challenge, `{:ok, proof}` with the `SIGN-IN-WITH-X` header value once
signed, or `{:error, {:siwx, reason}}`.

Before signing, the challenge is checked against the resource it was
issued for, as the spec requires: its `info.domain` must equal (ignoring case)
`siwx_opts[:domain]` when given, otherwise the host (optionally with
port) of `:resource_url`; and, when `:resource_url` is given, the origin
(scheme, host, port) of `info.uri` must equal the URL's origin. Failures
are reported as `:domain_mismatch` / `:uri_mismatch`. With neither a
trusted domain nor a resource URL, signing fails with `:domain_mismatch`.
Never derive the expected domain from the untrusted challenge.

`siwx_opts[:uri]` additionally requires an exact URI match, including path
and query, and a domain consistent with its host/port. The pin must be an
absolute HTTP(S) URL without userinfo or fragment. It is mandatory for
`transport: :mcp`; a domain alone is insufficient for automatic MCP proofs.

With `chain_id: :auto` the first entry of `supportedChains` whose family
the signer can sign is used (`eip155:*` needs `c:X402.Signer.sign_message/2`,
`solana:*` needs `c:X402.Signer.sign_ed25519/2`); no match yields
`:unsupported_chain`. Signing errors from `X402.Extensions.SIWX.sign/3`
are returned as they are.

Emits `[:x402, :client, :siwx]` with `status: :error`, `:reason`,
`:transport`, and `:chain_id` on failure; the drivers emit the `:ok` event
once they know the outcome. The chain is the selected signing chain, or
`nil` if origin validation or automatic selection failed before selecting
one. It is never `:auto`.

## Options

* `:resource_url` — the URL that returned the 402, for the origin check.
* `:transport` — atom identifying the caller in telemetry (`:http` or
  `:mcp` for the built-in drivers); defaults to `nil` for direct calls.
* `:before_sign` — optional zero-arity consent callback, invoked after
  origin/URI checks and chain selection but before wallet signing. Only
  `:ok` allows signing; other returns yield `{:siwx, :payment_cancelled}`.
  It is not called when no challenge is present.

## Examples

    iex> {:ok, signer} = X402.Signer.SolanaKey.new(:binary.copy(<<1>>, 32))
    iex> challenge = X402.Extensions.SIWX.challenge(
    ...>   domain: "api.example.com",
    ...>   uri: "https://api.example.com",
    ...>   supported_chains: [%{chain_id: "eip155:8453"}, %{chain_id: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"}]
    ...> )
    iex> payment_required = %{"x402Version" => 2, "accepts" => [], "extensions" => %{"sign-in-with-x" => challenge}}
    iex> {:ok, proof} = X402.Client.SIWX.authenticate(payment_required, signer, [chain_id: :auto],
    ...>   resource_url: "https://api.example.com/premium")
    iex> {proof.chain_id, proof.address}
    {"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9"}
    iex> {:ok, {:spec, fields}} = X402.Extensions.SIWX.decode_signed(proof.header)
    iex> fields["nonce"] == challenge["info"]["nonce"]
    true

    iex> X402.Client.SIWX.authenticate(%{"x402Version" => 2, "accepts" => []}, :signer, chain_id: :auto)
    :none

# `fetch_challenge`
*since 0.9.0* 

```elixir
@spec fetch_challenge(map()) :: {:ok, map()} | :error
```

Fetches the advertised challenge from a `PaymentRequired` map.

## Examples

    iex> challenge = %{"info" => %{"domain" => "api.example.com"}, "supportedChains" => []}
    iex> payment_required = %{"x402Version" => 2, "accepts" => [], "extensions" => %{"sign-in-with-x" => challenge}}
    iex> X402.Client.SIWX.fetch_challenge(payment_required)
    {:ok, challenge}

    iex> X402.Client.SIWX.fetch_challenge(%{"x402Version" => 2, "accepts" => []})
    :error

# `validate_opts`
*since 0.9.0* 

```elixir
@spec validate_opts(term()) :: {:ok, opts() | nil} | {:error, String.t()}
```

Validates a driver's `:siwx` option.

`nil` and `false` disable automatic Sign-In-With-X; a keyword list is
validated against the options above. Designed for `NimbleOptions` custom
validation.

## Examples

    iex> X402.Client.SIWX.validate_opts(false)
    {:ok, nil}

    iex> {:ok, opts} = X402.Client.SIWX.validate_opts(chain_id: :auto)
    iex> Enum.sort(opts)
    [address: nil, chain_id: :auto, domain: nil, signature_scheme: nil, uri: nil]

    iex> {:error, message} = X402.Client.SIWX.validate_opts(address: "0xabc")
    iex> message =~ ":chain_id"
    true

---

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