Create Airdrop Campaigns
In this guide, we will show you how you can use Solidity to create a campaigns via the Merkle Factories.
This guide assumes that you have already gone through the Protocol Concepts section.
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/lockup and @sablier/airdrops:
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ud2x18 } from "@prb/math/src/UD2x18.sol";
import { ud60x18 } from "@prb/math/src/UD60x18.sol";
import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol";
import { ISablierMerkleInstant } from "@sablier/airdrops/src/interfaces/ISablierMerkleInstant.sol";
import { ISablierMerkleLL } from "@sablier/airdrops/src/interfaces/ISablierMerkleLL.sol";
import { ISablierMerkleLT } from "@sablier/airdrops/src/interfaces/ISablierMerkleLT.sol";
import { ISablierMerkleVCA } from "@sablier/airdrops/src/interfaces/ISablierMerkleVCA.sol";
import { ISablierFactoryMerkleInstant } from "@sablier/airdrops/src/interfaces/ISablierFactoryMerkleInstant.sol";
import { ISablierFactoryMerkleLL } from "@sablier/airdrops/src/interfaces/ISablierFactoryMerkleLL.sol";
import { ISablierFactoryMerkleLT } from "@sablier/airdrops/src/interfaces/ISablierFactoryMerkleLT.sol";
import { ISablierFactoryMerkleVCA } from "@sablier/airdrops/src/interfaces/ISablierFactoryMerkleVCA.sol";
import { MerkleInstant, MerkleLL, MerkleLT, MerkleVCA } from "@sablier/airdrops/src/types/DataTypes.sol";
Create a contract called MerkleCreator, and declare a constant DAI of type IERC20, a constant LOCKUP of type
ISablierLockup and the factories constants of type ISablierFactoryMerkleInstant, ISablierFactoryMerkleLL,
ISablierFactoryMerkleLT and ISablierFactoryMerkleVCA.
contract MerkleCreator {
IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
ISablierFactoryMerkleInstant public constant INSTANT_FACTORY = ISablierFactoryMerkleInstant(0x7f70bA7C7373BaA4047cE450168cD24968321Bda);
ISablierFactoryMerkleLL public constant LL_FACTORY = ISablierFactoryMerkleLL(0x0781Ad660a5ED0041B45d44d45009a163CC0B578);
ISablierFactoryMerkleLT public constant LT_FACTORY = ISablierFactoryMerkleLT(0x336d464276e2c7C76927d975Ef866Df8a7Ecf8DD);
ISablierFactoryMerkleVCA public constant VCA_FACTORY = ISablierFactoryMerkleVCA(0x91FdBd7077d615f951a0defA81Ec30Bfd68dbd8D);
ISablierLockup public constant LOCKUP = ISablierLockup(0xcF8ce57fa442ba50aCbC57147a62aD03873FfA73);
}
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 Ethereum Sepolia. If you need to work with a different chain, Lockup addresses can be obtained from the Lockup Deployments page.
Factory address can be obtained from the Merkle Airdrops Deployments page.
Create function
There are four create functions available through the factories:
createMerkleInstant: creates campaign for instant distribution of tokens.createMerkleLL: creates campaign with a Lockup Linear distribution.createMerkleLT: creates campaign with a Lockup Tranched distribution.createMerkleVCA: creates campaign with a Variable Claim Amount distribution.
Which one you choose depends upon your use case. In this guide, we will use createMerkleLL.
Below we will focus only on the LL factory. The other factories follow the same pattern.
Function definition
Define a function called createMerkleLL that returns the address of newly deployed Merkle Lockup contract.
function createMerkleLL() public returns (ISablierMerkleLL merkleLL) {
// ...
}
Parameters
Merkle Factory uses MerkleLL.ConstructorParams
as the struct for the createMerkleLL function.
MerkleLL.ConstructorParams memory params;
Let's review each parameter of the struct in detail.
Token
The contract address of the ERC-20 token that you want to airdrop to your recipients. In this example, we will use DAI.
params.token = DAI;
Campaign Start Time
The unix timestamp indicating when the campaign starts. Users will be able to claim their airdrop after this time.
params.campaignStartTime = uint40(block.timestamp);
Expiration
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 tokens from the campaign.
params.expiration = uint40(block.timestamp + 12 weeks);
Initial Admin
This is the initial admin of the Airstream campaign. When a recipient claims his airdrop, a Lockup stream is created with this admin as the sender of the stream. Another role of admin is to clawback unclaimed tokens from the campaign post expiry and during grace period.
params.initialAdmin = address(0xBeeF);
IPFS CID
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.
params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX";
Merkle Root
These campaigns 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 programmatically, you can follow our guide on Merkle API.
params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123;
Campaign Name
The name of the campaign.
params.campaignName = "My First Campaign";
Shape
A custom stream shape name for visualization in the UI. This helps users understand the vesting schedule visually.
params.shape = "A custom stream shape";
Lockup
The address of the Sablier Lockup contract that will be used to create the streams when recipients claim their airdrop.
params.lockup = LOCKUP;
Vesting Start Time
The unix timestamp indicating when the vesting starts. If set to 0, the vesting will begin at the time of claim.
params.vestingStartTime = uint40(block.timestamp);
Cliff Duration
The duration of the cliff period in seconds. During this period, no tokens will be unlocked.
params.cliffDuration = 30 days;
Cliff Unlock Percentage
The percentage of tokens to unlock after the cliff period ends. We want to unlock 0.01% after the cliff period.
params.cliffUnlockPercentage = ud60x18(0.01e18);
Start Unlock Percentage
The percentage of tokens to unlock at the vesting start time. We want to unlock 0.01% at the start.
params.startUnlockPercentage = ud60x18(0.01e18);
Total Duration
The total duration of vesting in seconds. This should be 90 days for this example.
params.totalDuration = 90 days;
Cancelable
Boolean that indicates whether the stream will be cancelable or not after it has been claimed.
params.cancelable = false;
Transferable
Boolean that indicates whether the stream will be transferable or not. This is the stream that users obtain when they claim their airstream.
params.transferable = true;
Now that we have the params ready, it's time to setup rest of the input parameters.
Aggregate Amount
This is the total amount of tokens you want to airdrop to your users, denoted in units of the asset's decimals. Let's say you want to airdrop 100M tokens of DAI. Then, the aggregate amount would be .
uint256 aggregateAmount = 100_000_000e18;
Recipient Count
The total number of recipient addresses.
uint256 recipientCount = 10_000;
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 = LL_FACTORY.createMerkleLL({
params: params,
aggregateAmount: aggregateAmount,
recipientCount: recipientCount
});
Full code
Below you can see the full code, including the other factories. You can also access the code on GitHub through this link.
loading...