# `MPP.Methods.Tempo.SponsorBudget`
[🔗](https://github.com/ZenHive/mpp/blob/v0.16.0/lib/mpp/methods/tempo/sponsor_budget.ex#L1)

Atomic aggregate in-flight accounting for Tempo fee sponsorship.

Reservations are scoped by chain and sponsor identity and move through
`:prepared`, `:broadcasting`, and `:pending` phases. The store pins one limits
set while reservations are live and rejects divergent configurations.

The guarantee applies to one physical atomic store. `MPP.Tempo.ConCacheStore`
is suitable for a single BEAM node; every node and endpoint sponsoring the
same wallet must select the same shared backend for a cluster-wide bound.

Reservation expiry is deliberately conservative. A reservation remains live
through its transaction `valid_before` plus a named clock-skew margin, and the
state-derived store TTL covers that same boundary.

# `error_reason`

```elixir
@type error_reason() ::
  {:capacity_exhausted, pos_integer()}
  | :incompatible_state
  | :invalid_request
  | :limits_mismatch
  | :ownership_lost
  | :store_unavailable
```

Fail-closed budget error.

# `handle`

```elixir
@type handle() :: %{key: String.t(), reservation_id: String.t()}
```

Opaque ownership handle returned by `reserve/3`.

# `limits`

```elixir
@type limits() :: %{
  max_in_flight_total_fee: pos_integer(),
  max_in_flight_reservations: pos_integer()
}
```

Pinned aggregate sponsor ceilings.

# `reservation_params`

```elixir
@type reservation_params() :: %{
  chain_id: non_neg_integer(),
  sponsor_id: String.t(),
  fee: pos_integer(),
  valid_before: pos_integer(),
  limits: limits()
}
```

Reservation request accepted by `reserve/3`.

# `release`

```elixir
@spec release(MPP.Tempo.Store.store_ref(), handle(), keyword()) ::
  :ok | {:error, error_reason()}
```

Release an owned reservation after a pre-broadcast failure or terminal receipt.

# `reserve`

```elixir
@spec reserve(MPP.Tempo.Store.store_ref(), reservation_params(), keyword()) ::
  {:ok, handle()} | {:error, error_reason()}
```

Atomically reserve worst-case sponsor capacity before signing.

Pass `:reconcile` with a one-argument receipt fetcher to opt into bounded
pending-receipt reconciliation when the budget is at capacity. `:now` is
available for deterministic tests.

# `sweep`

```elixir
@spec sweep(map(), integer()) :: map()
```

Purely remove expired reservations at `now`.

Chain-valid reservations remain through `valid_before + clock-skew margin`;
the exact conservative boundary is retained and removal starts after it.

# `transition`

```elixir
@spec transition(
  MPP.Tempo.Store.store_ref(),
  handle(),
  :broadcasting | {:pending, String.t()},
  keyword()
) :: :ok | {:error, error_reason()}
```

Move an owned reservation to `:broadcasting` or `{:pending, tx_hash}`.

Only the random handle returned by `reserve/3` can mutate its reservation.

---

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