Create a Batch of Lockup Dynamic Streams
In this guide, we will show you how to programmatically batch create dynamic streams via the Sablier's Batch contract.
This guide assumes that you have already gone through the Protocol Concepts section.
This guide interacts with the Periphery contract.
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 { ud2x18 } from "@prb/math/src/UD2x18.sol";
import { ud60x18 } from "@prb/math/src/UD60x18.sol";
import { ISablierV2LockupDynamic } from "@sablier/v2-core/src/interfaces/ISablierV2LockupDynamic.sol";
import { Broker, LockupDynamic } from "@sablier/v2-core/src/types/DataTypes.sol";
import { ISablierV2BatchLockup } from "@sablier/v2-periphery/src/interfaces/ISablierV2BatchLockup.sol";
import { BatchLockup } from "@sablier/v2-periphery/src/types/DataTypes.sol";
Create a contract called BatchLDStreamCreator
, and declare a constant DAI
of type IERC20
, a constant
LOCKUP_DYNAMIC
of type ISablierV2LockupDynamic
, and a constant BATCH
of type ISablierV2BatchLockup
:
contract BatchLDStreamCreator {
IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
ISablierV2LockupDynamic public constant LOCKUP_DYNAMIC =
ISablierV2LockupDynamic(0x7CC7e125d83A581ff438608490Cc0f7bDff79127);
ISablierV2BatchLockup public constant BATCH = ISablierV2BatchLockup(0xEa07DdBBeA804E7fe66b958329F8Fa5cDA95Bd55);
}
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.
Batch create functions
There are two batch create functions for the Lockup Dynamic contract:
Which one you choose depends upon your use case. In this guide, we will use createWithTimestampsLD
.
Function definition
Define a function called batchCreateStreams
that takes a parameter perStreamAmount
and returns an array of ids for
the created streams:
function batchCreateStreams(uint128 perStreamAmount) public returns (uint256[] memory streamIds) {
// ...
}
Batch size
Next, declare a batch size, which is needed to calculate the transfer amount:
// Create a batch of two streams
uint256 batchSize = 2;
// Calculate the combined amount of DAI assets to transfer to this contract
uint256 transferAmount = perStreamAmount * batchSize;
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 Batch
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), transferAmount);
// Approve the Batch contract to spend DAI
DAI.approve(address(BATCH_LOCKUP), transferAmount);
For more guidance on how to approve and transfer ERC-20 assets, see this article on the Ethereum website.
Stream Parameters
Given that we declared a batchSize
of two, we need to define two
BatchLockup.CreateWithTimestampsLD
structs:
// Declare the first stream in the batch
BatchLockup.CreateWithTimestampsLD memory stream0;
stream0.sender = address(0xABCD); // The sender to stream the assets, he will be able to cancel the stream
stream0.recipient = address(0xCAFE); // The recipient of the streamed assets
stream0.totalAmount = perStreamAmount; // The total amount of each stream, inclusive of all fees
stream0.cancelable = true; // Whether the stream will be cancelable or not
stream0.transferable = false; // Whether the recipient can transfer the NFT or not
stream0.startTime = uint40(block.timestamp); // Set the start time to block timestamp
// Declare some dummy segments
stream0.segments = new LockupDynamic.Segment[](2);
stream0.segments[0] = LockupDynamic.Segment({
amount: uint128(perStreamAmount / 2),
exponent: ud2x18(0.25e18),
timestamp: uint40(block.timestamp + 1 weeks)
});
stream0.segments[1] = (
LockupDynamic.Segment({
amount: uint128(perStreamAmount - stream0.segments[0].amount),
exponent: ud2x18(2.71e18),
timestamp: uint40(block.timestamp + 24 weeks)
})
);
stream0.broker = Broker(address(0), ud60x18(0)); // Optional parameter left undefined
To add some variety, we will change the parameters of the second stream:
BatchLockup.CreateWithTimestampsLD memory stream1;
stream1.sender = address(0xABCD); // The sender to stream the assets, he will be able to cancel the stream
stream1.recipient = address(0xBEEF); // The recipient of the streamed assets
stream1.totalAmount = uint128(perStreamAmount); // The total amount of each stream, inclusive of all fees
stream1.cancelable = false; // Whether the stream will be cancelable or not
stream0.transferable = false; // Whether the recipient can transfer the NFT or not
stream0.startTime = uint40(block.timestamp); // Set the start time to block timestamp
// Declare some dummy segments
stream1.segments = new LockupDynamic.Segment[](2);
stream1.segments[0] = LockupDynamic.Segment({
amount: uint128(perStreamAmount / 4),
exponent: ud2x18(1e18),
timestamp: uint40(block.timestamp + 4 weeks)
});
stream1.segments[1] = (
LockupDynamic.Segment({
amount: uint128(perStreamAmount - stream1.segments[0].amount),
exponent: ud2x18(3.14e18),
timestamp: uint40(block.timestamp + 52 weeks)
})
);
stream1.broker = Broker(address(0), ud60x18(0)); // Optional parameter left undefined
Once both structs are declared, the batch array has to be filled:
// Fill the batch array
BatchLockup.CreateWithTimestampsLD[] memory batch = new BatchLockup.CreateWithTimestampsLD[](batchSize);
batch[0] = stream0;
batch[1] = stream1;
Invoke the batch create function
With all parameters set, we can now call the createWithTimestampsLD
function, and assign the ids of the newly created
streams to the array:
streamIds = BATCH.createWithTimestampsLD(LOCKUP_DYNAMIC, DAI, batch);
The complete Batch Lockup Dynamic stream creator contract
Below you can see the complete functioning code: a contract that batch creates Lockup Dynamic streams using Sablier's
Batch
that start at block.timestamp
. You can access the code on GitHub through this
link.
loading...