cardano-balance-transaction guide
Standalone Haskell library (no executable) that turns a partial Cardano
transaction into a fully balanced, ready-to-sign one. The single public
entry point is balanceTx. Targets the two most recent eras: Conway
and Dijkstra.
Repository map
| Path |
Purpose |
lib/Cardano/Balance/Tx/Balance.hs |
balanceTx entry point, ErrBalanceTx* errors, PartialTx, ChangeAddressGen, UTxOIndex, updateTx |
lib/Cardano/Balance/Tx/Balance/CoinSelection.hs |
Adapter to cardano-coin-selection (selection params/constraints/strategy) |
lib/Cardano/Balance/Tx/Balance/Surplus.hs |
distributeSurplus, TxFeeAndChange, surplus delta math |
lib/Cardano/Balance/Tx/Balance/TokenBundleSize.hs |
TokenBundleSizeAssessor, mkTokenBundleSizeAssessor |
lib/Cardano/Balance/Tx/Eras.hs |
RecentEra GADT, IsRecentEra, RecentEraConstraints, AnyRecentEra (Conway, Dijkstra) |
lib/Cardano/Balance/Tx/Tx.hs |
Tx/PParams types, serializeTx/deserializeTx, TxOut, min-ada |
lib/Cardano/Balance/Tx/TxWithUTxO.hs |
TxWithUTxO (+ construct, constructFiltered) |
lib/Cardano/Balance/Tx/Sign.hs |
estimateSignedTxSize, estimateSignedTxMinFee, witness counting |
lib/Cardano/Balance/Tx/SizeEstimation.hs |
estimateTxSize, estimateTxCost, TxSkeleton |
lib/Cardano/Balance/Tx/Redeemers.hs |
assignScriptRedeemers, ErrAssignRedeemers |
lib/Cardano/Balance/Tx/TimeTranslation.hs |
TimeTranslation, timeTranslationFromEpochInfo |
lib/Cardano/Balance/Tx/UTxOAssumptions.hs |
UTxOAssumptions, assumedInputScriptTemplate |
lib/Cardano/Balance/Tx/Primitive.hs + Primitive/Convert.hs |
Lightweight value types (qualified W) and ledger conversions |
lib/Cardano/Balance/Tx/{Gen,Primitive/Gen,TxWithUTxO/Gen}.hs |
QuickCheck generators |
test/spec/ |
Hspec/QuickCheck specs; test/data/ |
cabal.project / cardano-balance-tx.cabal |
Dependency pins (CHaP + GHC 9.12.2) and package metadata |
flake.nix / nix/project.nix / justfile |
Build tooling |
docs/ / mkdocs.yml |
MkDocs site |
Build, test, run
Everything routes through the flake (same as .github/workflows/ci.yml):
nix develop # dev shell (GHC 9.12.2, cabal, CHaP access)
just build # nix build .#lib .#unit-tests
just unit # nix run .#unit-tests
just unit "Surplus" # run a matching subset
just CI # build + tests + fourmolu + hlint + nixfmt
just format # fourmolu, cabal-fmt, nixfmt
just docs-build # mkdocs build --strict
Inside nix develop, cabal works too: cabal build lib:cardano-balance-tx -O0,
cabal test unit -O0.
Navigating the code
- Start at
balanceTx in Balance.hs (defined around line 529). Its
internal worker is balanceTxInner. The module re-exports the selection,
surplus, and error types callers need.
- The balancing loop is iterative: add inputs → re-estimate fee/size →
recompute change → repeat until the fee stabilises, then assign
redeemers and validate.
- Era handling is centralised in
Eras.hs: pattern-match RecentEra era
(RecentEraConway / RecentEraDijkstra); add era constraints via
IsRecentEra / RecentEraConstraints. To support a new era you touch
Eras.hs first.
- Ledger ↔ primitive conversions live in
Primitive/Convert.hs
(toConwayTxOut, toDijkstraTxOut, toLedgerCoin, …).
Using cardano-balance-tx
Consume it as a library (source-repository-package in cabal.project).
The public surface is one function:
balanceTx
:: forall era m changeState
. (MonadRandom m, IsRecentEra era)
=> PParams era
-> TimeTranslation
-> UTxOAssumptions
-> UTxOIndex era
-> ChangeAddressGen changeState
-> changeState
-> PartialTx era
-> ExceptT (ErrBalanceTx era) m (Tx era, changeState)
- Build the
UTxOIndex era with constructUTxOIndex.
- Failures come back as
ErrBalanceTx era (e.g.
ErrBalanceTxAssetsInsufficientError,
ErrBalanceTxUnableToCreateChangeError,
ErrBalanceTxInsufficientCollateralError).
- The era type parameter must satisfy
IsRecentEra — instantiate it at
Conway or Dijkstra.
Answering questions
- "What does this library do / how does balancing work?" →
README.md ("What is this", "Architecture") and
docs/architecture.md (pipeline + iteration diagrams).
- "Which eras are supported?" → Conway and Dijkstra (the two most
recent eras). Source of truth:
RecentEra in lib/Cardano/Balance/Tx/Eras.hs.
Babbage is a non-recent era kept only for serialization round-trip
fixtures.
- "How do I call it / what's the API?" → README "Usage" and
docs/modules.md; the signature is balanceTx in Balance.hs.
- "How do I build/test it?" →
README.md "Development",
docs/getting-started.md, the justfile, and .github/workflows/ci.yml.
- "Where did it come from?" →
NOTICE and the README "Origin"
section (extracted from cardano-wallet lib/balance-tx/).
- When a user claims a behaviour the docs don't cover, verify against the
source under
lib/Cardano/Balance/Tx/ before answering.
1---2name: cardano-balance-transaction-guide3description: Guide for working in the cardano-balance-transaction repository (the cardano-balance-tx Haskell library). Load when a task involves balancing Cardano transactions, the balanceTx entry point, coin selection, fee/size estimation, change construction, surplus distribution, token bundle size validation, Plutus redeemer reindexing, or the RecentEra (Conway/Dijkstra) abstraction. Triggers include: cardano-balance-tx, cardano-balance-transaction, balanceTx, PartialTx, ErrBalanceTx, UTxOIndex, ChangeAddressGen, RecentEra, IsRecentEra, RecentEraConway, RecentEraDijkstra, distributeSurplus, TokenBundleSizeAssessor, estimateTxSize, estimateTxCost, assignScriptRedeemers, TimeTranslation, UTxOAssumptions, cardano-coin-selection, cardano-ledger, lib/Cardano/Balance/Tx, "just build", "just unit", "nix run .#unit-tests", GHC 9.12.2, or questions about which Cardano eras this library supports.4---56# cardano-balance-transaction guide78Standalone Haskell library (no executable) that turns a partial Cardano9transaction into a fully balanced, ready-to-sign one. The single public10entry point is `balanceTx`. Targets the two most recent eras: **Conway11and Dijkstra**.1213## Repository map1415| Path | Purpose |16|------|---------|17| `lib/Cardano/Balance/Tx/Balance.hs` | `balanceTx` entry point, `ErrBalanceTx*` errors, `PartialTx`, `ChangeAddressGen`, `UTxOIndex`, `updateTx` |18| `lib/Cardano/Balance/Tx/Balance/CoinSelection.hs` | Adapter to `cardano-coin-selection` (selection params/constraints/strategy) |19| `lib/Cardano/Balance/Tx/Balance/Surplus.hs` | `distributeSurplus`, `TxFeeAndChange`, surplus delta math |20| `lib/Cardano/Balance/Tx/Balance/TokenBundleSize.hs` | `TokenBundleSizeAssessor`, `mkTokenBundleSizeAssessor` |21| `lib/Cardano/Balance/Tx/Eras.hs` | `RecentEra` GADT, `IsRecentEra`, `RecentEraConstraints`, `AnyRecentEra` (Conway, Dijkstra) |22| `lib/Cardano/Balance/Tx/Tx.hs` | `Tx`/`PParams` types, `serializeTx`/`deserializeTx`, `TxOut`, min-ada |23| `lib/Cardano/Balance/Tx/TxWithUTxO.hs` | `TxWithUTxO` (+ `construct`, `constructFiltered`) |24| `lib/Cardano/Balance/Tx/Sign.hs` | `estimateSignedTxSize`, `estimateSignedTxMinFee`, witness counting |25| `lib/Cardano/Balance/Tx/SizeEstimation.hs` | `estimateTxSize`, `estimateTxCost`, `TxSkeleton` |26| `lib/Cardano/Balance/Tx/Redeemers.hs` | `assignScriptRedeemers`, `ErrAssignRedeemers` |27| `lib/Cardano/Balance/Tx/TimeTranslation.hs` | `TimeTranslation`, `timeTranslationFromEpochInfo` |28| `lib/Cardano/Balance/Tx/UTxOAssumptions.hs` | `UTxOAssumptions`, `assumedInputScriptTemplate` |29| `lib/Cardano/Balance/Tx/Primitive.hs` + `Primitive/Convert.hs` | Lightweight value types (qualified `W`) and ledger conversions |30| `lib/Cardano/Balance/Tx/{Gen,Primitive/Gen,TxWithUTxO/Gen}.hs` | QuickCheck generators |31| `test/spec/` | Hspec/QuickCheck specs; `test/data/` | golden fixtures (`babbage/`, `conway/`, `dijkstra/`, `signedTxs/`) |32| `cabal.project` / `cardano-balance-tx.cabal` | Dependency pins (CHaP + GHC 9.12.2) and package metadata |33| `flake.nix` / `nix/project.nix` / `justfile` | Build tooling |34| `docs/` / `mkdocs.yml` | MkDocs site |3536## Build, test, run3738Everything routes through the flake (same as `.github/workflows/ci.yml`):3940```bash41nix develop # dev shell (GHC 9.12.2, cabal, CHaP access)42just build # nix build .#lib .#unit-tests43just unit # nix run .#unit-tests44just unit "Surplus" # run a matching subset45just CI # build + tests + fourmolu + hlint + nixfmt46just format # fourmolu, cabal-fmt, nixfmt47just docs-build # mkdocs build --strict48```4950Inside `nix develop`, cabal works too: `cabal build lib:cardano-balance-tx -O0`,51`cabal test unit -O0`.5253## Navigating the code5455- Start at `balanceTx` in `Balance.hs` (defined around line 529). Its56 internal worker is `balanceTxInner`. The module re-exports the selection,57 surplus, and error types callers need.58- The balancing loop is iterative: add inputs → re-estimate fee/size →59 recompute change → repeat until the fee stabilises, then assign60 redeemers and validate.61- Era handling is centralised in `Eras.hs`: pattern-match `RecentEra era`62 (`RecentEraConway` / `RecentEraDijkstra`); add era constraints via63 `IsRecentEra` / `RecentEraConstraints`. To support a new era you touch64 `Eras.hs` first.65- Ledger ↔ primitive conversions live in `Primitive/Convert.hs`66 (`toConwayTxOut`, `toDijkstraTxOut`, `toLedgerCoin`, …).6768## Using cardano-balance-tx6970Consume it as a library (`source-repository-package` in `cabal.project`).71The public surface is one function:7273```haskell74balanceTx75 :: forall era m changeState76 . (MonadRandom m, IsRecentEra era)77 => PParams era78 -> TimeTranslation79 -> UTxOAssumptions80 -> UTxOIndex era81 -> ChangeAddressGen changeState82 -> changeState83 -> PartialTx era84 -> ExceptT (ErrBalanceTx era) m (Tx era, changeState)85```8687- Build the `UTxOIndex era` with `constructUTxOIndex`.88- Failures come back as `ErrBalanceTx era` (e.g.89 `ErrBalanceTxAssetsInsufficientError`,90 `ErrBalanceTxUnableToCreateChangeError`,91 `ErrBalanceTxInsufficientCollateralError`).92- The era type parameter must satisfy `IsRecentEra` — instantiate it at93 `Conway` or `Dijkstra`.9495## Answering questions9697- **"What does this library do / how does balancing work?"** →98 `README.md` ("What is this", "Architecture") and99 `docs/architecture.md` (pipeline + iteration diagrams).100- **"Which eras are supported?"** → Conway and Dijkstra (the two most101 recent eras). Source of truth: `RecentEra` in `lib/Cardano/Balance/Tx/Eras.hs`.102 Babbage is a *non-recent* era kept only for serialization round-trip103 fixtures.104- **"How do I call it / what's the API?"** → README "Usage" and105 `docs/modules.md`; the signature is `balanceTx` in `Balance.hs`.106- **"How do I build/test it?"** → `README.md` "Development",107 `docs/getting-started.md`, the `justfile`, and `.github/workflows/ci.yml`.108- **"Where did it come from?"** → `NOTICE` and the README "Origin"109 section (extracted from `cardano-wallet` `lib/balance-tx/`).110- When a user claims a behaviour the docs don't cover, verify against the111 source under `lib/Cardano/Balance/Tx/` before answering.