The Transaction builder
The TransactionBuilder assembles a Transaction from three kinds of parts:
- Inputs, each pointing at an Output we are spending, with an unlocking script builder that will produce the Unlocking Script (ScriptSig), and optionally a signer.
- Outputs, each an amount together with a locking script builder.
- Optionally a Change Output, which receives whatever the Inputs hold beyond the Outputs and the fee (see fees and change).
Calling build(true) checks the result, creates the Transaction, and signs every Input that has a signer.
The examples on this page spend P2PKH Outputs, which is the standard pay-to-address case. The other pages
under Transactions cover the other script types, and the
Developer Guide explains Inputs, Outputs and scripts.
The examples share some setup. We will need Alice’s key and Address, Bob’s Address, and a constant for
the usual Sighash type. Fixtures.fundingTransaction() is the helper from
your first transaction.
// The Sighash type we sign with throughout: ALL, with the FORKID flag that BSV requires.static final int SIGHASH = SigHashType.ALL.value | SigHashType.FORKID.value;
PrivateKey alice = PrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");Address aliceAddress = Address.fromKey(NetworkAddressType.TEST_PKH, alice.getPublicKey());Address bobAddress = Address.fromString(NetworkType.TEST, "mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc");Lock and unlock builders
Section titled “Lock and unlock builders”A LockingScriptBuilder produces an Output’s Locking Script (ScriptPubkey), and an
UnlockingScriptBuilder produces an Input’s Unlocking Script, usually from a Signature it receives during
signing. The library has a pair for each standard script type:
| Script type | Locking builder | Unlocking builder | Page |
|---|---|---|---|
| P2PKH | P2PKHLockBuilder(address) |
P2PKHUnlockBuilder(publicKey) |
this page |
| P2PK | P2PKLockBuilder(publicKey) |
P2PKUnlockBuilder() |
P2PK |
| Multisig | P2MSLockBuilder(keys, m) |
P2MSUnlockBuilder() |
Multisig |
| P2SH | P2SHLockBuilder(redeemScript) |
P2SHUnlockBuilder(script) |
P2SH |
| Hodlocker | HodlLockBuilder(pubkeyHash, height) |
HodlUnlockBuilder(...) |
Hodlocker |
| Data | UnspendableDataLockBuilder, P2PKHDataLockBuilder |
(P2PKH for the latter) | Data outputs |
| Anything | DefaultLockBuilder(script) |
DefaultUnlockBuilder(script) |
Custom builders |
Each locking builder also has a constructor that takes an existing
Scriptand parses it. That is how we read the fields back out of an Output.
Spending from a Transaction
Section titled “Spending from a Transaction”When we have the whole funding Transaction, spendFromTransaction() takes the Output index and reads the
amount and the Locking Script from it. This is the form that our first transaction
uses. The arguments are the signer, the funding Transaction, the Output index, the Input’s
sequence number and the unlocker.
Every Input has its own signer, so Inputs owned by different keys can go into one Transaction.
// Each Input gets the signer for the key that owns the Output it spends.Transaction tx = new TransactionBuilder() .spendFromTransaction(new TransactionSigner(SIGHASH, alice), aliceFunding, 0, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendFromTransaction(new TransactionSigner(SIGHASH, carol), carolFunding, 0, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(carol.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(120_000)) .sendChangeTo(aliceAddress) .build(true);Spending from a UTXO record
Section titled “Spending from a UTXO record”Wallet APIs and indexers usually return UTXOs as records, with a txid, an index, an amount and a Locking
Script. spendFromUtxoMap() takes such a record as a Map, with these keys and value types:
// The shape an indexer or wallet API usually gives us, with scriptPubKey in hex.Map<String, Object> utxoMap = new HashMap<>();utxoMap.put("transactionId", fundingTxId);utxoMap.put("outputIndex", 0);utxoMap.put("satoshis", BigInteger.valueOf(100_000));utxoMap.put("sequenceNumber", TransactionInput.MAX_SEQ_NUMBER);utxoMap.put("scriptPubKey", "76a91494837d2d5d6106aa97db38957dcc294181ee91e988ac");
Transaction tx = new TransactionBuilder() .spendFromUtxoMap(new TransactionSigner(SIGHASH, alice), utxoMap, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000)) .sendChangeTo(aliceAddress) .build(true);It should be noted that the values are cast without any conversion, so outputIndex must be an Integer,
sequenceNumber a Long and satoshis a BigInteger.
Please note that
scriptPubKeymay be hex, as most APIs give it, or ASM (the formScript.toAsmString()gives us). The builder reads any even-length string of hex digits as hex.
Spending from a txid, index and amount
Section titled “Spending from a txid, index and amount”spendFromOutput() takes the txid, index and amount directly. To sign, the signer also needs the Locking Script
of the Output we spend, since the Signature commits to it, so the signer overload takes that script as well.
// We know the txid, index and amount. The signer also needs the Output's Locking Script.Script lockingScript = new P2PKHLockBuilder(aliceAddress).getLockingScript();Transaction tx = new TransactionBuilder() .spendFromOutput(new TransactionSigner(SIGHASH, alice), fundingTxId, 0, BigInteger.valueOf(100_000), lockingScript, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000)) .sendChangeTo(aliceAddress) .build(true);Do note that the older signer overload, without the Locking Script, is deprecated, and throws an
IllegalArgumentExceptionthat names the overload to use.
Spending from an outpoint
Section titled “Spending from an outpoint”spendFromOutpoint() takes the same information as a TransactionOutpoint.
TransactionOutpoint outpoint = new TransactionOutpoint(fundingTxId, 0, BigInteger.valueOf(100_000), new P2PKHLockBuilder(aliceAddress).getLockingScript());
Transaction tx = new TransactionBuilder() .spendFromOutpoint(new TransactionSigner(SIGHASH, alice), outpoint, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000)) .sendChangeTo(aliceAddress) .build(true);Signing later
Section titled “Signing later”If we leave out the signer, build() produces an unsigned Transaction in which each Unlocking Script is
empty. We then sign it once the Transaction is final, e.g. after setting the
lock time, or on another machine that holds the key.
// No signer this time, so build() leaves the Input's Unlocking Script empty.Transaction tx = new TransactionBuilder() .spendFromTransaction(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000)) .sendChangeTo(aliceAddress) .build(true);
// Later, after any changes to the Transaction, we sign Input 0 against the Output it spends.TransactionOutput utxo = fundingTx.getOutputs().get(0);new TransactionSigner(SIGHASH, alice).sign(tx, utxo, 0);TransactionSigner.sign() puts the Signature into the Input’s unlocker, and the unlocker builds the Unlocking
Script from it. The page on signing has the details.
Choosing between build(true) and build(false)
Section titled “Choosing between build(true) and build(false)”build(true) checks the Transaction before creating it, and throws a TransactionException if:
- the Outputs (including the change) add up to more than the Inputs,
- an Output, or the change, is below the dust limit of 256 satoshis (
OP_RETURNdata Outputs excepted), - the fee is more than 150 times the estimate, which guards against forgetting the Change Output,
- a fee set with
setFee()does not match what is left over, which can only happen without a Change Output.
build(false) skips all of these. We need it for Transactions that others will add Inputs to (see
Sighash types). The dust check leaves
data Outputs alone, since they carry no satoshis.
Do note that the checks never look at scripts or Signatures. For that, we run the interpreter.
To switch off one check at a time, we pass a TransactionOption to withOption():
DISABLE_MORE_OUTPUT_THAN_INPUT, DISABLE_LARGE_FEES or DISABLE_DUST_OUTPUTS. DISABLE_ALL switches them all
off.
// Without a Change Output, a 50,000-satoshi fee fails the check for large fees...assertThrows(TransactionException.class, () -> new TransactionBuilder() .spendFromTransaction(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000)) .build(true));
// ...unless we switch that one check off. The other checks still run.Transaction tx = new TransactionBuilder() .spendFromTransaction(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(50_000)) .withOption(TransactionOption.DISABLE_LARGE_FEES) .build(true);
