# wallet_state

**OTP app:** `:wallet_state`
**Module namespace:** `WalletState.*`
**Owner:** Platform Team

## Responsibilities
Non-financial distributed state management for coordination and deduplication.

Per **ADR 0001**: financial truth must NEVER be stored in this app.
Per **ADR 0004**: idempotency keys, distributed locks, and workflow snapshots live here.

## Public API

### `WalletState.IdempotencyKey`
Idempotency key registration and payload conflict detection (ADR 0005).

```elixir
# Hash a payload for conflict detection
hash = WalletState.IdempotencyKey.hash_payload(%{amount: 1000, to: "acc_xyz"})
```

Registration lifecycle:
1. Command arrives with `Idempotency-Key` header.
2. Register key - returns `:new`, `:replay` (with cached result), or `:conflict`.
3. If `:new`, process the command and store the result.
4. If `:replay`, return the cached result without reprocessing.

## Critical Invariant (ADR 0001)
This app stores **coordination state** only:
- Idempotency key -> result cache
- Distributed locks (per-resource mutex)
- Workflow/saga snapshots (non-financial progress tracking)

Financial amounts, ledger entries, and account balances are NEVER stored here.

## Idempotency Key Conflict Rules (ADR 0005)
| Scenario | Response |
|---|---|
| Same key + same payload | Return cached result (replayed=true) |
| Same key + different payload | `{:error, :conflict}` -> 409 IDEMPOTENCY_KEY_PAYLOAD_CONFLICT |
| Key in-flight | `{:error, :in_flight}` -> 409 STATE_TRANSITION_CONFLICT (retryable) |
| New key | `{:ok, :new}` -> process command |

## Allowed Dependencies
- `wallet_shared_kernel` - typed IDs and correlation helpers
