Quickstart¶
Build a transaction end-to-end. The swap wizard answers
chain-anchored swap fields, can derive its rate from a fresh quote,
and emits a unified intent on stdout. Pipe that intent into
tx-build --report -, then pipe the build-output envelope into
report-render. The disburse wizard resolves ADA or USDM
treasury disbursements. The withdraw wizard answers
chain-anchored fields for reward withdrawals.
1. Install¶
macOS (Apple Silicon)¶
brew tap lambdasistemi/tap
brew install amaru-treasury-tx
Linux (x86_64)¶
Grab the AppImage from the releases page:
curl -L \
https://github.com/lambdasistemi/amaru-treasury-tx/releases/latest/download/amaru-treasury-tx.AppImage \
-o amaru-treasury-tx
chmod +x ./amaru-treasury-tx
./amaru-treasury-tx --help
Or use the .deb / .rpm packages from the same release.
From source (any platform)¶
git clone git@github.com:lambdasistemi/amaru-treasury-tx.git
cd amaru-treasury-tx
nix develop
just build
Run from the published flake (no install)¶
EXE='nix run github:lambdasistemi/amaru-treasury-tx#amaru-treasury-tx --'
(Use this EXE everywhere amaru-treasury-tx appears below.)
2. Point at a mainnet node¶
export CARDANO_NODE_SOCKET_PATH=/path/to/cardano-node.socket
(Or pass --node-socket PATH to the CLI.)
3. Fetch the upstream metadata¶
curl -fsSL https://raw.githubusercontent.com/pragma-org/amaru-treasury/main/journal/2026/metadata.json \
-o metadata-mainnet.json
The wizard treats this file as an untrusted hint and verifies every consumed field against the on-chain registry NFT and build-time pinned Plutus blobs before producing an intent.
4. The famous swap, end to end, no intermediate files¶
amaru-treasury-tx \
--node-socket "$CARDANO_NODE_SOCKET_PATH" --network mainnet \
swap-wizard \
--wallet-addr addr1q802wxt6cg6aw0nl0vdzfxavu65rxu3yzhvgayw7chfxymduzkt66uw9t5kspx5jwjecx80dz4g33htknafhdhkvzd5st4f9xu \
--metadata metadata-mainnet.json \
--scope network_compliance \
--usdm 100000 \
--split 33 \
--ada-usdm 0.270 \
--slippage-bps 100 \
--validity-hours 28 \
--description "Swapping ADA for \$100k against an operator-supplied ADA/USDM quote" \
--justification "Required to pay Antithesis as vendor" \
--destination-label "Network Compliance's treasury" \
--extra-signer core_development \
| amaru-treasury-tx \
--node-socket "$CARDANO_NODE_SOCKET_PATH" --network mainnet \
tx-build --out /dev/null --report - \
| amaru-treasury-tx \
report-render --metadata metadata-mainnet.json
What the flags mean:
| Flag | What it controls |
|---|---|
--wallet-addr |
Wallet bech32 address. Wizard picks its largest pure-ADA UTxO as fuel + collateral. |
--metadata |
Local metadata.json (untrusted hint; verified against chain). |
--scope |
One of core_development, ops_and_use_cases, network_compliance, middleware. |
--usdm |
Total USDM the swap should buy. ADA spend is derived from the quote and slippage policy. |
--all-ada |
Alternative to --usdm. Spend the maximum ledger-valid ADA from pure ADA treasury UTxOs in the selected scope. Requires --split; incompatible with --chunk-usdm. |
--split N |
Slice the order into N equal chunks. Use --chunk-usdm X instead to pin per-chunk size. |
--ada-usdm |
Operator-supplied ADA/USDM quote (USDM per ADA). The wizard applies slippage on top. For live retrieval, use the separate swap-quote command. |
--slippage-bps |
Required slippage policy in basis points. There is no hidden default. |
--min-rate |
Expert path: pre-validated minimum USDM per ADA. Bypasses slippage. |
--validity-hours |
Optional. Omit to use the chain's current horizon (longest plutus-translatable slot). When present, must be inside the horizon — overshoot returns a typed wizard error before tx-build runs. |
--description / --justification / --destination-label |
Free-form rationale fields, pinned into the on-chain audit trail. |
--extra-signer SCOPE\|HEX |
Repeated for each witness owner beyond the selected scope owner. Scope names and 28-byte key hashes are accepted; --signer remains as an alias. |
tx-build --report - |
Emits { intent, result } on stdout. Successful result contains both tx-cbor and the mechanical report; expected build failures contain result.failure.code and result.failure.message. Final unsigned transactions are phase-1 preflighted against the sampled chain context before CBOR is written. |
report-render |
Reads the build-output envelope from stdin and renders Markdown. |
5. What flows where¶
| Stream | Contents |
|---|---|
swap-wizard stdout |
Unified swap intent generated from the quote-derived parameters. |
tx-build stdout |
Build-output envelope: top-level intent plus top-level result. |
successful result |
Contains required tx-cbor and report. |
| final stdout | Markdown review report. |
| stderr | swap-wizard: and tx-build: typed traces. |
For a remaining-ADA swap, replace --usdm 100000 with --all-ada
and keep --split N. The wizard uses verified metadata and live
treasury UTxOs to select pure ADA only, reserves the per-order
Sundae overhead plus the minimum treasury leftover, and logs the
computed ADA amount plus implied USDM target before emitting the
intent.
6. Read the audit files before signing¶
The command never asks for confirmation. Read the rendered Markdown
report before handing result.tx-cbor to any signer. The report is
derived from the same build-output envelope that carries the inline
intent and transaction CBOR.
Pre-signing audit checklist:
- the rendered transaction type is
swap; - the scope, required signers, and rationale match the operator decision;
- the validity slot and rendered UTC instant are acceptable;
- conservation has zero residual;
- no
result.failure.codeis present; phase-1 preflight failures are reported as build failures and notx-cboris emitted; result.tx-cboris present in the JSON envelope used for rendering.
Use the typed traces as the second audit trail. A successful run looks like this:
swap-wizard: network mainnet (magic 764824073)
swap-wizard: metadata = metadata-mainnet.json
swap-wizard: VERIFIED scope=network_compliance treasury=addr1xyezq8w… treasuryScriptHash=32201dc1… registryPolicyId=38c627d4… permissionsRewardAccount=a64d1b9e…
swap-wizard: owners core=7095faf3… ops=f3ab64b0… network_compliance=8bd03209… middleware=97e0f6d6…
swap-wizard: wallet utxos: 3
swap-wizard: treasury utxos: 1 (total 1450000000000 lovelace)
swap-wizard: tip slot 186446659
swap-wizard: NetworkConstants swapOrder=addr1x8ax5k9m… usdmPolicy=c48cbb3d… usdmToken=0014df105553444d sundaeFee=1280000
swap-wizard: wallet utxo selected 42e4c279…#0
swap-wizard: treasury utxos selected 64f27254…#0 leftover=1041836734694
swap-wizard: validity tip=186446659 upperBound=186547459 (+100800 slots)
swap-wizard: chunks total=408163265306 chunkSize=12368583797 full=33 remainder=5
swap-wizard: intent.json -> stdout
tx-build: parsed action=Swap network=mainnet
tx-build: connecting to /path/to/cardano-node.socket
tx-build: required utxos: 6
tx-build: handshake ok (magic 764824073 matches intent network=mainnet)
tx-build: built 14987 bytes fee=1041155 total_collateral=1561733
tx-build: re-evaluated 2 redeemers, 0 failed
tx-build: cbor -> /dev/null
tx-build: VALIDATION OK
The VERIFIED scope=… line and the NetworkConstants row are
the chain- and build-time roots binding the produced
transaction to the upstream pin. Read them before signing.
7. Sign + submit¶
After the report and traces pass review, extract result.tx-cbor
from the JSON envelope and create the required detached witnesses.
External signers such as hardware wallets, cardano-wallet-sign,
or MPC services can still feed attach-witness directly.
Create an encrypted age vault once from the Cardano signing-key
material, then use the built-in witness command for signing. Humans
should use --signing-key-paste; the pasted signing-key material is
hidden while the CLI reads it, and the vault passphrase prompt is
no-echo too.
Treat vault create as the import ceremony: pasted or streamed
signing-key material is the only plaintext key input, and normal
signing after this point uses treasury.vault.age plus the passphrase.
Accepted input is either a cardano-cli .skey JSON envelope or one
cardano-addresses address extended signing key line beginning
addr_xsk1. After verifying and backing up the encrypted vault, clear
the clipboard or source buffer under your custody policy.
amaru-treasury-tx --network mainnet vault create \
--signing-key-paste \
--label core_development \
--out treasury.vault.age
# Paste either the full cardano-cli .skey JSON or the addr_xsk1... line.
# The pasted bytes are hidden.
The addr_xsk1... input is the address-level extended signing key
format produced by cardano-addresses, not a root or account private
key. Paste the single bech32 line exactly as exported by the custody
tool.
For automation, stream the signing key from a secret manager and pass the vault passphrase through an inherited file descriptor:
exec 9<<<"$VAULT_PASSPHRASE"
secret-manager-read core-development-payment-skey \
| amaru-treasury-tx --network mainnet vault create \
--signing-key-stdin \
--label core_development \
--out treasury.vault.age \
--vault-passphrase-fd 9
exec 9<&-
exec 9<<<"$VAULT_PASSPHRASE"
The transaction passed to witness --tx may be either raw Conway CBOR
hex or a cardano-cli Tx ConwayEra JSON envelope. If the transaction
comes from cardano-cli, put the full JSON envelope in a file and pass
that file directly to witness; do not paste the JSON interactively
into de-envelope.
jq -e . cli.tx.body.json >/dev/null
test "$(jq -r .cborHex cli.tx.body.json | wc -l | tr -d ' ')" = 1
That check confirms the envelope parses as JSON and that cborHex
contains one logical line. Terminal or browser visual wrapping is fine;
real newlines inside the JSON string are not.
amaru-treasury-tx --network mainnet witness \
--tx unsigned.cbor.hex \
--vault treasury.vault.age \
--vault-passphrase-fd 9 \
--identity core_development \
--out core_development.witness.hex
exec 9<&-
owner_witness_hex="$(
tr -d '\n' < core_development.witness.hex
)"
amaru-treasury-tx attach-witness \
--tx unsigned.cbor.hex \
--witness "$owner_witness_hex" \
--out signed.cbor.hex
amaru-treasury-tx --network mainnet submit --tx signed.cbor.hex
For a cardano-cli envelope input, replace --tx unsigned.cbor.hex
with --tx cli.tx.body.json in the witness command above. Before
attach-witness, unwrap the same envelope once because attach-witness
uses the raw CBOR-hex contract:
amaru-treasury-tx de-envelope < cli.tx.body.json > unsigned.cbor.hex
If the transaction does not declare required signer hashes, add
--expected-key-hash HASH or the explicit --allow-unlisted-key
acknowledgement. Submit within minutes — the wizard's
validityUpperBoundSlot ticks down with the tip.
8. Deterministic quote override¶
amaru-treasury-tx \
--node-socket "$CARDANO_NODE_SOCKET_PATH" --network mainnet \
swap-wizard \
--wallet-addr addr1q... \
--metadata metadata-mainnet.json \
--scope network_compliance \
--usdm 100000 \
--split 33 \
--ada-usdm 0.270 \
--slippage-bps 100 \
--validity-hours 28 \
--description "Swapping ADA for \$100k against an operator-supplied ADA/USDM quote" \
--justification "Required to pay Antithesis as vendor" \
--destination-label "Network Compliance's treasury" \
--extra-signer core_development
For a live ADA/USDM quote, use the swap-quote command (it owns the
outbound HTTP). The wizard itself does no outbound HTTP after #110.
9. Disburse USDM or ADA¶
Most operator disbursements pay USDM, so disburse-wizard defaults to
--unit usdm. --amount is always in the smallest unit: 1e-6 USDM
for USDM, lovelace for ADA.
amaru-treasury-tx \
--node-socket "$CARDANO_NODE_SOCKET_PATH" --network mainnet \
disburse-wizard \
--wallet-addr addr1q... \
--metadata metadata-mainnet.json \
--scope network_compliance \
--beneficiary-addr addr1qvendor... \
--amount 100000000 \
--validity-hours 6 \
--description "Settle March vendor invoice" \
--justification "Approved network-compliance budget line" \
--destination-label "Vendor Ltd." \
--log disburse-wizard.log \
| amaru-treasury-tx \
--node-socket "$CARDANO_NODE_SOCKET_PATH" \
tx-build \
--log disburse-build.log \
--out disburse.cbor.hex
The example pays 100 USDM. For ADA, add --unit ada and pass
lovelace in --amount. The wizard verifies the registry anchors,
selects wallet fuel, selects treasury UTxOs, computes validity, and
emits a unified action = "disburse" intent for tx-build.
See Disburse for the existing-intent form, payload shape, USDM selection rules, and test evidence.
10. Withdraw rewards¶
The withdraw flow has the same wizard-to-builder shape:
amaru-treasury-tx \
--node-socket "$CARDANO_NODE_SOCKET_PATH" --network preprod \
withdraw-wizard \
--wallet-addr addr_test1... \
--metadata metadata-mainnet.json \
--scope core_development \
--validity-hours 6 \
--log withdraw-wizard.log \
| amaru-treasury-tx \
--node-socket "$CARDANO_NODE_SOCKET_PATH" \
tx-build \
--log withdraw-build.log \
--out withdraw.cbor.hex
If the selected treasury reward account has zero rewards,
withdraw-wizard exits 0 and writes no intent. See
Withdraw for the existing-intent form, schema shape, and
synthetic golden evidence.
11. When something goes wrong¶
| Exit | Action |
|---|---|
| 1 (tx-build) | The build aborted before producing CBOR. Expected builder failures print a normalized tx-build: ... failed ... diagnostic; report output, when requested, carries the same stable failure code/message. |
| 3 (swap-quote / swap-wizard / disburse-wizard / tx-build) | Setup or economic error. The trace's ABORT … line names the offending step: quote source failure, registry mismatch, empty wallet UTxO set, treasury affordability shortfall, beneficiary network mismatch, missing UTxOs in chain context, and similar fail-closed cases. |
| 4 (swap-wizard) | Translation error in the expert manual path. Re-check --min-rate, --chunk-usdm/--split. --validity-hours, when present, must be inside the current chain horizon — the wizard returns a typed WizardValidityOvershoot if not. |
| 6 (tx-build) | The N2C handshake reports a network magic that disagrees with the intent's network field. The trace's tx-build: NETWORK MISMATCH … line names both networks; point --node-socket at the right node. |
Both subcommands are fail-closed — neither writes a partial
output. If the trace ends without cbor -> … (or
intent.json -> … from the wizard), nothing was written.
Direct swap-wizard --min-rate remains available as an expert/manual
override for precomputed rates. That path does not create
params.json, so the operator must keep the external quote,
slippage, arithmetic, and affordability audit record separately.
12. Reproduce the known oracles (developer)¶
The golden suite rebuilds frozen transaction fixtures:
nix develop --quiet -c just golden swap
nix develop --quiet -c just golden withdraw
The swap fixture compares byte-for-byte against a bash/cardano-cli oracle. The withdraw fixture is synthetic until issue #17 records a live preprod reward oracle. Both freeze protocol parameters, resolved UTxOs, and evaluator ExUnits, so the tests do not depend on today's chain state. See Parity report, Withdraw, and Freeze workflow.
13. Smoke the signer UX and pipe contracts (developer)¶
Before cutting a release or handing a branch to operators, run:
nix develop --quiet -c just smoke
The smoke check runs the focused signer regression, checks the release-facing help surfaces, exercises the vault-backed witness path, including hidden paste and no-echo passphrase prompts, and exercises the withdraw fixture path through schema validation plus the synthetic CBOR golden.
14. Trust model¶
The full account of what the wizard verifies vs. what it asks
the operator to assert lives in
Trust model: bake-time and run-time
dependency graphs, the verifier's two trust roots, the
field-by-field map of where each intent.json value comes
from. Read it once before signing your first mainnet
transaction.