Skip to main content

Create an Airstream

In this guide, we will show you how to programmatically create an airstream via the Merkle Factory.

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


This guide interacts with the Periphery contracts.


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;

Now, import the relevant symbols from @sablier/v2-core and @sablier/v2-periphery:

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ISablierV2LockupLinear } from "@sablier/v2-core/src/interfaces/ISablierV2LockupLinear.sol";
import { LockupLinear } from "@sablier/v2-core/src/types/DataTypes.sol";
import { ISablierV2MerkleLL } from "@sablier/v2-periphery/src/interfaces/ISablierV2MerkleLL.sol";
import { ISablierV2MerkleLockupFactory } from "@sablier/v2-periphery/src/interfaces/ISablierV2MerkleLockupFactory.sol";
import { MerkleLockup } from "@sablier/v2-periphery/src/types/DataTypes.sol";

Create a contract called AirstreamCreator, and declare a constant DAI of type IERC20, a constant LOCKUP_LINEAR of type ISablierV2LockupLinear and a constant FACTORY of type ISablierV2MerkleLockupFactory.

contract AirstreamCreator {
IERC20 public constant DAI = IERC20(0x68194a729C2450ad26072b3D33ADaCbcef39D574);
ISablierV2LockupLinear public constant LOCKUP_LINEAR =
ISablierV2MerkleLockupFactory public constant FACTORY = ISablierV2MerkleLockupFactory(0x56E9180A8d2C35c99F2F8a1A5Ab8aBe79E876E8c);

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.

Airstream create functions

There are two Airstream create functions available through factory:

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

Function definition

Define a function called createLLAirstream that returns the address of newly deployed Merkle Lockup contract.

function createLLAirstream() public returns (ISablierV2MerkleLL merkleLL) {
// ...


MerkleLockupFactory uses MerkleLockup.ConstructorParams as the shared struct between createMerkleLL and createMerkleLT functions.

MerkleLockup.ConstructorParams memory baseParams;

Let's review each parameter of the shared struct in detail.


The contract address of the ERC-20 asset that you want to airdrop to your recipients. In this example, we will use DAI.

baseParams.asset = DAI;


Boolean that indicates whether the stream will be cancelable or not after it has been claimed.

baseParams.cancelable = true;


The unix timestamp indicating the expiration of the campaign. Once this time has been passed, users will no longer be able to claim their airdrop. And you will be able to clawback any unclaimed assets from the campaign.

baseParams.expiration = uint40(block.timestamp + 12 weeks);

Initial Admin

This is the initial admin of the Airstream campaign. When a recipient claims his airdrop, a Sablier stream is created with this admin as the sender of the stream. Another role of admin is to clawback unclaimed assets from the campaign post expiry and during grace period.

baseParams.initialAdmin = address(0xBeeF);


This is the content identifier (CID) for indexing the contract on IPFS. This is where we store addresses of the Airdrop recipients and their claim amount.

baseParams.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX";

Merkle Root

Airstreams use a Merkle tree data structure to store the airdrop data onchain. As a result, you only pay the gas fee to create the contract and store the Merkle root onchain. Airdrop recipients can then call the contract on their own to claim their airdrop.

If you want to create the Merkle root programatically, you can follow our guide on Merkle API.

baseParams.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123;


The name of the campaign. = "My First Campaign";


Boolean that indicates whether the stream will be transferable or not. This is the stream that users obtain when they claim their airstream.

baseParams.transferable = true;

Now that we have the baseParams ready, it's time to setup rest of the input parameters.

Stream Duration

For Lockup Linear, this is the struct containing (i) a cliff period duration and (ii) a total stream duration, both in seconds.

LockupLinear.Durations memory streamDurations;
streamDurations = LockupLinear.Durations({
cliff: 4 weeks,
total: 52 weeks

The stream duration applies only after the Airstream has been claimed by the recipient. Therefore, users claiming airstreams at different times will have their assets vested at different intervals. For example, if user B claims 2 days after user A, then the stream for user B will end 2 days after the stream for user A ends.

Aggregate Amount

This is the total amount of assets you want to airdrop to your users, denoted in units of the asset's decimals. Let say you want to airdrop 100M tokens of DAI. Then, the aggregate amount would be 100M1018100M*10^{18}.

uint256 aggregateAmount = 100000000e18;

Recipient Count

The total number of recipient addresses.

uint256 recipientCount = 10000;

Invoke the create function

With all parameters set, we can now call the createMerkleLL function, and assign the address of the newly created campaign to a variable:

merkleLL = FACTORY.createMerkleLL(baseParams, LOCKUP_LINEAR, streamDurations, aggregateAmount, recipientCount);

The complete Airstream creator contract

Below you can see the complete functioning code: a contract that creates an airstream campaign with Lockup Linear streams. You can access the code on GitHub through this link.

Airstream Creator