README + supporting docs were written for ourselves (deployment paths,
internal product comparisons, internal task lists, build pipeline
artifacts) instead of for users of the software. This pass refocuses
them on what the software is, how to install, configure, and use it.
- README.md: full rewrite. New shape — What it does / Architecture /
Build / Run / Configuration / MCP tools / Security model / Status /
License / Dependencies. Drops the internal "why we built it"
narrative, drops phase-status claims that drifted stale, drops
internal deployment paths.
- ROADMAP.md: deleted. Was an internal task-list with [x]/[ ] items
showing incremental private development. The README's Status
section now communicates what's actually shipped.
- docs/architecture.md: scrub cross-project comparisons referencing
unrelated internal Sulkta codebases.
- aiken-escrow/README.md: drop reference to a non-existent spec file;
rewrite the Status checklist to reflect what's actually done
rather than what was open at the time of writing.
- audits/2026-05-09-escrow-e2e.md: scrub internal image names +
container paths; the audit findings (chain hashes, validator hash,
what each tx proved) are the public-useful part and stay.
- audits/2026-05-09-escrow-internal-audit.md: drop references to
feature-flag-gated branches that no longer exist.
- Dockerfile: drop the dead `escrow_wip surface` phrase from comments.
- Cargo.toml: drop the cross-project comparison comment that named
an unrelated internal service.
- crates/aldabra-{core,dao}: scrub internal preprod-test naming from
source comments — same technical content, generic phrasing.
- README.md: drop "first Sulkta Rust project — workout for crafting-
table's Rust toolchain" paragraph + the `crafting-table build aldabra`
recipe. Both reference non-public Sulkta-internal infrastructure.
- Dockerfile: drop "Built nightly on Lucy (see lucy-infra/scripts/
nightly-builds.sh)" comment + the `lucy-registry:5000/aldabra/mcp`
internal image-name advertisement.
- Cargo.toml: drop the comment block referencing the deleted
`docs/internal-build-rewrites.md` + `crafting-table + Lucy + dev
hosts` Sulkta-internal-builds note. The patch block stands on its
own.
GitHub is canonical for aldabra now (per 2026-05-10 architecture call —
Gitea is a pull-mirror cache, not a forge we publish to). Build process
fetches pallas direct from github.com, no rewrite needed.
- Dockerfile: drop the `--mount=type=secret` git_credentials dance + the
url.insteadOf rewrite. cargo fetches pallas straight from
github.com/Sulkta-Coop/pallas at the locked SHA. No secret needed.
- docs/internal-build-rewrites.md: removed. The rewrite was the entire
reason for the doc, and the rewrite is gone.
Internal builds (Lucy / crafting-table / dev hosts) still hit
github.com for pallas, same as external builds. One extra WAN hop per
crate, but consistent everywhere and no environment-specific config.
Cargo.toml + Cargo.lock now point at https://github.com/Sulkta-Coop/pallas
for the pallas-fork patch entries. External clones from either public
mirror (github.com/Sulkta-Coop or gitlab.com/sulkta) build out of the box
— no LAN access needed.
Sulkta-internal builds short-circuit to LAN gitea via a `git config
url.X.insteadOf` rewrite on each host. Symmetric: covers both github and
gitlab → gitea. Same locked SHA either way; routing is environment-level,
not source-of-truth. See docs/internal-build-rewrites.md.
Dockerfile build-time rewrite also flipped to take public-URL inputs;
uses `gitea.sulkta.lan` instead of the bare LAN IP.
Two HIGH validator-side bugs + several MED/LOW off-chain issues found
in the subagent-driven audit on this branch. New validator hash:
a8081acef26935d9b5f44b92052178e17301b6d6e6808c91c5b56f5d.
## HIGH-1: Deposit redeemer let depositors drain tokens
aiken-escrow/validators/escrow.ak Deposit branch now requires
`value_geq_value(new_value, in_value)` before computing net_added.
Previously net_added could carry negative quantities (when new_value
< in_value component-wise), letting a depositor write a matching
new_d.deposits with reduced values and pocket the difference as
wallet change. Latent under v1 ADA-only MCP usage but the validator
must hold against all callers.
## HIGH-2: Empty/partial deposits enabled funds drain via Veto/Refund
Veto and Refund branches now require
`value_eq(deposits_to_value(d.deposits), in_value)` — the tracked
deposits must account for the full locked value. Previously
`refund_outputs_satisfy(_, [])` was vacuously true on empty deposits,
so a driver could fire Veto/Refund on an escrow opened with
`initial_contributor=None` (deposits=[], in_value>0) and pocket the
input's lovelace as change.
Defense in depth: escrow_open builder now refuses
`initial_contributor=None`. New helper `deposits_to_value` folds
deposit FlatValues into a Value via `assets.add` for the equality
check.
## MED: off-chain fixes
- escrow_open min-utxo bumped 1M → 2M (Conway-era inline-datum
+ script-address outputs need ~1.4-1.7 ADA, NOT the 1 ADA default).
- escrow_settle_unsigned + escrow_refund_timeout_unsigned now derive
`validity_lower_ms` via slot_to_posix_ms(network, slot) instead of
Koios's `block_time*1000` — the chain reconstructs `lower` from the
slot, so Koios's ~1s drift could pass off-chain preflight while the
chain rejects at the strict-`>` boundary.
- escrow_open_unsigned MCP tool no longer accepts (and silently
discards) `fee_lovelace` — the unsigned-tx builder auto-estimates.
## LOW: defensive depth
- escrow_veto + escrow_refund_timeout: `qty as u64` → `u64::try_from`
so a corrupt or adversarial datum with negative i128 qty can't slip
through with a wraparound.
## Tests
- 36 escrow builder tests pass (added rejects_no_initial_contributor)
- 132 dao tests pass under --features escrow_wip
- aldabra-mcp release build clean
## Infra
- Validator artifact files (plutus.json, validator.cbor.hex)
regenerated. Dockerfile already wired to bake them at
/etc/aldabra/escrow/ for MCP tools' validator_script_path arg.
- Internal audit findings written up at
audits/2026-05-09-escrow-internal-audit.md including the v2-deferred
items (multi-asset spend-input, lovelace-not-cross-checked, etc.)
Third-party audit still required before any mainnet deployment.
The pallas patch in [patch.crates-io] is now ssh://git@gitea after the
2026-05-06 token-scrub. Inside a docker build the rust container has no
SSH key and no known_hosts for gitea, so cargo's libgit2 / system-git
both reject the fetch.
Mount /root/.git-credentials as a BuildKit secret (mode=0400, required)
and set a build-time `url.HTTP.insteadOf SSH` rewrite. Cargo.toml and
Cargo.lock keep their SSH URLs — the rewrite is git-CLI-level so no
credential ever lands in the lock file or in any image layer.
Build invocation:
docker build --secret id=git_credentials,src=<creds-file> ...
where <creds-file> is one line `http://USER:PAT@192.168.0.5:3001`.
This mirrors the pattern crafting-table already uses on its runner
(.git-credentials + url.insteadOf rewrite). nightly-builds.sh on Lucy
will need an analogous --secret arg before it can rebuild this branch.
end-to-end working wallet: paste 24-word mnemonic, age-encrypt at rest,
on unlock derive root + payment + stake keys, build cip-19 base address,
serve four tools over mcp stdio (wallet.address, wallet.network,
wallet.balance, wallet.utxos).
deps added: ed25519-bip32 0.4 (pallas only ships raw ed25519, not the
cardano variant of bip32 hd derivation), cryptoxide 0.4 for pbkdf2-hmac-sha512,
age 0.10 for at-rest mnemonic encryption, rpassword 7 for tty-only passphrase
prompts, toml 0.9 for config.toml.
new modules:
- crates/aldabra-core/src/derive.rs — payment + stake key derivation, hash
- crates/aldabra-chain/src/koios.rs — real reqwest impl, asset aggregation
- crates/aldabra-mcp/src/{bootstrap,config,tools}.rs
caught one bug pre-flight: get_balance was clobbering same-asset
quantities across utxos instead of summing. fixed + regression test.
headless support via ALDABRA_PASSPHRASE env (mcp clients own stdin so
the rpassword prompt path can't run). docker secret / systemd
EnvironmentFile sources it in production.
dockerfile: multi-stage rust:1.95-bookworm → debian:bookworm-slim, tini
as pid1, non-root aldabra user, /var/lib/aldabra owned 700.
29 unit tests + 1 ignored live-koios test. preprod smoke test exercised
initialize → tools/list → tools/call wallet.address end-to-end via
piped json-rpc; correct preprod address came back from canonical
abandon-art mnemonic.
phase 2 (send) is next.