# `DpExchange.Core.Types.StakingTransaction`
[🔗](https://github.com/DistortionPoint/dp-exchange-core/blob/v0.3.120/lib/dp_exchange/core/types/staking_transaction.ex#L1)

One movement in or out of a staked position — and, for a redemption, how far it has got.

## Unstaking is a process, not an event

This is the field this type exists for. A redemption does not complete when it is
accepted: the asset unbonds on the chain's schedule, which can be days, and the venue pays
it out in parts. Gemini reports `amount`, `amountPaidSoFar` and `amountRemaining` on a
withdrawal precisely because the three differ for most of the operation's life.

**The unbonding constraint is observable, not documentary.** A package that recorded only
the requested `:amount` would report a redemption of ten as if ten had arrived, and the
caller would find out by spending money it does not have yet. So:

  * `:amount` — what was requested
  * `:amount_paid_so_far` — what has actually arrived
  * `:amount_remaining` — what is still pending

`nil` on the last two means the venue does not report progress, **not** that the operation
is complete. `settled?/1` says so explicitly rather than leaving a caller to infer it.

## `:type` is normalised, `:venue_type` is kept

Venues name these differently — Gemini's `transactionType` is one of `Deposit`, `Redeem`,
`Interest` and others. `:type` is the normalised atom a caller branches on; `:venue_type`
is the venue's own string, kept because a normalisation that loses the original cannot be
audited when it turns out to be wrong.

# `t`

```elixir
@type t() :: %DpExchange.Core.Types.StakingTransaction{
  amount: Decimal.t(),
  amount_paid_so_far: Decimal.t() | nil,
  amount_remaining: Decimal.t() | nil,
  asset: String.t(),
  id: String.t(),
  provider: atom(),
  provider_id: String.t() | nil,
  type: transaction_type(),
  venue_time: DateTime.t() | nil,
  venue_type: String.t() | nil
}
```

# `transaction_type`

```elixir
@type transaction_type() :: :stake | :unstake | :reward | :other
```

# `new`

```elixir
@spec new(keyword() | map()) :: t()
```

Builds a `t:t/0`, failing closed if a required field is absent or `nil`.

`@enforce_keys` guards presence, not `nil` — see `DpExchange.Core.Types.Validate`.

Also raises on a `:type` outside `:stake | :unstake | :reward | :other` (the venue's own word
belongs in `:venue_type`), on an `:amount` of zero or less, and on a negative
`:amount_paid_so_far` or `:amount_remaining`.

# `settled?`

```elixir
@spec settled?(t()) :: boolean() | nil
```

Whether a redemption has fully paid out.

`nil` when the venue reports no progress — **unknown, not complete**. A caller must not
read a missing `:amount_remaining` as "finished"; that is the substitution this whole
contract is written against.

---

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