Transaction building (sc-tools)
This skill documents the sc-tools transaction-building stack centered on Convex.BuildTx.
Workflow
- Build an unbalanced
TxBuilder era using MonadBuildTx helpers (spendPublicKeyOutput, spendPlutus*, payTo*, mint*, addWithdrawal, …).
- Make the body balancing-friendly:
- If you create outputs with tokens, inline datums, or reference scripts, apply
setMinAdaDepositAll with protocol parameters.
- If you build custom script witnesses, use
buildScriptWitness/buildRefScriptWitness (they set placeholder ex-units that balancing will replace).
- Balance it with
Convex.CoinSelection (adds missing inputs, fees, collateral, execution units, checks min-UTxO, etc).
- Sign and submit the resulting transaction.
Minimal skeleton
import Cardano.Api qualified as C
import Convex.BuildTx qualified as BuildTx
import Convex.CoinSelection qualified as CoinSelection
import Control.Tracer (nullTracer)
type Era = C.ConwayEra
mkTxBuilder
:: C.TxIn
-> C.AddressInEra Era
-> C.Value
-> BuildTx.TxBuilder Era
mkTxBuilder input recipient value =
BuildTx.execBuildTx $ do
BuildTx.spendPublicKeyOutput input
BuildTx.payToAddress recipient value
-- Later (in a MonadBlockchain/MonadError context):
--
-- (tx, _changes) <-
-- CoinSelection.balanceForWallet nullTracer wallet walletUtxos (mkTxBuilder input recipient value) CoinSelection.TrailingChange
-- _txIdOrErr <- sendTx tx
Make outputs min-UTxO safe (recommended)
If you can query protocol parameters (e.g. in mockchain or a node-backed environment), apply:
txb <- BuildTx.execBuildTxT $ do
...
pp <- queryProtocolParameters
BuildTx.setMinAdaDepositAll pp
This avoids common CheckMinUtxoValueError failures during balancing.
If the era becomes ambiguous, prefer a local type alias/annotation (e.g. type Era = C.ConwayEra) rather than visible type applications on execBuildTxT.
Inputs to collect (when writing a tx)
era: usually C.ConwayEra (specialize if you can).
NetworkId: required for address construction in many helpers (payToPublicKey, payToScriptInlineDatum, etc).
- Spending inputs:
C.TxIn for each UTxO you will spend.
- Script inputs:
- Validator/minting policy as
C.PlutusScript lang (inline) or a reference script C.TxIn + C.PlutusScriptVersion lang.
- Datum mode (inline datum vs datum hash) and redeemers (must have
Plutus.ToData).
- Outputs: recipient addresses, script hashes, datums, and the
C.Value for each output.
- Balancing context:
- Either a
Convex.Wallet.Wallet + its UtxoSet, or
- Payment credentials +
MonadUtxoQuery (see Convex.Query).
- Extra requirements: collateral UTxO availability (ADA-only), required signers (
addRequiredSignature), validity bounds, min-UTxO.
Use lenses when a helper doesn’t exist
Convex.BuildTx intentionally doesn’t wrap every TxBodyContent field. When you need to set
something not covered (validity interval, metadata, governance fields, …), use
Convex.CardanoApi.Lenses + addBtx:
import Control.Lens (set)
import Convex.CardanoApi.Lenses qualified as L
BuildTx.addBtx $
set L.txValidityUpperBound (C.TxValidityUpperBound C.shelleyBasedEra (Just upperSlot))
Gotchas (high-signal)
- Era constraints matter: inline datums, reference inputs, and reference scripts require
C.IsBabbageBasedEra era.
TxBuilder can observe the final tx body: functions like addInputWithTxBody are powerful but can loop if you make the witness depend on itself.
- Order:
TxBuilder’s Semigroup instance is intentionally reversed so that do a; b applies a before b.
- Indices are ledger-ordered: inputs are ordered by
TxIn, withdrawals by stake address, minting by policy ID. Use lookupIndex* / findIndex* helpers; don’t assume insertion order.
- Lookahead requires placeholders: when constructing Plutus witnesses manually, use
BuildTx.buildScriptWitness / BuildTx.buildRefScriptWitness (they use C.ExecutionUnits 0 0); balancing will substitute real ex-units.
Navigation
- API index: TRANSACTION.md
- Recipes: PATTERNS.md
- Debugging: TROUBLESHOOTING.md
Key code modules (in this repo):
src/base/lib/Convex/BuildTx.hs
src/coin-selection/lib/Convex/CoinSelection.hs
src/optics/lib/Convex/CardanoApi/Lenses.hs
1---2name: transaction3description: Build, balance, and sign Cardano transactions in Haskell using sc-tools (`Convex.BuildTx` for constructing unbalanced transactions; `Convex.CoinSelection` for coin selection/balancing/signing; `Convex.Query`/Blockfrost/node/mockchain backends for fetching inputs). Use when writing or debugging off-chain code that adds inputs/outputs/mints/withdrawals/collateral/reference scripts, sets validity intervals and required signers, or fixes balancing/script execution/submission failures.4---56# Transaction building (sc-tools)78This skill documents the sc-tools transaction-building stack centered on `Convex.BuildTx`.910## Workflow11121. Build an **unbalanced** `TxBuilder era` using `MonadBuildTx` helpers (`spendPublicKeyOutput`, `spendPlutus*`, `payTo*`, `mint*`, `addWithdrawal`, …).132. Make the body **balancing-friendly**:14 - If you create outputs with tokens, inline datums, or reference scripts, apply `setMinAdaDepositAll` with protocol parameters.15 - If you build custom script witnesses, use `buildScriptWitness`/`buildRefScriptWitness` (they set placeholder ex-units that balancing will replace).163. Balance it with `Convex.CoinSelection` (adds missing inputs, fees, collateral, execution units, checks min-UTxO, etc).174. Sign and submit the resulting transaction.1819## Minimal skeleton2021```haskell22import Cardano.Api qualified as C23import Convex.BuildTx qualified as BuildTx24import Convex.CoinSelection qualified as CoinSelection25import Control.Tracer (nullTracer)2627type Era = C.ConwayEra2829mkTxBuilder30 :: C.TxIn31 -> C.AddressInEra Era32 -> C.Value33 -> BuildTx.TxBuilder Era34mkTxBuilder input recipient value =35 BuildTx.execBuildTx $ do36 BuildTx.spendPublicKeyOutput input37 BuildTx.payToAddress recipient value3839-- Later (in a MonadBlockchain/MonadError context):40--41-- (tx, _changes) <-42-- CoinSelection.balanceForWallet nullTracer wallet walletUtxos (mkTxBuilder input recipient value) CoinSelection.TrailingChange43-- _txIdOrErr <- sendTx tx44```4546### Make outputs min-UTxO safe (recommended)4748If you can query protocol parameters (e.g. in mockchain or a node-backed environment), apply:4950```haskell51txb <- BuildTx.execBuildTxT $ do52 ...53 pp <- queryProtocolParameters54 BuildTx.setMinAdaDepositAll pp55```5657This avoids common `CheckMinUtxoValueError` failures during balancing.5859If the era becomes ambiguous, prefer a local type alias/annotation (e.g. `type Era = C.ConwayEra`) rather than visible type applications on `execBuildTxT`.6061## Inputs to collect (when writing a tx)6263- `era`: usually `C.ConwayEra` (specialize if you can).64- `NetworkId`: required for address construction in many helpers (`payToPublicKey`, `payToScriptInlineDatum`, etc).65- Spending inputs: `C.TxIn` for each UTxO you will spend.66- Script inputs:67 - Validator/minting policy as `C.PlutusScript lang` (inline) or a reference script `C.TxIn` + `C.PlutusScriptVersion lang`.68 - Datum mode (inline datum vs datum hash) and redeemers (must have `Plutus.ToData`).69- Outputs: recipient addresses, script hashes, datums, and the `C.Value` for each output.70- Balancing context:71 - Either a `Convex.Wallet.Wallet` + its `UtxoSet`, or72 - Payment credentials + `MonadUtxoQuery` (see `Convex.Query`).73- Extra requirements: collateral UTxO availability (ADA-only), required signers (`addRequiredSignature`), validity bounds, min-UTxO.7475## Use lenses when a helper doesn’t exist7677`Convex.BuildTx` intentionally doesn’t wrap every `TxBodyContent` field. When you need to set78something not covered (validity interval, metadata, governance fields, …), use79`Convex.CardanoApi.Lenses` + `addBtx`:8081```haskell82import Control.Lens (set)83import Convex.CardanoApi.Lenses qualified as L8485BuildTx.addBtx $86 set L.txValidityUpperBound (C.TxValidityUpperBound C.shelleyBasedEra (Just upperSlot))87```8889## Gotchas (high-signal)9091- **Era constraints matter**: inline datums, reference inputs, and reference scripts require `C.IsBabbageBasedEra era`.92- **`TxBuilder` can observe the final tx body**: functions like `addInputWithTxBody` are powerful but can loop if you make the witness depend on itself.93- **Order**: `TxBuilder`’s `Semigroup` instance is intentionally reversed so that `do a; b` applies `a` before `b`.94- **Indices are ledger-ordered**: inputs are ordered by `TxIn`, withdrawals by stake address, minting by policy ID. Use `lookupIndex*` / `findIndex*` helpers; don’t assume insertion order.95- **Lookahead requires placeholders**: when constructing Plutus witnesses manually, use `BuildTx.buildScriptWitness` / `BuildTx.buildRefScriptWitness` (they use `C.ExecutionUnits 0 0`); balancing will substitute real ex-units.9697## Navigation9899- API index: [TRANSACTION.md](TRANSACTION.md)100- Recipes: [PATTERNS.md](PATTERNS.md)101- Debugging: [TROUBLESHOOTING.md](TROUBLESHOOTING.md)102103Key code modules (in this repo):104105- `src/base/lib/Convex/BuildTx.hs`106- `src/coin-selection/lib/Convex/CoinSelection.hs`107- `src/optics/lib/Convex/CardanoApi/Lenses.hs`