Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ DEVNET_core_zmq_endpoint=tcp://127.0.0.1:23710
# See docs/local-network.md for detailed setup instructions.
LOCAL_dapi_addresses=http://127.0.0.1:2443,http://127.0.0.1:2543,http://127.0.0.1:2643
LOCAL_core_host=127.0.0.1
LOCAL_core_rpc_port=20302
LOCAL_core_rpc_port=19898
LOCAL_core_rpc_user=dashmate
# Use dashmate cli to retrive it:
# dashmate config get core.rpc.users.dashmate.password --config=local_seed
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ scripts/safe-cargo.sh +nightly fmt --all
* Screen constructors handle errors internally via `MessageBanner` and return `Self` with degraded state. Keep `create_screen()` clean — no error handling at callsites.
* **i18n-ready strings**: All user-facing strings (labels, messages, tooltips, errors) must be simple, complete sentences. Avoid concatenating fragments, positional assumptions, or grammar that breaks in other languages. Each string should be extractable as a single translation unit with named placeholders for dynamic values and no logic in the text itself. Current code uses standard Rust format specifiers (`{name}`, `{max}`). When i18n extraction happens later, these will become Fluent-style placeholders (`{ $name }`, `{ $max }`).
* **Never parse error strings** to extract information. Always use the typed error chain (downcast, match on variants, access structured fields). If no typed variant exists for the information you need, define a new `TaskError` variant or extend the existing error type. String parsing is fragile, breaks on message changes, and bypasses the type system.
* **Validation placement**: Pure input validation (format, length, character sets) lives in `model/` as stateless functions — single source of truth, unit-testable, no dependencies on `AppContext` or `Sdk`. Backend tasks are the authoritative enforcement layer: they call model validators for format checks AND perform stateful validation that requires network or database (existence checks, uniqueness, business rules). UI screens may call model validators for instant user feedback, but must never implement their own validation logic — always delegate to the model function.

### Error messages

Expand Down Expand Up @@ -109,13 +110,13 @@ User-facing error messages (shown in `MessageBanner` via `Display`) must follow
- **CLI ≠ MCP**: `src/bin/det_cli/` is a separate client that talks to the MCP server — it must work over HTTP too, not just in-process. Never put tool logic in the CLI binary; tools live in `src/mcp/tools/` and the CLI discovers them dynamically via `tools/list`.
- **Tool architecture**: each tool is a struct implementing `ToolBase` (metadata) + `AsyncTool<DashMcpService>` (invocation). Adding a tool requires only the struct + registering in `tool_router()` — zero CLI changes.
- **Tool naming**: `{domain}_{object}_{action}` — e.g. `core_address_create`, `platform_withdrawals_get`, `tool_describe`. CLI converts underscores to hyphens.
- **Context provider**: `ContextProvider::Shared(ArcSwap)` for HTTP mode (follows GUI network switches), `ContextProvider::Lazy(OnceCell)` for stdio (init on first tool call).
- **Context provider**: `ContextHolder::Shared(ArcSwap)` for HTTP mode (follows GUI network switches), `ContextHolder::Standalone(ArcSwapOption)` for stdio (init on first tool call).
- **Network safety**: tools accept optional `network` param — request fails if it doesn't match the active network. Exempt: `network_info`, `tool_describe`.
- **SPV sync**: wallet tools call `resolve::ensure_spv_synced()` before operating — polls SPV status with 1s interval, 10min timeout.
- **Backend dispatch**: tools reuse the app's `BackendTask` system via `dispatch::dispatch_task()` — creates a throwaway channel, calls `app_context.run_backend_task()`.
- **Schema quirk**: `schemars` v1 derives bare `true` for `serde_json::Value` fields — some MCP clients reject this. Use `#[schemars(transform)]` to override.
- **Error type**: `McpToolError` enum (InvalidParam, WalletNotFound, SpvSyncFailed, TaskFailed, Internal) converts to `rmcp::ErrorData` via `From`.
- **Docs**: `docs/MCP.md` (server config, tool reference), `docs/CLI.md` (usage, examples), `docs/EXPOSING_BACKEND_TASKS.md` (checklist for adding new MCP tools).
- **Docs**: `docs/MCP.md` (server config, tool reference), `docs/CLI.md` (usage, examples), `docs/MCP_TOOL_DEVELOPMENT.md` (checklist for adding new MCP tools).

### Key Dependencies

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ The default `cargo build` produces only the `dash-evo-tool` GUI binary. Optional

### Adding MCP tools

To expose a `BackendTask` as a new MCP/CLI tool, follow the step-by-step checklist in [docs/EXPOSING_BACKEND_TASKS.md](docs/EXPOSING_BACKEND_TASKS.md). It covers architecture rules, the standard invocation pattern, registration, and common pitfalls.
To expose a `BackendTask` as a new MCP/CLI tool, follow the step-by-step checklist in [docs/MCP_TOOL_DEVELOPMENT.md](docs/MCP_TOOL_DEVELOPMENT.md). It covers architecture rules, the standard invocation pattern, registration, and common pitfalls.

## Code quality

Expand Down
8 changes: 8 additions & 0 deletions docs/MCP.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ Set these in the app's `.env` file (see `.env.example`) or as environment variab
| Tool | Parameters | det-cli command | Description |
|---|---|---|---|
| `network_info` | — | `det-cli network-info` | Show active network and available configured networks |
| `network_reinit_sdk` | `network` | `det-cli network-reinit-sdk` | Rebuild Core RPC client and Platform SDK with current config (use after changing credentials) |
| `network_switch` | `network` | `det-cli network-switch` | Switch the active network (creates context if needed, may take a few seconds) |
| `core_wallets_list` | `network`? | `det-cli core-wallets-list` | List wallets loaded in the app (alias + seed hash) |
| `core_address_create` | `wallet_id`, `network`? | `det-cli core-address-create` | Generate a new receive address for a wallet |
| `core_balances_get` | `wallet_id`, `network`? | `det-cli core-balances-get` | Show wallet balances (total, confirmed, unconfirmed) in duffs |
Expand All @@ -88,6 +90,12 @@ Set these in the app's `.env` file (see `.env.example`) or as environment variab

Parameters marked `?` are optional. The `det-cli` column shows the equivalent CLI command (underscores become hyphens).

### SPV requirements

All wallet-facing tools wait for SPV to fully sync before executing. This includes both core-chain tools (`core_address_create`, `core_balances_get`, `core_funds_send`) and platform tools (`platform_addresses_list`, `identity_credits_topup`, `shielded_shield_from_core`). Even DAPI-only operations need SPV because the SDK verifies DAPI proofs against quorum and masternode list data from the synced chain. When another DET instance is already running, SPV falls back to a temporary directory and must sync from scratch.

Only metadata tools that make no network calls (`core_wallets_list`, `network_info`, `tool_describe`) skip the SPV gate.

## CLI interface (det-cli)

`det-cli` is the command-line interface for interacting with MCP tools. It can operate in two modes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl AsyncTool<DashMcpService> for MyNewTool {
// 3. Resolve wallet if needed
let seed_hash = resolve::wallet(&ctx, &param.wallet_id)?;

// 4. Wait for SPV sync if tool needs wallet/chain data
// 4. Wait for SPV sync (see SPV gate rule below)
resolve::ensure_spv_synced(&ctx).await?;

// 5. Build and dispatch the backend task
Expand All @@ -97,7 +97,7 @@ impl AsyncTool<DashMcpService> for MyNewTool {
- Skip `verify_network` only for `network_info` and `tool_describe`.
- For destructive tools (`read_only: false`), the `network` parameter **must be required** (not optional with `#[serde(default)]`). Use `resolve::require_network()` instead of `resolve::verify_network()` to prevent accidental cross-network operations that could spend funds on the wrong network.
- Skip wallet resolution if the tool doesn't operate on a wallet.
- Skip `ensure_spv_synced` if the tool reads only from the database or Platform SDK (no SPV-dependent data). For tools that only dispatch Platform state transitions (not Core UTXO spends), add an `// INTENTIONAL: no SPV sync needed` comment explaining why.
- **SPV gate rule**: Call `ensure_spv_synced` for **all wallet-facing tools** — both core-chain and platform/DAPI. The SDK verifies DAPI proofs against quorum and masternode list data from the synced SPV chain, so even platform-only queries fail without it. Skip only for metadata tools that make no network calls (`core_wallets_list`, `network_info`, `tool_describe`).

### 6. Register in `tool_router()`

Expand Down
Loading
Loading