# Prop account model

A prop account is an **evaluation contract over real signed trades**. The
platform never holds the trader's trading funds — traders sign their own trades
— while collateral and payouts settle on your firm's on-chain treasury.

Each firm is one prop tenant with its own `PropInstance` contract, deployed by
a shared factory. Your multisig owns the instance and its treasury. The
platform's risk engine acts as the contract's **observer**: it marks
pass, breach, and expiry, and sets payout caps. The trader deposits, quits, and
requests payouts directly from their own wallet.

Two program kinds share one engine: `challenge` (tier ladder, profit or points
target) and `flash` (collateral-to-funding ratio, time as currency).

## Lifecycle

Two paths reach `active`, and they don't overlap. An account the trader buys
goes `pending_payment` → `active` when the deposit lands on-chain. A child
account created by a tier-ladder or next-step promotion starts in
`pending_agreement` and goes → `active` when the trader agrees. From there:
`active` → `passed` → `paid_out`, with `breached`, `expired`, and `cancelled`
as terminal branches.

| Phase | What happens |
| --- | --- |
| **Catalog → buy** | The trader browses active templates and creates an account in `pending_payment` (`POST /api/v1/prop-accounts`). |
| **Deposit** | The trader's wallet calls the instance's deposit function. The entry fee is skimmed, the remainder becomes collateral, and the account activates on-chain. The platform composes the calldata via `POST /api/prop/deposit-calldata`; the wallet broadcasts. |
| **Deposit observed → active** | The claim watcher sees the on-chain `ChallengeDeposited` event and flips the account `pending_payment` → `active`. **No API call is involved** — this is the only path that activates a bought account. |
| **Evaluation** | The engine re-evaluates every rule on each trade, position, and funding change, plus a UTC-midnight roll. |
| **Breach** | Any rule returning breach is terminal. The on-chain forfeiture split applies and positions are flattened. |
| **Pass** | Pass conditions met. **No money moves** — the observer records the on-chain payout ceiling. |
| **Promotion → agree** | If the passed account's template carries a tier ladder or a next-step template, the engine creates a **child** account in `pending_agreement`. The trader accepts it with `POST /api/v1/prop-accounts/{id}/agree`, which moves that child to `active`. Called against any other status, `/agree` returns `409 invalid_state`. |
| **Funded** | A passed account keeps trading. The trader requests payouts; your multisig approves. |
| **Quit** | The trader voluntarily closes an active account; the quit forfeiture split applies and the remainder is refunded. Terminal. |
| **Expiry** | The observer marks expiry; the expiry split applies and collateral remainder is refunded. Terminal. |

## Hard enforcement is live and default-on

This is the core enforcement primitive, and it is not advisory.

On **every order**, the platform evaluates a hypothetical fill against each
active prop account on the trading account the order is placed from, and
rejects a breaching intent with `prop_breach_would_occur` (HTTP `409`) —
**before the venue ever sees the order**.

`PATCH /api/v1/firms/{id}/hard-enforcement` writes a per-firm override, but
program rules are enforced **by design**: with the platform's default settings
the override is not consulted at all. Do not treat it as a way to run a firm in
observe-only mode.

The practical consequence: a trader cannot breach by racing the evaluator. The
order simply doesn't reach the market.

## Rule catalog

Rules are **snapshot-frozen at account creation**, so editing a template never
retroactively passes or fails an in-flight account.

Rule types include `daily_loss_cap_usd` · `max_drawdown_usd` ·
`min_trading_days` · `profit_target_usd` · `max_concentration_pct` ·
`consistency_pct` · `allowed_venues` · `forbidden_symbols` · `points_target` ·
`inactivity_timer_s` · `collateral_drawdown_pct`. The registry is larger than
this list.

Each rule evaluates to
`{ type, status, detail, metric?, threshold? }`, where `status` is one of
`ok`, `breach`, `pass`, `pending`, `na`, or `missing_evaluator`.

`breach` is terminal. `pass` satisfies a pass condition. `pending` means a goal
is defined but not reached yet (`min_trading_days` at 3 of 5) — it does not
breach the account, but it is **not inert**: a single `pending` rule holds the
pass back, and it blocks a payout the same way a `breach` does. `na` (the rule
doesn't apply yet) and `missing_evaluator` (a rule type with no registered
evaluator) never block anything.

Render `pending` as in-progress, not as a failure — most in-flight challenges
carry at least one. `GET /api/v1/prop-accounts/{id}` returns these objects
verbatim as `results`.

A client consuming the catalog also gets parsed headline thresholds
(`maxDdUsd`, `dailyLossUsd`, `profitTargetUsd`) as first-class fields, so you
can render a challenge card without interpreting the rule objects.

Manage them with `prop:manage`: `/api/v1/prop-templates/*` for templates,
`/api/v1/risk-profiles/*` for reusable rule sets, `/api/v1/trading-groups/*`
for fee, leverage, and venue scoping.

## The funded-account payout model

A passed account is a **funded account that keeps trading**. Four properties
define the mechanic:

1. **The ceiling is profit × split.** At pass, the observer records a per-account
   payout ceiling equal to the trader's split-adjusted profit. A trader can
   never withdraw more than they earned.
2. **Two independent caps.** Every payout is gated by *both* the profit
   entitlement *and* treasury solvency.
3. **Multiple withdrawals, no close on payout.** Approving accumulates the
   payout total and leaves the account passed — the funded trader keeps
   trading. It only leaves that state via breach, quit, or expiry.
4. **Lazy ceiling raise.** When a funded trader requests a payout, their current
   entitlement is recomputed and the on-chain ceiling raised if they've earned
   more since passing. Raise-only.

Settlement is two-step: the trader's request creates a pending on-chain
request, and your multisig settles it. `POST /api/prop/payout-calldata`
pre-checks the live ceiling, account, and treasury, returning
`exceeds_profit_entitlement` (400) or `treasury_underfunded` (409) — though the
contract enforces both regardless of what the API says.

## Blocking vs suspending

Two operator controls that read alike and are not alike. Both need the
`prop:manage` scope and are hard-scoped to your own tenant.

| Call | What it does |
| --- | --- |
| `PATCH /api/v1/accounts/{id}/suspension` | Sets or clears the suspension. **Positions are untouched.** New opens are refused; the trader keeps what they hold. |
| `POST /api/v1/accounts/{id}/block` | Suspends **and flattens every open position**, then writes an `account.block` audit row. |
| `POST /api/v1/accounts/{id}/unblock` | Clears the suspension. **It does not restore anything.** |

:::danger[A block is not reversible]
`block` closes positions at the market. `unblock` only lifts the suspension —
the positions it flattened stay flattened, and the realised PnL stands. If you
want a reversible hold, use `PATCH /suspension`. Reach for `block` only when
you intend to take the trader out of the market immediately.
:::

`block` accepts an optional `reason`, which lands in the audit row alongside
how many positions were flattened and any that failed to close. Read those
errors — a flatten that partially failed leaves the account suspended with
positions still open, which is the one state neither name describes.

Both resolve the account by UUID **or** public account number.

## Diagnosing an evaluation

`GET /api/v1/analytics/prop-diagnosis/{propAccountId}` answers "where does this
challenge stand?" in one call, so you can build a trader-facing status screen
without re-deriving the rules yourself.

```bash
curl -s https://api.troncharts.xyz/api/v1/analytics/prop-diagnosis/$PROP_ACCOUNT_ID \
  -H "authorization: Bearer $TOKEN" \
  -H "x-tenant-slug: $TC_TENANT_SLUG"
```

Returns the account's `status`, its template (`templateId`, `templateName`,
`programKind`), the lifecycle timestamps (`startedAt`, `expiresAt`, `closedAt`,
`closedReason`), `payoutOwedUsd`, up to 50 `recentTrades` scoped to this
account's own fills within the challenge window, and a `recommendation`.

`recommendation` is one of `continue`, `pause`, `closed`, `claim_ready`.

:::note[What the recommendation is, and isn't]
It is a **status heuristic**, not rule analysis: terminal states map to
`closed`, a passed account to `claim_ready`, and three or more consecutive
recent losers to `pause`. It does not tell you which rule broke, how close the
account is to a threshold, or which trade caused a breach — per-rule
diagnostics are a later phase. Render it as a hint, and take the authoritative
rule state from the account's frozen rule set and its
[`/events`](https://docs.troncharts.xyz/docs/trading/accounts/#history--reconciliation) history.
:::

Scoped to the calling session's account: another account's prop record answers
`404`.

## Reading account state

| Endpoint | Returns |
| --- | --- |
| `GET /api/v1/prop-accounts` | Accounts you can see. |
| `GET /api/v1/prop-accounts/{id}` | One account with its frozen rule set and live metrics. |
| `GET /api/v1/prop-accounts/{id}/events` | The full state-transition history. |

Live progress is also pushed as `Prop-Account-Update` on
[`/ws/risk`](https://docs.troncharts.xyz/docs/realtime/risk/).