Reference
Contract reference
Every public function, who can call it, and when it reverts. The signatures on this page are generated from the compiled ABI, so they cannot drift from the deployed contract.
The contract
QUIVER is one contract on Robinhood Chain (id 4663), at 0x2866D49f364a70383591a958354A89F6BDf050f2 ↗. It is an ERC721Enumerable, so the full ERC-721 surface is available alongside the streaming functions.
Generated, not transcribed
Arrow struct and the error list on this page are all read from lib/abi.ts, which is regenerated from the Foundry artifact with npm run abi. Only the prose is written by hand. If a documented function stopped existing, this page would fail to build rather than lie to you.The Arrow struct
What getStreamreturns. Timestamps are absolute Unix seconds. Amounts are in the streamed token's own decimals.
- sender address
- Who funded the stream. Set at creation, never changes.
- start uint40
- When accrual begins.
- cliff uint40
- Nothing withdrawable before this. Equal to start means no cliff.
- cancelable bool
- Whether the sender can still cancel. Only ever goes true to false.
- canceled bool
- Whether it has been cancelled.
- token address
- The ERC-20 being streamed.
- end uint40
- When fully vested. Reset to the cancellation instant if cancelled.
- deposited uint128
- What the contract actually received, which is what the stream promises. Reduced to the vested amount on cancellation, so it is not a record of the original grant.
- withdrawn uint128
- How much has been taken out so far.
Creating
createStream(address token, address recipient, uint128 amount, uint40 start, uint40 cliff, uint40 end, bool cancelable) payable returns (uint256 id)amount of token, mints stream NFT id to recipient, and returns the id. The fee is passed as msg.value, and anything above the current fee is refunded in the same transaction.token· The ERC-20 to stream. Fixed for the life of the stream.recipient· Receives the NFT, and with it the sole right to withdraw.amount· How much to escrow. The stream is credited with what the contract actually receives, which is lower for a fee-on-transfer token.start· When accrual begins. May be in the past, which starts the stream partly vested.cliff· Nothing is withdrawable before this. Pass `start` for no cliff.end· When the stream is fully vested. Must be strictly after `start`.cancelable· Whether the sender keeps the right to cancel. Can be given up later, never regained.
Anyone, provided they have approved the contract for `amount` of `token`.
ZeroAddress· `token` or `recipient` is the zero address.ZeroAmount· `amount` is 0, or the contract received 0 tokens.BadTimeline· `end <= start`, or `cliff < start`, or `cliff > end`.InsufficientFee· `msg.value` is below the current `creationFee`.Overflow· The received amount does not fit in a uint128.ERC721InvalidReceiver· `recipient` has code and does not implement `onERC721Received`.SafeERC20FailedOperation· The token's `transferFrom` failed or returned false.EthTransferFailed· Refunding your excess `msg.value` failed, because the caller rejects ETH.ReentrancyGuardReentrantCall· A token callback tried to reenter the contract.
Reading a stream
getStream(uint256 id) view returns (Arrow)Arrow struct for a stream.id· The stream id, which is also the NFT token id.
Anyone.
NoStream· No stream with that id was ever created.
vestedOf(uint256 id) view returns (uint128)deposited at or after end.id· The stream id.
Anyone.
NoStream· No stream with that id was ever created.
deposited to the vested amount and end to the moment of cancellation, so the same formula returns the frozen total forever.withdrawableOf(uint256 id) view returns (uint128)vestedOf(id) - withdrawn. This is the amount withdrawMax sends.id· The stream id.
Anyone.
NoStream· No stream with that id was ever created.
refundableOf(uint256 id) view returns (uint128)deposited - vestedOf(id). Returns 0 for an already cancelled stream.id· The stream id.
Anyone.
NoStream· No stream with that id was ever created.
cancelable. A non-zero result does not mean the stream can actually be cancelled.streamsOfHolder(address holder) view returns (uint256[] ids)holder· The address to look up.
Anyone, via eth_call.
ERC721InvalidOwner· `holder` is the zero address.
streamsBySender(address sender) view returns (uint256[])sender· The funder to look up.
Anyone, via eth_call.
sentCount(address sender) view returns (uint256)sender· The funder to look up.
Anyone.
Withdrawing
withdraw(uint256 id, address to, uint128 amount)amount of the streamed token to to.id· The stream id.to· Where the tokens go. Does not have to be the holder.amount· How much to take. Must not exceed `withdrawableOf(id)`.
The NFT holder, or an address they approved for this token or for all.
ERC721NonexistentToken· No stream with that id was ever created.ERC721InsufficientApproval· The caller is neither the holder nor approved.ZeroAddress· `to` is the zero address.ZeroAmount· `amount` is 0.ExceedsWithdrawable· `amount` is more than is currently withdrawable.SafeERC20FailedOperation· The token's `transfer` failed or returned false.ReentrancyGuardReentrantCall· A token callback tried to reenter the contract.
withdrawMax(uint256 id, address to) returns (uint128 amount)id· The stream id.to· Where the tokens go.
The NFT holder, or an address they approved.
ZeroAmount· Nothing is withdrawable right now, for example during a cliff, or straight after another withdrawal....· Everything `withdraw` can revert with, since this calls it.
Cancelling
cancel(uint256 id)id· The stream id.
The sender only, and only while the stream is cancelable.
NoStream· No stream with that id was ever created.NotSender· The caller did not fund this stream. Holding the NFT does not help.NotCancelable· Created irrevocable, already renounced, or already cancelled.SafeERC20FailedOperation· Refunding the unvested part failed.ReentrancyGuardReentrantCall· A token callback tried to reenter the contract.
NotCancelable, not AlreadyCanceled, because cancel clears the flag and that check runs first. Full mechanics on Cancel and renounce.renounceCancel(uint256 id)id· The stream id.
The sender only, and only while the stream is cancelable.
NoStream· No stream with that id was ever created.NotSender· The caller did not fund this stream.NotCancelable· Already irrevocable, already renounced, or already cancelled.
The NFT surface
QUIVER is a standard ERC721Enumerable. Everything below behaves exactly as the standard says, which is the entire point: a stream is tradable because it needs no special handling.
- ownerOf(uint256 tokenId) view returns (address)
- The current recipient. The only address that can withdraw, aside from operators they approved.
- balanceOf(address owner) view returns (uint256)
- How many streams an address holds.
- tokenURI(uint256 id) view returns (string)
- The stream card, as a data URI built on-chain at read time. Returns an empty string if no descriptor is set. Reverts ERC721NonexistentToken for an unminted id.
- approve(address to, uint256 tokenId)
- Approve one address for one stream. Cleared on transfer.
- getApproved(uint256 tokenId) view returns (address)
- Who is approved for this stream.
- setApprovalForAll(address operator, bool approved)
- Approve an operator for every stream you hold, now and later.
- isApprovedForAll(address owner, address operator) view returns (bool)
- Whether an operator is approved for all of an owner's streams.
- transferFrom(address from, address to, uint256 tokenId)
- Move a stream. Does not check the receiver can hold NFTs.
- safeTransferFrom(address from, address to, uint256 tokenId)
- Move a stream, checking the receiver accepts ERC-721. Prefer this.
- safeTransferFrom(address from, address to, uint256 tokenId, bytes data)
- Same, with a data payload passed to the receiver hook.
- totalSupply() view returns (uint256)
- How many streams exist in total.
- tokenByIndex(uint256 index) view returns (uint256)
- Enumerate all streams globally.
- tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)
- Enumerate one holder's streams. What `streamsOfHolder` loops over.
- supportsInterface(bytes4 interfaceId) view returns (bool)
- ERC-165. True for ERC721, ERC721Enumerable and ERC721Metadata.
- name() view returns (string)
- The collection name.
- symbol() view returns (string)
- The collection symbol.
Public state
- nextId() view returns (uint256)
- The id the next stream will get. Starts at 1, so it is also one more than the number of streams ever created.
- creationFee() view returns (uint256)
- The fee, in wei, charged on the next creation. Read this rather than hardcoding it.
- MAX_CREATION_FEE() view returns (uint256)
- The hard cap on the fee. A compile-time constant, so nothing can raise it.
- accruedFees() view returns (uint256)
- Creation fees held by the contract, waiting to be swept. Accounted separately from escrowed stream tokens.
- treasury() view returns (address)
- Where `sweepFees` sends the accrued fees.
- descriptor() view returns (IQuiverDescriptor)
- The renderer that draws the stream card. Can change how a stream looks, never what it pays.
- owner() view returns (address)
- The protocol owner. Their reach is the fee, the treasury and the descriptor. Nothing else.
Admin
The complete owner surface. There is no admin withdraw, no pause, no upgrade, and no way for the owner to cancel a stream or touch an escrowed token.
setCreationFee(uint256 fee)fee· The new fee in wei. Must not exceed MAX_CREATION_FEE.
The owner only.
OwnableUnauthorizedAccount· The caller is not the owner.FeeTooHigh· `fee` is above the hard cap.
setTreasury(address treasury_)treasury_· The new treasury. Cannot be the zero address.
The owner only.
OwnableUnauthorizedAccount· The caller is not the owner.ZeroAddress· `treasury_` is the zero address.
setDescriptor(address descriptor_)descriptor_· The new renderer. The zero address disables the art, making tokenURI return an empty string.
The owner only.
OwnableUnauthorizedAccount· The caller is not the owner.
sweepFees()Anyone. The destination is fixed by the owner, so this is safe to leave open.
ZeroAmount· There is nothing to sweep.EthTransferFailed· The treasury rejected the ETH.ReentrancyGuardReentrantCall· The treasury tried to reenter the contract.
transferOwnership(address newOwner)newOwner· The new owner. Cannot be the zero address.
The owner only.
OwnableUnauthorizedAccount· The caller is not the owner.OwnableInvalidOwner· `newOwner` is the zero address.
renounceOwnership()The owner only.
OwnableUnauthorizedAccount· The caller is not the owner.
Events
Everything the contract emits, generated from the ABI. Streams can be reconstructed from these alone if you would rather index than poll.
- Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)
- Standard ERC-721 single-token approval.
- ApprovalForAll(address indexed owner, address indexed operator, bool approved)
- Standard ERC-721 operator approval.
- CancelRenounced(uint256 indexed id)
- The sender permanently gave up the right to cancel this stream.
- CreationFeeSet(uint256 fee)
- The owner changed the fee for future creations. Existing streams are unaffected.
- DescriptorSet(address descriptor)
- The owner swapped the card renderer.
- FeesSwept(address indexed to, uint256 amount)
- Accrued creation fees were sent to the treasury.
- OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
- Standard Ownable. Emitted at deployment and on every ownership change.
- StreamCanceled(uint256 indexed id, address indexed sender, uint128 refunded, uint128 vested)
- A stream was stopped. `refunded` went back to the sender, `vested` stays owed to the holder.
- StreamCreated(uint256 indexed id, address indexed sender, address indexed recipient, address token, uint128 amount, uint40 start, uint40 cliff, uint40 end, bool cancelable)
- A stream was funded and its NFT minted. `amount` is the net amount received, which can be below the amount requested for a fee-on-transfer token.
- Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
- Standard ERC-721. A stream changed hands, which changes who can withdraw. Minting shows as a transfer from the zero address.
- TreasurySet(address treasury)
- The owner changed where swept fees go.
- Withdrawn(uint256 indexed id, address indexed to, uint128 amount)
- Tokens were taken out of a stream, to the `to` address the caller chose.
Errors
QUIVER reverts with custom errors, never with strings. The selector is the first four bytes of the revert data, which is what you match on when decoding a failure.
QUIVER's own errors
- AlreadyCanceled()
- Declared, but unreachable in practice: cancel clears `cancelable`, so a second cancel hits NotCancelable first.
- BadTimeline()
- The schedule is impossible. Requires `start < end` and `start <= cliff <= end`.
- EthTransferFailed()
- An ETH transfer failed: a fee refund to a caller that rejects ETH, or a sweep to a treasury that does.
- ExceedsWithdrawable()
- You asked for more than is currently withdrawable from that stream.
- FeeTooHigh()
- A fee above MAX_CREATION_FEE was proposed, at construction or via setCreationFee.
- InsufficientFee()
- `msg.value` was below the current creationFee on createStream.
- NoStream()
- No stream with that id was ever created.
- NotAContract()
- NotCancelable()
- The stream is not cancelable: created irrevocable, renounced, or already cancelled. Also what a second cancel reverts with.
- NotSender()
- Only the address that funded a stream can cancel or renounce it.
- OperatorMustPayHolder()
- Overflow()
- The received deposit does not fit in a uint128.
- RenounceDisabled()
- ZeroAddress()
- An address argument was the zero address, where the contract requires a real one.
- ZeroAmount()
- An amount was zero where that makes no sense: creating with 0, receiving 0 tokens, withdrawing 0, or sweeping an empty balance.
Inherited from OpenZeppelin
These come from ERC721, Ownable, SafeERC20 and ReentrancyGuard. They are in the ABI, so you will see them in decoded reverts.
- ERC721EnumerableForbiddenBatchMint()
- Batch minting is not supported by the enumerable extension. QUIVER never batch mints.
- ERC721IncorrectOwner(address sender, uint256 tokenId, address owner)
- The `from` address in a transfer is not the current owner.
- ERC721InsufficientApproval(address operator, uint256 tokenId)
- The caller is neither the holder nor approved for that stream.
- ERC721InvalidApprover(address approver)
- The caller cannot approve that token.
- ERC721InvalidOperator(address operator)
- An invalid operator was passed to an approval function.
- ERC721InvalidOwner(address owner)
- A zero address was passed where an owner is required, typically `balanceOf(0)`.
- ERC721InvalidReceiver(address receiver)
- The recipient is a contract that does not implement `onERC721Received`. Creation and safe transfers reject it so tokens cannot be stranded.
- ERC721InvalidSender(address sender)
- Standard ERC-721 guard on an invalid `from`, including minting from a non-zero address.
- ERC721NonexistentToken(uint256 tokenId)
- The token id has never been minted.
- ERC721OutOfBoundsIndex(address owner, uint256 index)
- An enumeration index is past the end of the list.
- OwnableInvalidOwner(address owner)
- Ownership was being handed to the zero address.
- OwnableUnauthorizedAccount(address account)
- An owner-only function was called by somebody else.
- ReentrancyGuardReentrantCall()
- A callback tried to reenter a guarded function.
- SafeERC20FailedOperation(address token)
- The ERC-20 transfer or transferFrom failed, or returned false.