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

Server-side Sign-In-With-X: challenge issuance, proof verification, and
payer records.

Bundles the pieces a resource server needs to let a wallet that already
paid skip payment: it builds the challenge advertised on 402 responses
(`challenge/1`), verifies `SIGN-IN-WITH-X` proofs (`verify/2`), records
which address paid for which resource after settlement
(`record_payment/4`), and combines verification with that history
(`authenticate/3`). `X402.Plug.PaymentGate` drives it through its `:siwx`
option; use it directly from other frameworks.

    {:ok, siwx} =
      X402.Extensions.SIWX.Server.new(
        domain: "api.example.com",
        uri: "https://api.example.com",
        supported_chains: [%{chain_id: "eip155:8453"}],
        nonce_cache: {X402.Extensions.PaymentIdentifier.ETSCache, MyApp.SIWXNonces}
      )

    # on every 402
    {:ok, challenge} = X402.Extensions.SIWX.Server.challenge(siwx)

    # on a request carrying SIGN-IN-WITH-X
    case X402.Extensions.SIWX.Server.authenticate(siwx, header, resource_url) do
      {:ok, %{address: address}} -> serve(conn)
      {:error, :not_authorized} -> respond_402(conn)
      {:error, code} -> respond_402(conn, error: code)
    end

    # after a successful settlement
    :ok = X402.Extensions.SIWX.Server.record_payment(siwx, payer, resource_url, settle_response)

## Nonces

Each challenge carries a fresh nonce. With a `:nonce_cache` (any
`X402.Extensions.PaymentIdentifier.Cache` adapter) issued nonces are
recorded and consumed on verification, so a proof authenticates exactly
once and only against a challenge this server issued. Without a cache —
the default — a proof is only bound by its `issuedAt` window
(`:max_age_seconds`) and can be replayed within it; configure a cache in
production.

## Storage

Payer records live in an `X402.Extensions.SIWX.Storage` adapter keyed by
`{address, resource}`; `X402.Extensions.SIWX.ETSStorage` (its default
process) is used unless `:storage` is set. Pass `{module, server}` to
address a specific storage process; the module must then expose the
server-taking `get/3`, `put/5`, and `delete/3` like `ETSStorage` does.
EVM addresses are lowercased for storage and lookup; case-sensitive
addresses such as Solana public keys are left unchanged. Verified
identities retain the address spelling from the proof.

# `session`

```elixir
@type session() :: %{
  address: String.t(),
  chain_id: String.t(),
  fields: X402.Extensions.SIWX.fields(),
  access: X402.Extensions.SIWX.Storage.access_record()
}
```

A verified identity together with the payment record that authorizes it.

# `storage`

```elixir
@type storage() :: module() | {module(), term()}
```

A storage module, or a module paired with the server it should address.

# `t`

```elixir
@type t() :: %{
  domain: String.t(),
  uri: String.t(),
  supported_chains: [map()],
  statement: String.t() | nil,
  resources: [String.t()],
  expiration_seconds: pos_integer(),
  max_age_seconds: pos_integer(),
  clock_skew_seconds: non_neg_integer(),
  nonce_cache: X402.Extensions.PaymentIdentifier.Cache.adapter() | nil,
  storage: storage(),
  ttl_ms: pos_integer(),
  verifier: module(),
  ed25519_verifier: module()
}
```

Validated server configuration, built by `new/1`.

# `authenticate`
*since 0.9.0* 

```elixir
@spec authenticate(t(), X402.Extensions.SIWX.decoded() | String.t(), String.t()) ::
  {:ok, session()}
  | {:error, X402.Extensions.SIWX.verify_error() | :not_authorized}
```

Verifies a proof and checks that its address has a payment record for
`resource`.

Returns the identity with the access record under `:access`,
`{:error, :not_authorized}` when the proof is valid but no record exists
(respond with a fresh challenge and payment requirements), or the
verification error otherwise.

# `authorized`
*since 0.9.0* 

```elixir
@spec authorized(t(), String.t(), String.t()) ::
  {:ok, X402.Extensions.SIWX.Storage.access_record()}
  | {:error, :not_authorized}
```

Looks up the payment record for an address and resource.

# `challenge`
*since 0.9.0* 

```elixir
@spec challenge(t()) :: {:ok, map()} | {:error, term()}
```

Builds a fresh challenge and, with a `:nonce_cache`, records its nonce as
issued.

Returns the map to advertise under `PaymentRequired.extensions["sign-in-with-x"]`.
Fails only when the nonce could not be recorded; a challenge whose nonce
is unknown to the server would never verify, so it is better not to
advertise one.

## Examples

    iex> siwx = X402.Extensions.SIWX.Server.new!(
    ...>   domain: "api.example.com",
    ...>   uri: "https://api.example.com",
    ...>   supported_chains: [%{chain_id: "eip155:8453"}]
    ...> )
    iex> {:ok, challenge} = X402.Extensions.SIWX.Server.challenge(siwx)
    iex> {challenge["info"]["domain"], challenge["supportedChains"]}
    {"api.example.com", [%{"chainId" => "eip155:8453", "type" => "eip191"}]}

# `new`
*since 0.9.0* 

```elixir
@spec new(keyword()) :: {:ok, t()} | {:error, NimbleOptions.ValidationError.t()}
```

Validates server options.

## Options

* `:domain` (`t:String.t/0`) - Required. Public host advertised in the challenge and required in proofs.

* `:uri` (`t:String.t/0`) - Required. Public URI advertised in the challenge and required in proofs.

* `:supported_chains` - Required. Accepted chains, in the form `X402.Extensions.SIWX.challenge/1` takes.

* `:statement` - Human-readable statement included in the challenge. The default value is `nil`.

* `:resources` (list of `t:String.t/0`) - Resource URIs included in the challenge. The default value is `[]`.

* `:expiration_seconds` (`t:pos_integer/0`) - Lifetime of an advertised challenge (`expirationTime - issuedAt`). The default value is `300`.

* `:max_age_seconds` (`t:pos_integer/0`) - Maximum age of a proof's `issuedAt`. The default value is `300`.

* `:clock_skew_seconds` (`t:non_neg_integer/0`) - Tolerance for a proof's `issuedAt` slightly in the future. The default value is `60`.

* `:nonce_cache` - `X402.Extensions.PaymentIdentifier.Cache` adapter tuple (or an
  `ETSCache` server name) that records issued nonces and consumes them
  on verification. Strongly recommended: without it proofs can be
  replayed within `:max_age_seconds`. The default value is `nil`.

* `:storage` - `X402.Extensions.SIWX.Storage` module, or `{module, server}` to address
  a specific storage process. The default value is `X402.Extensions.SIWX.ETSStorage`.

* `:ttl_ms` (`t:pos_integer/0`) - How long a recorded payment grants access, in milliseconds. The default value is `86400000`.

* `:verifier` - `X402.Extensions.SIWX.Verifier` for `eip155:*` proofs. The default value is `X402.Extensions.SIWX.Verifier.Default`.

* `:ed25519_verifier` - `X402.Extensions.SIWX.Verifier` for `solana:*` proofs. The default value is `X402.Extensions.SIWX.Verifier.Ed25519`.

## Examples

    iex> {:ok, siwx} = X402.Extensions.SIWX.Server.new(
    ...>   domain: "api.example.com",
    ...>   uri: "https://api.example.com",
    ...>   supported_chains: [%{chain_id: "eip155:8453"}]
    ...> )
    iex> {siwx.storage, siwx.nonce_cache, siwx.ttl_ms}
    {X402.Extensions.SIWX.ETSStorage, nil, 86_400_000}

    iex> {:error, %NimbleOptions.ValidationError{}} =
    ...>   X402.Extensions.SIWX.Server.new(domain: "api.example.com")

# `new!`
*since 0.9.0* 

```elixir
@spec new!(keyword()) :: t()
```

Validates server options, raising `NimbleOptions.ValidationError` on
failure.

# `record_payment`
*since 0.9.0* 

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

Records that `address` paid for `resource`, granting access for `:ttl_ms`.

`payment_proof` is stored alongside the record (the facilitator's settle
response, typically) and returned by `authenticate/3` under
`access.payment_proof`.

# `remember_nonce`
*since 0.9.0* 

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

Records a challenge nonce as issued in the `:nonce_cache`.

A no-op (`:ok`) without a cache. `challenge/1` calls this for you; use it
when advertising a challenge built some other way.

# `revoke`
*since 0.9.0* 

```elixir
@spec revoke(t(), String.t(), String.t()) :: :ok
```

Revokes the payment record for an address and resource.

# `verify`
*since 0.9.0* 

```elixir
@spec verify(t(), X402.Extensions.SIWX.decoded() | String.t()) ::
  {:ok, X402.Extensions.SIWX.identity()}
  | {:error, X402.Extensions.SIWX.verify_error()}
```

Verifies a `SIGN-IN-WITH-X` proof against the server configuration.

Accepts the raw header value or the tuple `X402.Extensions.SIWX.decode_signed/1`
returns; see `X402.Extensions.SIWX.verify/2` for the checks and error
codes. With a `:nonce_cache` the nonce is consumed on success.

---

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