Patterns worth knowing · ≈ 14 min

Vesting: paying out over time

A cliff, then a straight line. The arithmetic that governs most team and investor allocations.

Vesting releases an allocation gradually instead of all at once. The standard shape has two parameters:

  • a cliff — nothing at all is claimable before it
  • a duration — after the cliff, the allocation unlocks linearly until it is fully released
before the cliff        → 0
between cliff and end   → total × elapsed ÷ duration
after the end           → total

Time is measured in block height, for the reason the voting lesson gave: it is a count every node agrees on exactly. Animica blocks average about two minutes, so a one-year vest is roughly 260,000 blocks.

Track what has been claimed, not what is left. The claimable amount is

vested(now) - already_claimed

If you instead decrement a "remaining" counter, every rounding error compounds and the final claim leaves dust stranded forever. Recomputing vested from the original total each time is self-correcting: the last claim always lands exactly on the total, whatever happened in between.

And multiply before you divide. total * elapsed // duration is right; total * (elapsed // duration) is zero for the entire vest until the very end, because integer division of a fraction less than one is zero. Contracts have shipped with that bug.

This is one of the few places where a contract genuinely needs to think about integer division. There are no floats in the VM — for good reason, since IEEE-754 rounding varies by platform — so the rounding behaviour you get is floor division, and you should choose the order of operations deliberately.

Your turn

Implement vested_at(height) and claim_at(height). Nothing vests before start + cliff. Between cliff and start + duration it is linear in the whole elapsed time since start. After that everything is vested. claim_at pays out only what has not already been claimed.

Hints

Stuck? Ask

Answered by Animica's own free inference network. It is donated GPU capacity, so give it 20-30 seconds.