Skip to content

hackphobic/txir

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

txir

A Conway-era Cardano transaction IR shaped as a dependency graph, with serde round-tripping.

txir models a transaction as a typed tree where each node carries exactly the data needed to construct it. The variant is the documentation: a SimpleSpend input depends only on a UTxO reference; a Plutus spend additionally carries the resolved output, 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.

Example

use txir::*;

let tx = TxIR::new()
    // simple pubkey spend — children: tx_hash + output_index
    .add_input(InputNode {
        utxo: UTxORef {
            tx_hash: TxHash(Hex([0xab; 32])),
            output_index: 0,
        },
        resolved: None,
        witness: WitnessRequirement::Key,
    })
    // plutus spend with reference script
    .add_input(InputNode {
        utxo: UTxORef {
            tx_hash: TxHash(Hex([0xcd; 32])),
            output_index: 1,
        },
        resolved: None, // builder will resolve from chain
        witness: WitnessRequirement::Plutus(PlutusWitness {
            script: ScriptSource::Reference {
                input: UTxORef {
                    tx_hash: TxHash(Hex([0xef; 32])),
                    output_index: 0,
                },
                script_hash: ScriptHash(Hex([0x11; 28])),
                body: None,
            },
            redeemer: Redeemer {
                data: PlutusData::Constr { tag: 0, fields: vec![] },
                ex_units: None, // builder will evaluate
            },
            datum: Some(DatumSource::Inline),
        }),
    })
    .with_fee(FeeNode::Auto);

let json = serde_json::to_string_pretty(&tx)?;
let back: TxIR = serde_json::from_str(&json)?;

Coverage

Conway era, end to end:

  • Inputs, reference inputs, outputs
  • Collateral, collateral return, total collateral
  • Mint with native or Plutus witnesses
  • Withdrawals
  • Full certificate lineup: stake lifecycle, pool lifecycle, vote delegation, combined Conway certs, DRep registration / update / deregistration, constitutional committee auth / resignation
  • Governance proposals (parameter change, hard fork, treasury withdrawals, no confidence, update committee, new constitution, info) and votes
  • Treasury donation and current treasury value
  • Validity intervals, required signers, network ID, transaction metadata

Design

Dependencies as children

Each node holds the data it depends on directly, so a builder walking the tree always knows what to do at every step. 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.

This also means cross-references are explicit: a ScriptSource::Reference names the UTxORef it depends on. A validate() pass can walk the tree, collect every reference, and check that each is present in tx.reference_inputs.

Resolution model

Leaves the builder may want to fill in later use Option or Auto variants rather than a custom wrapper:

Field Deferred form Meaning
InputNode.resolved None Builder resolves from chain
ScriptSource::Reference.body None Builder fetches at fee-calc time
DatumSource::Reference.body None Same
Redeemer.ex_units None Builder evaluates via Plutus VM
FeeNode Auto Builder computes from size + ex-units
TotalCollateralNode Auto Builder computes from collateral inputs

So the same IR can describe a fully-specified transaction or a sketch waiting on chain state.

Unified witness

One WitnessRequirement enum covers inputs, mints, withdrawals, certs, and votes — Key, NativeScript, and Plutus cases. Not every variant is meaningful in every context (mints can't be Key, votes don't carry a datum), and the types don't enforce that — a validate() pass should. The trade buys substantial deduplication for a small, well-documented invariant surface.

Status

Pre-release. The shape is stable, but field additions are likely as Cardano evolves (e.g. ProtocolParamUpdate is intentionally thin). Breaking changes will follow semver.

License

(your choice)

About

Typed intermediate representation for Cardano transactions.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages