feat(v2): rewrite models.py for v2 schema
Replaces the Lamassu-era data models (LamassuConfig, StoredLamassuTransaction, single-config CRUD carriers) with the v2 Pydantic surface matching m005: - Machine / CreateMachineData / UpdateMachineData: per-operator multi-machine registry keyed by Nostr npub. Replaces single-row LamassuConfig + SSH fields. - DcaClient now scoped per (machine_id, user_id). Includes autoforward fields for satmachineadmin#8 (best-effort LN-address forwarding for LPs). - DcaDeposit gains machine_id + creator_user_id (audit trail finding from v1). - DcaSettlement: idempotency carrier for bitSpire kind-21000 events. Carries platform_fee_sats + operator_fee_sats as absolute ints (v1 hook for the v2 customer-discount engine — see plan). - CommissionSplitLeg / CommissionSplit / SetCommissionSplitsData: operator's remainder-distribution rules. SetCommissionSplitsData validates legs sum to 1.0 at the boundary so crud.py only sees valid sets. - DcaPayment dropped transaction_type in favor of leg_type discriminator (dca | super_fee | operator_split | settlement | autoforward | refund). Gains settlement_id + machine_id + operator_user_id + destination_*. - TelemetrySnapshot: sparse beacon + fleet snapshot fields, all nullable so we degrade gracefully against today's minimal kind-30078 payload. - SuperConfig / UpdateSuperConfigData: super-only platform-fee carrier. - PartialDispenseData (satmachineadmin#3), SettleBalanceData (satmachineadmin#4): operator UX action carriers. Stays on pydantic v1 @validator pattern + Optional[X] hints to match the rest of the codebase. The UP045 / N805 lint noise is pre-existing tech debt across the repo, not introduced here. Refs: plan at ~/.claude/plans/snug-gliding-shamir.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ae4e241d1c
commit
013e3d5f6b
1 changed files with 330 additions and 159 deletions
485
models.py
485
models.py
|
|
@ -1,27 +1,107 @@
|
|||
# Description: Pydantic data models dictate what is passed between frontend and backend.
|
||||
# Satoshi Machine v2 — Pydantic data models.
|
||||
#
|
||||
# The v2 schema replaces the Lamassu-era single-config, super-only data model
|
||||
# with a per-operator multi-machine layout that ingests bitSpire settlements
|
||||
# over Nostr kind-21000. See migrations.py::m005_satmachine_v2_overhaul and
|
||||
# the plan at ~/.claude/plans/snug-gliding-shamir.md.
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
# =============================================================================
|
||||
# Machines — one row per bitSpire ATM, owned by exactly one operator.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class CreateMachineData(BaseModel):
|
||||
"""Operator adds a machine to their fleet by Nostr npub.
|
||||
|
||||
`wallet_id` is the LNbits wallet that will receive bitSpire settlements
|
||||
for this machine. The same operator can own multiple machines; each
|
||||
machine gets its own wallet so per-machine accounting via Payment.tag
|
||||
(set to "satmachine:{machine_npub}") works natively.
|
||||
"""
|
||||
|
||||
machine_npub: str
|
||||
wallet_id: str
|
||||
name: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
fiat_code: str = "GTQ"
|
||||
# Used only when bitSpire's settlement event omits net_sats/fee_sats
|
||||
# in Payment.extra (older bitSpire or edge cases). See plan's
|
||||
# lamassu-next informational issue #1.
|
||||
fallback_commission_pct: float = 0.05
|
||||
|
||||
@validator("fallback_commission_pct")
|
||||
def commission_in_unit_range(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if v < 0 or v > 1:
|
||||
raise ValueError("fallback_commission_pct must be between 0 and 1")
|
||||
return round(float(v), 4)
|
||||
|
||||
|
||||
class Machine(BaseModel):
|
||||
id: str
|
||||
operator_user_id: str
|
||||
machine_npub: str
|
||||
wallet_id: str
|
||||
name: Optional[str]
|
||||
location: Optional[str]
|
||||
fiat_code: str
|
||||
is_active: bool
|
||||
fallback_commission_pct: float
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class UpdateMachineData(BaseModel):
|
||||
name: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
fiat_code: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
wallet_id: Optional[str] = None
|
||||
fallback_commission_pct: Optional[float] = None
|
||||
|
||||
@validator("fallback_commission_pct")
|
||||
def commission_in_unit_range(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if v < 0 or v > 1:
|
||||
raise ValueError("fallback_commission_pct must be between 0 and 1")
|
||||
return round(float(v), 4)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DCA Clients — LP registrations, scoped per (machine, user).
|
||||
# =============================================================================
|
||||
|
||||
|
||||
# DCA Client Models
|
||||
class CreateDcaClientData(BaseModel):
|
||||
machine_id: str
|
||||
user_id: str
|
||||
wallet_id: str
|
||||
username: str
|
||||
dca_mode: str = "flow" # 'flow' or 'fixed'
|
||||
username: Optional[str] = None
|
||||
dca_mode: str = "flow" # 'flow' | 'fixed'
|
||||
fixed_mode_daily_limit: Optional[float] = None
|
||||
# Auto-forward DCA distributions to an external LN address (best-effort;
|
||||
# sats stay in LNbits wallet on forward failure — see satmachineadmin#8).
|
||||
autoforward_ln_address: Optional[str] = None
|
||||
autoforward_enabled: bool = False
|
||||
|
||||
|
||||
class DcaClient(BaseModel):
|
||||
id: str
|
||||
machine_id: str
|
||||
user_id: str
|
||||
wallet_id: str
|
||||
username: Optional[str]
|
||||
dca_mode: str
|
||||
fixed_mode_daily_limit: Optional[int]
|
||||
fixed_mode_daily_limit: Optional[float]
|
||||
autoforward_ln_address: Optional[str]
|
||||
autoforward_enabled: bool
|
||||
status: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
|
@ -31,19 +111,34 @@ class UpdateDcaClientData(BaseModel):
|
|||
username: Optional[str] = None
|
||||
dca_mode: Optional[str] = None
|
||||
fixed_mode_daily_limit: Optional[float] = None
|
||||
autoforward_ln_address: Optional[str] = None
|
||||
autoforward_enabled: Optional[bool] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
# Deposit Models (Now storing GTQ directly)
|
||||
class ClientBalanceSummary(BaseModel):
|
||||
client_id: str
|
||||
machine_id: str
|
||||
total_deposits: float # confirmed deposits in fiat
|
||||
total_payments: float # DCA fiat-equivalent distributed
|
||||
remaining_balance: float # deposits - payments
|
||||
currency: str
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Deposits — fiat the operator (or super) records against an LP at a machine.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class CreateDepositData(BaseModel):
|
||||
client_id: str
|
||||
amount: float # Amount in GTQ (e.g., 150.75)
|
||||
machine_id: str
|
||||
amount: float
|
||||
currency: str = "GTQ"
|
||||
notes: Optional[str] = None
|
||||
|
||||
@validator('amount')
|
||||
def round_amount_to_cents(cls, v):
|
||||
"""Ensure amount is rounded to 2 decimal places for DECIMAL(10,2) storage"""
|
||||
|
||||
@validator("amount")
|
||||
def round_amount(cls, v):
|
||||
if v is not None:
|
||||
return round(float(v), 2)
|
||||
return v
|
||||
|
|
@ -52,9 +147,11 @@ class CreateDepositData(BaseModel):
|
|||
class DcaDeposit(BaseModel):
|
||||
id: str
|
||||
client_id: str
|
||||
amount: float # Amount in GTQ (e.g., 150.75)
|
||||
machine_id: str
|
||||
creator_user_id: str
|
||||
amount: float
|
||||
currency: str
|
||||
status: str # 'pending' or 'confirmed'
|
||||
status: str # 'pending' | 'confirmed' | 'rejected'
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
confirmed_at: Optional[datetime]
|
||||
|
|
@ -65,179 +162,253 @@ class UpdateDepositData(BaseModel):
|
|||
currency: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
@validator('amount')
|
||||
def round_amount_to_cents(cls, v):
|
||||
@validator("amount")
|
||||
def round_amount(cls, v):
|
||||
if v is not None:
|
||||
return round(float(v), 2)
|
||||
return v
|
||||
|
||||
|
||||
class UpdateDepositStatusData(BaseModel):
|
||||
status: str
|
||||
status: str # 'pending' | 'confirmed' | 'rejected'
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
# Payment Models
|
||||
class CreateDcaPaymentData(BaseModel):
|
||||
client_id: str
|
||||
amount_sats: int
|
||||
amount_fiat: float # Amount in GTQ (e.g., 150.75)
|
||||
# =============================================================================
|
||||
# Settlements — one per bitSpire kind-21000 event.
|
||||
# =============================================================================
|
||||
# platform_fee_sats and operator_fee_sats are absolute audit-grade values.
|
||||
# Today they equal the contractual split; tomorrow (post-v1 promo engine)
|
||||
# they record who-forgave-what. DO NOT collapse them into a single pct.
|
||||
# See plan section "Customer discounts & promotions (post-v1)".
|
||||
|
||||
|
||||
class CreateDcaSettlementData(BaseModel):
|
||||
machine_id: str
|
||||
bitspire_event_id: str # nostr event id — the idempotency key
|
||||
bitspire_txid: Optional[str] = None
|
||||
payment_hash: str
|
||||
gross_sats: int
|
||||
fiat_amount: float
|
||||
fiat_code: str = "GTQ"
|
||||
exchange_rate: float
|
||||
transaction_type: str # 'flow', 'fixed', 'manual', 'commission'
|
||||
lamassu_transaction_id: Optional[str] = None
|
||||
payment_hash: Optional[str] = None
|
||||
transaction_time: Optional[datetime] = None # Original ATM transaction time
|
||||
net_sats: int
|
||||
commission_sats: int
|
||||
platform_fee_sats: int
|
||||
operator_fee_sats: int
|
||||
used_fallback_split: bool = False
|
||||
tx_type: str # 'cash_out' | 'cash_in'
|
||||
bills_json: Optional[str] = None
|
||||
cassettes_json: Optional[str] = None
|
||||
|
||||
|
||||
class DcaSettlement(BaseModel):
|
||||
id: str
|
||||
machine_id: str
|
||||
bitspire_event_id: str
|
||||
bitspire_txid: Optional[str]
|
||||
payment_hash: str
|
||||
gross_sats: int
|
||||
fiat_amount: float
|
||||
fiat_code: str
|
||||
exchange_rate: float
|
||||
net_sats: int
|
||||
commission_sats: int
|
||||
platform_fee_sats: int
|
||||
operator_fee_sats: int
|
||||
used_fallback_split: bool
|
||||
tx_type: str
|
||||
bills_json: Optional[str]
|
||||
cassettes_json: Optional[str]
|
||||
status: str # 'pending' | 'processed' | 'partial' | 'refunded' | 'errored'
|
||||
error_message: Optional[str]
|
||||
processed_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Commission splits — operator-defined remainder allocation per machine.
|
||||
# =============================================================================
|
||||
# machine_id=NULL means operator's default; non-null means per-machine override.
|
||||
# Sum of pct across rows for a (operator_user_id, machine_id) scope must be 1.0,
|
||||
# enforced at write-time in crud.py.
|
||||
|
||||
|
||||
class CommissionSplitLeg(BaseModel):
|
||||
"""Single leg of an operator's commission-split rule set."""
|
||||
|
||||
wallet_id: str
|
||||
label: Optional[str] = None
|
||||
pct: float
|
||||
sort_order: int = 0
|
||||
|
||||
@validator("pct")
|
||||
def pct_in_unit_range(cls, v):
|
||||
if v < 0 or v > 1:
|
||||
raise ValueError("pct must be between 0 and 1")
|
||||
return round(float(v), 4)
|
||||
|
||||
|
||||
class CommissionSplit(BaseModel):
|
||||
id: str
|
||||
machine_id: Optional[str] # None = operator's default ruleset
|
||||
operator_user_id: str
|
||||
wallet_id: str
|
||||
label: Optional[str]
|
||||
pct: float
|
||||
sort_order: int
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class SetCommissionSplitsData(BaseModel):
|
||||
"""Replaces the entire ruleset for a given scope.
|
||||
|
||||
`machine_id=None` writes the operator's default ruleset (applies to any
|
||||
machine without an explicit override). Otherwise scoped per machine.
|
||||
"""
|
||||
|
||||
machine_id: Optional[str] = None
|
||||
legs: list[CommissionSplitLeg]
|
||||
|
||||
@validator("legs")
|
||||
def legs_sum_to_one(cls, v):
|
||||
total = round(sum(leg.pct for leg in v), 4)
|
||||
if abs(total - 1.0) > 0.0001:
|
||||
raise ValueError(f"split percentages must sum to 1.0 (got {total})")
|
||||
return v
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Payments — every distribution leg (DCA / super_fee / split / settle / etc.)
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class CreateDcaPaymentData(BaseModel):
|
||||
settlement_id: Optional[str] = None
|
||||
client_id: Optional[str] = None
|
||||
machine_id: str
|
||||
operator_user_id: str
|
||||
leg_type: str
|
||||
# 'dca' | 'super_fee' | 'operator_split' | 'settlement' | 'autoforward' | 'refund'
|
||||
destination_wallet_id: Optional[str] = None
|
||||
destination_ln_address: Optional[str] = None
|
||||
amount_sats: int
|
||||
amount_fiat: Optional[float] = None
|
||||
exchange_rate: Optional[float] = None
|
||||
transaction_time: datetime
|
||||
external_payment_hash: Optional[str] = None
|
||||
|
||||
|
||||
class DcaPayment(BaseModel):
|
||||
id: str
|
||||
client_id: str
|
||||
settlement_id: Optional[str]
|
||||
client_id: Optional[str]
|
||||
machine_id: str
|
||||
operator_user_id: str
|
||||
leg_type: str
|
||||
destination_wallet_id: Optional[str]
|
||||
destination_ln_address: Optional[str]
|
||||
amount_sats: int
|
||||
amount_fiat: float # Amount in GTQ (e.g., 150.75)
|
||||
exchange_rate: float
|
||||
transaction_type: str
|
||||
lamassu_transaction_id: Optional[str]
|
||||
payment_hash: Optional[str]
|
||||
status: str # 'pending', 'confirmed', 'failed'
|
||||
amount_fiat: Optional[float]
|
||||
exchange_rate: Optional[float]
|
||||
transaction_time: datetime
|
||||
external_payment_hash: Optional[str]
|
||||
status: str # 'pending' | 'completed' | 'failed' | 'refunded'
|
||||
error_message: Optional[str]
|
||||
created_at: datetime
|
||||
transaction_time: Optional[datetime] = None # Original ATM transaction time
|
||||
|
||||
|
||||
# Client Balance Summary (Now storing GTQ directly)
|
||||
class ClientBalanceSummary(BaseModel):
|
||||
client_id: str
|
||||
total_deposits: float # Total confirmed deposits in GTQ
|
||||
total_payments: float # Total payments made in GTQ
|
||||
remaining_balance: float # Available balance for DCA in GTQ
|
||||
currency: str
|
||||
# =============================================================================
|
||||
# Telemetry — sparse beacon (kind-30078) + fleet snapshot (kind-30079) state.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
# Transaction Processing Models
|
||||
class LamassuTransaction(BaseModel):
|
||||
transaction_id: str
|
||||
amount_fiat: float # Amount in GTQ (e.g., 150.75)
|
||||
amount_crypto: int
|
||||
exchange_rate: float
|
||||
transaction_type: str # 'cash_in' or 'cash_out'
|
||||
status: str
|
||||
timestamp: datetime
|
||||
class TelemetrySnapshot(BaseModel):
|
||||
machine_id: str
|
||||
# Beacon (kind-30078) — all fields are nullable because the upstream payload
|
||||
# is sparse today. As lamassu-next#43 lands, the post-#43 columns fill in.
|
||||
beacon_cash_in: Optional[bool] = None
|
||||
beacon_cash_out: Optional[bool] = None
|
||||
beacon_cash_level: Optional[str] = None
|
||||
beacon_fiat: Optional[str] = None
|
||||
beacon_model: Optional[str] = None
|
||||
beacon_name: Optional[str] = None
|
||||
beacon_location: Optional[str] = None
|
||||
beacon_geo: Optional[str] = None
|
||||
beacon_fees_json: Optional[str] = None
|
||||
beacon_limits_json: Optional[str] = None
|
||||
beacon_denominations_json: Optional[str] = None
|
||||
beacon_version: Optional[str] = None
|
||||
beacon_received_at: Optional[datetime] = None
|
||||
# Fleet telemetry (kind-30079) — operator-only, awaits lamassu-next#42.
|
||||
telemetry_json: Optional[str] = None
|
||||
telemetry_received_at: Optional[datetime] = None
|
||||
|
||||
|
||||
# Lamassu Transaction Storage Models
|
||||
class CreateLamassuTransactionData(BaseModel):
|
||||
lamassu_transaction_id: str
|
||||
fiat_amount: float # Amount in GTQ (e.g., 150.75)
|
||||
crypto_amount: int
|
||||
commission_percentage: float
|
||||
discount: float = 0.0
|
||||
effective_commission: float
|
||||
commission_amount_sats: int
|
||||
base_amount_sats: int
|
||||
exchange_rate: float
|
||||
crypto_code: str = "BTC"
|
||||
fiat_code: str = "GTQ"
|
||||
device_id: Optional[str] = None
|
||||
transaction_time: datetime
|
||||
# =============================================================================
|
||||
# Super config — singleton row with the platform fee.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class StoredLamassuTransaction(BaseModel):
|
||||
class SuperConfig(BaseModel):
|
||||
id: str
|
||||
lamassu_transaction_id: str
|
||||
fiat_amount: float # Amount in GTQ (e.g., 150.75)
|
||||
crypto_amount: int
|
||||
commission_percentage: float
|
||||
discount: float
|
||||
effective_commission: float
|
||||
commission_amount_sats: int
|
||||
base_amount_sats: int
|
||||
exchange_rate: float
|
||||
crypto_code: str
|
||||
fiat_code: str
|
||||
device_id: Optional[str]
|
||||
transaction_time: datetime
|
||||
processed_at: datetime
|
||||
clients_count: int # Number of clients who received distributions
|
||||
distributions_total_sats: int # Total sats distributed to clients
|
||||
super_fee_pct: float
|
||||
super_fee_wallet_id: Optional[str]
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
# Lamassu Configuration Models
|
||||
class CreateLamassuConfigData(BaseModel):
|
||||
host: str
|
||||
port: int = 5432
|
||||
database_name: str
|
||||
username: str
|
||||
password: str
|
||||
# Source wallet for DCA distributions
|
||||
source_wallet_id: Optional[str] = None
|
||||
# Commission wallet for storing commission earnings
|
||||
commission_wallet_id: Optional[str] = None
|
||||
# SSH Tunnel settings
|
||||
use_ssh_tunnel: bool = False
|
||||
ssh_host: Optional[str] = None
|
||||
ssh_port: int = 22
|
||||
ssh_username: Optional[str] = None
|
||||
ssh_password: Optional[str] = None
|
||||
ssh_private_key: Optional[str] = None # Path to private key file or key content
|
||||
# DCA Client Limits
|
||||
max_daily_limit_gtq: float = 2000.0 # Maximum daily limit for Fixed mode clients
|
||||
|
||||
@validator('max_daily_limit_gtq')
|
||||
def round_max_daily_limit(cls, v):
|
||||
"""Ensure max daily limit is rounded to 2 decimal places"""
|
||||
if v is not None:
|
||||
return round(float(v), 2)
|
||||
class UpdateSuperConfigData(BaseModel):
|
||||
super_fee_pct: Optional[float] = None
|
||||
super_fee_wallet_id: Optional[str] = None
|
||||
|
||||
@validator("super_fee_pct")
|
||||
def fee_in_unit_range(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if v < 0 or v > 1:
|
||||
raise ValueError("super_fee_pct must be between 0 and 1")
|
||||
return round(float(v), 4)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Operator UX action carriers — partial-tx and balance-settlement features.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class PartialDispenseData(BaseModel):
|
||||
"""Resolves satmachineadmin#3 — operator confirms actual bills dispensed
|
||||
when bitSpire reports an error mid-dispense.
|
||||
|
||||
Either `dispensed_fraction` (0..1) for ratio-based recompute, or
|
||||
`dispensed_sats` for explicit recompute. Exactly one must be set.
|
||||
"""
|
||||
|
||||
settlement_id: str
|
||||
dispensed_fraction: Optional[float] = None
|
||||
dispensed_sats: Optional[int] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
@validator("dispensed_fraction")
|
||||
def fraction_in_unit_range(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
if v < 0 or v > 1:
|
||||
raise ValueError("dispensed_fraction must be between 0 and 1")
|
||||
return v
|
||||
|
||||
|
||||
class LamassuConfig(BaseModel):
|
||||
id: str
|
||||
host: str
|
||||
port: int
|
||||
database_name: str
|
||||
username: str
|
||||
password: str
|
||||
is_active: bool
|
||||
test_connection_last: Optional[datetime]
|
||||
test_connection_success: Optional[bool]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
# Source wallet for DCA distributions
|
||||
source_wallet_id: Optional[str] = None
|
||||
# Commission wallet for storing commission earnings
|
||||
commission_wallet_id: Optional[str] = None
|
||||
# SSH Tunnel settings
|
||||
use_ssh_tunnel: bool = False
|
||||
ssh_host: Optional[str] = None
|
||||
ssh_port: int = 22
|
||||
ssh_username: Optional[str] = None
|
||||
ssh_password: Optional[str] = None
|
||||
ssh_private_key: Optional[str] = None
|
||||
# Poll tracking
|
||||
last_poll_time: Optional[datetime] = None
|
||||
last_successful_poll: Optional[datetime] = None
|
||||
# DCA Client Limits
|
||||
max_daily_limit_gtq: float = 2000.0 # Maximum daily limit for Fixed mode clients
|
||||
|
||||
|
||||
class UpdateLamassuConfigData(BaseModel):
|
||||
host: Optional[str] = None
|
||||
port: Optional[int] = None
|
||||
database_name: Optional[str] = None
|
||||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
# Source wallet for DCA distributions
|
||||
source_wallet_id: Optional[str] = None
|
||||
# Commission wallet for storing commission earnings
|
||||
commission_wallet_id: Optional[str] = None
|
||||
# SSH Tunnel settings
|
||||
use_ssh_tunnel: Optional[bool] = None
|
||||
ssh_host: Optional[str] = None
|
||||
ssh_port: Optional[int] = None
|
||||
ssh_username: Optional[str] = None
|
||||
ssh_password: Optional[str] = None
|
||||
ssh_private_key: Optional[str] = None
|
||||
# DCA Client Limits
|
||||
max_daily_limit_gtq: Optional[int] = None
|
||||
class SettleBalanceData(BaseModel):
|
||||
"""Resolves satmachineadmin#4 — operator settles small remaining LP balance
|
||||
from their own wallet at the current exchange rate."""
|
||||
|
||||
client_id: str
|
||||
funding_wallet_id: str
|
||||
# If None, settle the full remaining balance.
|
||||
amount_fiat: Optional[float] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
@validator("amount_fiat")
|
||||
def round_amount(cls, v):
|
||||
if v is not None:
|
||||
return round(float(v), 2)
|
||||
return v
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue