QUIVER

Use cases

Cancel and renounce

Cancelling is the most misread function in the contract. It is not a clawback, it is a stop. And renouncing the right to it is the only promise QUIVER lets you make that nothing can undo.

Cancelling is not a clawback

The instinct is that cancelling undoes the stream and returns the deposit. It does not, and it never could without stealing from the recipient.

When the sender calls cancel, the contract splits the escrow at the current instant. The vested part stays owed to the NFT holder in full, and they can withdraw it afterwards, at their leisure, with no deadline. Only the unvested remainder is transferred back to the sender, immediately, in the same transaction.

The vested part
Stays in the contract, still owed to the NFT holder. Cancelling does not touch it, and does not put a clock on claiming it.
The unvested part
Goes back to the sender in the cancel transaction. This is the only value that moves.
The stream
Stops accruing at that instant, and never resumes. It stays readable and still owns its NFT.

What it does to the state

There is no special cancelled branch in the vesting maths. The contract freezes the stream by rewriting its schedule so the ordinary formula keeps giving the right answer forever:

the freeze
deposited  = vested at this instant   # the stream now promises exactly what it owes
end        = now                      # so vestedOf() returns deposited from here on
cliff      = min(cliff, now)          # keeps start <= cliff <= end true after the rewrite
start      = min(start, now)
canceled   = true
cancelable = false

Two consequences worth knowing. deposited after a cancellation is not what was originally deposited, it is what stayed owed, so do not read it as the historical grant. And refundableOf returns 0 for a cancelled stream, because there is nothing left to refund.

A cancelled stream can still pay out

The holder's withdrawal path is untouched. If they had 30% vested when you cancelled, those tokens sit in the contract with their name on them until they take them. Cancelling does not expire the claim, and there is no way for the sender to reach it afterwards.

Who can cancel, and when it reverts

Only the sender, the address that funded the stream, and only if the stream is still cancelable. The NFT holder cannot cancel. The protocol owner cannot cancel. There is no admin path.

NoStream
The id was never minted.
NotSender
You are not the address that funded it. Holding the NFT does not help.
NotCancelable
The stream was created irrevocable, or the right was renounced, or it is already cancelled.

A detail that surprises people

Cancelling a second time reverts with NotCancelable, not AlreadyCanceled, because cancel clears the cancelable flag on its way out, and that check runs first. AlreadyCanceled is declared in the ABI but no call path currently reaches it. Worth knowing if you are decoding revert data and matching selectors.

Renouncing: the one way door

renounceCancel(id)permanently gives up the sender's right to cancel. It flips cancelable to false and emits CancelRenounced. From that block on, cancel on that stream reverts forever.

There is no undo. No admin function restores it, no upgrade path exists, and no argument you can make to us changes it. That is not an oversight to route around, it is the entire feature. A promise you can revoke is not a promise, and a reader can only trust cancelable == false precisely because nothing can flip it back.

Check the schedule before you close the door

Renouncing on a stream with a wrong end date leaves you with a wrong end date forever. Read the stream back and verify every field first. This is exactly why the team vesting pattern says create cancelable, verify, then renounce, rather than creating irrevocable in one shot.
bash · verify, then renounce
RPC=https://rpc.mainnet.chain.robinhood.com
QUIVER=0x2866D49f364a70383591a958354A89F6BDf050f2

# read every field back before you commit
cast call $QUIVER \
  "getStream(uint256)((address,uint40,uint40,bool,bool,address,uint40,uint128,uint128))" \
  $ID --rpc-url $RPC

# permanent, from the next block on
cast send $QUIVER "renounceCancel(uint256)" $ID \
  --rpc-url $RPC --private-key $KEY

Choosing, at creation

The choice is really about what you are trying to prove, and to whom.

  • Cancelable fits a contributor who is still proving themselves. If they leave in month three, the unvested part comes back and the vested part is theirs, which is what you agreed to anyway.
  • Irrevocable fits anything you are asking a stranger to trust: a team allocation you want the market to believe, an investor unlock, a stream anyone might buy. It is the strongest statement the contract can make.
  • Cancelable now, renounced later is the middle path, and usually the right one. Keep the right while it is useful, give it up the moment it stops being useful, and let the chain record when you did.

One thing to keep in mind if a stream might be sold: a cancelable stream is near-worthless to a buyer, because the sender can gut it in the same block as the sale. See the trap on Tradable streams.