Start here
Concepts
Eight words carry the whole protocol. Most of them mean something slightly more specific here than they do elsewhere, and the difference is usually where people get surprised.
Stream
One escrowed grant with a schedule, stored on-chain as a struct the contract calls an Arrow, and minted as an ERC-721 to the recipient. Creating a stream moves the entire amount into the contract immediately. There is no drip from the funder's wallet, no allowance to keep topped up, and no way for a stream to be underfunded later.
Streams are isolated. Each one can only ever pay out its own deposit, so no stream can reach into another's escrow, even though a single contract holds them all.
What a stream is made of
These are the exact fields of the struct, read from the contract ABI. Timestamps are absolute Unix seconds, not durations.
- sender address
- Who funded the stream. Never changes, and never gets withdrawal rights.
- start uint40
- When accrual begins. May be in the past, which starts the stream partly vested.
- cliff uint40
- Nothing is withdrawable before this. Equal to start means no cliff.
- cancelable bool
- Whether the sender can still cancel. Only ever goes from true to false.
- canceled bool
- Whether it has been cancelled. A cancelled stream accrues nothing further.
- token address
- The ERC-20 being streamed. Fixed at creation.
- end uint40
- When the stream is fully vested. After cancellation, this is reset to the moment of cancellation.
- deposited uint128
- The amount actually received by the contract, which is what the stream promises. After cancellation, this is reduced to the amount that had vested.
- withdrawn uint128
- How much has been taken out so far. Only ever grows.
Sender
Whoever funded the stream, recorded at creation as msg.sender. The sender is not the owner of anything. They cannot withdraw a single token, not even from a stream they paid for in full.
Their only power is cancellation, and only if the stream was created cancelable. They can also give that power up permanently. The sender field never changes, so a stream can always be traced back to who funded it.
Recipient
Whoever holds the NFT right now, which is not necessarily who it was minted to. This is the distinction that trips people up: the recipient is not a fixed address stored in the stream, it is the answer to ownerOf(id) at this moment.
Only the holder, or an address they approved, can withdraw. They pick the destination, so tokens can be sent somewhere other than the holder address. Sell the NFT and the buyer becomes the recipient instantly, with no migration step and nobody's permission.
Cliff
A timestamp before which nothing is withdrawable. The single most misunderstood part of vesting: a cliff gates the withdrawal, it does not pause the accrual. Value builds behind it the whole time.
On a 4 year stream with a 1 year cliff, the vested amount is growing from day one. It is simply not touchable. The day the cliff lands, a full year of accrual becomes withdrawable in one lump, which is 25% of the grant. It does not restart the clock or shorten the stream.
Internally there is no separate no-cliff flag. cliff == start means no cliff, because a gate that opens at the start gates nothing. The contract enforces start <= cliff <= end and end > start, and rejects anything else with BadTimeline.
Vested
What time has released so far, counting tokens already withdrawn. It only ever grows, and it is a pure function of the clock:
before cliff vested = 0
between start/end vested = deposited * (now - start) / (end - start)
at or after end vested = depositedNote the asymmetry: the cliff zeroes out the withdrawal, but the accrual term is measured from start, not from the cliff. That is exactly why the cliff pays a lump sum instead of starting from zero.
Time is block.timestamp
deposited no matter what. There is no oracle here.Withdrawable
Vested minus already withdrawn. What the holder can take at this instant, and the amount withdrawMax sends.
A withdrawal never changes the schedule. It only increments withdrawn, so taking tokens out early does not slow anything down or move the end date. Withdrawing is purely a question of when you want the tokens in hand.
Cancelable
Whether the sender can still stop the stream. Set at creation, and it is a one way switch: it can go from true to false, never back. Nothing in the contract can restore it.
Cancelling is not a clawback. The vested part stays owed to the holder and remains withdrawable afterwards. Only the unvested remainder returns to the sender, and the stream stops accruing at that instant. The full mechanics are on Cancel and renounce.
Tradable
Not a flag, a consequence. Because the stream is an ERC-721 and the token is the claim, selling the NFT sells the remaining schedule. There is no permission to ask and no feature to enable. Every stream is tradable by construction.
- It shows up in any wallet that renders NFTs, with the card drawn on-chain from the contract's own state.
- It sells on any marketplace that understands ERC-721, with no custom code.
- It composes as collateral anywhere ERC-721 is understood, because the claim is transferable without cooperation from the funder.
The catch worth knowing before you buy one is on Tradable streams: a cancelable stream can be cancelled by its sender, including right before your purchase settles.