Phase 4c + 4d. Closes the DAO write-path arc (excluding GAT minting, which is Phase 4c-bis since Sulkta has never executed a proposal). ## proposal_advance (Phase 4c) State-machine builder with 5 transitions: - Draft → VotingReady (cosigner threshold met, all cosigner stakes ref'd as txInfo.referenceInputs, sum staked_amount ≥ to_voting) - Draft → Finished (drafting period elapsed without enough cosigners) - VotingReady → Locked (winner outcome exists with votes ≥ execute, no tie) - VotingReady → Finished (locking period elapsed without winner) - Locked → Finished (executing period elapsed; for InfoOnly proposals) Validator (PAdvanceProposal in Proposal/Scripts.hs:657) requires output proposal datum equals input with ONLY status mutated. Builder mirrors exactly. Per-transition preflights match validator gates. Cosigner stake refs go in as txInfo.referenceInputs (not regular inputs) per witnessStakes pattern (Proposal/Scripts.hs:366) — sum of staked_amount is computed from the ref-input set. GAT-minting Locked→Finished path (effected proposals) deferred to 4c-bis. The pmintGATs governor redeemer is a separate tx that fires ONLY when the executing period is in window AND the winner outcome has effects to mint GATs for. Sulkta's first proposal was InfoOnly so this path never exercised on chain yet. 11 unit tests covering every transition + every preflight reject. ## stake_destroy (Phase 4d) Burns StakeST token + returns gov-tokens to owner. From Stake/Redeemers.hs pdestroy (~L432): owner signs (no delegatees), all locks empty, no stake output at stakes_addr. From stakePolicy burn branch (~L161): burntST quantity = -spentST. Tx shape: spend stake (Destroy redeemer) + maybe a funding utxo + collateral; mint -1 StakeST; one wallet output carrying gov-tokens + (stake.lovelace + funding - fee). Funding optional — stake's own lovelace usually covers fees. 4 unit tests including the funding-optional path. ## MCP tools dao_proposal_advance_unsigned auto-picks the right transition from proposal status + chain tip vs window boundaries. Mainnet-only gate. Fetches cosigner stake refs by matching owner pkh against proposal.cosigners. dao_stake_destroy_unsigned fetches the wallet's stake (via owner pkh match), pulls StakeST asset name from chain, burns it. |
||
|---|---|---|
| crates | ||
| docs | ||
| .dockerignore | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
| ROADMAP.md | ||
aldabra
Rust-native Cardano lite wallet with an MCP-server interface — built for LLM-first usage (send, receive, mint, Plutus interaction).
Status: Phase 1 scaffold (2026-05-04). Compiles, structure in place, real wallet primitives still landing. See
ROADMAP.md.
Why
The existing Cardano MCP servers are either read-only doc gateways (Jimmyh-world/Cardano_MCP) or built on Blockfrost (web3-mcp) which is a centralized API we deliberately don't depend on. Sulkta runs its own Koios + Ogmios endpoints on Rackham; we want a wallet that talks directly to those.
Also: it's the first Sulkta Rust project — useful as a workout for
crafting-table's Rust toolchain (per Sulkta-Coop/lucy-infra
spec-crafting-table.md).
Architecture
Three crates in a Cargo workspace:
| 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. |
┌─────────────────────────────┐
LLM client │ aldabra-mcp (bin) │ stdio
─────────► │ tool handlers, lifecycle │ ────►
└──────────┬──────────────────┘
│
┌────────┴────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ aldabra-core │ │ aldabra-chain │
│ keys, sign │ │ Koios/Ogmios │
└──────────────┘ └──────────────┘
MCP tools (target)
Phase 1:
wallet.address— derived base address at account 0, index 0wallet.balance— ADA + native asset balance at the wallet's addresswallet.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 assetwallet.policy.create— generate a policy script (timelock, multisig)
Phase 4:
wallet.script.attach— attach an inline datum + reference scriptwallet.script.spend— spend a Plutus-locked UTXO with redeemerwallet.stake.delegate— delegate to a pool
Build
# Local (requires rustc 1.75+)
cargo build --release
# Through crafting-table (preferred — validates the toolchain there)
crafting-table build aldabra
Run
# Direct invocation (smoke test only — does nothing useful in phase 1)
./target/release/aldabra
# As an MCP server registered with Claude Code:
# add to ~/.claude.json:
# "aldabra": {
# "command": "/path/to/aldabra",
# "env": {
# "ALDABRA_DATA": "/mnt/cache/appdata/aldabra"
# }
# }
Security model
- Mnemonic source: interactive bootstrap on first run, paste once, encrypted at rest with age. Never written to disk in plaintext.
- Derived keys: in-memory only,
ZeroizeOnDropon 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 preprodfor testing without real ADA.
See also
ROADMAP.md— phased buildoutdocs/architecture.md— deeper design notes- txpipe/pallas — the Rust Cardano building blocks we depend on
- Emurgo/cardano-serialization-lib — reference TX builder if pallas-txbuilder doesn't cover something
- modelcontextprotocol/rust-sdk — rmcp, the Rust MCP server SDK we use