name: transaction
description: 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.
Transaction building (sc-tools)
This skill documents the sc-tools transaction-building stack centered on Convex.BuildTx.
Workflow
- Build an unbalanced
TxBuilder erausingMonadBuildTxhelpers (spendPublicKeyOutput,spendPlutus*,payTo*,mint*,addWithdrawal, …). - Make the body balancing-friendly:
- If you create outputs with tokens, inline datums, or reference scripts, apply
setMinAdaDepositAllwith protocol parameters. - If you build custom script witnesses, use
buildScriptWitness/buildRefScriptWitness(they set placeholder ex-units that balancing will replace).
- If you create outputs with tokens, inline datums, or reference scripts, apply
- 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: usuallyC.ConwayEra(specialize if you can).NetworkId: required for address construction in many helpers (payToPublicKey,payToScriptInlineDatum, etc).- Spending inputs:
C.TxInfor each UTxO you will spend. - Script inputs:
- Validator/minting policy as
C.PlutusScript lang(inline) or a reference scriptC.TxIn+C.PlutusScriptVersion lang. - Datum mode (inline datum vs datum hash) and redeemers (must have
Plutus.ToData).
- Validator/minting policy as
- Outputs: recipient addresses, script hashes, datums, and the
C.Valuefor each output. - Balancing context:
- Either a
Convex.Wallet.Wallet+ itsUtxoSet, or - Payment credentials +
MonadUtxoQuery(seeConvex.Query).
- Either a
- 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. TxBuildercan observe the final tx body: functions likeaddInputWithTxBodyare powerful but can loop if you make the witness depend on itself.- Order:
TxBuilder’sSemigroupinstance is intentionally reversed so thatdo a; bappliesabeforeb. - Indices are ledger-ordered: inputs are ordered by
TxIn, withdrawals by stake address, minting by policy ID. UselookupIndex*/findIndex*helpers; don’t assume insertion order. - Lookahead requires placeholders: when constructing Plutus witnesses manually, use
BuildTx.buildScriptWitness/BuildTx.buildRefScriptWitness(they useC.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.hssrc/coin-selection/lib/Convex/CoinSelection.hssrc/optics/lib/Convex/CardanoApi/Lenses.hs