feat: server-side launch preflight with structured checks and runtime AI wallet health

- POST /api/launch/preflight and GET /api/traders/:id/preflight run every
  launch prerequisite (AI model credential, claw402 wallet key + Base USDC
  balance, strategy, exchange config, live account state, funding minimums)
  and return a structured checklist with stable codes; minimums (1 USDC AI
  fee / 12 USDC trading) now live server-side and are exposed in the response.
- POST /traders/:id/start enforces the preflight (400 with error_key
  trader.start.preflight_failed + full checklist; ?force=true to override).
  Funding gates use max(available, equity) so restarting a bot with deployed
  margin is not blocked; indeterminate probes (RPC outage) degrade to
  warnings instead of blocking.
- Run loop now surfaces runtime health via GetStatus: safe_mode(+reason) and
  ai_wallet_status/balance (ok|low|empty|unknown), including typed detection
  of claw402 ErrInsufficientFunds instead of burying it in logs; safe-mode
  state is mutex-guarded for API readers.
- Onboarding wallet endpoints switch to the cached, error-aware balance query
  and report balance_status so the UI can tell an RPC outage from an empty
  wallet.
This commit is contained in:
tinkle-community
2026-07-05 20:38:38 +09:00
parent e61f12d307
commit b17d97effc
10 changed files with 843 additions and 14 deletions

View File

@@ -237,8 +237,17 @@ Only include fields you want to change.`,
`:id = trader_id from GET /api/my-traders. Stops and permanently removes the trader and all its data.`,
s.handleDeleteTrader)
s.routeWithSchema(protected, "POST", "/traders/:id/start", "Start trader — begins live trading",
`:id = trader_id from GET /api/my-traders. No request body needed. The trader must have a valid exchange and AI model configured.`,
`:id = trader_id from GET /api/my-traders. No request body needed. The trader must have a valid exchange and AI model configured.
Runs launch preflight first and returns 400 with {"error_key":"trader.start.preflight_failed","preflight":{...}} when checks fail. Append ?force=true to skip balance gates.`,
s.handleStartTrader)
s.routeWithSchema(protected, "GET", "/traders/:id/preflight", "Run launch readiness checks for a trader",
`:id = trader_id from GET /api/my-traders.
Returns: {"ready":<bool>,"checks":[{"id":"ai_model|ai_wallet|ai_wallet_funds|strategy|exchange_config|exchange_account|exchange_funds","status":"ok|failed|warning|skipped","code":"<string>","message":"<string>","required":<number>,"actual":<number>,"asset":"<string>","address":"<string>"}],"min_ai_fee_usdc":<number>,"min_trading_usdc":<number>}`,
s.handleTraderPreflight)
s.routeWithSchema(protected, "POST", "/launch/preflight", "Run launch readiness checks before creating a trader",
`Body: {"ai_model_id":"<EXACT id from GET /api/models>","exchange_id":"<EXACT id from GET /api/exchanges>","strategy_id":"<optional, EXACT id from GET /api/strategies>"}
Returns the same shape as GET /api/traders/:id/preflight. Use this before POST /api/traders to surface every missing prerequisite at once.`,
s.handleLaunchPreflight)
s.routeWithSchema(protected, "POST", "/traders/:id/stop", "Stop trader — halts live trading",
`:id = trader_id from GET /api/my-traders. No request body needed. Gracefully stops the trading loop.`,
s.handleStopTrader)