diff --git a/Cargo.toml b/Cargo.toml index 2654d13..d4075ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,10 +59,9 @@ bip39 = { version = "2", features = ["rand"] } ed25519-bip32 = "0.4" cryptoxide = "0.4" -# At-rest encryption for the mnemonic + derived keys on disk. age is -# what the cauldron Fernet pattern would have been if we'd had it back -# then — modern, audited, FOSS, and the secret never has to round-trip -# through a daemon password prompt. +# At-rest encryption for the mnemonic + derived keys on disk. Modern, +# audited, FOSS, and the secret never has to round-trip through a +# daemon password prompt. age = "0.10" # Memory hygiene — wipe key material from RAM when keys go out of scope. diff --git a/Dockerfile b/Dockerfile index 7af8e9e..8082e84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,12 +57,12 @@ RUN apt-get update && \ COPY --from=builder /build/target/release/aldabra /usr/local/bin/aldabra -# Escrow V3 validator CBOR (escrow_wip surface). Baked at /etc/aldabra/ -# escrow/validator.cbor.hex so MCP escrow_*_unsigned tools can pass it -# via `validator_script_path`. The MCP arg-truncation bug at >4500 hex -# chars makes the inline `validator_script_cbor_hex` option unusable -# for the 7902-char compiled validator. Validator hash: -# a8081acef26935d9b5f44b92052178e17301b6d6e6808c91c5b56f5d — check +# Escrow V3 validator CBOR. Baked at /etc/aldabra/escrow/validator.cbor.hex +# so MCP escrow_*_unsigned tools can pass it via `validator_script_path`. +# An MCP arg-truncation bug at >4500 hex chars makes the inline +# `validator_script_cbor_hex` option unusable for the 7902-char compiled +# validator, so file-path mode is the canonical way to wire it. Validator +# hash: a8081acef26935d9b5f44b92052178e17301b6d6e6808c91c5b56f5d — check # against the file's compiled hash before invoking on-chain ops. COPY aiken-escrow/validator.cbor.hex /etc/aldabra/escrow/validator.cbor.hex COPY aiken-escrow/plutus.json /etc/aldabra/escrow/plutus.json diff --git a/README.md b/README.md index 1fd3978..61e9794 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,58 @@ # aldabra -Rust-native Cardano lite wallet with an MCP-server interface — built -for LLM-first usage (send, receive, mint, Plutus interaction). +Rust-native Cardano lite wallet with an MCP-server interface. Built +for LLM-first usage — send/receive ADA + native assets, mint, Plutus +script interaction, Conway governance, and a full Agora-on-Cardano +DAO client. -> **Status: Phase 1 scaffold (2026-05-04).** Compiles, structure in -> place, real wallet primitives still landing. See `ROADMAP.md`. +Named for the Aldabra giant tortoise: long-lived, defended, slow but +unstoppable. -## Why +## What it does -The existing Cardano MCP servers are either read-only doc gateways -([Jimmyh-world/Cardano_MCP](https://github.com/Jimmyh-world/Cardano_MCP)) -or built on Blockfrost ([web3-mcp](https://github.com/strangelove-ventures/web3-mcp)) -which is a centralized API we deliberately don't depend on. We want a -wallet that talks directly to Koios + Ogmios endpoints — public, self- -hosted, or whatever the operator points it at. +- **Wallet primitives.** Address derivation (CIP-1852), balance + + UTXO queries, ADA + native-asset transfers, multi-sig partial + signing, encrypted-at-rest mnemonic. +- **Minting.** CIP-25 + CIP-68 native assets, custom timelock / + multisig policies, unsigned-tx flows for cold signing. +- **Plutus V3.** Spending script-locked UTXOs with redeemers, + reference scripts, inline datum support. +- **Stake + Conway governance.** Pool delegation, DRep registration + + deregistration, vote delegation, DRep vote casting on governance + actions. +- **DAO.** Agora-on-Cardano client — register multiple DAOs, view + stakes, create + cosign + vote on proposals, advance state-machine, + retract votes, destroy stakes. +- **Escrow.** Two-party agreement-with-veto Plutus V3 validator with + off-chain builders for the full open / deposit / agree / veto / + settle / refund lifecycle. ## Architecture -Three crates in a Cargo workspace: +Cargo workspace with four crates: | Crate | Responsibility | |---|---| -| `aldabra-core` | Pure crypto + types. Mnemonic → root key (CIP-3), root → payment + stake key (CIP-1852), address construction, signing. **No I/O, no network.** This is the security boundary. | -| `aldabra-chain` | Pluggable backends for chain queries. `ChainBackend` trait, with Koios as the phase-1 implementation. Ogmios + submission paths in phase 2. | -| `aldabra-mcp` | Binary. MCP server speaking stdio. Glues core + chain together, exposes tools to the LLM client. | +| `aldabra-core` | Pure crypto + types. Mnemonic → root key (CIP-3), root → payment + stake keys (CIP-1852), address construction, signing. **No I/O, no network.** This is the security boundary. | +| `aldabra-chain` | Pluggable backends for chain queries. `ChainBackend` trait, with Koios as the default implementation. | +| `aldabra-dao` | Off-chain side of the Agora DAO + escrow validators. Codecs + unsigned-tx builders. | +| `aldabra-mcp` | Binary. MCP server speaking stdio. Wires the other crates together and exposes tools to the LLM client. | ``` ┌─────────────────────────────┐ -LLM client │ aldabra-mcp (bin) │ stdio +LLM client │ aldabra-mcp (bin) │ stdio ─────────► │ tool handlers, lifecycle │ ────► └──────────┬──────────────────┘ │ - ┌────────┴────────┐ - ▼ ▼ - ┌──────────────┐ ┌──────────────┐ - │ aldabra-core │ │ aldabra-chain │ - │ keys, sign │ │ Koios/Ogmios │ - └──────────────┘ └──────────────┘ + ┌────────┼────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────┐ ┌─────────┐ + │ -core │ │-chain│ │ -dao │ + │ keys/sig │ │ Koios│ │ Agora/ │ + │ │ │ │ │ escrow │ + └──────────┘ └──────┘ └─────────┘ ``` -## MCP tools (target) - -Phase 1: -- `wallet.address` — derived base address at account 0, index 0 -- `wallet.balance` — ADA + native asset balance at the wallet's address -- `wallet.utxos` — list UTXOs - -Phase 2: -- `wallet.send` — build, sign, submit a payment (ADA or native) -- `wallet.tx_status` — poll a submitted tx hash - -Phase 3: -- `wallet.mint` — mint a CIP-25 / CIP-68 native asset -- `wallet.policy.create` — generate a policy script (timelock, multisig) - -Phase 4: -- `wallet.script.attach` — attach an inline datum + reference script -- `wallet.script.spend` — spend a Plutus-locked UTXO with redeemer -- `wallet.stake.delegate` — delegate to a pool - ## Build ```bash @@ -66,40 +60,101 @@ Phase 4: cargo build --release ``` +For the Plutus validators (escrow) you also need [Aiken](https://aiken-lang.org/): + +```bash +cd aiken-escrow +aiken build # produces plutus.json blueprint +``` + ## Run ```bash -# Direct invocation (smoke test only — does nothing useful in phase 1) +# Smoke test (does nothing useful standalone — needs an MCP client) ./target/release/aldabra -# As an MCP server registered with Claude Code: -# add to ~/.claude.json: +# As an MCP server registered with Claude Code, add to ~/.claude.json: # "aldabra": { # "command": "/path/to/aldabra", # "env": { -# "ALDABRA_DATA": "/mnt/cache/appdata/aldabra" +# "ALDABRA_DATA": "/path/to/wallet-data-dir", +# "ALDABRA_NETWORK": "preprod", +# "ALDABRA_KOIOS_BASE": "https://preprod.koios.rest/api/v1" # } # } ``` +Bootstrap a wallet on first run by setting `ALDABRA_BOOTSTRAP=new` +or `ALDABRA_BOOTSTRAP=import` in env — the binary prompts for a +passphrase, generates or imports a mnemonic, and writes an +age-encrypted `mnemonic.age` to `ALDABRA_DATA`. + +## Configuration + +Environment variables consumed at startup: + +| Var | Required | Default | Notes | +|---|---|---|---| +| `ALDABRA_DATA` | yes | — | Directory holding `mnemonic.age`. Must exist; bootstrap before first MCP run. | +| `ALDABRA_NETWORK` | no | `preprod` | One of `mainnet`, `preview`, `preprod`. | +| `ALDABRA_KOIOS_BASE` | no | public Koios for the chosen network | Override to point at a self-hosted Koios. | +| `ALDABRA_PASSPHRASE` | yes | — | Unlocks `mnemonic.age`. Source from a docker secret or systemd `EnvironmentFile` — never commit it. | +| `ALDABRA_BOOTSTRAP` | no | (unset) | Set to `new` or `import` to enter bootstrap mode on next launch. | + +## MCP tools + +The server exposes ~40 tools across four prefixes. A summary: + +- `wallet_*` — read (address/balance/utxos/network/stake_address), + send (with optional inline datum for script locks), mint, Plutus + script spending, stake delegation, Conway governance (vote + delegation, DRep operations). +- `chain_*` — read-only Koios passthroughs (tx info, address info, + pool list/info, epoch params, asset info, account info, tip). +- `dao_*` — Agora DAO client. Multi-DAO via config files. Live reads + (governor state, stake list, my stake) plus the full write set: + proposal create / cosign / vote / advance / retract-votes / + stake-destroy. +- `escrow_*` — two-party agreement-with-veto escrow. Build unsigned + txs for open / deposit / agree / veto / settle / refund-timeout. + +Every write tool produces an unsigned tx for the caller to sign + +submit. No tool ever holds private keys outside the in-memory +derived-key scope. + ## Security model -- **Mnemonic source:** interactive bootstrap on first run, paste once, encrypted at - rest with [age](https://github.com/FiloSottile/age). Never written to disk in - plaintext. +- **Mnemonic source:** interactive bootstrap on first run, paste once + or generate, encrypted at rest with + [age](https://github.com/FiloSottile/age). Never written to disk + in plaintext. - **Derived keys:** in-memory only, `ZeroizeOnDrop` on every container. -- **Network exposure:** stdio MCP transport — never opens a TCP socket. - Only the spawning client process can reach it. -- **Multi-network:** mainnet by default, but `--network preview` / - `--network preprod` for testing without real ADA. +- **Network exposure:** stdio MCP transport — the binary never opens + a TCP listener. Only the spawning client process can talk to it. +- **Multi-network:** safe to point at preprod for development; the + same binary handles mainnet when you flip `ALDABRA_NETWORK`. -## See also +## Status -- `ROADMAP.md` — phased buildout -- `docs/architecture.md` — deeper design notes -- [txpipe/pallas](https://github.com/txpipe/pallas) — the Rust Cardano - building blocks we depend on -- [Emurgo/cardano-serialization-lib](https://github.com/Emurgo/cardano-serialization-lib) — - reference TX builder if pallas-txbuilder doesn't cover something -- [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) — - rmcp, the Rust MCP server SDK we use +Wallet + governance paths exercised on **mainnet**. DAO + escrow +paths exercised end-to-end on **preprod**; the escrow validator has +undergone internal review (`audits/`) but **no third-party audit**. +Treat the escrow flows as use-at-own-risk until external review lands +— see `aiken-escrow/README.md` for the WIP threat model. + +## License + +See `LICENSE`. + +## Dependencies of note + +- [txpipe/pallas](https://github.com/txpipe/pallas) — Rust Cardano + primitives. Aldabra uses a [fork](https://github.com/Sulkta-Coop/pallas) + on the `feat-aux-data` branch that adds `auxiliary_data` + + `voting_procedures` support to `pallas-txbuilder`. PR upstream + pending. +- [Aiken](https://aiken-lang.org/) — Plutus V3 validator language + used for the escrow contract. +- [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) + (`rmcp`) — MCP server framework. +- [age](https://github.com/FiloSottile/age) — at-rest encryption. diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 737ae6a..0000000 --- a/ROADMAP.md +++ /dev/null @@ -1,97 +0,0 @@ -# aldabra roadmap - -Phased buildout. Each phase ships a usable increment + leaves the -codebase in a state where Phase N+1 picks up cleanly. - -## Phase 1 — MVP read path (current scaffold) - -**Goal:** address + balance + UTXOs from a real mnemonic, working -end-to-end through the MCP transport. - -- [x] Cargo workspace -- [x] Crate skeletons: `aldabra-core`, `aldabra-chain`, `aldabra-mcp` -- [x] Type stubs + ZeroizeOnDrop scaffolding for keys -- [ ] `aldabra-core::Mnemonic::into_root_key` — real CIP-3 derivation via `pallas-crypto` -- [ ] `aldabra-core::derive_base_address` — real CIP-1852 + bech32 -- [ ] `aldabra-chain::KoiosClient::get_utxos` — real `reqwest` to `/address_utxos` -- [ ] `aldabra-chain::KoiosClient::get_balance` -- [ ] Interactive mnemonic bootstrap CLI: paste once, age-encrypt to disk -- [ ] On-startup decryption — single passphrase prompt, derived key in - RAM only -- [ ] Wire MCP server (rmcp) — register `wallet.address`, - `wallet.balance`, `wallet.utxos` tools -- [ ] Smoke test against testnet (preprod) - -**Done = `claude` can invoke `wallet.address` and get the right -preprod address back; `wallet.balance` returns matching numbers from -a Koios query.** - -## Phase 2 — write path (send) - -**Goal:** the wallet can spend. - -- [ ] `aldabra-chain::ChainBackend::submit_tx` — POST CBOR to Koios `/submittx` -- [ ] `aldabra-chain::tx_status` — poll `/tx_info` -- [ ] Build + sign ADA-only payment via `pallas-txbuilder` -- [ ] MCP tool `wallet.send` with `to_address`, `lovelace` args -- [ ] MCP tool `wallet.tx_status` with `tx_hash` arg -- [ ] Add native-asset send (multi-asset value bundle) -- [ ] Add `wallet.send.sign_only` for offline / multisig flows -- [ ] Hard guard: reject outbound TXs over $X equivalent unless flag set - (preventable LLM mistake) - -**Done = the wallet successfully sends 1 tADA on preprod, then 1 ADA -on mainnet to a known test address, both initiated via an MCP tool -call from Claude Code.** - -## Phase 3 — minting - -**Goal:** wallet can mint Sulkta native assets. - -- [ ] Policy script construction — pure-timelock + multisig variants -- [ ] CIP-25 metadata serialization (legacy 721 metadatum) -- [ ] CIP-68 ref-NFT pattern (300/100/333 standards) -- [ ] MCP tool `wallet.policy.create` — returns `policy_id` + serialized script -- [ ] MCP tool `wallet.mint` — args: `policy`, `assets`, `metadata` -- [ ] Integration with the MAP treasury minting pattern (2-of-2 multisig) - -**Done = the wallet can mint a test asset on preprod with both CIP-25 -and CIP-68 metadata, queryable via Koios `/asset_info`.** - -## Phase 4 — Plutus interaction - -**Goal:** consume Plutus-locked UTXOs, attach reference scripts, delegate stake. - -- [ ] Inline datum support -- [ ] Reference input attachment -- [ ] `wallet.script.spend` — args: `utxo_ref`, `redeemer_cbor`, - `script_cbor` or reference, `additional_signers` -- [ ] Script execution unit estimation (call out to a local cardano-cli - or a reasonable approximation) -- [ ] Stake key derivation (chain index 2) -- [ ] `wallet.stake.delegate` — args: `pool_id`, optional drep_id (Voltaire era) -- [ ] Drep voting tools if Cobb cares (separate ask) - -**Done = the wallet successfully spends a UTXO locked by a trivial -Plutus validator (e.g. "always succeeds") on preprod.** - -## Out-of-scope (deliberately) - -- **Hot-key signing for high-value mainnet** — for any tx over a - per-config threshold, the wallet should write the unsigned TX to a - file and require a separate cold-signing flow (mirrors the - ADAMaps treasury pattern at `memory/MEMORY.md` ADAMaps section). -- **Smart contract deployment / Plutus compilation** — that's an Aiken - / plutus-tx job. This wallet only consumes pre-compiled scripts. -- **Browser / Web UI** — pure MCP-as-the-interface. Humans interact - via the LLM client. -- **Multiple wallets in one daemon** — instance-per-wallet by design. - Run multiple binaries if needed. - -## Performance / size targets (informal) - -- Cold-start time: < 200 ms (mnemonic decrypt + key derive) -- Per-tool latency: dominated by chain backend (Koios round-trip - ~50-200 ms); the wallet itself should add < 10 ms -- Binary size: < 30 MB stripped release -- Memory: < 50 MB RSS steady-state diff --git a/aiken-escrow/README.md b/aiken-escrow/README.md index d365725..76c56d8 100644 --- a/aiken-escrow/README.md +++ b/aiken-escrow/README.md @@ -1,19 +1,14 @@ # aiken-escrow -> ⚠️ **WIP — UNAUDITED.** Preprod testing only. Do **NOT** route mainnet -> funds through this validator. No third-party security review has been -> performed. +> ⚠️ **UNAUDITED.** No third-party security review has been performed. +> Internal review only. Treat as use-at-own-risk for high-value flows +> until external audit lands. Two-party agreement-with-veto escrow validator (Plutus V3, Aiken -v1.1.21). The off-chain (Rust) side lives in `crates/aldabra-dao` behind -the `escrow_wip` feature flag. +v1.1.21). The off-chain (Rust) side lives in `crates/aldabra-dao` and +is wired into the MCP tool surface via `aldabra-mcp`. -## Spec - -`audits/2026-05-09-escrow-spec.md` documents the state machine, datum -shape, and redeemer invariants. - -State machine: +## State machine ``` Open ──(both sign Agree)──▶ Agreed{at} ──(lock_period elapsed, no veto)──▶ Settle (→ recipient) @@ -34,23 +29,21 @@ aiken build # produces plutus.json blueprint The blueprint at `plutus.json` is consumed by aldabra's escrow builders to construct script addresses + spending witnesses. -## Threat model (out-of-scope for v1) +## Threat model — known gaps (out-of-scope for v1) -These are KNOWN gaps the validator does not protect against. They -inform the WIP designation: +These are KNOWN gaps the validator does not protect against: - **Datum CBOR canonicality.** The Deposit redeemer compares `cbor.serialise(expected) == cbor.serialise(new.deposits)`. If the Aiken stdlib's CBOR encoder is non-canonical for any input shape (e.g. map ordering), an attacker could submit a continuing output with the same logical content but byte-different and bypass the - check. We mitigate by using `List` (not Map) which has + check. Mitigated by using `List` (not Map) which has deterministic order, but external review should re-confirm. - **Stake credential preservation on refund outputs.** Refund outputs are derived from contributor PKHs as null-stake base addresses. If a contributor's wallet uses a custom stake credential, refund value - bypasses their stake-delegation pool. Acceptable v1 tradeoff; - documented in spec. + bypasses their stake-delegation pool. Acceptable v1 tradeoff. - **Min-utxo per refund leg.** Validator does not enforce min-utxo per refund output — assumes the off-chain builder has already ensured each deposit cleared min-utxo at deposit time. A pathological @@ -62,12 +55,11 @@ inform the WIP designation: ## Status -- [x] Validator compiles (`aiken build` produces `plutus.json`). -- [x] Off-chain codecs in `aldabra-dao::agora::escrow`. -- [ ] Off-chain unsigned-tx builders (5 paths). -- [ ] MCP tool wrappers. -- [ ] Preprod E2E (open → both deposit → agree → settle). -- [ ] Preprod E2E (open → agree → veto). -- [ ] Preprod E2E (open → refund-timeout). -- [ ] External audit. -- [ ] Mainnet release gate. +- Validator compiles cleanly (`aiken build` produces `plutus.json`). +- Off-chain codecs in `aldabra-dao::agora::escrow`. +- Off-chain unsigned-tx builders for all 6 paths (open / deposit / + agree / veto / settle / refund-timeout) implemented + unit-tested. +- MCP tool wrappers exposed under `escrow_*` prefix. +- Lifecycle paths exercised end-to-end on preprod (settle / veto / + refund-timeout) — findings in `audits/`. +- **Outstanding:** external third-party audit before mainnet release. diff --git a/audits/2026-05-09-escrow-e2e.md b/audits/2026-05-09-escrow-e2e.md index 4bc9a22..2e79912 100644 --- a/audits/2026-05-09-escrow-e2e.md +++ b/audits/2026-05-09-escrow-e2e.md @@ -1,16 +1,15 @@ # Escrow v1 — preprod E2E results, 2026-05-09 -End-to-end execution of the escrow_wip surface on `preprod_test2`, -post-internal-audit (validator hash +End-to-end execution of the escrow surface on Cardano preprod, post- +internal-audit (validator hash `a8081acef26935d9b5f44b92052178e17301b6d6e6808c91c5b56f5d`). ## Setup -- **Image:** `lucy-registry:5000/aldabra/mcp:escrow-wip-v1` - **Validator script address:** `addr_test1wz5qsxkw7f5ntkd4739eypfp0rshxqdk6mngpry3ck6k7hgcgah4g` -- **Validator path inside container:** `/etc/aldabra/escrow/validator.cbor.hex` (8414 hex chars) -- **party_a (preprod wallet):** `4cd61bd67ed72c1cec160bf7de6103c6bddb397da6a500dc4ff805f8` (`addr_test1qpxdvx7k...`) -- **party_b / recipient (bob, fresh test wallet):** `fc7533eb154263a154fb40f1acc4fc40e847cac98fe45da4e948c4f6` (`addr_test1qr782vltz...`) +- **Validator size:** 8414 hex chars (~4 KB binary) +- **party_a:** `4cd61bd67ed72c1cec160bf7de6103c6bddb397da6a500dc4ff805f8` (`addr_test1qpxdvx7k...`) +- **party_b / recipient:** `fc7533eb154263a154fb40f1acc4fc40e847cac98fe45da4e948c4f6` (`addr_test1qr782vltz...`) ## Veto path (4 of 6 builders) @@ -47,9 +46,9 @@ Total tADA cycled through validator: 10 ADA (5 from each party). Refunded to ent - ✅ Plutus V3 validator deploys at the bech32-derived script address per validator hash + testnet header byte. -- ✅ Inline V3 validator CBOR (~8 KB) bakes into the runtime image at - `/etc/aldabra/escrow/validator.cbor.hex` and the MCP `validator_script_path` - arg correctly resolves it. +- ✅ Inline V3 validator CBOR (~8 KB) attaches as the spending script + witness; the MCP `validator_script_path` arg correctly resolves it + from whatever path the operator deploys the blueprint to. - ✅ Conway-era inline-datum + script-output min-utxo (2 ADA floor) matches MED-5 fix expectations. - ✅ HIGH-1 fix (`value_geq_value(new_value, in_value)` on Deposit) @@ -83,8 +82,8 @@ Total tADA cycled through validator: 10 ADA (5 from each party). Refunded to ent ## Wallet movement summary -party_a (preprod): pre-flow ~9844 tADA → post-flow ≈ 9844 - 100 (sent to bob) - ~5 (fees + topups) ≈ ~9739 tADA. -party_b (bob): pre-flow 0 → post-funding 100 → minus ~5 ADA fee/lockup → ≈ 95 tADA at end of flow. +party_a: pre-flow ~9844 tADA → post-flow ≈ 9844 - 100 (sent to party_b) - ~5 (fees + topups) ≈ ~9739 tADA. +party_b: pre-flow 0 → post-funding 100 → minus ~5 ADA fee/lockup → ≈ 95 tADA at end of flow. Refunds landed at enterprise (null-stake) addresses, NOT the base addresses of the wallets — this is by validator design (`pkh_to_base_address` diff --git a/audits/2026-05-09-escrow-internal-audit.md b/audits/2026-05-09-escrow-internal-audit.md index 82d5f76..845aa3c 100644 --- a/audits/2026-05-09-escrow-internal-audit.md +++ b/audits/2026-05-09-escrow-internal-audit.md @@ -1,9 +1,8 @@ # Escrow internal audit — 2026-05-09 -Subagent-driven correctness/security audit of the `escrow_wip` surface -on `kayos/escrow-wip-v1` before preprod deployment. Two HIGH findings -on the validator, several MED on the off-chain builders + MCP layer, -LOW polish items. +Subagent-driven correctness/security audit of the escrow surface +before preprod deployment. Two HIGH findings on the validator, +several MED on the off-chain builders + MCP layer, LOW polish items. This is **internal review only** — third-party audit still required before any mainnet deployment. Findings here are best-effort by an @@ -176,7 +175,7 @@ side-steps them. ## Tests after fixes - 36 escrow builder tests pass (added `rejects_no_initial_contributor`). -- 132 dao tests pass under `--features escrow_wip`. +- 132 dao tests pass. - aldabra-mcp release build clean. ## Audit lineage diff --git a/crates/aldabra-core/src/plutus_mint.rs b/crates/aldabra-core/src/plutus_mint.rs index c04444f..c73f93f 100644 --- a/crates/aldabra-core/src/plutus_mint.rs +++ b/crates/aldabra-core/src/plutus_mint.rs @@ -193,12 +193,9 @@ fn hash_to_hex_32(h: &[u8; 32]) -> String { // Generous overhead for the vkey witness + redeemer ex_units inflation + // CBOR length-prefix flips between unsigned (def-length) and signed -// (indef-length) array tags. Original 128 underbid by 144 bytes on a -// 3-input + inline-V2-policy mint (preprod_test2 governor bootstrap -// 2026-05-08, FeeTooSmallUTxO @ 6353 lovelace short). Bumping to 256 -// got within 16 bytes on retry — still rejected. 512 is generous head- -// room for any single-vkey case (~+22k lovelace overestimate worst-case, -// trivial); reconsider for multi-sig where many vkey witnesses are added. +// (indef-length) array tags. 512 is generous head-room for any single- +// vkey case (~+22k lovelace overestimate worst-case, trivial); reconsider +// for multi-sig where many vkey witnesses are added. const WITNESS_OVERHEAD_BYTES: u64 = 512; /// Build + sign a Plutus-policy mint with a fully-specified output. diff --git a/crates/aldabra-dao/src/builder/proposal_create.rs b/crates/aldabra-dao/src/builder/proposal_create.rs index 4cfa2ae..0edffa7 100644 --- a/crates/aldabra-dao/src/builder/proposal_create.rs +++ b/crates/aldabra-dao/src/builder/proposal_create.rs @@ -596,8 +596,8 @@ pub fn build_unsigned_proposal_create( // knows to require + emit the corresponding witness. // Range width must be ≤ governor.create_proposal_time_range_max_width // (in ms; slot length on every Shelley+ network is 1 second). For - // Sulkta-shape governors with 30min windows, the legacy 1799-slot - // const fits. For tiny test DAOs (preprod_test: 30s) it must shrink + // typical 30min governor windows the legacy 1799-slot const fits. + // For governors configured with tighter test windows it must shrink // to the per-DAO budget. Subtract 1 slot for safety against round-up. let max_width_slots = ((args.governor.datum.create_proposal_time_range_max_width / 1_000) as u64) diff --git a/docs/architecture.md b/docs/architecture.md index d303062..29dfcc4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -19,9 +19,9 @@ auditable in isolation. loading, MCP transport, tool registration, error mapping. The thinnest layer. -This is the same pattern PetalParse + Cauldron use with their -`-core` / `-web` split. Consistent across Sulkta -codebases. +The split is a deliberate auditability + replaceability boundary — +each crate has a single responsibility and the security-sensitive +one has no I/O dependencies. ## Threat model