Per rescope 2026-05-06: real code repos get SSH for git auth, no
embedded credentials in URLs at all. Companion to commit a3a8421
which dropped the embedded token; this drops the HTTP transport
in favor of pure SSH key auth.
- Cargo.toml [patch.crates-io] URLs now ssh://git@192.168.0.5:23/...
- Cargo.lock source URLs match.
- .cargo/config.toml [net] git-fetch-with-cli = true unchanged —
still required so cargo defers to system git, which uses the
configured SSH identity.
Hosts that build aldabra need:
- /root/.ssh/config alias 'gitea' → 192.168.0.5:23, IdentityFile
pointing at a key registered to the kayos Gitea account
- The corresponding private key
The ed25519 key generated for this is at
/root/.openclaw/keys/id_ed25519_kayos_gitea on the dev box. Pubkey
registered on kayos's Gitea account 2026-05-06. The crafting-table
runner on Lucy still uses an HTTP credential helper for now — that's
operational state in a kayos-controlled container, allowed under
the rescope. Will migrate to SSH later if Cobb wants full parity.
Hard rule from Cobb 2026-05-06: zero secrets hardcoded in committed
source. The [patch.crates-io] block had the kayos Gitea PAT embedded
in the URL, which cargo then duplicated into Cargo.lock's source URLs.
Fix:
- Cargo.toml [patch.crates-io] URLs are now tokenless
(http://192.168.0.5:3001/...)
- Cargo.lock source URLs scrubbed to match
- .cargo/config.toml adds [net] git-fetch-with-cli = true so cargo
defers to system git for fetches; system git authenticates via
the user's git credential helper (~/.git-credentials chmod 600).
Operators (devs + crafting-table runner) need a working git credential
helper for the LAN Gitea, configured out-of-band (NOT in this repo).
Pattern: `git config --global credential.helper store` +
`echo http://USER:TOKEN@192.168.0.5:3001 > ~/.git-credentials &&
chmod 600 ~/.git-credentials`. After Cobb rotates the kayos PAT,
update that file on every host that builds aldabra.
HIGH:
- HIGH-1 enforce_value_cap helper applied to wallet.send,
wallet.mint, wallet.mint.cip68_nft, wallet.script.spend. each
gained a `force` arg; cap also covers the user_lovelace+ref_lovelace
sum on cip68_nft. wallet.stake.delegate skipped (2 ada deposit is
protocol-fixed, not a transfer to a non-wallet destination).
- HIGH-2 wallet.tx_summary mcp tool — read-only decode of a conway
tx cbor → typed TxSummary (inputs, outputs+assets, fee, certs,
mint, witness count, aux-data presence). new aldabra-core::inspect
module. callers MUST run this before wallet.sign_partial /
wallet.submit_signed_tx on any cbor they didn't build themselves.
MEDIUM:
- M-1 zeroize stack-resident extended_bytes after SecretKeyExtended
consumes them. tx.rs::payment_key_to_private + sign.rs::add_witness.
- M-2 atomic 0o600 mnemonic file create via OpenOptions+
OpenOptionsExt. removes the prior toctou window between fs::write
(default umask) and chmod 600.
- M-3 prompt_or_env_passphrase + unlock_passphrase helpers wrap the
passphrase in Zeroizing<String>. ALDABRA_PASSPHRASE env still
unzeroizable in the env block itself (documented headless tradeoff).
- M-4 is_hex_64 validator on submit_tx response — koios error wrapped
in quotes can no longer round-trip as a fake tx_hash.
LOW + cleanup:
- L-1 checked_add for inner sums of checked_sub patterns in tx.rs.
remaining sites (mint.rs, stake.rs, plutus.rs) deferred — same
pattern, can't overflow with realistic cardano amounts but
defensive. picked up next.
- L-2 root key scoped to a block in main.rs — XPrv drops + wipes
after deriving payment_key + stake_key + address. saves ~96 bytes
of secret material lifetime.
- L-3 TxStatus gained a Pending variant for the mempool-but-not-yet-
confirmed case. previously rendered as Confirmed{block_height: None}
which was misleading.
- L-4 .expect("we built this key") → typed ? propagation in
tx.rs::prepare_payment.
- L-5 removed dead fns (build_and_sign, decode_hex) + unused imports.
WALLET GENERATION (audit prompted gap-find):
aldabra had only an import path. no "generate fresh wallet" tool.
- Mnemonic::generate() — bip39::Mnemonic::generate_in(English, 24)
with the rand feature. returns (Mnemonic, Zeroizing<String>) so
the caller can display the phrase once for cold backup.
- aldabra --generate-mnemonic — print fresh phrase, exit. no disk.
- aldabra --bootstrap-new — generate + display + encrypt one-shot.
- bip39 dep gains the rand feature for OsRng-backed generation.
- standard 24-word BIP-39, recoverable from any cardano wallet.
mcp tools: 16 → 17 (added wallet.tx_summary).
unit tests: 88 → 93. cargo audit clean (0 cves), cargo build clean
(0 warnings). all four cli flags smoke-tested:
--generate-mnemonic prints + exits; --bootstrap-new generates +
encrypts + derives a real preprod address; mnemonic.age has 0o600
perms confirmed atomic.
audit doc memory/spec-aldabra-audit-2026-05-04.md updated with
status markers.
unblocks named mints. wallet.mint now accepts an optional `metadata`
arg (json object); explorers + wallets render the asset with name/image
instead of <asset1xyz...>.
new aldabra-core::metadata module:
- json_to_metadatum: serde_json::Value → Metadatum (recursive). numbers
must fit i64 (cardano metadata Int width). strings >64 bytes split
into Array<Text> chunks at utf-8 char boundaries (CIP-25 v2
long-string convention). null is rejected.
- build_cip25_aux_data(policy_id_hex, asset_name_hex, json_value):
builds the label-721 wrapper (Map { 721: Map { policy_bytes:
Map { name_bytes: attrs }, "version": "2.0" } }), wraps in
AuxiliaryData::PostAlonzo, returns cbor bytes.
mint module:
- new build_signed_mint_with_metadata + build_unsigned_mint now take
optional cip25_metadata. backward-compat: build_signed_mint is a
thin no-metadata wrapper.
- prepare_mint + build_mint_staging plumb aux_data_cbor through.
staging.auxiliary_data(bytes) is the new fork API surface — when
set, conway::build_conway_raw decodes + computes
auxiliary_data_hash automatically.
- regression test build_signed_mint_with_metadata_produces_aux_hash:
decodes the resulting signed cbor, asserts both
body.auxiliary_data_hash is Some and tx.auxiliary_data is present.
catches the failure mode where metadata is silently dropped.
mcp wallet.mint gains a `metadata` arg field surfaced via schemars
JsonSchema. tools/list shape correctly carries the optional json
object.
depends on Sulkta-Coop/pallas@feat-aux-data — vendored via
[patch.crates-io] in the workspace Cargo.toml. PR upstream pending.
56 → 65 unit tests. 8 → 8 mcp tools (count unchanged, wallet.mint
gained an arg).
new aldabra-core::mint module:
- PolicySpec enum: SingleSig, SingleSigTimelock, NofK
- SingleSig{pkh}: ScriptPubkey native script
- SingleSigTimelock{pkh, slot}: ScriptAll[ScriptPubkey, InvalidHereafter(slot)]
- NofK{n, [pkhs]}: ScriptNOfK
- PolicySpec::single_sig(payment) + single_sig_timelock(payment, slot)
convenience constructors that derive the pkh from a PaymentKey.
- policy_id() = pallas_traverse::ComputeHash<28>::compute_hash, which
is blake2b-224 of (0x00 || cbor) — the canonical native-script hash.
- to_cbor() for callers that want the script bytes raw.
build_signed_mint / build_unsigned_mint:
- two-pass fee like the send path, plus a few extras specific to mint:
staging.mint_asset(policy, name, qty), .script(Native, cbor),
.disclosed_signer(payment_pkh) — the disclosed_signer surfaces the
required signature in the tx body so the chain knows which witness
to verify against the script.
- positive qty mints (asset goes into dest output), negative qty burns
(asset comes out of input holdings, change preserves leftover).
- token-bearing change must hold ≥ min_utxo lovelace — same guard as
the send path.
mcp tools:
- wallet.policy.create — args: invalid_after_slot? — returns
{policy_id_hex, script_cbor_hex, type}.
- wallet.mint — args: dest_address, dest_lovelace (≥ 1 ADA),
asset_name_hex, quantity (i64), invalid_after_slot? — auto-generates
a single-sig policy bound to the wallet's payment key, builds, signs,
submits.
8 → 10 mcp tools. 48 → 56 unit tests.
3.2 (CIP-25 metadata) is BLOCKED on pallas-txbuilder 0.32/0.35 — both
hardcode `auxiliary_data: None` in the conway builder. options for next
session: (a) post-build CBOR injection, (b) assemble tx via
pallas-primitives directly, (c) wait for upstream. flagged in the
spec doc.
3.3 (CIP-68) depends on 3.2. 3.6 (MAP 2-of-2) needs the multi-key
signing flow on the build side; PolicySpec::NofK variant is ready but
build_signed_mint only sign with one key today.
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.
Repo skeleton for sulkta-wallet, the rust-native cardano lite wallet
with MCP server interface. Builds end-to-end, types in place,
real cardano primitives land next pass.
Crates:
wallet-core — pure crypto + types. mnemonic, key derivation,
signing. No I/O. Security boundary.
wallet-chain — pluggable backends. ChainBackend trait, Koios
client (stub for now). Ogmios + submit in phase 2.
wallet-mcp — the binary. stdio MCP transport via rmcp.
Phase plan in ROADMAP.md, threat model in docs/architecture.md.
This is also Cobb's first Rust project + a real-world workout for
crafting-table's rust toolchain.