Skip to main content

Create a Lockup Tranched stream

Lockup Tranched are streams with discrete unlocks. In this guide, we will show you how to programmatically create a Lockup Tranched stream.

This guide assumes that you have already gone through the Protocol Concepts section.

note

This guide interacts with the Core contracts directly.

caution

The code in this guide is not production-ready, and is implemented in a simplistic manner for the purpose of learning.

Set up a contract

Declare the Solidity version used to compile the contract:

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;

Import the relevant symbols from @sablier/v2-core:

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ud60x18 } from "@prb/math/src/UD60x18.sol";
import { ISablierV2LockupTranched } from "@sablier/v2-core/src/interfaces/ISablierV2LockupTranched.sol";
import { Broker, LockupTranched } from "@sablier/v2-core/src/types/DataTypes.sol";

Create a contract called LockupTranchedStreamCreator, and declare a constant DAI of type IERC20 and a constant LOCKUP_TRANCHED of type ISablierV2LockupTranched:

contract LockupTranchedStreamCreator {
IERC20 public constant DAI = IERC20(0x68194a729C2450ad26072b3D33ADaCbcef39D574);
ISablierV2LockupTranched public constant LOCKUP_TRANCHED =
ISablierV2LockupTranched(0x3a1beA13A8C24c0EA2b8fAE91E4b2762A59D7aF5);
}

In the code above, the contract addresses are hard-coded for demonstration purposes. However, in production, you would likely use input parameters to allow flexibility in changing the addresses.

Also, these addresses are deployed on Mainnet. If you need to work with a different chain, the Sablier addresses can be obtained from the Deployment Addresses page.

Create functions

There are two create functions in the Lockup Linear contract:

Which one you choose depends upon your use case. In this guide, we will use createWithDurations.

Function definition

Define a function called createStream which takes two parameters, amount0 and amount1, and which returns the id of the created stream:

function createStream(uint128 amount0, uint128 amount1) public returns (uint256 streamId) {
// ...
}

Next, sum up the amount0 and amount1 parameters to get the total amount of the stream, which will be needed in many of the steps below:

uint256 totalAmount = amount0 + amount1;

ERC-20 steps

To create a stream, the caller must approve the creator contract to pull the tokens from the calling address's account. Then, we have to also approve the Sablier contract to pull the assets that the creator contract will be in possession of after they are transferred from the calling address (you):

// Transfer the provided amount of DAI tokens to this contract
DAI.transferFrom(msg.sender, address(this), totalAmount);

// Approve the Sablier contract to spend DAI
DAI.approve(address(LOCKUP_TRANCHED), totalAmount);

For more guidance on how to approve and transfer ERC-20 assets, see this article on the Ethereum website.

Parameters

Sablier uses structs to encode the parameters of its create functions. The struct associated with createWithDurations is LockupTranched.CreateWithDurations, and it can be initialized like this:

LockupTranched.CreateWithDurations memory params;

Let's review each parameter in detail.

Sender

The address streaming the assets, with the ability to cancel the stream:

params.sender = msg.sender;

Recipient

The address receiving the assets:

params.recipient = address(0xCAFE);

Total amount

The total amount of ERC-20 assets to be paid, including the stream deposit and any potential fees, all denoted in units of the asset's decimals.

params.totalAmount = totalAmount;

Asset

The contract address of the ERC-20 asset used for streaming. In this example, we will stream DAI:

params.asset = DAI;

Cancelable

Boolean that indicates whether the stream will be cancelable or not.

params.cancelable = true;

Transferable

Boolean that indicates whether the stream will be transferable or not.

params.transferable = true;

Tranches With Duration

Tranches are what the protocol uses to compose the Lockup Tranched stream. For a full exposition of tranches, see the Tranches guide.

Each tranche is characterized by a specific amount and timestamp. Because we are using createWithDurations in this example, these tranches are supplied to the function in the form of an array containing LockupTranched.TrancheWithDuration structs.

Let's define two dummy tranches:

params.tranches = new LockupTranched.TrancheWithDuration[](2);
params.tranches[0] = LockupTranched.TrancheWithDuration({
amount: amount0,
duration: uint40(4 weeks)
});
params.tranches[1] = (
LockupTranched.TrancheWithDuration({
amount: amount1,
duration: uint40(6 weeks)
})
);

In this example, the first tranche (amount0) will unlock at the end of the 4 weeks after the stream was created and the second tranche (amount1) will unlock after further 6 weeks. Thus, the total amount will be unlocked in 10 weeks.

Broker

An optional parameter that can be set in order to charge a fee as a percentage of totalAmount.

In the following example, we will leave this parameter uninitialized (i.e. set to zero), because it doesn't make sense to charge yourself a fee. In practice, this parameter will mostly be used by front-end applications.

params.broker = Broker(address(0), ud60x18(0));
info

Wondering what's up with that ud60x18 function? It's a casting function that wraps a basic integer to the UD60x18 value type. This type is part of the math library PRBMath, which is used in Sablier for fixed-point calculations.

Invoke the create function

With all parameters set, we can now call the createWithDurations function, and assign the ID of the newly created stream to a variable:

streamId = LOCKUP_TRANCHED.createWithDurations(params);

The complete Lockup Tranched stream creator contract

Below you can see the complete functioning code: a contract that creates a Lockup Tranched stream. You can access the code on GitHub through this link.

Lockup Tranched Stream Creator
loading...