Skip to content

Lock time and sequence numbers

A Transaction’s nLockTime stops miners from including it before a given block height (values below 500,000,000) or Unix time in seconds (values from 500,000,000). It turns out that nodes enforce it only if at least one Input has a sequence number below 0xFFFFFFFF. When all the Inputs are final, the lock time is ignored. See the transaction format for both fields.

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 fundingTx = fundingTransaction(P2PKHLockBuilder.fromAddress(myAddress).getScriptPubkey(), BigInt.from(100000));
final signer = DefaultTransactionSigner(sighashAll, aliceKey);
final tx = TransactionBuilder()
.spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER,
P2PKHUnlockBuilder(aliceKey.publicKey))
.spendToPKH(myAddress, BigInt.from(99900))
.lockUntilBlockHeight(900000) // after the Inputs, since it lowers their sequence numbers
.build(true);
tx.nLockTime; // 900000
tx.inputs[0].sequenceNumber; // 0xFFFFFFFE, so the lock time is enforced

lockUntilBlockHeight lowers the sequence numbers of the Inputs already added to 0xFFFFFFFE, so we call it after the spendFrom… calls:

final tx = TransactionBuilder()
.lockUntilBlockHeight(900000) // no Inputs yet, so no sequence number is changed
.spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER,
P2PKHUnlockBuilder(aliceKey.publicKey))
.spendToPKH(myAddress, BigInt.from(99900))
.build(true);
tx.inputs[0].sequenceNumber; // 0xFFFFFFFF: every Input is final, so nodes ignore nLockTime

We give lockUntilUnixTime the seconds since the epoch. It does not change sequence numbers, so we pass a non-final sequence number when adding the Input:

final unlockAt = DateTime.utc(2030, 1, 1).millisecondsSinceEpoch ~/ 1000; // seconds
final tx = TransactionBuilder()
// lockUntilUnixTime does not touch sequence numbers, so we pass a non-final one ourselves.
.spendFromTxnWithSigner(signer, fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER - 1,
P2PKHUnlockBuilder(aliceKey.publicKey))
.spendToPKH(myAddress, BigInt.from(99900))
.lockUntilUnixTime(unlockAt)
.build(true);
tx.nLockTime; // 1893456000

Every spendFrom… method takes the Input’s sequence number. TransactionInput.MAX_SEQ_NUMBER (0xFFFFFFFF) marks a final Input, and anything lower makes the Input non-final. We can also set transaction.inputs[i].sequenceNumber before signing.

final tx = Transaction.fromHex(raw);
final lockTime = tx.nLockTime;
final isBlockHeight = lockTime < Transaction.LOCKTIME_THRESHOLD; // below 500000000: a block height
final enforced = tx.inputs.any((input) => input.sequenceNumber != TransactionInput.MAX_SEQ_NUMBER);