Fees and change
Any coins in our Inputs that we don’t explicitly send to an Output are paid to the miner as the fee. That
is rarely what we want. Instead, we add a Change Output which sends the remainder, less the fee, back to
ourselves. The TransactionBuilder works out that change amount for us when we call sendChangeToPKH(address)
(or sendChangeToLockBuilder(locker) for another script).
The examples use the keys and the fundingTransaction helper described in About the examples. This page’s examples share the following setup:
final myAddress = aliceKey.toAddress(networkType: NetworkType.TEST);final recipient = Address('n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg');final fundingTx = fundingTransaction(P2PKHLockBuilder.fromAddress(myAddress).getScriptPubkey(), BigInt.from(100000));final signer = DefaultTransactionSigner(sighashAll, aliceKey);P2PKHUnlockBuilder unlocker() => P2PKHUnlockBuilder(aliceKey.publicKey);Fee per kilobyte
Section titled “Fee per kilobyte”withFeePerKb(n) sets the rate in satoshis per 1000 bytes. The default is 50. Do note that the Change Output is
placed first in the Transaction.
final builder = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(60000)) .sendChangeToPKH(myAddress) .withFeePerKb(500); // satoshis per 1000 bytes
final tx = builder.build(true);
// The Change Output comes first. The fee is whatever the Outputs leave unspent.final change = tx.outputs[0].satoshis;final paidOut = tx.outputs.fold(BigInt.zero, (BigInt sum, out) => sum + out.satoshis);final fee = BigInt.from(100000) - paidOut; // 30 satoshisA fixed fee
Section titled “A fixed fee”withFee(satoshis) sets the fee exactly, and the change is whatever remains.
final tx = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(60000)) .sendChangeToPKH(myAddress) .withFee(BigInt.from(120)) // exactly 120 satoshis .build(true);
tx.outputs[0].satoshis; // 39880: 100000 - 60000 - 120A fee from the signed size
Section titled “A fee from the signed size”Signatures are deterministic, so we can build once to measure the signed size, and then build again with the right fee:
// Let's build once to learn the signed size, then rebuild with a fee for that size.TransactionBuilder draft(BigInt fee) => TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(60000)) .sendChangeToPKH(myAddress) .withFee(fee);
const satoshisPerKb = 500;final signedBytes = draft(BigInt.zero).build(false).serialize().length ~/ 2;final fee = BigInt.from((signedBytes * satoshisPerKb / 1000).ceil());
final tx = draft(fee).build(true);Without a Change Output
Section titled “Without a Change Output”If we do not call sendChangeTo…, everything that is not paid out becomes the fee. When the Outputs use up the
Inputs exactly, no Change Output is added either.
final builder = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(99900));final tx = builder.build(false);
builder.getFee(); // 100: everything not paid out goes to the minerCall order matters
Section titled “Call order matters”The change amount is calculated at the moment we call sendChangeToPKH, withFee or withFeePerKb, from the
Outputs added so far. We therefore add every payment first.
// Wrong! The change is worked out when sendChangeToPKH is called,// before the 60000-satoshi payment exists.final wrong = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .sendChangeToPKH(myAddress) .spendToPKH(recipient, BigInt.from(60000)) .build(true); // passes the checks, yet pays out more than the Input holdsThe fix is to call sendChangeToPKH after the last spendTo…, or to call withFee or withFeePerKb last, which
recalculates the change. A safe order is: Inputs, Outputs, sendChangeTo…, withFee…, build.
What build(true) checks
Section titled “What build(true) checks”build(true) throws before signing when the amounts look wrong:
// Spending more than the Inputs hold:final overspend = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(200000));// overspend.build(true) throws TransactionAmountException
// An Output under the dust limit (TransactionBuilder.DUST_AMOUNT, 50 satoshis):final dust = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(10)) .sendChangeToPKH(myAddress);// dust.build(true) throws TransactionAmountException
// A fee far above the estimate and no change address:final hugeFee = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(1000));// hugeFee.build(true) throws TransactionFeeException| Exception | Cause |
|---|---|
TransactionAmountException |
the Outputs exceed the Inputs, or an Output or the change is below 50 satoshis |
TransactionFeeException |
the fee is more than 50 times the estimate and there is no Change Output, or withFee does not match what is left over |
TransactionException |
a negative amount, or more than 21 million BSV |
Individual checks can be turned off with withOption, for example to allow a zero-satoshi
data Output:
// We switch individual checks off when the Output is intentional:final tx = TransactionBuilder() .spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, unlocker()) .spendToPKH(recipient, BigInt.from(10)) .sendChangeToPKH(myAddress) .withOption(TransactionOption.DISABLE_DUST_OUTPUTS) .build(true);The options are DISABLE_MORE_OUTPUT_THAN_INPUT, DISABLE_LARGE_FEES and DISABLE_DUST_OUTPUTS.
build(false) skips all of the checks.

