Streams

Covering how to create, withdraw from and cancel streams.‌

Introduction

Every interaction with the Sablier protocol is in relation to a specific " stream" - how we refer to a real-time payment. A stream has four properties:

  1. Enacted between two different Ethereum addresses, a sender and a recipient

  2. Has a fixed value

  3. Has a fixed duration

  4. Funded with an ERC-20 token that can't be changed‌

For example, imagine a 3,000 DAI salary paid by Alice to Bob over the whole month of January. The start time would be Jan 1 and the stop time Feb 1. Every second makes Bob richer; on Jan 10, he would have approximately 1,000 DAI.‌

Non-Constant Functions

Create Stream

The create stream function transfers the tokens into the Sablier smart contract, stamping the rules of the stream into blockchain storage. As soon as the clock hits the start time of the stream, a little bit of money starts getting "transferred" from the sender to the recipient once every second.‌

We used quotation marks because what actually happens is not a transaction, rather an abstract allocation of funds. Every second, the in-contract allowance of the sender decreases and the recipient's increases, but the tokens are not transferred to an external Ethereum address; this would be excessively expensive in terms of gas costs.

function createStream(address recipient, uint256 deposit, address tokenAddress, uint256 startTime, uint256 stopTime) returns (uint256)
  • msg.sender : The address which shall fund the stream, and pay the recipient in real-time.

  • recipient : The address towards which the money shall be streamed.

  • deposit : The amount of money to be streamed, in units of the streaming currency.

  • tokenAddress : The address of the ERC-20 token to use as streaming currency.

  • startTime : The unix timestamp for when the stream starts, in seconds.

  • stopTime : The unix timestamp for when the stream stops, in seconds.

  • RETURN : The stream's id as an unsigned integer on success, reverts on error.

Before creating a stream, users must first approve the Sablier contract to access their token balance.

The transaction must be processed by the Ethereum blockchain before the start time of the stream, or otherwise the contract reverts with a "start time before block.timestamp" message.

The Deposit Gotcha

The deposit must be a multiple of the difference between the stop time and the start time, or otherwise the contract reverts with a "deposit not multiple of time delta" message. Practically, this means that you may not able to always use exact amounts like 3,000. You may have to divide the fixed deposit by the time delta and subtract the remainder from the initial number, ending up streaming a value that is very, very close to the fixed deposit, but not quite it.

For example, if:

  • The token has 18 decimals

  • The time delta is 2592000 (30 days)

You will have to stream 2999999999999998944000 instead of 3000000000000000000000. The former divides evenly by 2592000, the latter does not.

Solidity

Sablier sablier = Sablier(0xabcd...); // get a handle for the Sablier contract
address recipient = 0xcdef...;
uint256 deposit = 2999999999999998944000; // almost 3,000, but not quite
uint256 startTime = block.timestamp + 3600; // 1 hour from now
uint256 stopTime = block.timestamp + 2592000 + 3600; // 30 days and 1 hour from now
Erc20 token = Erc20(0xcafe...); // get a handle for the token contract
token.approve(address(sablier), deposit); // approve the transfer
// the stream id is needed later to withdraw from or cancel the stream
uint256 streamId = sablier.createStream(recipient, deposit, address(token), startTime, stopTime);

Ethers.js

const sablier = new ethers.Contract(0xabcd..., sablierABI, signer); // get a handle for the Sablier contract
const recipient = 0xcdef...;
const deposit = "2999999999999998944000"; // almost 3,000, but not quite
const now = Math.round(new Date().getTime() / 1000); // get seconds since unix epoch
const startTime = now + 3600; // 1 hour from now
const stopTime = now + 2592000 + 3600; // 30 days and 1 hour from now
const token = new ethers.Contract(0xcafe..., erc20ABI, signer); // get a handle for the token contract
const approveTx = await token.approve(sablier.address, deposit); // approve the transfer
await approveTx.wait();
const createStreamTx = await sablier.createStream(recipient, deposit, token.address, startTime, stopTime);
await createStreamTx.wait();

Withdraw from Stream

The withdraw from stream function transfers an amount of money to the recipient's address. The amount must be less than or equal to the available balance. This function can only be called by the sender or the recipient of the stream, not any third-party.

function withdrawFromStream(uint256 streamId, uint256 amount) returns (bool)
  • streamId : The id of the stream to withdraw money from.

  • amount : The amount of money to withdraw.

  • RETURN : True on success, false on error.

To be able to call this function, you have to wait until the clock goes past the start time of the stream.

Solidity

Sablier sablier = Sablier(0xabcd...);
uint256 streamId = 42;
uint256 amount = 100;
require(sablier.withdrawFromStream(streamId, amount), "something went wrong");

Ethers.js

const sablier = new ethers.Contract(0xabcd..., sablierABI, signer);
const streamId = 42;
const amount = 100;
const withdrawFromStreamTx = await sablier.withdrawFromStream(streamId, amount);
await withdrawFromStreamTx.wait();

Cancel Stream

The cancel stream function revokes a previously created stream and returns the money back to the sender and/or the recipient. If the clock did not hit the start time, all the money is returned to the sender. If the clock did go past the start time, but not past the stop time, the sender and the recipient both get a pro-rata amount. Finally, if the clock went past the stop time, all the money goes the recipient. This function can be called only by the sender.

function cancelStream(uint256 streamId) returns (bool)
  • streamId : The id of the stream to cancel.

  • RETURN : True on success, false on error.

Solidity

‌Sablier sablier = Sablier(0xabcd...);
uint256 streamId = 42;
require(sablier.cancelStream(streamId), "something went wrong");

Ethers.js

const sablier = new ethers.Contract(0xabcd..., sablierABI, signer);
const streamId = 42;
const cancelStreamTx = await sablier.cancelStream(streamId);
await cancelStreamTx.wait();

Constant Functions

Get Stream

The get stream function returns all properties for the provided stream id.

function getStream(uint256 streamId) view returns (address sender, address recipient, address tokenAddress, uint256 balance, uint256 startTime, uint256 stopTime, uint256 remainingBalance, uint256 ratePerSecond)
  • streamId : The id of the stream to query.

  • RETURN

    • sender : The address that created and funded the stream.

    • recipient : The address towards which the money is streamed.

    • tokenAddress : The address of the ERC-20 token used as streaming currency.

    • startTime : The unix timestamp for when the stream starts, in seconds.

    • stopTime : The unix timestamp for when the stream stops, in seconds.

    • remainingBalance : How much money is still allocated to this stream, in the smart contract.

    • ratePerSecond : How much money is allocated from the sender to the recipient every second.

Solidity

‌Sablier sablier = Sablier(0xabcd...);
uint256 streamId = 42;
(uint256 sender, uint256 recipient, uint256 deposit, address tokenAddress, uint256 startTime, uint256 stopTime, uint256 remainingBalance, uint256 ratePerSecond) = sablier.getStream(streamId);

Ethers.js

const sablier = new ethers.Contract(0xabcd..., sablierABI, signer);
const streamId = 42;
const stream = await sablier.getStream(streamId);

Balance Of

The balance of function returns the real-time balance for an address, with respect to a specific stream.

function balanceOf(uint256 streamId, address who) view returns (uint256)
  • streamId : The id of the stream for which to query the balance.

  • who : The address for which to query the balance.

  • RETURN : The available balance in units of the streaming currency.

This is the balance that remained in the contract, not the amount of money streamed. If the contract streamed 1,000 tokens to Bob, but Bob withdrew 400, this function would return 600 and not 1,000.

Solidity

‌Sablier sablier = Sablier(0xabcd...);
uint256 streamId = 42;
uint256 senderAddress = 0xcdef...;
uint256 balance = sablier.balanceOf(streamId, senderAddress);

Ethers.js

const sablier = new ethers.Contract(0xabcd..., sablierABI, signer);
const streamId = 42;
const senderAddress = 0xcdef...;
const balance = await sablier.balanceOf(streamId, senderAddress);

Delta Of

The delta of function returns either the difference between now and the start time of the stream OR between the stop time and the start time of the stream, whichever is smaller. If however the clock did not hit the start time of the stream, the value returned is 0 instead.

function deltaOf(uint256 streamId) view returns (uint256)
  • streamId : The id of the stream for which to query the delta.

  • RETURN : The time delta in seconds.

Solidity

Sablier sablier = Sablier(0xabcd...);
uint256 streamId = 42;
uint256 delta = sablier.deltaOf(streamId);

Ethers.js

const sablier = new ethers.Contract(0xabcd..., sablierABI, signer);
const streamId = 42;
const delta = await sablier.deltaOf(streamId);

Error Table

The table below lists all possible reasons for reverting a contract call that creates, withdraws from or cancels a stream. The "Id" column is merely a counter, because the smart contract does not yield error codes, just strings.

Id

Error

Reason

1

stream does not exist

The provided stream id does not point to a valid stream

2

caller is not the sender or the recipient of the stream

The contract call originates from an unauthorised third-party

3

token transfer failure

Possibly insufficient allowance, but not strictly true

4

recipient token transfer failure

Malicious token

5

sender token transfer failure

Malicious token

6

stream to the zero address

​Attempt to stream money to the zero address

7

stream to the contract itself

​Attempt to stream money to the Sablier contract

8

stream to the caller

Happens when the caller attempts to stream money to herself

9

deposit is zero

Attempt to stream 0 tokens​

10

start time before block.timestamp

Money cannot be streamed retroactively​

11

stop time before the start time

​Negative streaming is not allowed

12

deposit smaller than time delta

​The deposit, measured in units of the token, is smaller than the time delta

13

deposit not multiple of time delta

The deposit has a non-zero remainder when divided by the time delta​

15

amount is zero

Attempt to withdraw 0 tokens

16

amount exceeds the available balance

Attempt to withdraw more money than the available balance

17

recipient balance calculation error

Happens only when streaming an absurdly high number of tokens (close to 2^256)

The contract call could revert with no reason provided. In this case, you probably did not approve the Sablier contract to spend your token balance, although this is not strictly true. Ping us on Discord if you get stuck.