Hodlocker
HodlLockBuilder locks coins to a Public Key hash until a block height. Its script is a P2PKH check
sitting behind an OP_CHECKLOCKTIMEVERIFY:
<height> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyHash> OP_EQUALVERIFY OP_CHECKSIGThe spending Transaction’s nLockTime must be at least the height, and its Input’s sequence number
must be below the maximum so that nLockTime applies. The page on
lock time and sequence numbers covers those fields. The examples use the
setup from the Transaction builder.
Creating the lock
Section titled “Creating the lock”// Locked to Alice's Public Key hash until block 800,000.HodlLockBuilder locker = new HodlLockBuilder(alice.getPublicKey().getPubKeyHash(), BigInteger.valueOf(800_000));Transaction fundingTx = Fixtures.fundingTransaction(locker, 100_000);Spending after the lock height
Section titled “Spending after the lock height”The builder cannot set nLockTime, and the Signature covers it, so we build without a signer, set the lock
time, and then sign.
// The Input's sequence number must be below the maximum, otherwise nLockTime is ignored.HodlUnlockBuilder unlocker = new HodlUnlockBuilder(null, alice.getPublicKey(), null);Transaction tx = new TransactionBuilder() .spendFromTransaction(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER - 1, unlocker) .spendTo(new P2PKHLockBuilder(aliceAddress), BigInteger.valueOf(99_000)) .build(true);
// The builder has no lock-time setter, so we set it on the Transaction and then sign.tx.setLockTime(lockTime);new TransactionSigner(SIGHASH, alice).sign(tx, fundingTx.getOutputs().get(0), 0);In the preceding example HodlUnlockBuilder takes three parameters:
- the Signature, which is null here because the signer adds it,
- the Public Key,
- an optional extra push, also null here.
What the interpreter enforces
Section titled “What the interpreter enforces”With the CHECKLOCKTIMEVERIFY flag and without UTXO_AFTER_GENESIS (i.e. the pre-Genesis rules), the lock
holds.
// OP_CHECKLOCKTIMEVERIFY is enforced with the CHECKLOCKTIMEVERIFY flag, and without UTXO_AFTER_GENESIS.Set<Script.VerifyFlag> cltvFlags = EnumSet.of(Script.VerifyFlag.SIGHASH_FORKID, Script.VerifyFlag.CHECKLOCKTIMEVERIFY);Interpreter interpreter = new Interpreter();
// With nLockTime at 800,000 the spend is accepted.assertDoesNotThrow(() -> interpreter.correctlySpends(onTime.getInputs().get(0).getScriptSig(), utxo.getScript(), onTime, 0, cltvFlags, Coin.valueOf(100_000)));
// One block early, at 799,999, it is rejected.assertThrows(ScriptException.class, () -> interpreter.correctlySpends(early.getInputs().get(0).getScriptSig(), utxo.getScript(), early, 0, cltvFlags, Coin.valueOf(100_000)));With the post-Genesis flags from Fixtures.FLAGS, the early spend passes as well.
// Under the post-Genesis rules OP_CHECKLOCKTIMEVERIFY is a no-op, so the early spend passes.assertDoesNotThrow(() -> Fixtures.verifySpend(early, 0, utxo));Reading a Hodlocker Output
Section titled “Reading a Hodlocker Output”HodlLockBuilder parsed = HodlLockBuilder.fromScript(utxo.getScript());BigInteger lockHeight = parsed.getLockHeight();The Script template registry also recognises these scripts, under the name
HODLocker.

