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
35 changes: 30 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ under `crates/`:
- **`just run`** — builds `deckard-signerd`, then runs the app (the signerd binary is spawned as a
sibling). This is the command you'll use most for UI work.
- **`cargo test --workspace`** — runs the full test suite.
- **`just swap-e2e`** — the live-ish swap end-to-end on a fresh anvil Sepolia fork (needs `anvil`
+ an archive `RPC_URL_SEPOLIA`, same as `just demo`). A real CoW order can't be accepted from a
fork, so this drives the in-fork swap stub: propose → human-approve → real exact-gross approve
broadcast → sign → stubbed submit + fill. It's an `#[ignore]`d test, so default `cargo test`
skips it.
- **`just bundle`** — builds a distributable macOS `Deckard.app`.

> The fast `just core` loop is for iterating — the **full Definition of Done still applies before
Expand Down Expand Up @@ -121,6 +126,21 @@ cargo build -p deckard-mcp # one-time: builds ./target/debug/deckard-mcp (n
./target/debug/deckard-mcp policy # read the active fence
```

The swap flow has a human approval in the middle — the CLI can't self-approve, so a human
must approve the proposed order in the Deckard app before `submit-order` will sign:

```sh
./target/debug/deckard-mcp swap-quote --sell-token 0x… --buy-token 0x… --sell-amount-eth 0.05
# price only -> prints buy_amount_min + a request_id
./target/debug/deckard-mcp swap --sell-token 0x… --buy-token 0x… --sell-amount-eth 0.05
# propose -> needs_approval + a request_id
# a human approves the swap in the Deckard app (hold-to-confirm), then:
./target/debug/deckard-mcp submit-order <request_id> # sign + submit to the CoW orderbook
```

On the demo fork the quote and the fill are a labelled simulation (`simulated: true`) — the
live CoW orderbook can't accept a fork order.

> The CLI reads the demo env from your shell. If you didn't launch it from a shell that
> has the demo env exported, prefix the demo block (the same four vars `install --demo`
> emits): `DECKARD_SOCKET_PATH`, `DECKARD_CONFIG_DIR`, `DECKARD_CHAIN_ID=11155111`,
Expand Down Expand Up @@ -165,13 +185,16 @@ is the odd one out — it's anvil's upstream fork source, read only by `just dem
| `DECKARD_RPC_URL` | app, daemon | public RPC | **Yes** — may carry an API key; redacted in logs |
| `DECKARD_VERIFIED_READS` | core (app/daemon) | on (`1`) | No |
| `DECKARD_DEMO_FORK_BLOCK` | core (app/daemon) | unset (live, unpinned) | No |
| `DECKARD_DEMO_SWAP_STUB` | core (cow-client) on/off flag; set by `just demo` + `install --demo` (fill RPC = `DECKARD_RPC_URL`) | unset (real orderbook) | No |
| `DECKARD_ALLOW_SCREEN_CAPTURE` | app (capture block) | off (block honored per setting) | No |
| `DECKARD_SIGNERD_BIN` | app (daemon resolution) | sibling of app binary | No |
| `RPC_URL_SEPOLIA` | `just demo` / `just demo-check` only (anvil `--fork-url`) | unset (required for demo) | **Yes** — archive RPC, may carry an API key |

> Demo block (set by `just demo`, emitted by `deckard-mcp install --demo`):
> `DECKARD_CONFIG_DIR=~/.deckard/demo`, `DECKARD_SOCKET_PATH=~/.deckard/demo/signerd.sock`,
> `DECKARD_CHAIN_ID=11155111`, `DECKARD_RPC_URL=http://127.0.0.1:8545`. `just demo` also
> `DECKARD_CHAIN_ID=11155111`, `DECKARD_RPC_URL=http://127.0.0.1:8545`,
> `DECKARD_DEMO_SWAP_STUB=1` (on/off flag: fixture quote + simulated fork fill — a live CoW order
> can't be accepted from a fork; the fill credits balances on `DECKARD_RPC_URL`). `just demo` also
> sets `DECKARD_VERIFIED_READS=0` (Helios is mainnet-only) and `DECKARD_DEMO_FORK_BLOCK=10822990`.

## Definition of Done
Expand Down Expand Up @@ -251,10 +274,12 @@ Per `STATUS.md` — don't overstate maturity:
that holds the key and gates writes; the shield hero (auto-private via Railgun), which is **wired
and black-box tested on an anvil fork.**
- **Not done / partial:** the **Send** UI is gated ("next release", first post-launch
milestone); **Swap** is TODO; the receive-watcher auto-detect is TODO. The agent / MCP
surface (`deckard-mcp`) **is built** — a key-less CLI + MCP stdio server that proposes
writes to the daemon (see the demo loop above). Some tests are `#[ignore]` (they need
`anvil` + an archive RPC) — see the test caveats in `STATUS.md`.
milestone); the receive-watcher auto-detect is TODO. **Swap** v1 is built — agent-reachable
via the MCP swap flow (`swap-quote` / `swap` / `submit-order`), human-approved in the app, and
on the demo fork the fill is a labelled simulation. The agent / MCP surface (`deckard-mcp`)
**is built** — a key-less CLI + MCP stdio server that proposes writes to the daemon (see the
demo loop above). Some tests are `#[ignore]` (they need `anvil` + an archive RPC) — see the
test caveats in `STATUS.md`.

## Reporting security issues

Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,9 @@ cargo build -p deckard-mcp # one-time: builds ./target/debug/decka
- The **shield** hero (auto-private via [Railgun](https://railgun.org/)) — **wired and black-box
tested on an anvil fork.**
- The **agent surface** (`deckard-mcp`): a key-less CLI **and** MCP stdio sidecar — the
`mcp.v0.1` **6-tool** profile (`deckard_wallet_address`, `deckard_wallet_balance`,
`deckard_policy_get`, `deckard_shield`, `deckard_execute`, `deckard_revoke_all`). Deliberately
`mcp.v0.1` **9-tool** profile (`deckard_wallet_address`, `deckard_wallet_balance`,
`deckard_policy_get`, `deckard_shield`, `deckard_execute`, `deckard_swap_quote`, `deckard_swap`,
`deckard_submit_order`, `deckard_revoke_all`). Deliberately
**no raw `propose` and no `resolve`**, so an injected agent can't submit an arbitrary intent or
approve its own request.
- The **demo loop** (`just demo` / `demo-fund` / `demo-check`) on a local Sepolia fork — see
Expand All @@ -152,7 +153,8 @@ cargo build -p deckard-mcp # one-time: builds ./target/debug/decka

- **Send** UI is gated — it's the **first post-launch milestone** (the daemon + app-socket path
is built and tested; only the UI is held back).
- **Swap** is a TODO (CoW, deferred).
- **Swap** v1: agent-reachable via `deckard_swap_quote` / `deckard_swap` / `deckard_submit_order`
(CoW Protocol); a human approves each swap in the app, and the demo fork simulates the fill.
- The **receive-watcher** auto-detect is a TODO (today you fund/refresh manually).
- No automatic idle-lock and no Touch ID yet — passphrase-only, lock is explicit.
- Some tests are `#[ignore]` (need `anvil` + an archive RPC) — see the test caveats in `STATUS.md`.
Expand Down Expand Up @@ -228,8 +230,8 @@ UI work.

## Where to read more

- [`docs/build/31-agent-quickstart.md`](docs/build/31-agent-quickstart.md) — the canonical agent-facing quickstart: install one-liner, the 6 tools, policy fields, every deny reason with its fix, demo constants.
- [`docs/build/30-mcp-shape.md`](docs/build/30-mcp-shape.md) — the MCP / daemon-socket wire contract and the `mcp.v0.1` 6-tool profile.
- [`docs/build/31-agent-quickstart.md`](docs/build/31-agent-quickstart.md) — the canonical agent-facing quickstart: install one-liner, the 9 tools, policy fields, every deny reason with its fix, demo constants.
- [`docs/build/30-mcp-shape.md`](docs/build/30-mcp-shape.md) — the MCP / daemon-socket wire contract and the `mcp.v0.1` 9-tool profile.
- [`CONTRIBUTING.md`](CONTRIBUTING.md#demo--local-chain-dev-loop) — the demo / local-chain dev loop in full, with the `DECKARD_*` env-var table.
- [`THREAT-MODEL.md`](THREAT-MODEL.md) — what the agent surface defends against, what it does not, and the residual risks.
- [`SECURITY.md`](SECURITY.md) — the at-rest keystore model, honest caveats, and how to report a vulnerability privately.
Expand Down
15 changes: 8 additions & 7 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ Legend: ✅ done + tested · 🟡 partial / integrated-not-finished · 🧪 spik
> `just demo` read the policy at **~20 s**, proposed the shield at **~28 s**, and broadcast at **~32 s** — about
> **0.5 min** to the hero transaction (two runs: 30 s with the doc read locally, 38 s with the doc fetched over the
> network; receipts verified on the fork); **Send UI stays gated**
> ("next release") and **Swap is a disabled TODO** (Send + Swap is the first post-launch milestone). "Tested" below
> still includes some mocked-transport / fake-daemon unit tests — noted per row.
> ("next release") and **Swap v1 is agent-reachable** via MCP (`deckard_swap_quote` → `deckard_swap` → human approves
> in the app → `deckard_submit_order`); on the demo fork the fill is a labelled simulation (`simulated: true`), never a
> fabricated mainnet success. "Tested" below still includes some mocked-transport / fake-daemon unit tests — noted per row.

## The v1 demo — *receive → instantly private → can't switch it off* (agent-driven)

Expand All @@ -32,7 +33,7 @@ Legend: ✅ done + tested · 🟡 partial / integrated-not-finished · 🧪 spik
| **1 · receive** | detect inbound + surface it | 🟡 receive (addr + QR) built; manual refresh; **auto-detect watcher ⬜** | `deckard-app/receive.rs`; `policy.auto_shield_min_wei` exists | a Helios `get_logs` watcher that emits the shield intent |
| **2 · shield (HERO)** | shield received funds via Railgun | ✅ **app-reachable** (compose → review → hold-to-confirm) + **shielded-balance view** (Total + composition, real lifecycle states, privacy mask); core builder + daemon broadcast; black-box `shield_e2e` proves the privacy property (`#[ignore]`, network) | `deckard-app/shield_view.rs` + `render_shield` in `shell.rs`; `deckard-core/shield.rs` + `railgun_keys.rs` (KAT-gated 0zk derivation) + signerd broadcast + `shield_e2e` | re-record demo `.gif`; auto-watcher trigger |
| **3 · walkaway** | cut the RPC on camera, stay verified | ✅ R2 spiked + verified-reads integrated; 🟡 in-app live cut | spike `5e3a16d`; reads `9e19e9a`; `ReadStatus` badge in app | failover supervisor (`Degraded`) + the in-app cut |
| **agent surface (MCP)** | Claude Desktop drives `shield` via MCP | ✅ `deckard-mcp` shipped — key-less CLI + MCP stdio sidecar, `mcp.v0.1` 6-tool profile over the daemon socket | `deckard-mcp` (`server.rs`/`sidecar.rs`); `docs/build/30-mcp-shape.md` | (post-launch) `simulate` (deferred to the daemon) |
| **agent surface (MCP)** | Claude Desktop drives `shield` + `swap` via MCP | ✅ `deckard-mcp` shipped — key-less CLI + MCP stdio sidecar, `mcp.v0.1` 9-tool profile over the daemon socket | `deckard-mcp` (`server.rs`/`sidecar.rs`); `docs/build/30-mcp-shape.md` | (post-launch) `simulate` (deferred to the daemon) |

## Crates / tracks

Expand All @@ -41,8 +42,8 @@ Legend: ✅ done + tested · 🟡 partial / integrated-not-finished · 🧪 spik
| `deckard-contract` | ✅ `Intent`/`Decision`/`Policy` + `ReadStatus` + `calldata_ok` non-empty invariant | 32 | `da29a37` `9e19e9a` `a0a37fd` |
| `deckard-core` | ✅ `EthProvider` (C1) + balances/Multicall3 (C2) + encrypted keystore (C3) + Helios verified reads + key-less shield builder + KAT-gated Railgun seed→0zk viewing-key derivation (`railgun_keys.rs`) | 13+ | `e1aa079` `42e04ad` `57f21bc` `9e19e9a` `3aae92d` |
| `deckard-signerd` | ✅ process-isolated signer daemon + policy gate + STOP/zeroize + Helios read + calldata broadcast + **mainnet guardrail** (chain-1 auto-Allow → `NeedsApproval`, resolved by the app's hold-to-confirm) + RelayAdapt pre-check + **reason/RPC redaction** | `daemon_e2e` · `parity` · `anvil_e2e` · `shield_e2e` (#[ignore]) | `a24f62c` `9e19e9a` `2f28b8b` `72ad5cb` |
| `deckard-mcp` | ✅ key-less CLI + MCP stdio sidecar (`mcp.v0.1` 6-tool profile: `deckard_wallet_address` / `wallet_balance` / `policy_get` / `shield` / `execute` / `revoke_all`); holds no key, proposes Intents to the daemon socket; secret-flag hard-reject + transcript canary scan | `acceptance` 9, all run by default against a *mock signerd* (T1 six-tool profile · T6 within-cap shield → mock `tx_hash` · T7/T9 secret-free transcript) | `a82cc38` |
| `deckard-app` (GPUI) | ✅ onboarding / portfolio / receive / palette / settings + **shield view** (compose → review → hold-to-confirm + shielded-balance composition + privacy mask) + `ReadStatus` badge + socket signer client + **env plumbing** (`DECKARD_CONFIG_DIR`/`SOCKET_PATH`/`CHAIN_ID`/`RPC_URL`, `DECKARD_VERIFIED_READS`, `DECKARD_DEMO_FORK_BLOCK`) + **"DEMO FORK — not mainnet" banner**; 🟡 Send UI gated ("next release"), Swap | `send_path` | C1–C3 + the shield/MCP/demo work |
| `deckard-mcp` | ✅ key-less CLI + MCP stdio sidecar (`mcp.v0.1` 9-tool profile: `deckard_wallet_address` / `wallet_balance` / `policy_get` / `shield` / `execute` / `swap_quote` / `swap` / `submit_order` / `revoke_all`); holds no key, proposes Intents + swap orders to the daemon socket; secret-flag hard-reject + transcript canary scan | `acceptance` 11, all run by default against a *mock signerd* (T1 nine-tool profile · T6 within-cap shield → mock `tx_hash` · swap quote/propose + submit-needs-approval · T7/T9 secret-free transcript) | `a82cc38` |
| `deckard-app` (GPUI) | ✅ onboarding / portfolio / receive / palette / settings + **shield view** (compose → review → hold-to-confirm + shielded-balance composition + privacy mask) + `ReadStatus` badge + socket signer client + **env plumbing** (`DECKARD_CONFIG_DIR`/`SOCKET_PATH`/`CHAIN_ID`/`RPC_URL`, `DECKARD_VERIFIED_READS`, `DECKARD_DEMO_FORK_BLOCK`) + **"DEMO FORK — not mainnet" banner**; 🟡 Send UI gated ("next release"), Swap ✅ (CoW compose → quote → review; agent-reachable via MCP, human-approved; demo-fork fill simulated) | `send_path` | C1–C3 + the shield/MCP/demo work |

> Test caveats: `signerd/shield_e2e` is `#[ignore]` (network — needs a local `anvil`/fork + archive RPC, NOT run by default `cargo test`); `anvil_e2e` runs by default but **silently skips if `anvil` is missing**; `deckard-core`'s reads are tested against a *mocked* transport (live-Helios path is untested by default); `deckard-app`'s `send_path` test uses a *fake recording daemon*, not real signerd/chain. The **MCP acceptance suite runs by default but drives a *mock signerd*** (deterministic `mock_tx_hash`, no live chain) — it asserts the tool surface, the decision/approval flow, and the secret-free transcript, not a real broadcast. The daemon STOP/zeroize + propose→Decision→execute tests, the mainnet-guardrail/redaction tests, and the railgun-key KAT are real and run by default.

Expand All @@ -58,7 +59,7 @@ Legend: ✅ done + tested · 🟡 partial / integrated-not-finished · 🧪 spik

## v0 wallet base (`specs/SPEC-v0.md`)

balances ✅ · receive ✅ · keystore + onboarding ✅ (BIP-39 vault) · **send** (daemon + app-socket path ✅/tested, UI gated 🟡) · **swap** (CoW, deferred; button disabled)
balances ✅ · receive ✅ · keystore + onboarding ✅ (BIP-39 vault) · **send** (daemon + app-socket path ✅/tested, UI gated 🟡) · **swap** (CoW; compose → quote → review in-app, agent-reachable via MCP with human approval; demo-fork fill simulated)

## Remaining for a minimum recordable demo

Expand All @@ -67,7 +68,7 @@ The reachability + visible-state gaps the 2026-06-06 audit flagged are now built
- **A · Beat 2 (hero) — shield on screen** ✅ built: shield view (compose → review → hold-to-confirm) + shielded-balance composition + privacy mask; viewing key derived from the seed (`railgun_keys.rs`, KAT-gated). Re-record the demo `.gif`.
- **B · Beat 3 — in-app walkaway** 🟡: the `ReadStatus` badge already renders; the single-client "cut → NOT VERIFIED → reconnect → Verified" framing is the minimum viable cut. (Shape-A failover supervisor still deferred.)
- **C · Beat 1 — receive landing** 🟡: balance refreshes on manual refresh (QR exists); auto-watcher → auto-shield is the fuller version, still ⬜.
- **D · the "agent-driven" spine** ✅: `deckard-mcp` ships — drive `deckard_shield`/`deckard_execute` from Claude Desktop, or fall back to an in-app agent loop over the same daemon socket.
- **D · the "agent-driven" spine** ✅: `deckard-mcp` ships — drive `deckard_shield`/`deckard_execute` (and the swap flow `deckard_swap_quote`/`deckard_swap`/`deckard_submit_order`) from Claude Desktop, or fall back to an in-app agent loop over the same daemon socket.
- **E · one continuous take + polish** to `DESIGN.md` (onboarding → funded → 3 beats) — pending, plus the `.gif` re-record.

**Smallest take:** A + B + C with D as the live agent (or narrated stand-in). **Largest remaining item is recording the take, not a build.**
Expand Down
10 changes: 8 additions & 2 deletions THREAT-MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,16 @@ discipline:
approval mode are evaluated by the daemon process that owns the key — the sidecar
and the app only *propose*.
2. **The mainnet guardrail removes hands-free spend on chain 1.** See below.
3. **The launch tool surface is 6 tools** (`mcp.v0.1`): no raw `propose` (intents are
3. **The launch tool surface is 9 tools** (`mcp.v0.1`): no raw `propose` (intents are
constructed daemon-side from typed `shield` arguments and the Shield target is
pre-checked against the canonical RelayAdapt address), and no `resolve` (an
injected agent cannot approve its own request).
injected agent cannot approve its own request). The three swap tools
(`deckard_swap_quote` / `deckard_swap` / `deckard_submit_order`) carry **no `resolve`**
either: a swap is proposed and later signed, but the **approval is human-only** over the
private control channel the sidecar cannot reach, and on a demo fork the fill is a
clearly-labelled simulation (`simulated: true`), never a fabricated mainnet success. The
`DECKARD_DEMO_SWAP_STUB` knob that drives that simulation is demo-only — unset in
production, where the real CoW orderbook is always used.
4. **Reasons are redacted at the daemon boundary.** Error/`reason` strings cross into
agent transcripts that leave your machine; every embedded URL is scrubbed to
`scheme://host[:port]` before it leaves the daemon (transport errors love to echo
Expand Down
Loading