tx-graph
Emit one Conway transaction, or a bounded set of Conway transaction
CBOR files, as RDF. The emitted graph contains the optional
operator-entity overlay, the transaction body (inputs, outputs,
addresses with payment + stake credentials, fee, mint, withdrawal,
certificates, collateral, proposals), and their cross-references in
canonical Turtle or JSON-LD. When the rules file registers a CIP-57
blueprint for a script, Plutus datum and redeemer fields are decoded
into typed fixture-local predicates; decode failures keep the raw
bytes and add a cardano:decodeError literal.
tx-graph is a pure transformation. It does not query a node, read a
UTxO JSON file, or fetch missing transactions. If a parent transaction
is present in the input set, the input is resolved from that in-memory
set; otherwise the graph remains well-formed and the missing parent is
reported on stderr.
tx-graph — pure (rules + [cbor]) → ttl transformation
Usage: tx-graph [--rules FILE] [--in-dir DIR] [--out-dir DIR]
[--format FORMAT] [CBOR...]
tx-graph — operator-entity overlay + body emitter. Loads operator-authored
rules (overlay-only mode) or drives the joint-graph body emitter on a lattice
of Conway transactions (--in-dir / positional / stdin). The lattice resolves
itself internally — no node, no UTxO file, no external chain source. Output
format defaults to Turtle.
Available options:
--rules FILE Operator-authored rules file (.yaml/.yml or .ttl).
Used alone, emits overlay-only Turtle to stdout.
Combined with inputs, merged into the joint graph(s).
--in-dir DIR Directory of *.cbor files; each is one Conway
transaction in the input lattice. Mutually exclusive
with positional arguments.
--out-dir DIR Write one <txid-hex>.ttl per input into DIR. If
absent and exactly one input is given, emits to
stdout.
--format FORMAT Output format: 'turtle' or 'json-ld'.
(default: "turtle")
CBOR... Conway tx CBOR file paths. '-' reads one tx from
stdin. Mutually exclusive with --in-dir.
-h,--help Show this help text
Input modes
| Input | Rules | Output | Behaviour |
|---|---|---|---|
--rules rules.yaml only |
required | stdout | Overlay only — emits the operator-entity overlay (cardano:Entity + cardano:Identifier blocks for every entity in the rules file). |
one positional CBOR path, or - |
optional | stdout unless --out-dir is supplied |
Single graph — emits the transaction body and, when rules are supplied, the overlay plus entity-labelled credentials. |
| multiple positional CBOR paths | optional | --out-dir DIR required |
Batch graph emission — computes every tx id, indexes the set in memory, resolves inputs against that set, and writes one <txid>.ttl per input. |
--in-dir DIR |
optional | --out-dir DIR required |
Directory graph emission — reads every *.cbor child in sorted order and emits the same batch graph set. |
--in-dir and positional CBOR arguments are mutually exclusive.
Single-input stdout mode is the convenient path for one-off
inspection. Batch mode is the path for a bounded transaction lattice:
load exactly the CBOR files that define the boundary, then query the
emitted Turtle files with SPARQL.
Examples
Emit the operator-entity overlay from a rules file:
Emit one transaction graph to stdout:
Read one transaction from stdin:
Emit a bounded transaction set produced by tx-fetch:
JSON-LD output for one input:
Decode an inline datum against the blueprint registered by the
rules file. The predicate names come from the blueprint constructor
and field titles, and use the fixture-local : namespace rather
than the canonical cardano: vocabulary:
_:outputDatum1 a cardano:Datum ;
cardano:hasHash _:hash_datum_c9bc91a9f2f9d50c ;
:SwapOrder_recipient _:outputDatum1_recipient .
_:outputDatum1_recipient :_0_pubKeyHash _:outputDatum1_recipient_pubKeyHash .
_:outputDatum1_recipient_pubKeyHash a cardano:Identifier ;
cardano:leafType "Bytes" ;
cardano:bytesHex "64f35d26b237ad58e099041bc14c687ea7fdc58969d7d5b66e2540ef" .
Output shape
The Turtle output is canonical (byte-stable) and structured as:
- Prefix declarations —
cardano:,rdfs:, fixture-local:. - Operator-entity overlay — verbatim from the rules loader
(
Cardano.Tx.Graph.Rules.Load), onecardano:Entityblock per declared entity plus onecardano:Identifierblock per(leafType, bytesHex)pair. - Transaction body —
_:tx a cardano:Transaction ; ..., followed by per-cluster blocks for inputs, outputs, mints, withdrawals, certs, collateral inputs, and proposals. - Blueprint-decoded payloads — datum and redeemer sub-blocks
use
:<Constructor>_<field>predicates when a registered CIP-57 blueprint decodes the Plutus data; otherwise the graph keepscardano:hasRawBytes, withcardano:decodeErroron decode failure. - Address decompositions — one block per unique address, linking the address to its payment and stake credentials, and each credential to the corresponding identifier blank node (entity-named or raw-bytes-named).
The JSON-LD output is the same triple set serialized as a single
JSON document with @context (the three prefixes) and @graph (a
flat array of subject-grouped objects). --format turtle and
--format json-ld produce set-equivalent triple sets; Turtle is
the byte-diff anchor in the test harness.
Per-section triple coverage
The body emitter walks every Conway-era body field that carries semantic content. The triples below are emitted regardless of whether operator rules are supplied; rules.yaml only changes how the bytes are labelled (entity-named vs raw-bytes-named bnodes).
Transaction-level predicates
cardano:hasInput,cardano:hasReferenceInput,cardano:hasCollateralInput— one edge perTxInin the corresponding ledger field.cardano:hasOutput,cardano:hasCollateralReturn— one edge per body output. The collateral-return output is on the same predicate family but distinguishable by its anchor predicate (used bytx-view --view cli-treeto elide it from the spending-output list).cardano:hasFee— integer lovelace literal.cardano:hasValidityInterval— sub-block withcardano:invalidBefore/cardano:invalidHereafterintegers when set.cardano:hasWithdrawal— one edge perWithdrawal.cardano:hasMint— one edge per minted asset class (positive or negative quantity).cardano:hasCertificate— one edge perCertificate(stake registration / delegation / pool registration / drep declaration / vote delegation / governance authorisation).cardano:hasRequiredSigner— one edge per required-signer identifier (PaymentKey leaf).cardano:hasProposal,cardano:hasVote— one edge each per governance proposal / vote.cardano:hasRedeemer— one edge per Plutus redeemer with itscardano:hasPurpose,cardano:hasIndex,cardano:hasData, andcardano:hasExUnitssub-blocks.cardano:scriptDataHash,cardano:auxiliaryDataHash— when set.cardano:totalCollateral— integer lovelace when set.cardano:hasReferenceScript(per output) — emitted on the output's body when the output carries an attached reference script.
Input triples
cardano:fromTxOutRef→ typedcardano:TxOutRefsub-block.cardano:hasTxId(on theTxOutRef) →_:hash_txid_<full-hex>bnode carryingcardano:leafType "TxId"+cardano:bytesHex.cardano:hasIndex(on theTxOutRef) — integer.cardano:resolvedTo→ resolved-cardano:Outputsub-block when the parent transaction is present in the input set. Carries the parent's address + value at the time of consumption.
Output triples
cardano:atAddress→ typedcardano:Addresssub-block (see Address decompositions below).cardano:lovelace— integer.cardano:hasAssetValue→ typed RDF list ofcardano:Assetentries when the output carries native tokens.cardano:hasDatum→ typedcardano:Datumsub-block when the output carries either an inline datum (withcardano:hasRawBytesand/or blueprint-decoded predicates) or a datum hash (withcardano:hasHash).cardano:hasReferenceScript→ typed sub-block when the output has an attached reference script (inline or hash-only).
Asset / multi-asset triples
The cardano:hasAssetValue payload is an RDF list. Each entry is a
typed cardano:Asset block:
cardano:hasIdentifier→_:cred_assetclass_<full-hex>bnode with the concatenatedpolicy-id ++ asset-namebytes andcardano:leafType "AssetClass".cardano:quantity— integer (positive on outputs, signed onhasMint).
Address decomposition
Each unique (payment-cred, stake-ref) pair emits one
cardano:Address block:
cardano:bech32— full bech32 address literal.cardano:hasPaymentCredential→<base>CredPaymentsub-block.cardano:hasStakeCredential→<base>CredStakesub-block (when the stake reference is non-null).
Each credential sub-block carries one
cardano:hasIdentifier edge to the underlying identifier bnode
(entity-named when rules.yaml covers the credential's
(leafType, bytesHex) pair, otherwise raw-bytes-named).
Blueprint-decoded datum / redeemer payloads
When the rules file registers a CIP-57 blueprint for the script controlling the datum, the walker emits a typed-emit projection of the Plutus data tree:
- Each constructor's titled fields become
:<ConstructorTitle>_<fieldTitle>predicates rooted at the enclosing data subject. - A constructor without a
titlefalls back to a positional:_<index>predicate. "dataType": "map"(CIP-57 SchemaMap) — a PlutusMappayload renders as an RDF list ofcardano:Asset-shapedOpenObject {"key" -> k, "value" -> v}entries; the walker materialises each entry as a positional:_<i>bnode with the decoded:keyand:valuetriples on the entry.- Plutus byte fields →
_:<scope>_<field>_<i>bnodes withcardano:bytesHexliteral andcardano:leafType "Bytes". - Plutus integer fields → integer literals directly on the enclosing predicate.
- Decode failure →
cardano:decodeError "<reason>"literal on the enclosing data subject; raw bytes are preserved oncardano:hasRawBytes.
Bnode-label naming scheme
Bnode labels are file-local and only need to be unique inside the Turtle document. The emitter mints them deterministically from the underlying bytes so that the same identifier emitted from two positions in a tx collapses to a single RDF node (intentional deduplication for the operator overlay) while distinct identifiers never collide on label:
- Identifier leaves (TxId, datum hash, script hash, payment /
stake credentials, asset classes) →
_:<family>_<role>_<full-hex>where: <family>iscredfor operator-declarable credential leaves (payment / stake key / script, asset class, policy, pool id, drep key / script) andhashfor body-walker hash leaves (TxId, datum hash, script hash, scriptdata hash, auxiliarydata hash).<role>is the leafType in lowercase (paymentkey,paymentscript,stakekey,stakescript,assetclass,txid, …).<full-hex>is the lowercase base16 encoding of the bytes, not truncated. (Earlier emitter versions truncated to 16 hex chars; that was a bnode-collision hazard for stub or long-zero-prefix byte sets and is fixed in 0.2.3.0.)- Address-level bnodes →
<base>Addr,<base>CredPayment,<base>CredStakewhere<base>is the entity slug (when rules.yaml binds the payment credential) or the raw-bytes form, concatenated with the stake credential when present:<paymentBase>_s<stakeRole><stakeFullHex>for non-null stake. This pinning is what makes two addresses sharing a payment credential but differing in stake credential mint distinct bnodes (the SundaeSwap V3 order-book pattern surfaced the payment-cred-only collision in 0.2.2.0; fixed in 0.2.3.0). - Per-tx-position bnodes —
_:input<N>,_:output<N>,_:refInput<N>,_:collateral<N>,_:withdrawal<N>,_:certificate<N>, etc. — are positional (1-based on the ledger body). These collide across distinct Turtle files emitted for distinct transactions (each tx's body has its own_:input1); for cross-tx SPARQL the recommended pattern is one Turtle file per tx + multiple--datafiles at query time, which lets the SPARQL engine rename per-file blank nodes uniquely.
Library entry point
import Cardano.Tx.Graph.Emit
import Cardano.Tx.Graph.Rules.Load (loadRulesFile, RulesLoadResult (..))
emitJoint :: FilePath -> ConwayTx -> ResolvedUTxO -> IO ByteString
emitJoint rulesPath tx utxo = do
Right result <- loadRulesFile rulesPath
let Right graph =
emit tx utxo (rulesEntities result) (rulesBlueprints result)
pure (serialize Turtle graph)
Both emit and serialize are pure; the only IO happens at the
rules-loading and CBOR-loading boundaries. The CLI builds the
ResolvedUTxO argument from the input transaction set before calling
the emitter.
See also
- rewriting-rules grammar — the YAML sugar and Turtle subset the rules loader consumes.
- tx-inspect — collapse + rename pipeline for the same Conway transactions, rendered as a structured human report.