# `MPP.Tempo.ConCacheStore`
[🔗](https://github.com/ZenHive/mpp/blob/v0.10.0/lib/mpp/tempo/con_cache_store.ex#L1)

Built-in ETS-based dedup store using ConCache (TTL-enabled ETS wrapper by Saša Jurić).

Backed by the `con_cache` dependency. This store is started automatically by
the `:mpp` application as the **default** dedup store (replay protection on by
default — issue #7), so most single-node deployments need no setup.

## Setup (custom instances)

To run an additional/renamed instance, add it to your supervision tree:

    children = [
      MPP.Tempo.ConCacheStore.child_spec(name: :my_dedup, ttl: to_timeout(minute: 10))
    ]

Then pass in method_config:

    plug MPP.Plug,
      method: MPP.Methods.Tempo,
      method_config: %{
        "rpc_url" => "https://rpc.moderato.tempo.xyz",
        "store" => MPP.Tempo.ConCacheStore
      }

If you override the cache name in your supervision tree, pass the same name in
`method_config`:

    children = [
      MPP.Tempo.ConCacheStore.child_spec(name: :my_custom_dedup, ttl: to_timeout(minute: 10))
    ]

    plug MPP.Plug,
      method: MPP.Methods.Tempo,
      method_config: %{
        "rpc_url" => "https://rpc.moderato.tempo.xyz",
        "store" => {MPP.Tempo.ConCacheStore, name: :my_custom_dedup}
      }

## TTL and Challenge Expiry

The store's TTL **must be ≥ your challenge `expires_in`** to prevent replay.
If the cache evicts a tx hash before the challenge expires, the same tx can
be replayed. A good default is 2× the challenge expiry.

Example: if your Plug uses `expires_in: 300` (5 min), set the store TTL to
at least `to_timeout(minute: 10)`.

## Options

  * `:ttl` — time-to-live for entries in milliseconds. Default: 10 minutes (2× the
    default Plug `expires_in` of 300 seconds).
  * `:name` — registered name for the ConCache process. Default: `:mpp_tempo_dedup`.
    Override to avoid child ID collisions if your app already supervises other
    ConCache instances.
  * `:ttl_check_interval` — how often to sweep expired entries. Default: 30 seconds.

# `check_and_mark`

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

Atomically reserves a dedup key in the default ConCache.

# `check_and_mark`

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

Atomically reserves a dedup key, optionally using a non-default ConCache name from opts.

Same as `check_and_mark/2` when `opts` is `[]`.

# `child_spec`

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

Returns a child spec for the ConCache process.

Start under your application's supervision tree:

    children = [
      MPP.Tempo.ConCacheStore.child_spec(ttl: :timer.minutes(10))
    ]

The child spec `id` is `{MPP.Tempo.ConCacheStore, name}` to avoid collisions
with other ConCache instances in the same supervisor.

# `get`

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

Looks up a dedup key in the default ConCache.

# `get`

```elixir
@spec get(
  String.t(),
  keyword()
) :: {:ok, term()} | :not_found
```

Looks up a dedup key, optionally using a non-default ConCache name from opts.

Same as `get/1` when `opts` is `[]`.

# `put`

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

Stores a dedup key in the default ConCache.

# `put`

```elixir
@spec put(String.t(), term(), keyword()) :: :ok
```

Stores a dedup key, optionally using a non-default ConCache name from opts.

Same as `put/2` when `opts` is `[]`.

---

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