The pages before this one described what a transaction does. This one describes what it is made of: a small set of nested objects, each with a fixed list of fields and nothing else.
Four objects, nested
A transaction is not a flat record. It is one object holding two arrays, and the entries in those arrays hold objects of their own.
Transaction
├─ version, lockTime, subnetworkId, gas, payload
├─ inputs[]
│ └─ Input
│ ├─ previousOutpoint → { transactionId, index }
│ ├─ signatureScript
│ ├─ sequence
│ └─ computeCommit
└─ outputs[]
└─ Output
├─ value
├─ scriptPublicKey → { version, script }
└─ covenant → { authorizingInput, covenantId } (optional) That is the whole model. Everything a wallet shows you that is not in this tree - the fee, the sender, the amount an input is worth, the balance - is either computed from it or fetched from somewhere else.
The transaction
Seven fields wrap the two arrays.
| Field | Holds | Why it exists |
|---|---|---|
version | 0 or 1 | Selects which field set inputs and outputs use. See below. |
inputs | array of Input | The coins being spent. A non-coinbase transaction with none is rejected outright. |
outputs | array of Output | The coins being created. |
lockTime | u64 | The earliest point the transaction may be included. Zero means immediately. |
subnetworkId | 20 bytes | All zeros for an ordinary transaction. A reserved value marks the coinbase. |
gas | u64 | Zero for an ordinary transaction. Carries the gas commitment on a subnetwork transaction. |
payload | bytes | Arbitrary data, empty by default, opened to ordinary transactions at Crescendo. |
A plain payment sets three of them. Version zero, the inputs, the outputs. Lock time stays zero, the subnetwork id stays all zeros, gas stays zero, and the payload stays empty - so the four fields that look most exotic are the four an ordinary wallet never touches.
Lock time is worth one more sentence, because it is two things sharing a field. Below five hundred billion the value is read as a minimum DAA score; at or above it, as a Unix timestamp in milliseconds. The threshold is far past any block count the network will reach and far before any plausible timestamp, so the two ranges cannot collide.
An input names a coin, it does not contain it
This is the single most important thing about the model.
| Field | Holds | Why it exists |
|---|---|---|
previousOutpoint | outpoint | Which coin is being spent. |
signatureScript | bytes | The proof that spending it is allowed - signatures, and for pay-to-script-hash the redeem script too. |
sequence | u64 | Relative lock time, and the finality flag. See below. |
computeCommit | enum | How much script execution this input is paying for. |
The outpoint is two fields and nothing more:
| Field | Holds |
|---|---|
transactionId | 32 bytes - the transaction that created the coin |
index | u32 - which of that transaction’s outputs it was |
Notice what is absent: the amount. An input says that coin, not this much. The value being spent appears nowhere in the transaction, which means a transaction on its own cannot be checked. Somebody has to look the coin up.
Sequence carries two meanings in one number. The top bit is a disable flag; when it is set, the input has no relative lock. When it is clear, the low thirty-two bits are a number of blocks that must have passed since the coin’s own DAA score before the input may be spent. Separately, if every input is set to the maximum value - which has that top bit set - the transaction counts as final and its lock time no longer holds it back.
The compute commitment is one of two variants, and the transaction version decides which:
| Variant | Type | Used by |
|---|---|---|
sigOpCount | u8 | Version 0 |
computeBudget | u16 | Version 1 |
Both resolve to the same thing - an allowance of script units for that input, on top of a small free budget every input gets. A sigop count says “this input checks N signatures”. A compute budget prices arbitrary execution directly, which is what a covenant or a proof verification needs.
An output is an amount and a lock
| Field | Holds | Why it exists |
|---|---|---|
value | u64 | The amount, in sompi. Integers only; KAS is a display convention. |
scriptPublicKey | struct | The condition for spending the coin next. |
covenant | optional | The output’s covenant binding. Version 1 only. |
The locking script is versioned, though only one version exists so far:
| Field | Holds |
|---|---|
version | u16 - zero is the only value consensus accepts today |
script | bytes - decoded from the recipient’s address |
And a covenant binding, when present, is also two fields:
| Field | Holds |
|---|---|
authorizingInput | u16 - the index of the input that permitted this output |
covenantId | 32 bytes - the lineage the output belongs to |
Which lock types are actually in circulation is charted at output script classes.
The coin record a node supplies
Because an input is only a pointer, validating a transaction means pairing every input with the coin it names. That coin is its own object, held in the node’s UTXO set rather than in the transaction:
| Field | Holds | Used for |
|---|---|---|
amount | u64 | The value being spent. This is where the fee arithmetic gets its inputs. |
scriptPublicKey | struct | The lock the signature script has to satisfy. |
blockDaaScore | u64 | When the coin was created - the baseline for relative locks and for maturity. |
isCoinbase | bool | Whether the longer coinbase maturity rule applies. |
covenantId | optional | The lineage the coin carries, checked against the covenant rules on spend. |
Validation is a join between a transaction and a state. The transaction brings pointers and proofs, the node brings the coins, and a transaction paired with its coins is what the rules actually run against.
Two practical consequences follow. A wallet cannot build a transaction without first fetching its own coins - that is what getUtxosByAddresses is for, and why the node serving it needs the UTXO index switched on. And a node that has never seen the parent transaction cannot judge the child at all, which is exactly the situation the orphan pool exists to hold.
What is computed rather than stored
| Value | Where it comes from |
|---|---|
| Transaction id | A hash over the transaction with signature scripts and payload left out, so the id is settled before anyone signs. |
| Transaction hash | A hash over everything, signatures included. |
| Fee | The gap: total input value minus total output value. See Fees & Mass. |
| Compute mass | Derived from the size and the compute commitments. |
| Transient mass | Derived from serialized size. |
| Storage mass | Derived from the input and output amounts. |
Storage mass is the one exception to the heading. The transaction carries a commitment to it, and consensus recalculates the figure and rejects the transaction if the two disagree - so it is stored and computed, and the stored copy is only ever a claim being checked. The SDKs fill it in; it is not a field you set.
The two versions
| Version | Enables |
|---|---|
0 | The original layout. Inputs commit a sigop count; outputs have no covenant field. |
1 | Covenant bindings on outputs, compute budgets on inputs, non-native subnetworks, gas commitments. Arrived with Toccata. |
The two are not mixable. A version 0 transaction cannot carry a covenant binding, and a version 1 transaction commits compute per input rather than counting signature operations. Version 0 stays valid indefinitely, so an ordinary payment needs nothing from the second row.
See Also: UTXO Model, Fees & Mass