feat(v2): own dca_lp writes + auto-init on first dashboard access

Pairs with the satmachineadmin Phase 1 refactor that hoisted LP state
into `satoshimachine.dca_lp` (one row per user). This extension is now
the WRITER for that table; satmachineadmin only reads it during
distribution.

API surface:
  - `GET  /api/v1/dca-client/preferences` — returns the LP's
    `dca_lp` row, AUTO-CREATING it with the authenticated wallet
    as the default DCA destination on first call. Hitting this
    endpoint is the act that marks the LP as onboarded on the
    operator side (gating their deposit creation).
  - `PUT  /api/v1/dca-client/preferences` — LP-side update of
    wallet / mode / fixed-mode limit / autoforward fields. Ensures
    the row exists before applying. Replaces the old
    `PUT /autoforward` endpoint (which is gone).
  - `GET  /api/v1/dca-client/positions` — same shape as before
    but also auto-inits dca_lp on entry (so opening the dashboard
    onboards the LP). Now INNER JOINs dca_lp so only onboarded
    LPs see positions (matches the operator-side "must onboard
    before deposits" gate).
  - `GET  /api/v1/dca-client/transactions` — unchanged.

Models:
  - New `LpPreferences` / `UpdateLpPreferences` exposing the
    dca_lp fields.
  - `UpdateClientAutoforward` removed (replaced by the broader
    `UpdateLpPreferences`).
  - `PerMachinePosition.dca_mode` now sourced from `dca_lp` (it's
    LP-wide, echoed on each position row for legacy display
    compatibility).

CRUD:
  - `_fetch_user_clients` rewritten: INNER JOIN dca_lp, drop
    references to removed `dca_clients.wallet_id` / `.dca_mode`
    columns (they don't exist anymore post-Phase-1).
  - New: `get_lp_preferences`, `ensure_lp_preferences`,
    `update_lp_preferences`. The first writes nothing; the second
    is the get-or-create that defends the auto-onboard invariant.
  - `update_lp_autoforward` removed — write path is now
    `update_lp_preferences` against `dca_lp`, not the multi-row
    UPDATE on `dca_clients` that used to be needed because the
    state was denormalised across enrolments.

Note: the legacy static/js/index.js in this extension references
endpoints that no longer exist (`/registration-status`, `/register`,
`/dashboard/summary`, ...) — that's pre-existing tech debt from when
the LP UX was moved to ~/dev/webapp. Not regressed by this commit;
the deprecated frontend is out of scope. For now LP onboarding works
via direct API call (curl `GET /preferences` once with the LP's wallet
admin key); the webapp will own the proper UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-16 15:11:27 +02:00
commit 7dac898a10
3 changed files with 166 additions and 66 deletions

View file

@ -15,7 +15,12 @@ from pydantic import BaseModel
class PerMachinePosition(BaseModel):
"""LP's position at a single machine."""
"""LP's position at a single machine.
`dca_mode` was previously per-(machine, LP) and is now LP-wide (lives
on `dca_lp.default_dca_mode`). Echoed here for legacy UI display only
every position for a given LP shares the same value.
"""
machine_id: str
machine_npub: str
@ -36,18 +41,48 @@ class ClientDashboardSummary(BaseModel):
user_id: str
total_sats_accumulated: int
total_fiat_invested: float # confirmed deposits across all machines
current_fiat_balance: float # confirmed deposits - DCA - settlement legs
pending_fiat_deposits: float # deposits in 'pending' status
average_cost_basis: float # total_sats / total_fiat_invested-spent
current_sats_fiat_value: float # current rate × total_sats (best-effort)
total_fiat_invested: float # confirmed deposits across all machines
current_fiat_balance: float # confirmed deposits - DCA - settlement legs
pending_fiat_deposits: float # deposits in 'pending' status
average_cost_basis: float # total_sats / total_fiat_invested-spent
current_sats_fiat_value: float # current rate × total_sats (best-effort)
total_transactions: int
total_machines: int # how many machines this LP is on
total_machines: int # how many machines this LP is on
last_transaction_date: Optional[datetime]
currency: str # display currency; if multi-currency, "MIX"
currency: str # display currency; if multi-currency, "MIX"
positions: List[PerMachinePosition] = []
class LpPreferences(BaseModel):
"""LP-controlled DCA preferences (one row per user in `dca_lp`).
Auto-created on first satmachineclient dashboard access with the LP's
authenticated wallet as the default `dca_wallet_id`; they can change
any field via `PUT /api/v1/dca-client/preferences`. Distribution
reads from here at payout time operator cannot override.
"""
user_id: str
dca_wallet_id: str
default_dca_mode: str # 'flow' | 'fixed'
fixed_mode_daily_limit: Optional[float]
autoforward_ln_address: Optional[str]
autoforward_enabled: bool
created_at: datetime
updated_at: datetime
class UpdateLpPreferences(BaseModel):
"""LP-side preference updates. All fields optional; only ones provided
are touched. Use to switch DCA wallet, change mode, toggle autoforward."""
dca_wallet_id: Optional[str] = None
default_dca_mode: Optional[str] = None
fixed_mode_daily_limit: Optional[float] = None
autoforward_ln_address: Optional[str] = None
autoforward_enabled: Optional[bool] = None
class ClientTransaction(BaseModel):
"""A single distribution leg landing in the LP's wallet."""
@ -55,7 +90,7 @@ class ClientTransaction(BaseModel):
machine_id: str
machine_npub: str
settlement_id: Optional[str]
leg_type: str # 'dca' | 'settlement' | 'autoforward' (LP-visible)
leg_type: str # 'dca' | 'settlement' | 'autoforward' (LP-visible)
amount_sats: int
amount_fiat: Optional[float]
exchange_rate: Optional[float]
@ -64,10 +99,6 @@ class ClientTransaction(BaseModel):
transaction_time: datetime
class UpdateClientAutoforward(BaseModel):
"""LPs can manage their own auto-forward setting per #8. Applies to all
of the LP's dca_clients rows (across every machine they're on)."""
autoforward_enabled: Optional[bool] = None
autoforward_ln_address: Optional[str] = None
# `UpdateClientAutoforward` removed in the dca_lp refactor; preferences
# (autoforward, wallet, mode) now flow through `UpdateLpPreferences`
# against `PUT /api/v1/dca-client/preferences`.