From 79d642635c2453d89484104d5a620c34e94907e7 Mon Sep 17 00:00:00 2001 From: Jordan Millar Date: Thu, 14 May 2026 14:17:58 -0400 Subject: [PATCH 1/2] Remove legacy TxOut from Compatible and Experimental APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit createCompatibleTx now takes [Exp.TxOut (ShelleyLedgerEra era)] plus an explicit Map L.DataHash (L.Data ...) for supplemental datums, mirroring how it already takes Exp.TxCertificates / Exp.TxVotingProcedures / Exp.TxProposalProcedures. Supplemental datums were previously rescued out of the legacy TxOut constructor inside convScriptData' and folded into TxBodyScriptData; Exp.TxOut intentionally does not carry them (only the hash; the full datum lives in the witness set), so callers now thread them in explicitly. Deletes the legacy bridge helpers from Experimental.Tx (fromLegacyTxOut, legacyDatumToDatum, supplementalDatumFromLegacy, toLedgerDatum, DatumDecodingError). They existed only to migrate callers from legacy TxOut CtxTx era; with the Compatible API no longer accepting that type, the bridges have no remaining callers inside cardano-api. They were re-exported only via the experimental namespace, whose stability contract permits breaking changes. After this change, neither Cardano.Api.Compatible.* nor Cardano.Api.Experimental.* references OldApi.TxOut / TxOut CtxTx era. The legacy TxOut ctx era type itself stays in place — UTxO, Tx body construction, fee/balancing, LedgerState, and Byron all still depend on it. --- cardano-api/src/Cardano/Api/Compatible/Tx.hs | 41 +++++------- .../src/Cardano/Api/Experimental/Tx.hs | 6 -- .../Tx/Internal/BodyContent/New.hs | 67 ------------------- .../Test/Cardano/Api/Experimental/Fee.hs | 2 +- 4 files changed, 19 insertions(+), 97 deletions(-) diff --git a/cardano-api/src/Cardano/Api/Compatible/Tx.hs b/cardano-api/src/Cardano/Api/Compatible/Tx.hs index d95d684530..16c24eb9ef 100644 --- a/cardano-api/src/Cardano/Api/Compatible/Tx.hs +++ b/cardano-api/src/Cardano/Api/Compatible/Tx.hs @@ -37,6 +37,7 @@ import Cardano.Ledger.Alonzo.TxWits qualified as Alonzo import Cardano.Ledger.Api qualified as L import Cardano.Ledger.Core qualified as L +import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map import Data.Maybe import Data.Maybe.Strict @@ -44,7 +45,6 @@ import Data.Monoid import Data.OSet.Strict (OSet) import Data.Sequence.Strict qualified as Seq import GHC.Exts (IsList (..)) -import GHC.Stack import Lens.Micro hiding (ix) data AnyProtocolUpdate era where @@ -72,14 +72,19 @@ createCompatibleTx :: forall era . ShelleyBasedEra era -> [TxIn] - -> [TxOut CtxTx era] + -> [Exp.TxOut (ShelleyLedgerEra era)] + -> Map L.DataHash (L.Data (ShelleyLedgerEra era)) + -- ^ Supplemental datums to include in the witness set. Use 'mempty' if + -- none are required. The legacy 'TxOut CtxTx era' bundled supplemental + -- datums inside outputs; 'Exp.TxOut' only carries the datum hash, so + -- callers thread the full datum bodies in here explicitly. -> Lovelace -- ^ Fee -> AnyProtocolUpdate era -> AnyVote era -> Exp.TxCertificates (ShelleyLedgerEra era) -> Either ProtocolParametersConversionError (Tx era) -createCompatibleTx sbe ins outs txFee' anyProtocolUpdate anyVote txCertificates' = +createCompatibleTx sbe ins outs extraDatums txFee' anyProtocolUpdate anyVote txCertificates' = shelleyBasedEraConstraints sbe $ do (updateTxBody, extraScriptWitnesses) <- case anyProtocolUpdate of @@ -186,7 +191,7 @@ createCompatibleTx sbe ins outs txFee' anyProtocolUpdate anyVote txCertificates' [ monoidForEraInEon era ( \aeo -> alonzoEraOnwardsConstraints aeo $ Endo $ do - let sData = convScriptData' sbe outs scriptWitnesses + let sData = convScriptData' sbe extraDatums scriptWitnesses let (datums, redeemers) = case sData of TxBodyScriptData _ ds rs -> (ds, rs) TxBodyNoScriptData -> (mempty, L.Redeemers mempty) @@ -223,25 +228,16 @@ convCertificates (Exp.TxCertificates cs) = convScriptData' :: ShelleyBasedEra era - -> [TxOut CtxTx era] + -> Map L.DataHash (L.Data (ShelleyLedgerEra era)) -> [(ScriptWitnessIndex, AnyWitness (ShelleyLedgerEra era))] -> TxBodyScriptData era -convScriptData' sbe txOuts' scriptWitnesses = +convScriptData' sbe extraDatums scriptWitnesses = caseShelleyToMaryOrAlonzoEraOnwards (const TxBodyNoScriptData) ( \w -> let redeemers = getAnyPlutusScriptWitnessRedeemerPointerMap w scriptWitnesses - datums = mconcat [getAnyWitnessScriptData wit | (_, wit) <- scriptWitnesses] - - supplementalDatums = - let ds = [d | TxOut _ _ (TxOutSupplementalDatum _ d) _ <- txOuts'] - in Alonzo.TxDats $ - fromList - [ (L.hashData d', d') - | d <- ds - , let d' = toAlonzoData d - ] + supplementalDatums = alonzoEraOnwardsConstraints w $ Alonzo.TxDats extraDatums in TxBodyScriptData w (datums <> supplementalDatums) redeemers ) sbe @@ -268,17 +264,16 @@ getAnyPlutusScriptWitnessRedeemerPointerMap w wits = ] createCommonTxBody - :: HasCallStack - => ShelleyBasedEra era + :: ShelleyBasedEra era -> [TxIn] - -> [TxOut ctx era] + -> [Exp.TxOut (ShelleyLedgerEra era)] -> Lovelace -> L.TxBody L.TopTx (ShelleyLedgerEra era) createCommonTxBody era ins outs txFee' = - let txIns' = map toShelleyTxIn ins - txOuts' = map (toShelleyTxOutAny era) outs - in shelleyBasedEraConstraints era $ - L.mkBasicTxBody + shelleyBasedEraConstraints era $ + let txIns' = map toShelleyTxIn ins + txOuts' = map (\(Exp.TxOut o) -> o) outs + in L.mkBasicTxBody & L.inputsTxBodyL .~ fromList txIns' & L.outputsTxBodyL diff --git a/cardano-api/src/Cardano/Api/Experimental/Tx.hs b/cardano-api/src/Cardano/Api/Experimental/Tx.hs index 2e8d487520..779934ad1f 100644 --- a/cardano-api/src/Cardano/Api/Experimental/Tx.hs +++ b/cardano-api/src/Cardano/Api/Experimental/Tx.hs @@ -158,12 +158,6 @@ module Cardano.Api.Experimental.Tx , setTxVotingProcedures , setTxWithdrawals - -- * Legacy Conversions - , DatumDecodingError (..) - , legacyDatumToDatum - , fromLegacyTxOut - , supplementalDatumFromLegacy - -- * TxBodyContent sub type , TxCertificates (..) , TxMintValue (..) diff --git a/cardano-api/src/Cardano/Api/Experimental/Tx/Internal/BodyContent/New.hs b/cardano-api/src/Cardano/Api/Experimental/Tx/Internal/BodyContent/New.hs index 7ef0d706a2..281a68ea50 100644 --- a/cardano-api/src/Cardano/Api/Experimental/Tx/Internal/BodyContent/New.hs +++ b/cardano-api/src/Cardano/Api/Experimental/Tx/Internal/BodyContent/New.hs @@ -69,12 +69,6 @@ module Cardano.Api.Experimental.Tx.Internal.BodyContent.New , extractWitnessableWithdrawals , extractWitnessableVotes , extractWitnessableProposals - - -- * Legacy conversions - , DatumDecodingError (..) - , legacyDatumToDatum - , fromLegacyTxOut - , supplementalDatumFromLegacy ) where @@ -117,7 +111,6 @@ import Cardano.Api.Plutus.Internal.Script ) import Cardano.Api.Plutus.Internal.Script qualified as OldScript import Cardano.Api.Plutus.Internal.ScriptData qualified as Api -import Cardano.Api.Pretty import Cardano.Api.Serialise.Cbor (serialiseToCBOR) import Cardano.Api.Tx.Internal.Body ( CtxTx @@ -126,7 +119,6 @@ import Cardano.Api.Tx.Internal.Body , toShelleyTxIn , toShelleyWithdrawal ) -import Cardano.Api.Tx.Internal.Output qualified as OldApi import Cardano.Api.Tx.Internal.Sign import Cardano.Api.Tx.Internal.TxMetadata import Cardano.Api.Value.Internal @@ -153,7 +145,6 @@ import Control.Monad import Data.Aeson (ToJSON (..), (.=)) import Data.Aeson qualified as Aeson import Data.ByteString.Base16 qualified as Base16 -import Data.ByteString.Short qualified as SBS import Data.Functor import Data.List qualified as List import Data.Map.Ordered.Strict (OMap) @@ -169,7 +160,6 @@ import Data.Set qualified as Set import Data.Text.Encoding qualified as Text import Data.Typeable (cast) import GHC.Exts (IsList (..)) -import GHC.Stack (HasCallStack) import Lens.Micro -- | Error that can occur when constructing an unsigned transaction. @@ -521,63 +511,6 @@ extractDatumsAndHashes TxOutDatumHash{} = Nothing extractDatumsAndHashes (TxOutSupplementalDatum h d) = Just (h, d) extractDatumsAndHashes (TxOutDatumInline h d) = Just (h, d) -hashableScriptDatumToDatumAndHash :: L.Era era => Api.HashableScriptData -> (L.DataHash, L.Data era) -hashableScriptDatumToDatumAndHash sd = - (Api.unScriptDataHash $ Api.hashScriptDataBytes sd, Api.toAlonzoData sd) - -legacyDatumToDatum - :: forall era. IsEra era => OldApi.TxOutDatum CtxTx era -> Maybe (Datum CtxTx (LedgerEra era)) -legacyDatumToDatum (OldApi.TxOutDatumHash _ h) = Just (TxOutDatumHash $ Api.unScriptDataHash h) -legacyDatumToDatum (OldApi.TxOutSupplementalDatum _ hd) = do - let (hash, d) = obtainCommonConstraints (useEra @era) $ hashableScriptDatumToDatumAndHash hd - Just (TxOutSupplementalDatum hash d) -legacyDatumToDatum (OldApi.TxOutDatumInline _ hd) = do - let (hash, d) = obtainCommonConstraints (useEra @era) $ hashableScriptDatumToDatumAndHash hd - Just (TxOutDatumInline hash d) -legacyDatumToDatum OldApi.TxOutDatumNone = Nothing - -fromLegacyTxOut - :: forall era - . HasCallStack - => IsEra era - => OldApi.TxOut CtxTx era - -> Either DatumDecodingError (TxOut (LedgerEra era), Map L.DataHash (L.Data (LedgerEra era))) -fromLegacyTxOut tOut@(OldApi.TxOut _ _ d _) = do - let o = OldApi.toShelleyTxOutAny (convert $ useEra @era) tOut - newDatum :: L.Datum (LedgerEra era) <- obtainCommonConstraints (useEra @era) $ toLedgerDatum d - let txOut = obtainCommonConstraints (useEra @era) $ TxOut $ o & L.datumTxOutL .~ newDatum - suppDats = obtainCommonConstraints (useEra @era) $ supplementalDatumFromLegacy d - return (txOut, suppDats) - --- | Extract supplemental datum data from a legacy 'TxOutDatum'. --- Returns 'mempty' for non-supplemental datums. -supplementalDatumFromLegacy - :: L.Era (LedgerEra era) - => OldApi.TxOutDatum CtxTx era - -> Map L.DataHash (L.Data (LedgerEra era)) -supplementalDatumFromLegacy (OldApi.TxOutSupplementalDatum _ h) = - let ledgerData = Api.toAlonzoData h - in fromList [(L.hashData ledgerData, ledgerData)] -supplementalDatumFromLegacy _ = mempty - -newtype DatumDecodingError = DatumDecodingError String - deriving (Show, Eq) - -instance Error DatumDecodingError where - prettyError (DatumDecodingError msg) = "Datum decoding error: " <> pshow msg - -toLedgerDatum - :: L.Era (LedgerEra era) - => OldApi.TxOutDatum CtxTx era -> Either DatumDecodingError (L.Datum (LedgerEra era)) -toLedgerDatum OldApi.TxOutDatumNone = Right L.NoDatum -toLedgerDatum (OldApi.TxOutDatumHash _ (Api.ScriptDataHash h)) = Right $ L.DatumHash h -toLedgerDatum (OldApi.TxOutSupplementalDatum _ h) = - Right $ L.DatumHash (Api.unScriptDataHash $ Api.hashScriptDataBytes h) -toLedgerDatum (OldApi.TxOutDatumInline _ h) = - case L.makeBinaryData $ SBS.toShort $ Api.getOriginalScriptDataBytes h of - Left e -> Left $ DatumDecodingError e - Right bd -> Right $ L.Datum bd - data TxInsReference era = TxInsReference [TxIn] (Set (Datum CtxTx era)) newtype TxTotalCollateral = TxTotalCollateral {unTxTotalCollateral :: L.Coin} diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Experimental/Fee.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Experimental/Fee.hs index 6c6d57027c..398ca83c96 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Experimental/Fee.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Experimental/Fee.hs @@ -775,7 +775,7 @@ prop_createCompatibleTx_preserves_all_certs = H.property $ do let sbe = convert Exp.ConwayEra inputCerts = Exp.mkTxCertificates Exp.ConwayEra allCerts Api.ShelleyTx _ ledgerTx <- - H.evalEither $ createCompatibleTx sbe [] [] 0 (NoPParamsUpdate sbe) NoVotes inputCerts + H.evalEither $ createCompatibleTx sbe [] [] mempty 0 (NoPParamsUpdate sbe) NoVotes inputCerts let bodyCerts = ledgerTx ^. L.bodyTxL . L.certsTxBodyL Seq.length bodyCerts H.=== expectedCount From e90405b46c67d9bc31977d029b2de11e411e0384 Mon Sep 17 00:00:00 2001 From: Jordan Millar Date: Thu, 14 May 2026 15:40:59 -0400 Subject: [PATCH 2/2] Add changelog fragment for PR #1209 --- ...acy_txout_from_compat_and_experimental.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .changes/20260514_cardano_api_remove_legacy_txout_from_compat_and_experimental.yml diff --git a/.changes/20260514_cardano_api_remove_legacy_txout_from_compat_and_experimental.yml b/.changes/20260514_cardano_api_remove_legacy_txout_from_compat_and_experimental.yml new file mode 100644 index 0000000000..9b06019656 --- /dev/null +++ b/.changes/20260514_cardano_api_remove_legacy_txout_from_compat_and_experimental.yml @@ -0,0 +1,20 @@ +project: cardano-api +pr: 1209 +kind: + - breaking +description: | + Remove legacy `TxOut ctx era` from the Compatible and Experimental APIs. + + `createCompatibleTx` now takes `[Exp.TxOut (ShelleyLedgerEra era)]` plus an explicit `Map L.DataHash (L.Data (ShelleyLedgerEra era))` for supplemental datums, mirroring how it already takes `Exp.TxCertificates` / `Exp.TxVotingProcedures` / `Exp.TxProposalProcedures`. The legacy `TxOut CtxTx era` bundled supplemental datums inside outputs; `Exp.TxOut` only carries the datum hash (the full datum lives in the witness set), so callers now thread them in explicitly. + + Deletes the legacy bridge helpers re-exported from `Cardano.Api.Experimental.Tx`: + + - `fromLegacyTxOut` + - `legacyDatumToDatum` + - `supplementalDatumFromLegacy` + - `toLedgerDatum` + - `DatumDecodingError` + + These existed solely to convert legacy `TxOut CtxTx era` / `TxOutDatum CtxTx era` values into the experimental world. With the Compatible API no longer accepting the legacy type, they had no remaining in-repo callers. They are not re-exported from top-level `Cardano.Api`. + + The legacy `TxOut ctx era` type itself is unchanged — `UTxO`, `Tx` body construction, fee/balancing, `LedgerState`, and Byron all still depend on it.