Before you trust it
Security and limits
The point of this page is not to reassure you. It is to tell you exactly what holds by construction, what depends on us behaving, and what we will not claim.
Start here: it is not audited
No audit
That is a lot of evidence. It still is not an audit, and evidence is not proof. Read the code before you trust it with size, and size your exposure to what you can afford to lose.
We would rather say this at the top of the page than bury it under a list of guarantees. Everything below is true, and none of it substitutes for an audit.
What holds by construction
These are properties of the code, not promises about our conduct. They hold whether we are honest, compromised, or gone.
- No admin withdraw
- There is no function, anywhere, that lets the owner move an escrowed token. Not a pause, not a rescue, not an upgrade. The owner's reach is the fee, the treasury, and the card renderer.
- No upgrade path
- The contract is not a proxy. The code at that address is the code that runs, forever. Nothing about it can be changed after the fact, including by us.
- The sender cannot withdraw
- Funding a stream buys no claim on it. Only the NFT holder, or an operator they approved, can take tokens out.
- The fee is capped in code
- MAX_CREATION_FEE is a compile-time constant of 0.01 ETH. It cannot be raised, and it applies only at creation. Withdrawing is free forever.
- Fee changes are never retroactive
- The fee is read once, at creation, and never stored on the stream. Nothing in the withdrawal path consults it, so an existing stream cannot be affected by a later change.
- Streams are isolated
- Each stream can only ever pay out its own deposit, bounded by deposited minus withdrawn. One contract holds them all, and no stream can reach another's escrow. Covered by a fuzz invariant.
- It never over-pays
- Across every schedule and every instant, total payouts cannot exceed what was deposited. Also fuzzed.
- Fee-on-transfer is handled
- The deposit is measured by the balance actually received, not by the amount you asked for. The contract never promises tokens it does not hold.
- Renouncing is permanent
- There is no path in the contract that sets cancelable back to true. It is checked by a dedicated test, because the whole trust argument rests on it.
- Reentrancy is guarded
- createStream, withdraw, cancel and sweepFees are all nonReentrant, and token transfers happen after state is written.
What the owner can actually do
Being precise about this matters more than insisting we are trustworthy. The complete owner surface is four functions:
setCreationFee, under the hard cap, affecting only future creations.setTreasury, which changes where fees land. It cannot touch escrowed tokens.setDescriptor, which changes how the card looks. It can never change what a stream pays, and a renderer that reverts cannot block a withdrawal or a transfer.transferOwnershipandrenounceOwnership, the standard Ownable pair.
That is the entire list. If the owner key were stolen tomorrow, the worst an attacker could do is set the fee to the cap, point the treasury at themselves, and break the artwork. Your streams would keep paying out on schedule, and they could not take a single escrowed token.
The honest limits
Things that are true, that we would rather you hear from us than discover later. None of these are bugs. Several are consequences of choices we would make again.
A cancelable stream is only as good as its sender
If cancelable is true, the sender can cancel at any moment and take back everything unvested. This is the feature working as designed, and it is also the reason buying a cancelable stream is close to buying nothing: the sender can gut it in the block right after your purchase. Insist on cancelable == false, and check it yourself on-chain.
Vesting is not a lockup
Once tokens vest, they can be withdrawn and sold. A stream constrains when tokens become available, not what anyone does with them afterwards. And since the NFT is transferable, a holder can sell the claim to the future allocation even while unable to touch the tokens early. Renouncing proves you cannot take it early. It does not promise you will never sell.
Time is block.timestamp
A validator can nudge the block timestamp by a few seconds, which moves the vested amount by that much. On streams measured in days or years this is noise, and the total stays bounded by deposited regardless. But it is a real dependency, and on a stream of a few minutes it would not be noise.
Exotic tokens are still exotic
Fee-on-transfer is handled by measuring what arrives. Rebasing tokens are a different story: depositedis fixed at creation, so if a token's balances change underneath the contract, the accounting will not track it. Tokens that can pause or blacklist transfers can freeze a withdrawal, and nothing in QUIVER can override that. You are always exposed to the token you chose to stream.
Two views are unbounded
streamsOfHolder and streamsBySender loop with no pagination. Off-chain that is free. From a contract, an address with enough streams can push the call past the block gas limit. Treat both as eth_call only, and use sentCount or tokenOfOwnerByIndex for bounded on-chain access.
Approvals are approvals to withdraw
An address you approve on QUIVER can withdraw a stream's tokens to any destination it likes, not just move the NFT. setApprovalForAll grants that over every stream you hold, now and in the future. This is standard ERC-721 behaviour, and it surprises people anyway.
Fees can be stranded
sweepFees sends ETH to the treasury with a plain call. If the treasury cannot receive ETH, the sweep reverts and the accrued fees sit in the contract. This affects our fee revenue only, never escrowed stream tokens, which are ERC-20s on a separate accounting line.
If this site disappears
Nothing happens to your stream. The schedule and the funds are in the contract, and both dashboards here are rebuilt from plain eth_call, with no backend, no indexer, and no database. Anyone can read a stream, withdraw, cancel or transfer with a block explorer and no frontend at all.
That is not a talking point, it is why createStream pays extra gas to index streams on both sides. The protocol had to remain fully usable if our infrastructure went away. See Integration for exactly how.
Before you commit real size
- Read the contract. It is one file, and it is short enough to read in full.
- Create a small stream first, with a short schedule, and watch it complete end to end.
- Verify the stream on-chain after creating it, before you renounce anything. A wrong schedule on an irrevocable stream is wrong forever.
- Check
cancelableyourself, from the chain, whenever anyone asks you to trust a stream. That is the whole point of it being on-chain.