Baklava

Locks

An amount put out of reach until a block height, and paid out by anybody once that height arrives. It is what a vesting schedule is made of, what a mainnet distribution sits behind, and the second provable promise a token creator can make — the first being a burn, which is not the same promise and must never be shown as though it were.

Using it

lock, look, release
./baklava timelock -asset BKLV -amount 20000000 -height 2600000
./baklava timelock -asset <token id> -amount 250000000 -height 2600000 -to bkx1...
./baklava timelock -asset pool:<pool id> -amount 50000000 -height 2600000
./baklava tranche  -tranche 5e3540b1...
./baklava release  -tranche 5e3540b1...

Or /timelock, /tranche and /release from the console. It is /timelock and not /lock because /lock locks the wallet, and a command name that collides is a command somebody runs by accident.

A lock is an address, not a flag on a balance.

The money moves. It leaves the locker's account and sits at SHA-256("baklava/release/v1")[:20], twenty bytes no key produces, and a rule moves it out again when the height arrives.

The obvious shape is a condition inside the debit path — and that path is on every module's route, so it has to stay arithmetic. Worse, a balance that means different things to different readers is how a wallet ends up displaying money nobody can spend. This is the same shape the chain already wears three times over: a stake is moved rather than marked, a listed item belongs to the escrow, and a burned share sits at an address with no key.

What the rules are

Three kinds of assetBKLV, a token balance, or a liquidity share. The tranche records which, so nothing has to work it out by looking the id up somewhere.
Anybody may release itAnd it pays the address the tranche names, never the sender — who pays the fee for the privilege. There is nothing left to decide once the height has arrived, so requiring a particular sender would only mean a payment that never arrives because one person forgot.
Nothing can cancel itNo transition redirects a tranche, brings it forward, or takes it back. A height already past is refused rather than paid at once: accepting it would charge a fee for a transaction that changed nothing and read, to whoever sent it, as though the lock had worked.
A share lock pays its own senderThe one asymmetry. A free recipient would make it a share transfer with one block of delay — and a share's non-transferability is an absence rather than a check, so ending it sideways in a rule about time would describe a transferable share without ever deciding what one is. What a share lock is for is the proof “I cannot take this liquidity out before N”, and that proof has no use for a recipient field.
It costs no price of its ownA token and a collection pay ten BKLV because they write a row every node carries for ever. A tranche is deleted the moment it is paid, so it pays a fee and nothing else.

A lock is not a burn

They look alike on a page and they are opposite promises. A burn is one-way: the reserves behind those shares can never leave, ever. A lock comes back at its height and then they can. A surface that prints them in one column teaches a reader they are the same thing, so Launch shows them as separate states with the height on the row.

It is also where a mainnet supply sits before it reaches anybody.

A genesis file can carry a release list: tranches written at height 0, funded from the release address, on a schedule no transaction can change. The derivation refuses both directions of a mismatch — owing more than is held is a promise the chain cannot keep, and holding more than is owed is a silent burn.

Put a keyless treasury at the end of it and no key exists anywhere on the path: the supply leaves the release address on its schedule, into a treasury nobody holds, and out of there only through a spend proposal standing four epochs in public.

The reasoning in full is under The lock in the protocol document.