# `X402.Extensions.PaymentIdentifier.ETSCache`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/extensions/payment_identifier/ets_cache.ex#L1)

ETS-backed cache adapter for payment identifier idempotency.

Entries expire after `:ttl_ms` (default: 1 hour). Expired entries are removed
by an internal periodic cleanup loop.

> #### Per-node only {: .warning}
>
> The ETS table lives on the local node. In a clustered BEAM deployment each
> node keeps its own independent table, so this adapter cannot prevent the
> same payment proof from being served once per node. See the
> "Clustered deployments" section in
> `X402.Extensions.PaymentIdentifier.Cache` for a shared-store adapter
> sketch.

# `server`

```elixir
@type server() :: GenServer.server()
```

Server identifier accepted by `GenServer.call/3`.

# `child_spec`
*since 0.1.0* 

```elixir
@spec child_spec(keyword()) :: Supervisor.child_spec()
```

Returns a child specification for `X402.Extensions.PaymentIdentifier.ETSCache`.

# `delete`
*since 0.1.0* 

```elixir
@spec delete(server(), X402.Extensions.PaymentIdentifier.Cache.key()) ::
  X402.Extensions.PaymentIdentifier.Cache.write_result()
```

Deletes a payment identifier entry from the cache.

# `get`
*since 0.1.0* 

```elixir
@spec get(server(), X402.Extensions.PaymentIdentifier.Cache.key()) ::
  X402.Extensions.PaymentIdentifier.Cache.get_result()
```

Looks up a payment identifier in the cache.

# `put`
*since 0.1.0* 

```elixir
@spec put(
  server(),
  X402.Extensions.PaymentIdentifier.Cache.key(),
  X402.Extensions.PaymentIdentifier.Cache.value()
) :: X402.Extensions.PaymentIdentifier.Cache.write_result()
```

Stores a payment identifier result in the cache.

# `put_new`
*since 0.4.0* 

```elixir
@spec put_new(
  server(),
  X402.Extensions.PaymentIdentifier.Cache.key(),
  X402.Extensions.PaymentIdentifier.Cache.value()
) :: :ok | {:error, :already_exists | :cache_full | :invalid_cache_value}
```

Atomically inserts a payment identifier only if it does not already exist.

Returns `:ok` if the entry was inserted, or `{:error, :already_exists}` if
a non-expired entry for `payment_id` is already present. This is used to
prevent concurrent requests from double-settling the same payment proof.

When the table is at `:max_size` and purging expired entries does not free a
slot, returns `{:error, :cache_full}` instead of evicting a live claim —
evicting live claims would let cheap junk claims drop legitimate replay
locks. Size `:max_size` for your expected claim TTL × request rate.

# `start_link`
*since 0.1.0* 

```elixir
@spec start_link(keyword()) ::
  GenServer.on_start() | {:error, NimbleOptions.ValidationError.t()}
```

Starts an ETS-backed idempotency cache process.

## Options

* `:name` (`t:term/0`) - Registered name for the ETS cache process. The default value is `X402.Extensions.PaymentIdentifier.ETSCache`.

* `:ttl_ms` (`t:non_neg_integer/0`) - Time-to-live for entries in milliseconds. The default value is `3600000`.

* `:cleanup_interval_ms` (`t:pos_integer/0`) - How often expired entries are cleaned up, in milliseconds. The default value is `60000`.

* `:max_size` (`t:pos_integer/0`) - Maximum number of entries in the cache. The default value is `10000`.

---

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