A Conway-era Cardano transaction IR shaped as a dependency graph, with JSON serde round-tripping and validation.
txir models a transaction as a typed tree where each node carries exactly the data needed to construct it. A key InputNode depends only on a UTxO reference; a Plutus spend additionally carries the script source, datum source, redeemer, and execution units — all as typed fields, with no out-of-band registries mapping redeemers to inputs by index.
The whole tree is Serialize + Deserialize, so the IR doubles as a stable interchange format between builders, validators, snapshot tests, and non-Rust tooling.
use txir::prelude::*;
use pallas_addresses::{Address, Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
let key_hash = PubKeyHash::new([0x11u8; 28]);
let addr = Address::Shelley(ShelleyAddress::new(
Network::Testnet,
ShelleyPaymentPart::key_hash(key_hash),
ShelleyDelegationPart::Null,
));
let tx = TxIR {
inputs: vec![InputNode {
utxo: OutputRef { transaction_id: TxHash::new([0xabu8; 32]), index: 0 },
resolved: None,
witness: InputWitness::Key,
}],
outputs: vec![
OutputNode { address: addr.clone(), value: build::value::lovelace(40_000_000), datum: None, reference_script: None },
OutputNode { address: addr, value: build::value::lovelace(9_500_000), datum: None, reference_script: None },
],
..TxIR::new()
};
let json = serde_json::to_string_pretty(&tx).unwrap();
validate(&tx).expect("valid tx");Several fields use Option or Auto variants to represent values that a downstream builder may fill in later:
| Field | Deferred form | Meaning |
|---|---|---|
InputNode.resolved |
None |
Builder resolves from chain |
ScriptSource::Reference.body |
None |
Builder fetches for fee-calc |
Redeemer.ex_units |
None |
Builder evaluates via Plutus VM |
FeeNode |
Auto |
Builder computes from size + ex-units |
CollateralPlan.total |
None |
Builder computes from collateral inputs |
Each node holds the data it depends on directly. There are no side tables associating redeemers with inputs by ordinal, no implicit script lookups, no "you must also remember to add X to the witness set." If a Plutus action needs a redeemer, it's a field on that action.
Cross-references are explicit: ScriptSource::Reference names the OutputRef it depends on. validate() checks that every such reference resolves to an entry in tx.reference_inputs.
validate(tx: &TxIR) -> Result<(), Vec<ValidationError>> walks every node and reports every detectable invariant violation. No short-circuit — all checks run.
Checks include: duplicate inputs, dangling references, reference-script body integrity, mint-policy mismatches, empty mint assets, collateral rules, validity interval ordering, network consistency, datum presence, witness-kind/body agreement, and credential-witness matching.
| Feature | Default | Description |
|---|---|---|
json |
yes | serde::Serialize/Deserialize on all public types |
validate |
yes | validate() function and ValidationError types |
pallas-tx-convert |
no | to_cbor() — CBOR encoding for simple payments only (inputs, outputs, fee, ttl); any other field returns ConvertError::Unsupported. See module docs. |
- pallas-primitives — provides atomic Cardano types (
TransactionInput,Value,NativeScript, etc.) and CBOR codec. - pallas-addresses — address encoding/decoding.
- pallas-crypto — Blake2b hashing for script hash and datum hash computation.
- pallas-codec — CBOR serialization framework (
minicbor). - plutus-parser —
PlutusDatatype and#[derive(AsPlutus)]derive macro.
txir does not build or submit transactions. It is an intermediate representation.
This crate covers Conway-era transactions only. Older eras are explicitly out of scope.
cargo run --example simple_payment
cargo run --example plutus_spend
cargo run --example mint_and_send
cargo run --example governance_voteThis crate is pre-release. APIs may change. Semantic versioning will begin at 0.2.0.