Parsing and serialising
A Transaction is serialised to raw hex for broadcasting and storage, and parsed back from it again. The
byte layout is described in the guide’s Transaction format
page.
The examples use the keys and the fundingTransaction helper described in About the examples. This page works with a signed Transaction, signed, and its raw hex, rawHex:
// A signed Transaction to work with: the one from "Your first transaction".final myAddress = aliceKey.toAddress(networkType: NetworkType.TEST);final fundingTx = fundingTransaction(P2PKHLockBuilder.fromAddress(myAddress).getScriptPubkey(), BigInt.from(100000));final signed = TransactionBuilder() .spendFromTxnWithSigner(DefaultTransactionSigner(sighashAll, aliceKey), fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, P2PKHUnlockBuilder(aliceKey.publicKey)) .spendToPKH(Address('n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg'), BigInt.from(60000)) .sendChangeToPKH(myAddress) .withFeePerKb(100) .build(true);final rawHex = signed.serialize();Parsing
Section titled “Parsing”final tx = Transaction.fromHex(rawHex);
tx.id; // transaction id, hex, in the byte order block explorers showtx.version; // 2tx.nLockTime; // 0
for (final input in tx.inputs) { input.prevTxnId; // the transaction it spends from input.prevTxnOutputIndex; // which output of that transaction input.sequenceNumber; input.script; // the unlocking script}for (final output in tx.outputs) { output.satoshis; // BigInt output.script; // the locking script}Input scripts parse as DefaultUnlockBuilders. To read their parts, we parse them with the matching builder,
such as P2PKHUnlockBuilder.fromScript. To recognise
Output types, we use script templates.
Serialising
Section titled “Serialising”final hex = signed.serialize(); // raw transaction, ready to broadcastfinal copy = Transaction.fromHex(hex);
copy.serialize() == hex; // true: parsing and serialising preserve every bytecopy.id == signed.id; // trueFinding the Outputs that pay you
Section titled “Finding the Outputs that pay you”When a Transaction arrives, we compare its Locking Scripts with our own to find the Outputs our wallet can spend:
// Which Outputs of a received Transaction can this wallet spend?final myScript = P2PKHLockBuilder.fromAddress(myAddress).getScriptPubkey().toHex();
final mine = <int>[];for (var i = 0; i < tx.outputs.length; i++) { if (tx.outputs[i].script.toHex() == myScript) mine.add(i);}As a map
Section titled “As a map”toObject() gives us a map for logging or JSON, and Transaction.fromJSONMap reads the same shape back.
final Map<String, dynamic> obj = signed.toObject();// {hash: ..., version: 2, inputs: [{prevTxId, outputIndex, sequenceNumber, script}],// outputs: [{satoshis, script}], nLockTime: 0}; scripts are hexBIP69 ordering
Section titled “BIP69 ordering”sort() orders the Inputs and Outputs by the BIP69 rules, so that their order reveals nothing about which Output
is the change. We sort before signing:
// We sort the Inputs and Outputs (BIP69) while the Transaction is unsigned.// Sorting a signed Transaction would invalidate its Signatures.final tx = TransactionBuilder() .spendFromTxn(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, P2PKHUnlockBuilder(aliceKey.publicKey)) .spendFromTxn(fundingB, 0, TransactionInput.MAX_SEQ_NUMBER, P2PKHUnlockBuilder(aliceKey.publicKey)) .spendToPKH(Address('n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg'), BigInt.from(90000)) .spendToPKH(myAddress, BigInt.from(39000)) .build(false) ..sort();
// Now we sign each Input against the Output it spends, by its new position.final utxos = {fundingTx.id: fundingTx.outputs[0], fundingB.id: fundingB.outputs[0]};final signer = DefaultTransactionSigner( SighashType.SIGHASH_ALL.value | SighashType.SIGHASH_FORKID.value, aliceKey);for (var i = 0; i < tx.inputs.length; i++) { signer.sign(tx, utxos[tx.inputs[i].prevTxnId]!, i);}
