# Sablier Full Documentation ## Schema Source: https://docs.sablier.com/api/airdrops/graphql/schema # Schema ## Overview We provide auto-generated GraphQL schema documentation for both The Graph and Envio: - [The Graph schema docs](/api/airdrops/graphql/the-graph/overview) - [Envio schema docs](/api/airdrops/graphql/envio/overview) ## Query Syntax Differences | Feature | The Graph | Envio | | --- | --- | --- | | Pagination | `first` | `limit` | | Pagination | `skip` | `offset` | | Get by ID | `lockupStream(id: "...")` / `flowStream(id: "...")` | `LockupStream_by_pk(id: "...")` / `FlowStream_by_pk(id: "...")` | | Get multiple | `lockupStreams{}` / `flowStreams{}` | `LockupStream(where: {...}){}` / `FlowStream(where: {...}){}` | | Nested items | `campaigns{ id, asset: {id, symbol}}` | `Campaign{ asset_id, asset: {id, symbol}}` | ## Entities This is the raw GraphQL file that is used by both The Graph and Envio for generating the final schemas hosted on their services. The schema only contains entities: ```graphql enum ActionCategory { Claim Clawback Create LowerMinFeeUSD RedistributionEnabled TransferAdmin } enum CampaignCategory { Instant LockupLinear LockupTranched VariableClaimAmount } """ ERC-20 asset """ type Asset @entity(immutable: false) { """ Unique identifier: `asset-{chainId}-{address}` """ id: String! """ Address of the ERC-20 token. """ address: Bytes! """ The chain ID where the asset exists (e.g., 137 for Polygon). """ chainId: BigInt! """ Decimals of the ERC20 token. """ decimals: BigInt! """ Name of the ERC20 token. """ name: String! """ Symbol of the ERC20 token. """ symbol: String! """ Campaigns that rely on this asset. """ campaigns: [Campaign!]! @derivedFrom(field: "asset") } type Watcher @entity(immutable: false) { """ The chain ID. There is one watcher per subgraph. """ id: String! """ Global counter for actions. """ actionCounter: BigInt! """ Alias for id. """ chainId: BigInt! """ Global counter. """ campaignCounter: BigInt! } """ An address that has sent USDC to the Sablier treasury for the billing process. """ type Sponsor @entity(immutable: false) { """ Unique identifier: `{chainId}_{address}` """ id: String! """ Address of the sponsor. """ address: Bytes! """ The chain ID where the sponsorship was made (sponsorships are chain-specific). """ chainId: BigInt! """ Number of sponsorships made by this sponsor. """ sponsorshipCount: Int! """ List of individual sponsorship transfers made by this sponsor. """ sponsorships: [Sponsorship!]! @derivedFrom(field: "sponsor") """ Cumulative raw USDC amount paid by the sponsor. """ totalAmount: BigInt! """ Human-readable cumulative USDC amount paid by the sponsor (e.g., "1729.12"). """ totalAmountDisplay: String! } """ A single USDC transfer to the Sablier treasury. Immutable — one per Transfer event. """ type Sponsorship @entity(immutable: true) { """ Unique identifier: `{chainId}_{txHash}_{logIndex}` """ id: String! """ Raw USDC amount. """ amount: BigInt! """ Human-readable USDC amount (e.g., "1719.12"). """ amountDisplay: String! """ Block number of the transaction. """ block: BigInt! """ Address of the campaign contract that emitted the Sponsor event. """ campaignAddress: Bytes! """ ID of the campaign entity (format: `{address}-{chainId}`). """ campaignId: String! """ The ID of the chain where the transfer occurred (e.g., 1 for Ethereum). """ chainId: BigInt! """ Log index of the Transfer event within the transaction. """ logIndex: Int! """ Address of the transaction signer (may differ from `from` for contract wallets). """ sender: Bytes! """ The sponsor who made this transfer. """ sponsor: Sponsor! """ Unix timestamp of the transaction. """ timestamp: BigInt! """ Hash of the transaction. """ txHash: Bytes! } """ A generic entity for tracking protocol actions. There may be multiple actions for a single tx. """ type Action @entity(immutable: true) { """ Unique identifier: `action-{chainId}-{txHash}-{logIndex}` """ id: String! """ Unique global id as tracked by the `Watcher` entity. """ subgraphId: BigInt! """ Transaction details: block number """ block: BigInt! """ The chain ID where the action was created (e.g., 137 for Polygon). """ chainId: BigInt! """ The tx.origin of the Ethereum transaction. """ from: Bytes! """ Transaction details: hash """ hash: Bytes! """ Transaction details: timestamp """ timestamp: BigInt! """ Contract through which the stream actions has been triggered """ campaign: Campaign! """ Category of action, e.g., Create. """ category: ActionCategory! """ The Sablier fee paid in the native token of the chain (e.g., ETH for Mainnet). See https://docs.sablier.com/concepts/fees """ fee: BigInt """ Claim action data: amount. """ claimAmount: BigInt """ Claim action data: index. """ claimIndex: BigInt """ Claim action data: original recipient address of the airdrop, which is part of the Merkle tree. """ claimRecipient: Bytes """ Claim action data: the actual address that will receive the airdrop, which can be different from the `claimRecipient`. This field is available in v2.0 and later. """ claimTo: Bytes """ Claim action data: stream ID as provided by the Lockup subgraph: {contractAddress}-{chainId}-{tokenId}. """ claimStreamId: String """ Claim action data: ERC-721 token id as provided by the Lockup contract. """ claimTokenId: BigInt """ Clawback action data: amount """ clawbackAmount: BigInt """ Clawback action data: from """ clawbackFrom: Bytes """ Clawback action data: to """ clawbackTo: Bytes """ Forgone amount for VCA campaigns only. """ vcaForgoneAmount: BigInt } """ User activity grouped by day. """ type Activity @entity(immutable: false) { """ Unique identifier: `activity-{campaignId}-{dayOfSnapshot}` """ id: String! """ Total amount claimed during the day. """ amount: BigInt! """ Campaign the activity is linked to. """ campaign: Campaign! """ Number of claims completed during the day. """ claims: BigInt! """ Day index: Unix timestamp / 24 * 60 * 60. """ day: BigInt! """ Timestamp of the start of the day. """ timestamp: BigInt! } """ Entity for Merkle campaigns. """ type Campaign @entity(immutable: false) { """ Unique identifier: `{contractAddress}-{chainId}` """ id: String! """ The chain ID where the campaign was created (e.g., 137 for Polygon). """ chainId: BigInt! """ Unique global id as tracked by the subgraph watcher. """ subgraphId: BigInt! """ Hash of the Ethereum transaction that created this campaign. """ hash: Bytes! """ Unix timestamp of the Ethereum transaction that created this campaign. """ timestamp: BigInt! """ Actions triggered by this campaign. """ actions: [Action!]! @derivedFrom(field: "campaign") """ Action in which the admin clawed back funds from the campaign. """ clawbackAction: Action """ Type of campaign, e.g. Instant. """ category: CampaignCategory! """ User-provided name for the campaign, which is null in Airdrops v1.1. """ name: String """ Internal name generated by us, derived from `name` or generated from scratch in older versions. """ nickname: String! """ List of daily activity snapshots for days in which at least one action was triggered. """ activities: [Activity!]! @derivedFrom(field: "campaign") """ Address of the campaign contract. """ address: Bytes! """ Address of the campaign admin, with permission to clawback. """ admin: Bytes! """ Total airdrop amount. """ aggregateAmount: BigInt! """ Underlying ERC-20 token distributed via the campaign. """ asset: Asset! """ Total amount claimed so far. """ claimedAmount: BigInt! """ Number of claims made so far. """ claimedCount: BigInt! """ Unix timestamp when the campaign underwent a clawback. """ clawbackTime: BigInt """ Unix timestamp when the campaign expires and clawback becomes available (if `expires` is true). """ expiration: BigInt """ Flag indicating if the campaign expires or is claimable indefinitely. """ expires: Boolean! """ The factory contract that deployed this campaign. """ factory: Factory! """ Minimum fee charged by this campaign, denominated in the native token of the chain (e.g., ETH for Mainnet). Only available in v1.3 and later See https://docs.sablier.com/concepts/fees """ fee: BigInt """ IPFS content identifier for the list of recipients and other static details. """ ipfsCID: String! """ Index of the campaign based on the `campaignCounter` in the `Factory` entity. """ position: BigInt! """ Merkle root. """ root: Bytes! """ Unix timestamp when the campaign starts and claiming becomes available. """ campaignStartTime: BigInt! """ Total number of recipients. """ totalRecipients: BigInt! """ Version of the campaign contract, e.g., v1.3. """ version: String! """ Address of the Lockup contract through which streams are created. """ lockup: Bytes """ Flag indicating whether the claimed streams will be cancelable initially. """ streamCancelable: Boolean """ Flag indicating whether the claimed streams will have a cliff. Only available for Linear streams. """ streamCliff: Boolean """ The duration of the cliff that the stream will have, in seconds. Only available for Linear streams. """ streamCliffDuration: BigInt """ The amount that will unlock at the cliff of the claimed stream, expressed as a percentage of the total amount. Only available for Linear streams. """ streamCliffPercentage: BigInt """ Flag indicating whether the claimed stream will have an initial unlock. Only available for Linear streams. """ streamInitial: Boolean """ The initial unlock amount of the claimed stream, expressed as a percentage of the total. Only available for Linear streams. """ streamInitialPercentage: BigInt """ The shape of the distribution. """ streamShape: String """ Flag indicating if the claimed stream will have a preset start time or it will use the claim time as the start time. """ streamStart: Boolean """ Unix timestamp for the start time. """ streamStartTime: BigInt """ Snapshot of the duration in seconds for produced streams. """ streamTotalDuration: BigInt """ Tranches of the claimed stream. """ streamTranches: [Tranche!]! @derivedFrom(field: "campaign") """ Flag indicating whether the claimed streams will be transferable. """ streamTransferable: Boolean """ Flag indicating whether the forgone amount will be redistributed. Only available for VCA campaigns. """ vcaRedistributionEnabled: Boolean """ Total amount forgone by early claimers. Only available for VCA campaigns. """ vcaForgoneAmount: BigInt """ The percentage unlocked immediately at vesting start. Only available for VCA campaigns. """ vcaUnlockPercentage: BigInt """ Unix timestamp when VCA vesting begins. Only available for VCA campaigns. """ vcaStartTime: BigInt """ Unix timestamp when VCA vesting ends. Only available for VCA campaigns. """ vcaEndTime: BigInt } """ Entity for Merkle factories, which deploy campaigns. """ type Factory @entity(immutable: false) { """ Unique identifier: `factory-{chainId}-{address}` """ id: String! """ The address of the factory contract. """ address: Bytes! """ Factory alias, e.g., `MSF2`. For historical reasons, the alias comes from the the name of the `MerkleStreamFactory` contract. """ alias: String! """ Factory index for campaigns """ campaignCounter: BigInt! """ The chain ID where the factory was created (e.g., 137 for Polygon). """ chainId: BigInt! """ Campaigns deployed by this factory. """ campaigns: [Campaign!]! @derivedFrom(field: "factory") } """ Used in Merkle Lockup campaigns for the vesting schedule. """ type Tranche @entity(immutable: true) { """ Unique identifier: `tranche-{campaignId}-{position}` """ id: String! """ The campaign in which this tranche was created. """ campaign: Campaign! """ Duration of the tranche, in seconds. """ duration: BigInt! """ Total duration accrued at the end of the tranche. This is the sum of this tranche's duration and all previous tranches' durations. """ endDuration: BigInt! """ Total percentage unlocked at the end of the tranche. This is the sum of this tranche's percentage and all previous tranches' percentages. """ endPercentage: BigInt! """ Percentage of the total amount unlocked by this tranche. """ percentage: BigInt! """ Position of the tranche inside the array. """ position: BigInt! """ Total duration accrued at the start of the tranche. This is the sum of all previous tranches' durations. """ startDuration: BigInt! """ Total percentage unlocked at the start of the tranche. This is the sum of all previous tranches' percentages. """ startPercentage: BigInt! } ``` --- ## Sablier Airdrops Source: https://docs.sablier.com/api/airdrops/indexers # Sablier Airdrops This page documents the indexers for the [Sablier Airdrops](/concepts/airdrops) protocol, which powers the [Airdrops](/apps/features/airdrops) product in the Sablier Interface. :::info Vested airdrops will create a [Lockup](/concepts/lockup/overview) stream when a user makes a claim. ::: ## Envio ### Source Code [ Envio indexer for Sablier Airdropsenvio/airdrops ](https://github.com/sablier-labs/indexers/blob/main/envio/airdrops) ### Endpoints Envio is a multi-chain indexer. There's a single GraphQL API endpoint that aggregates data across chains. This approach differs from other vendors like The Graph, which require a separate indexer for each chain where Sablier is available. The standard Envio endpoints require [Hasura](https://docs.envio.dev/docs/HyperIndex/navigating-hasura) GraphQL query syntax. :::tip [Migrating from The Graph?] Envio also exposes `/converter` endpoints (see the table below) that accept subgraph-compatible queries, convert them to HyperIndex (standard GraphQL), and return responses in the same format as a standard subgraph. ::: #### Table | Chain | Production URL | Converter URL | Playground URL | Explorer URL | | --- | --- | --- | --- | --- | | Abstract | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Arbitrum | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Avalanche | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Base | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Blast | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Berachain | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | BNB Chain | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Chiliz | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Gnosis | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | HyperEVM | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Linea Mainnet | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Ethereum | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Mode | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Monad | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Morph | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | OP Mainnet | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Polygon | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Sonic | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Scroll | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Sei Network | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Sophon | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Superseed | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Unichain | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | XDC | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | ZKsync Era | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Base Sepolia | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | | Sepolia | [Production](https://indexer.hyperindex.xyz/508d217/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/508d217/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F508d217%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/merkle-envio) | ## The Graph ### Source Code [ Graph indexer for Sablier Airdropsgraph/airdrops ](https://github.com/sablier-labs/indexers/blob/main/graph/airdrops) ### Endpoints In the table below, you will see three URLs: - `Production URL`: requires a Studio API key for making queries. - `Testing URL`: doesn't require an API key but it's rate-limited to 3000 queries per day. Opening this URL in the browser opens a GraphiQL playground. - `Explorer URL`: The explorer URL for the indexer, if available. To learn how to create a Studio API key, check out [this guide](https://thegraph.com/docs/en/studio/managing-api-keys). The API key has to be provided via an `Authorization: Bearer ` header. Here are two examples in curl and JavaScript: **curl** ```bash curl -X POST \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query": "{ _meta: { block { number } } }"' \ ``` **JavaScript** ```js import { GraphQLClient } from "graphql-request"; async function getBlockNumber() { const client = new GraphQLClient("", { headers: { Authorization: `Bearer `, }, }); const query = `query { _meta: { block { number } } }`; const result = await client.request(query); console.log(result); } ``` | Chain | Production URL | Testing URL | Explorer URL | | --- | --- | --- | --- | | Abstract | [Production](https://gateway.thegraph.com/api/subgraphs/id/DRrf6mYEhRt9QieKvTjDHnSWcBm3GW96hpedMKVxLABx) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-abstract/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/DRrf6mYEhRt9QieKvTjDHnSWcBm3GW96hpedMKVxLABx) | | Arbitrum | [Production](https://gateway.thegraph.com/api/subgraphs/id/HkHDg6NVVVeobhpcU4pTPMktyC25zd6xAQBGpYrWDgRr) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-arbitrum/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/HkHDg6NVVVeobhpcU4pTPMktyC25zd6xAQBGpYrWDgRr) | | Avalanche | [Production](https://gateway.thegraph.com/api/subgraphs/id/CpbN5Ps25UzqfdoqYdrjoSK4Him6nwDvdLK6a2sGS1PA) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-avalanche/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/CpbN5Ps25UzqfdoqYdrjoSK4Him6nwDvdLK6a2sGS1PA) | | Base | [Production](https://gateway.thegraph.com/api/subgraphs/id/4SxPXkQNifgBYqje2C4yP5gz69erZwtD7GuLWgXHSLGe) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-base/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/4SxPXkQNifgBYqje2C4yP5gz69erZwtD7GuLWgXHSLGe) | | Base Sepolia | [Production](https://gateway.thegraph.com/api/subgraphs/id/4R2hm27YJ7CVEJ97BbBJz2r4KTKYc8sTqqzrD8UzEfJt) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-base-sepolia/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/4R2hm27YJ7CVEJ97BbBJz2r4KTKYc8sTqqzrD8UzEfJt) | | Berachain | [Production](https://gateway.thegraph.com/api/subgraphs/id/CnYsdmzuY3Mebwywvqv1WrXw9UZuPMTrxoGgR2UdThJE) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-berachain/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/CnYsdmzuY3Mebwywvqv1WrXw9UZuPMTrxoGgR2UdThJE) | | BNB Chain | [Production](https://gateway.thegraph.com/api/subgraphs/id/FXQT42kQPvpMJgsF5Bs6CqpxVvPP1LBqEhWThCCLMeGL) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-bsc/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/FXQT42kQPvpMJgsF5Bs6CqpxVvPP1LBqEhWThCCLMeGL) | | Chiliz | [Production](https://gateway.thegraph.com/api/subgraphs/id/6LK1aqrhzZCp6c88MEBDAR1VDLpZQiXpBKkceJ5Lu4LU) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-chiliz/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/6LK1aqrhzZCp6c88MEBDAR1VDLpZQiXpBKkceJ5Lu4LU) | | Denergy | [Production](https://thegraph.denergychain.com/subgraphs/name/denergychain/sablier-airdrops-denergychain) | N/A | [Explorer](https://thegraph.denergychain.com/subgraphs/name/denergychain/sablier-airdrops-denergychain/graphql) | | Ethereum | [Production](https://gateway.thegraph.com/api/subgraphs/id/DFD73EcSue44R7mpHvXeyvcgaT8tR1iKakZFjBsiFpjs) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-ethereum/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/DFD73EcSue44R7mpHvXeyvcgaT8tR1iKakZFjBsiFpjs) | | Gnosis | [Production](https://gateway.thegraph.com/api/subgraphs/id/kQEY5PYbjx4SMKyMUwqJHRLDzKH1aUqGsf1cnibU7Kn) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-gnosis/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/kQEY5PYbjx4SMKyMUwqJHRLDzKH1aUqGsf1cnibU7Kn) | | Lightlink | [Production](https://graph.phoenix.lightlink.io/query/subgraphs/name/lightlink/sablier-airdrops-lightlink) | N/A | [Explorer](https://graph.phoenix.lightlink.io/query/subgraphs/name/lightlink/sablier-airdrops-lightlink/graphql) | | Linea Mainnet | [Production](https://gateway.thegraph.com/api/subgraphs/id/6koYFSd8FQizdQWLTdRpL1yTmAbpMgN1vZN5W6ajZiTN) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-linea/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/6koYFSd8FQizdQWLTdRpL1yTmAbpMgN1vZN5W6ajZiTN) | | OP Mainnet | [Production](https://gateway.thegraph.com/api/subgraphs/id/CHJtCNDzPqngpa1YJoaVrjuufZL6k6VkEkG9ZFUMQzF7) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-optimism/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/CHJtCNDzPqngpa1YJoaVrjuufZL6k6VkEkG9ZFUMQzF7) | | Polygon | [Production](https://gateway.thegraph.com/api/subgraphs/id/FRbBKiDyM5YpFAqHLXRfQWwQdMGzFL82hqoPXPpXzAHE) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-polygon/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/FRbBKiDyM5YpFAqHLXRfQWwQdMGzFL82hqoPXPpXzAHE) | | Scroll | [Production](https://gateway.thegraph.com/api/subgraphs/id/Ev4xS8VxuoUcpgqz5A2BkTgQxQeskm4Fg41XzVJ2DX9) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-scroll/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/Ev4xS8VxuoUcpgqz5A2BkTgQxQeskm4Fg41XzVJ2DX9) | | Sepolia | [Production](https://gateway.thegraph.com/api/subgraphs/id/8PLGDyXEsPgRTAnozL7MAjmTUFY4TBzs8i4F9Pq3wwSh) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-sepolia/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/8PLGDyXEsPgRTAnozL7MAjmTUFY4TBzs8i4F9Pq3wwSh) | | Sonic | [Production](https://gateway.thegraph.com/api/subgraphs/id/5g8orwpm5Rf83G8eqDzDjodt3sG2D64cbiLC98Utmv4Q) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-sonic/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/5g8orwpm5Rf83G8eqDzDjodt3sG2D64cbiLC98Utmv4Q) | | Unichain | [Production](https://gateway.thegraph.com/api/subgraphs/id/4rQMJ85hKNhcaDyirGipGvcqS4auGU3QCFRBnpiexyNy) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-unichain/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/4rQMJ85hKNhcaDyirGipGvcqS4auGU3QCFRBnpiexyNy) | | ZKsync Era | [Production](https://gateway.thegraph.com/api/subgraphs/id/64iDUwNVWKukw67nqTXif5taEfLug4Qf1c2suAv5hrqN) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-airdrops-zksync/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/64iDUwNVWKukw67nqTXif5taEfLug4Qf1c2suAv5hrqN) | --- ## Examples of common flows Source: https://docs.sablier.com/api/airdrops/merkle-api/examples # Examples of common flows Here are common architectural flows you may need to integrate into your application. These include methods to gather prerequisite data for each backend [functionality](/api/airdrops/merkle-api/functionality) described earlier. ## Get a campaign's CID To obtain a campaign's IPFS CID, you can: 1. Check the [create](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleInstant#events) events emitted by the Merkle factory 2. Use the Sablier [indexers](/api/overview) to query for that information. For approach "B", run the following query against the official endpoints: **The Graph** ```graphql query getCampaignData($campaignId: String!){ campaign(id: $campaignId){ id lockup root ipfsCID aggregateAmount totalRecipients } } ``` **Envio** ```graphql query getCampaignData($campaignId: String!){ Campaign(where: {id: {_eq: $campaignId}} ){ id lockup root ipfsCID aggregateAmount totalRecipients } } ``` ## Check eligibility for an address To check if an address is eligible, use the [/api/eligibility](/api/airdrops/merkle-api/functionality#eligibility-apieligibility) route provided by the merkle-api backend. #### Steps 1. Get the campaign CID (see [the example](/api/airdrops/merkle-api/examples#get-a-campaigns-cid) above) 2. Call the `/api/eligibility` route using the CID and wallet address Read more in the dedicated route documentation within the [/api/eligibility](/api/airdrops/merkle-api/functionality#eligibility-apieligibility) section of the functionality page. Alternatively, you can check eligibility by searching for the address in the list stored within the IPFS file from step 1. You'll still need to download the file first using its CID. ## Get the `tokenId` after a claim After someone claims, you may want to show them a preview of the stream or its NFT. To do this, you'll need the `tokenId` (or `streamId`) related to that user's claim. To obtain a `tokenId` linked to a claim, you can: 1. Listen to the [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claim) method and the Claim event emitted by the Merkle contract instance 2. Use the [Sablier Indexers](/api/overview) to query for that information. For approach "B", run the following query against the official endpoints (ensure the address is lowercase): **The Graph** ```graphql query getClaimForRecipient($campaignId: String!, $recipient: String) { actions(where: { campaign: $campaignId, category: Claim, claimRecipient: $recipient }) { campaign { id lockup } claimTokenId claimRecipient claimIndex } } ``` **Envio** ```graphql query getClaimForRecipient($campaignId: String!, $recipient: String) { Action( where: { _and: [{ campaign: { _eq: $campaignId } }, { category: { _eq: Claim } }, { claimRecipient: { _eq: $recipient } }] } ) { campaignObject { id lockup } claimTokenId claimRecipient claimIndex } } ``` #### Bonus: Stream NFT To get the Stream NFT, use the same query as option "B" to retrieve the `lockup` contract (where the Stream NFT is issued) and its `tokenId`. With these, you can call the [`tokenURI`](/reference/lockup/contracts/contract.LockupNFTDescriptor#tokenuri) method, which returns the SVG code of the onchain NFT. :::note Extract the SVG tags The actual SVG tags are encoded inside the `tokenURI` method response. You can feed this blob to an HTML `img` tag or render the SVG tags directly. To extract this code in plain format (not `base64`), run the following JavaScript code to decode the response: ```typescript const toPart = output.split("data:application/json;base64,").pop(); const toString = Buffer.from(toPart || "", "base64").toString("utf-8"); const toJSON = JSON.parse(toString); const blob = _.get(toJSON, "image")?.split("data:image/svg+xml;base64,")[1]; const toSVG = Buffer.from(blob || "", "base64").toString("utf-8"); ``` ::: ## Check if a user claimed their stream To check if a user has already claimed their stream from a campaign, you can: 1. Call the [`hasClaimed`](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase#hasclaimed) method from the Merkle contract instance 2. Use the [Sablier Indexers](/api/overview) to query for that information. #### Approach A For approach "A", perform the following actions: 1. Get the campaign's CID, Lockup contract address, and user address (for the first items, see the [Get a campaign's CID](/api/airdrops/merkle-api/examples#get-a-campaigns-cid) example above) 2. Check user eligibility (see [Eligibility](/api/airdrops/merkle-api/examples#check-eligibility-for-an-address) example above) and extract their `index` 3. Call the `hasClaimed` method inside the `lockup` contract using the `index` from step 2 :::note A user's index represents their assigned order number in the eligibility list. We use this value to generate a proof if the recipient is eligible and to register their claim directly in the contract. ::: #### Approach B For approach "B", run the same query as in the [Get the tokenId after a claim](/api/airdrops/merkle-api/examples#get-the-tokenid-after-a-claim) example. If the query returns results, the user has claimed their stream. :::tip A missing claim could also mean the user wasn't eligible initially. We recommend checking for eligibility alongside any existing claim checks. ::: --- ## Functionality Source: https://docs.sablier.com/api/airdrops/merkle-api/functionality # Functionality ## Architecture The backend is written in Rust and can run locally or in self-hosted environments using [`warp`](https://github.com/seanmonstar/warp), as well as on Vercel with its [`rust runtime`](https://github.com/vercel-community/rust). You can integrate the backend functionality using our official endpoints (below), but we recommend self-hosting for improved reliability. This allows you to fork the repository and provide your own IPFS and Pinata access keys. The official endpoints are documented in the [Airdrops Indexers](/api/airdrops/indexers) section. ## Create: `/api/create` Call this route to create a new Merkle airdrop campaign. ### Prerequisites Before creating a campaign, you'll need: - The `decimals` of the token you're basing the campaign on - A CSV file containing `[address, amount]` pairs for every campaign recipient Download a template CSV file from the link below or preview it [here](https://github.com/sablier-labs/sablier-labs.github.io/blob/1933e3b5176c93b236d9a483683dad3a282cc39a/templates/airstream-template.csv). [ Sablier Template for Airdrops (CSV)airstream-template.csv ](https://files.sablier.com/templates/airstream-template.csv) :::tip The CSV contains a header row, followed by address and amount pairs. Amounts should be in human-readable form—the API handles decimal padding automatically. ::: ### Description | | | | --- | --- | | **Endpoint** | `/api/create` | | **Method** | `POST` | | **Query Params** | `{decimals: number}` | | **Body** | `FormData` on `{data: File}` | | **Response** | See in [Rust](https://github.com/sablier-labs/merkle-api/blob/main/src/data_objects/response.rs#L22) Overview TS (below) | ```typescript type Response = { /** IPFS content identifier for the uploaded file */ cid: string; /** Expected number of recipients */ recipients: string; /** HEX root of the Merkle tree */ root: string; /** Humanized status */ status: string; /** Expected amount of assets required by the campaign */ total: string; }; ``` ### Functionality The `/api/create` route performs the following actions: **1\. Validation and processing** - Validates the CSV file and its contents - Adds decimal padding to every amount - Builds the Merkle tree and generates a root hash - Computes intermediary data (total expected amount, number of recipients) - Prepares an object containing the list, tree, and computed data **2\. Upload to IPFS** - Uploads the object as a JSON file to IPFS - Retrieves the IPFS CID (unique identifier of the uploaded file) **3\. Return data to client** - Returns the root hash, IPFS CID, and intermediary data to the client - The client uses this data to call the factory and deploy a new campaign ### Code For implementation details, check out the source code: [ create.rsGithub - sablier-labs/merkle-api ](https://github.com/sablier-labs/merkle-api/blob/main/src/controller/create.rs) --- ## Eligibility: `/api/eligibility` Call this route to check if a recipient is eligible to claim a stream from the Merkle airdrop campaign. ### Prerequisites To check eligibility for an address, you'll need: - The recipient's address - The CID of the IPFS file (see the [create](/api/airdrops/merkle-api/functionality#create-apicreate) flow above) linked to the campaign For obtaining the CID, see options in the [Common flows](/api/airdrops/merkle-api/examples#get-a-campaigns-cid) page. ### Description | | | | --- | --- | | **Endpoint** | `/api/eligibility` | | **Method** | `GET` | | **Query Params** | `{address: string, cid: string}` | | **Response** | See in [Rust](https://github.com/sablier-labs/merkle-api/blob/main/src/data_objects/response.rs#L32) Overview TS (below) | ```typescript type Response = { /** Address of the requested recipient */ address: IAddress; /** Amount the recipient is eligible for */ amount: string; /** Position of the recipient in the list */ index: 0; /** Merkle proof */ proof: string[]; }; ``` ### Functionality The `/api/eligibility` route performs the following actions: 1. Retrieves the campaign's IPFS file and extracts the recipient list and Merkle tree 2. Searches for the provided wallet address ### Code For implementation details, check out the source code: [ eligibility.rsGithub - sablier-labs/merkle-api ](https://github.com/sablier-labs/merkle-api/blob/main/src/controller/eligibility.rs) --- ## Validity: `/api/validity` Call this route to check if an IPFS CID links to a valid Merkle airdrop campaign file. Since users may accidentally provide invalid IPFS CIDs, we use this route to perform sanity checks before allowing admins to create campaigns in the UI. ### Prerequisites To check validity, you'll need: - The CID of the IPFS file linked to the campaign For obtaining the CID, see options in the [Common flows](/api/airdrops/merkle-api/examples#get-a-campaigns-cid) page. ### Description | | | | --- | --- | | **Endpoint** | `/api/validity` | | **Method** | `GET` | | **Query Params** | `{cid: string}` | | **Response** | See in [Rust](https://github.com/sablier-labs/merkle-api/blob/main/src/data_objects/response.rs#L41) Overview TS (below) | ```typescript type Response = { /** IPFS content identifier for the uploaded file */ cid: string; /** Expected number of recipients */ recipients: string; /** HEX root of the Merkle tree */ root: string; /** Expected amount of assets required by the campaign */ total: string; }; ``` ### Functionality The `/api/validity` route performs the following actions: 1. Retrieves the campaign's IPFS file 2. Runs sanity checks on the file contents ### Code For implementation details, check out the source code: [ validity.rsGithub - sablier-labs/merkle-api ](https://github.com/sablier-labs/merkle-api/blob/main/src/controller/validity.rs) --- ## Overview Source: https://docs.sablier.com/api/airdrops/merkle-api/overview # Overview Sablier Airdrops use pre-configured Merkle trees, a data structure that efficiently stores recipient lists and their individual claim details. ## General data flow Merkle trees enable minimal onchain storage while maintaining cryptographic proof of eligibility. The system stores only the tree's root hash in the deployed Airstream contract, while the complete tree and recipient list reside in IPFS. Operations like eligibility checks or claim detail requests follow this process: 1. Read data from the IPFS file 2. Generate Merkle proofs from the tree 3. Interact with the contract using the obtained data ## Open-source solution We've built a Rust backend service that provides REST API access to Merkle tree functionality. This service handles tree creation, storage, and data retrieval for both the Sablier Interface and third-party applications. ## Integrations Integrate this API by forking the repository to run your own instance. We actively improve and optimize this service, so submit suggestions or issues directly on GitHub. :::tip If you want to integrate the eligibility/claim functionality, you can self-host the above API by forking the repository. ::: [ Sablier: Merkle APIMerkle API for the Sablier Protocol ](https://github.com/sablier-labs/merkle-api) --- ## Getting Started Source: https://docs.sablier.com/api/getting-started # Getting Started ## Getting Started This document will help you understand how to read data from the Sablier smart contracts, either by using GraphQL indexers or by calling the smart contracts directly. Each method has its own advantages and limitations, which we will explore in detail. ## Choosing Your Approach You have two options: 1. **GraphQL Indexers**: Recommended for most use cases due to ease of use and comprehensive data access. 2. **Direct Smart Contract Calls**: Offers a more decentralized approach but comes with limitations. ### GraphQL Indexers This is the preferred method for accessing Sablier data. The recommended stack includes: - **[React](https://react.dev)** - **[GraphQL Code Generator](https://the-guild.dev/graphql/codegen/docs/getting-started)**: Automatically generates types from your GraphQL fragments and queries. - **[TanStack Query](https://tanstack.com/query)**: React hooks to fetch data from the GraphQL endpoints. #### Advantages - Simplifies data access with a unified API. - Provides caching and data unification. - Easy integration with modern frontend frameworks. #### Considerations - Understanding the GraphQL schema may require some initial effort. #### Frontend Sandbox To help you get started quickly, we offer a pre-configured frontend sandbox with the recommended stack: [ Sablier Frontend Sandbox ](https://github.com/sablier-labs/sandbox) ### Direct Smart Contract Calls This approach provides a more decentralized way to interact with the blockchain but has several limitations: - **Data Retrieval**: Cannot retrieve a list of all streams for a user directly. - **Historical Data**: Limited access to past events unless the app is set up to listen to web3 events via RPC. - **Pagination**: Not supported. ## Conclusion Choosing the right approach depends on your specific needs and the trade-offs you're willing to make. For most applications, using one of our GraphQL indexers will provide a more seamless and efficient experience. However, if decentralization is a priority, direct smart contract calls may be the way to go. For more detailed information, refer to the [Overview](/api/overview) document and explore the specific indexers available for the Sablier protocols: - [Streams indexers (Lockup & Flow)](/api/streams/indexers) - [Airdrops indexers](/api/airdrops/indexers) --- ## Identifiers Source: https://docs.sablier.com/api/ids # Identifiers ## Stream IDs ### Contracts Onchain, each Lockup and Flow contract assigns a [`streamId`](/reference/lockup/contracts/abstracts/abstract.SablierLockupState#nextstreamid) to each stream, which is the same as the ERC-721 [`tokenId`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc721) (each stream is tokenized as an ERC-721). The `streamId`/ `tokenId` in the contract is a simple numerical value (a `uint256`). For example, number `42` is a valid `streamId`/ `tokenId`. You will always need this value when interacting with Sablier via a JSON-RPC endpoint because it is required by every contract method associated with a stream, e.g. [`streamedAmountOf`](/reference/lockup/contracts/interfaces/interface.ISablierLockup#streamedamountof). ### Indexers Offchain, in the GraphQL schema, we need to be able to identify streams across different EVM chains and different contract versions. Additionally, it would be nice to have short, easily readable aliases. Thus, we have come up with the following identifiers: | Identifier | Format | Example | | --- | --- | --- | | Stream ID | `{contractAddress}-{chainId}-{tokenId}` | `0xe0bfe071da104e571298f8b6e0fce44c512c1ff4-137-21` | | Stream Alias | `{contractAlias}-{chainId}-{tokenId}` | `LK-137-21` | Both examples from the table above translate to: \*\*a stream on Polygon (chain ID `137`) created via the Lockup v2.0 contract at address `0xe0bfe071da104e571298f8b6e0fce44c512c1ff4`, with the token ID `21`. ### Aliases Here's the full list of contract aliases, organized by protocol. #### Lockup | Alias | Contract Name | Release | | --- | --- | --- | | LK3 | SablierLockup | Lockup v4.0 | | LK2 | SablierLockup | Lockup v3.0 | | LK | SablierLockup | Lockup v2.0 | | LD3 | SablierV2LockupDynamic | Lockup v1.2 | | LD2 | SablierV2LockupDynamic | Lockup v1.1 | | LD | SablierV2LockupDynamic | Lockup v1.0 | | LL | SablierV2LockupLinear | Lockup v1.0 | | LL2 | SablierV2LockupLinear | Lockup v1.1 | | LL3 | SablierV2LockupLinear | Lockup v1.2 | | LT3 | SablierV2LockupTranched | Lockup v1.2 | #### Flow | Alias | Contract Name | Release | | --- | --- | --- | | FL4 | SablierFlow | Flow v3.0 | | FL3 | SablierFlow | Flow v2.0 | | FL2 | SablierFlow | Flow v1.1 | | FL | SablierFlow | Flow v1.0 | #### Airdrops | Alias | Contract Name | Release | | --- | --- | --- | | MF2\_EXEC | SablierFactoryMerkleExecute | Airdrops v3.0 | | MF2\_INST | SablierFactoryMerkleInstant | Airdrops v3.0 | | MF2\_LL | SablierFactoryMerkleLL | Airdrops v3.0 | | MF2\_LT | SablierFactoryMerkleLT | Airdrops v3.0 | | MF2\_VCA | SablierFactoryMerkleVCA | Airdrops v3.0 | | MF\_INST | SablierFactoryMerkleInstant | Airdrops v2.0 | | MF\_LL | SablierFactoryMerkleLL | Airdrops v2.0 | | MF\_LT | SablierFactoryMerkleLT | Airdrops v2.0 | | MF\_VCA | SablierFactoryMerkleVCA | Airdrops v2.0 | | MSF4 | SablierMerkleFactory | Airdrops v1.3 | | MSF3 | SablierV2MerkleLockupFactory | Airdrops v1.2 | | MSF2 | SablierV2MerkleStreamerFactory | Airdrops v1.1 | ## Airdrop IDs ### Contracts Airdrops do not have any onchain IDs because every airdrop is a separate contract deployed by one of the Merkle Factory contracts. ### Indexers The ID for an airdrop is the chain ID concatenated with the contract address of the airdrop contract: | Identifier | Format | Example | | --- | --- | --- | | Airdrop ID | `{contractAddress}-{chainId}` | `0xf50760d8ead9ff322631a1f3ebf26cc7891b3708-137` | The example from the table above translates to: **_an airdrop on Polygon (chain ID `137`), with the contract address `0xf50760d8ead9ff322631a1f3ebf26cc7891b3708`_**. :::info We have decided not to generate aliases for Airdrops, but this may change in the future. ::: --- ## Sablier Legacy Source: https://docs.sablier.com/api/legacy # Sablier Legacy These are the indexers for the [legacy version](/apps/legacy) of Sablier (deployed between 2019-2021). Sablier Legacy is only indexed by The Graph. ## Source Code [ Sablier Legacy SubgraphThe Graph subgraph for the legacy Sablier protocol ](https://github.com/sablier-labs/legacy-subgraph) ## Endpoints In the table below, you will see three URLs: - `Production URL`: requires a Studio API key for making queries. - `Testing URL`: doesn't require an API key but it's rate-limited to 3000 queries per day. Opening this URL in the browser opens a GraphiQL playground. - `Explorer URL`: The explorer URL for the indexer, if available. To learn how to create a Studio API key, check out [this guide](https://thegraph.com/docs/en/studio/managing-api-keys). The API key has to be provided via an `Authorization: Bearer ` header. Here are two examples in curl and JavaScript: **curl** ```bash curl -X POST \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query": "{ _meta: { block { number } } }"' \ ``` **JavaScript** ```js import { GraphQLClient } from "graphql-request"; async function getBlockNumber() { const client = new GraphQLClient("", { headers: { Authorization: `Bearer `, }, }); const query = `query { _meta: { block { number } } }`; const result = await client.request(query); console.log(result); } ``` | Chain | Production URL | Testing URL | | --- | --- | --- | | Avalanche | [sablier-avalanche](https://gateway-arbitrum.network.thegraph.com/api/API_KEY/subgraphs/id/DK2gHCprwVaytwzwb5fUrkFS9xy7wh66NX6AFcDzMyF9) | [Testing](https://api.studio.thegraph.com/query/57079/sablier-avalanche/version/latest) | | Arbitrum | [sablier-arbitrum](https://gateway-arbitrum.network.thegraph.com/api/API_KEY/subgraphs/id/94SP9QVcxmGV9e2fxuTxUGcZfcv4tjpPCGyyPVyMfLP) | [Testing](https://api.studio.thegraph.com/query/57079/sablier-arbitrum/version/latest) | | BSC | [sablier-bsc](https://gateway-arbitrum.network.thegraph.com/api/API_KEY/subgraphs/id/3Gyy7of99oBRqHcCMGJXpHw2xxxZgXxVmFPFR1vL6YhT) | [Testing](https://api.studio.thegraph.com/query/57079/sablier-bsc/version/latest) | | Ethereum | [sablier](https://gateway-arbitrum.network.thegraph.com/api/API_KEY/subgraphs/id/DkSXWkgJD5qVqfsrfzkLC5WELVX3Dbj3ByWcYjDJieCh) | [Testing](https://api.studio.thegraph.com/query/57079/sablier/version/latest/) | | Optimism | [sablier-optimism](https://gateway-arbitrum.network.thegraph.com/api/API_KEY/subgraphs/id/BEnQbvBdXnohC1DpM9rSb47C1FbowK39HfPNCEHjgrBt) | [Testing](https://api.studio.thegraph.com/query/57079/sablier-optimism/version/latest) | | Polygon | [sablier-matic](https://gateway-arbitrum.network.thegraph.com/api/API_KEY/subgraphs/id/6UMNQfMeh3pV5Qmn2NDX2UKNeUk9kh4oZhzzzn5e8rSz) | [Testing](https://api.studio.thegraph.com/query/57079/sablier-matic/version/latest) | | Ronin | [sablier-ronin](https://subgraph.satsuma-prod.com/d8d041c49d56/sablierlabs/sablier-ronin/api) | N/A | --- ## Overview Source: https://docs.sablier.com/api/overview # Overview This document provides an overview of the Sablier APIs, which consist of two main components: the GraphQL indexers and the Merkle API. These components are essential for accessing and managing data within the Sablier ecosystem. :::tip Ready to start building? Jump to the [Getting Started](/api/getting-started) section. ::: ## GraphQL Indexers The Sablier smart contracts do not inherently provide a structured way to access data. With multiple contract versions deployed on the blockchain, accessing data in a unified manner can be challenging. To address this, we integrated two data indexing services: The Graph and Envio. These services act as middleware, enabling data unification, caching, and querying through a GraphQL-based API. ### Source Code The source code for both The Graph and Envio is open-source and available in this GitHub repository: [ Sablier IndexersData indexers for the Sablier Protocol ](https://github.com/sablier-labs/indexers) ### The Graph [The Graph](https://thegraph.com/) offers infrastructure for indexing EVM events through GraphQL API endpoints known as 'subgraphs'. It has been the vendor of choice in the EVM space since Sablier Legacy's launch in 2019. Our latest products use new implementations of these subgraphs, which are documented in subsequent sections. Note that each chain has its own subgraphs. - [The Graph docs: How to Query a Subgraph](https://thegraph.com/docs/en/subgraphs/querying/introduction/) - [Streams indexers (Lockup & Flow)](/api/streams/indexers#the-graph) - [Airdrops indexers](/api/airdrops/indexers#the-graph) ### Envio [Envio](https://envio.dev) is a modern, fast, and flexible tool for accessing onchain data. Its HyperIndex service provides a GraphQL-based API for accessing cached data, which is then served to our client interfaces. Envio indexers mirror the GraphQL API of The Graph subgraphs, offering a fallback in case of issues with The Graph. Unlike The Graph, Envio's indexers are chain-agnostic, with a single GraphQL indexer for each Sablier protocol. - [Envio docs: Navigating Hasura](https://docs.envio.dev/docs/HyperIndex/navigating-hasura) - [Streams indexers (Lockup & Flow)](/api/streams/indexers#envio) - [Airdrops indexers](/api/airdrops/indexers#envio) ## Merkle API For the [Airdrops](/apps/features/airdrops) product, we created a backend service to streamline Merkle tree generation, essential for creating blockchain airdrops. This API handles the validation, creation, and management of Merkle trees, defining eligibility and claiming rules for airdrop campaigns. The service is open-source and can be used by integrators as a plug-and-play solution for deploying their own campaigns. Learn more about the Merkle API service in its dedicated [section](/api/airdrops/merkle-api/overview). [ Sablier: Merkle APIMerkle API for the Sablier Protocol ](https://github.com/sablier-labs/merkle-api) --- ## Schema Source: https://docs.sablier.com/api/streams/graphql/schema # Schema ## Overview We provide auto-generated GraphQL schema documentation for both The Graph and Envio: - [The Graph schema docs](/api/streams/graphql/the-graph/overview) - [Envio schema docs](/api/streams/graphql/envio/overview) ## Query Syntax Differences | Feature | The Graph | Envio | | --- | --- | --- | | Pagination | `first` | `limit` | | Pagination | `skip` | `offset` | | Get by ID | `lockupStream(id: "...")` / `flowStream(id: "...")` | `LockupStream_by_pk(id: "...")` / `FlowStream_by_pk(id: "...")` | | Get multiple | `lockupStreams{}` / `flowStreams{}` | `LockupStream(where: {...}){}` / `FlowStream(where: {...}){}` | | Nested items | `campaigns{ id, asset: {id, symbol}}` | `Campaign{ asset_id, asset: {id, symbol}}` | ## Entities This is the raw GraphQL file that is used by both The Graph and Envio for generating the final schemas hosted on their services. The schema only contains entities: ```graphql enum FlowActionCategory { Approval ApprovalForAll Adjust Create Deposit Pause Refund Restart Transfer Void Withdraw } enum FlowStreamCategory { Flow } enum LockupActionCategory { Approval ApprovalForAll Cancel Create Renounce Transfer Withdraw } enum LockupStreamCategory { LockupDynamic LockupLinear LockupTranched } enum ShapeSource { Event Inferred } """ ERC-20 asset """ type Asset @entity(immutable: false) { """ Unique identifier: `asset-{chainId}-{address}` """ id: String! """ Address of the ERC-20 token. """ address: Bytes! """ The chain ID where the asset exists (e.g., 137 for Polygon). """ chainId: BigInt! """ Decimals of the ERC20 token. """ decimals: BigInt! """ Name of the ERC20 token. """ name: String! """ Symbol of the ERC20 token. """ symbol: String! """ Flow streams that rely on this token """ flowStreams: [FlowStream!]! @derivedFrom(field: "asset") """ Lockup streams that rely on this token """ lockupStreams: [LockupStream!]! @derivedFrom(field: "asset") } type Watcher @entity(immutable: false) { """ The chain ID. There is one watcher per subgraph. """ id: String! """ Alias for id. """ chainId: BigInt! """ Global counter for Flow actions. """ flowActionCounter: BigInt! """ Global counter for Flow streams. """ flowStreamCounter: BigInt! """ Global counter for Lockup actions. """ lockupActionCounter: BigInt! """ Global counter for Lockup streams. """ lockupStreamCounter: BigInt! } type FlowStream @entity(immutable: false) { """ Unique identifier: `{contractAddress}-{chainId}-{tokenId}` """ id: String! """ Like the id: `{contractAlias}-{chainId}-{tokenId}` """ alias: String! """ The chain ID where the stream was created (e.g., 137 for Polygon). """ chainId: BigInt! """ Unique global id as tracked by the `Watcher` entity. 🚨 This may change if new data sources are added and the chronological order of streams changes. """ subgraphId: BigInt! """ The id provided by the NFT contract. It's the ERC-721 tokenId. """ tokenId: BigInt! """ Hash of the Ethereum transaction that created this stream. """ hash: Bytes! """ Unix timestamp of the Ethereum transaction that created this stream. """ timestamp: BigInt! """ Actions triggered by this stream. """ actions: [FlowAction!]! @derivedFrom(field: "stream") """ ERC-20 token distributed via this stream. """ asset: Asset! """ ERC-20 token decimals. Stored here to avoid loading the asset entity on each stream. Note: the "Value" suffix is added because of a bug in GraphQL Code Generator. """ assetDecimalsValue: BigInt! """ The batch the stream may be part of. Note: this is available only when created within a batch create transaction. """ batch: FlowBatch! """ Category used for sorting. """ category: FlowStreamCategory! """ The address of the contract the stream originates from. """ contract: Bytes! """ Position in the batch, if available. """ position: BigInt! """ Current recipient of the stream, with permission to withdraw funds to any third-party address. Note: the recipient can change on NFT transfer. """ recipient: Bytes! """ Manager of the stream, with ability to cancel the stream. """ sender: Bytes! """ Unix timestamp for the start of the stream. """ startTime: BigInt! """ Flag indicating the transferability of the stream. This is set when the stream is created, and cannot be changed later. """ transferable: Boolean! """ Version of contract, e.g., v1.0. """ version: String! """ The sum of all withdrawn amounts. """ withdrawnAmount: BigInt! """ This is equivalent to the value returned by ERC20.balanceOf, and it changes after deposit and withdrawal. """ availableAmount: BigInt! """ The account that created the stream, which can be different from the sender. """ creator: Bytes! """ Unix timestamp indicating the time when the stream will become insolvent. """ depletionTime: BigInt! """ The sum of all deposits. """ depositedAmount: BigInt! """ The amount of debt forgiven by a void action. """ forgivenDebt: BigInt! """ Action in which the payment rate was adjusted. """ lastAdjustmentAction: FlowAction """ Unix timestamp for when the payment rate was adjusted. """ lastAdjustmentTimestamp: BigInt! """ Flag indicating if a stream is paused. """ paused: Boolean! """ Action in which the stream was paused. """ pausedAction: FlowAction """ Unix timestamp for when the stream was paused. """ pausedTime: BigInt """ Current payment rate per second, denominated in 18 decimals. """ ratePerSecond: BigInt! """ The sum of all refunds. """ refundedAmount: BigInt! """ The amount streamed up until the time of the last adjustment, denominated in 18 decimals. """ snapshotAmount: BigInt! """ Flag indicating if a stream is voided. """ voided: Boolean! """ Action in which the stream was voided. """ voidedAction: FlowAction """ Unix timestamp for when the stream was voided. """ voidedTime: BigInt } type LockupStream @entity(immutable: false) { """ Unique identifier: `{contractAddress}-{chainId}-{tokenId}` """ id: String! """ Like the id: `{contractAlias}-{chainId}-{tokenId}` """ alias: String! """ The chain ID where the stream was created (e.g., 137 for Polygon). """ chainId: BigInt! """ Unique global id as tracked by the `Watcher` entity. 🚨 This may change if new data sources are added and the chronological order of streams changes. """ subgraphId: BigInt! """ The id provided by the NFT contract. It's the ERC-721 tokenId. """ tokenId: BigInt! """ Hash of the Ethereum transaction that created this stream. """ hash: Bytes! """ Unix timestamp of the Ethereum transaction that created this stream. """ timestamp: BigInt! """ Actions triggered by this stream. """ actions: [LockupAction!]! @derivedFrom(field: "stream") """ ERC-20 token distributed via this stream. """ asset: Asset! """ ERC-20 token decimals. Stored here to avoid loading the asset entity on each stream. Note: the "Value" suffix is added because of a bug in GraphQL Code Generator. """ assetDecimalsValue: BigInt! """ The batch the stream may be part of. Note: this is available only when created within a batch create transaction. """ batch: LockupBatch! """ Category used for sorting. """ category: LockupStreamCategory! """ The address of the contract the stream originates from. """ contract: Bytes! """ Position in the batch, if available. """ position: BigInt! """ Current recipient of the stream, with permission to withdraw funds to any third-party address. Note: the recipient can change on NFT transfer. """ recipient: Bytes! """ Manager of the stream, with ability to cancel the stream. """ sender: Bytes! """ Unix timestamp for the start of the stream. """ startTime: BigInt! """ Flag indicating the transferability of the stream. This is set when the stream is created, and cannot be changed later. """ transferable: Boolean! """ Version of contract, e.g., v1.0. """ version: String! """ The sum of all withdrawn amounts. """ withdrawnAmount: BigInt! """ Action in which the stream was canceled. """ canceledAction: LockupAction """ Action in which the stream was made non-cancelable. Note: if the stream was made non-cancelable from the get-go, this is the same as the Create action. """ renounceAction: LockupAction """ Flag indicating the cancelability of the stream. """ cancelable: Boolean! """ Flag indicating if the stream was canceled. """ canceled: Boolean! """ Unix timestamp for the when the stream was canceled. """ canceledTime: BigInt """ The amount deposited when the stream was created. """ depositAmount: BigInt! """ Snapshot of the duration in seconds (the difference between end and start time). """ duration: BigInt! """ Unix timestamp for the end of the stream. """ endTime: BigInt! """ The account that funded the stream, which can be different from the sender. """ funder: Bytes! """ The amount that is still held by the stream regardless of whether if was fully vested or not. This is the difference between the deposit amount and all withdrawn amounts. """ intactAmount: BigInt! """ Flag indicating if the stream has been fully withdrawn (intactAmount is zero). """ depleted: Boolean! """ Owner of the proxy when the stream is created through a PRBProxy (https://github.com/PaulRBerg/prb-proxy) Note that proxy = stream sender, and proxender = owner of proxy """ proxender: Bytes """ Flag for streams created through a PRBProxy. """ proxied: Boolean! """ Unix timestamp for when the stream was made non-cancelable. This can coincide with the create time. """ renounceTime: BigInt """ An optional parameter to specify the shape of the distribution. Available since Lockup v2.0. """ shape: String """ The source of the shape value: Event (from contract event) or Inferred (computed by indexer). """ shapeSource: ShapeSource """ Flag for Linear streams with a cliff. """ cliff: Boolean """ The amount that will unlock at the cliff time. """ cliffAmount: BigInt """ Unix timestamp for the start of the cliff. """ cliffTime: BigInt """ The smallest step in time between two consecutive token unlocks. Applies only for LockupLinear and available since Lockup v4.0 """ granularity: BigInt """ Flag for Linear stream with an initial unlock. Available since Lockup v2.0. """ initial: Boolean """ The initial unlock amount of a Linear stream. Available since Lockup v2.0. """ initialAmount: BigInt """ Segments of a Dynamic stream. """ segments: [Segment!]! @derivedFrom(field: "stream") """ Segments of a Tranched stream. """ tranches: [Tranche!]! @derivedFrom(field: "stream") } """ A generic entity for tracking Flow protocol actions. """ type FlowAction @entity(immutable: true) { """ Unique identifier: `action-{chainId}-{txHash}-{logIndex}` """ id: String! """ Unique global id as tracked by the `Watcher` entity. """ subgraphId: BigInt! """ Block number of the Ethereum transaction. """ block: BigInt! """ The chain ID where the action was created (e.g., 137 for Polygon). """ chainId: BigInt! """ The tx.origin of the Ethereum transaction. """ from: Bytes! """ Hash of the Ethereum transaction. """ hash: Bytes! """ Unix timestamp of the Ethereum transaction. """ timestamp: BigInt! """ Category of action, e.g., Deposit. """ category: FlowActionCategory! """ Contract through which the action was triggered. """ contract: Bytes! """ The Sablier fee paid in the native token of the chain (e.g., ETH for Mainnet). See https://docs.sablier.com/concepts/fees """ fee: BigInt """ Stream linked to this action, if any. """ stream: FlowStream """ Address of 1st actor. Who this is depends upon the action type, e.g. for Create, it is the sender. """ addressA: Bytes """ Address of 2nd actor. Who this is depends upon the action type, e.g. for Transfer, it is the recipient. """ addressB: Bytes """ 1st amount. What this is depends upon the action type, e.g. for Deposit, it is the deposit amount. """ amountA: BigInt """ 2nd amount. What this is depends upon the action type, e.g. for Withdraw, it is the refund amount. """ amountB: BigInt } """ A generic entity for tracking Lockup protocol actions. """ type LockupAction @entity(immutable: true) { """ Unique identifier: `action-{chainId}-{txHash}-{logIndex}` """ id: String! """ Unique global id as tracked by the `Watcher` entity. """ subgraphId: BigInt! """ Block number of the Ethereum transaction. """ block: BigInt! """ The chain ID where the action was created (e.g., 137 for Polygon). """ chainId: BigInt! """ The tx.origin of the Ethereum transaction. """ from: Bytes! """ Hash of the Ethereum transaction. """ hash: Bytes! """ Unix timestamp of the Ethereum transaction. """ timestamp: BigInt! """ Category of action, e.g., Deposit. """ category: LockupActionCategory! """ Contract through which the action was triggered. """ contract: Bytes! """ The Sablier fee paid in the native token of the chain (e.g., ETH for Mainnet). See https://docs.sablier.com/concepts/fees """ fee: BigInt """ Stream linked to this action, if any. """ stream: LockupStream """ Address of 1st actor. Who this is depends upon the action type, e.g. for Create, it is the sender. """ addressA: Bytes """ Address of 2nd actor. Who this is depends upon the action type, e.g. for Transfer, it is the recipient. """ addressB: Bytes """ 1st amount. What this is depends upon the action type, e.g. for Deposit, it is the deposit amount. """ amountA: BigInt """ 2nd amount. What this is depends upon the action type, e.g. for Withdraw, it is the refund amount. """ amountB: BigInt } """ Creating streams in bulk is possible using the Sablier batch contracts. """ type FlowBatch @entity(immutable: true) { """ Unique identifier: `batch-{chainId}-{txHash}-{batcher}` """ id: String! """ Hash of the Ethereum transaction that created this batch. """ hash: Bytes """ Timestamp of the transaction that created this batch. """ timestamp: BigInt """ The sender address that created this batch. """ batcher: FlowBatcher """ Index of the batch based on the `batchCounter` in the `Batcher` entity. """ position: BigInt """ Number of streams part of this batch. """ size: BigInt! """ Streams part of this batch. """ streams: [FlowStream!]! @derivedFrom(field: "batch") } """ Sender address that created batches. """ type FlowBatcher @entity(immutable: false) { """ Unique identifier: `batcher-{chainId}-{sender}` """ id: String! """ Total number of batches started by this sender. """ batchCounter: BigInt! """ Batches started by this sender. """ batches: [FlowBatch!]! @derivedFrom(field: "batcher") } """ Creating streams in bulk is possible using the Sablier batch contracts. """ type LockupBatch @entity(immutable: true) { """ Unique identifier: `batch-{chainId}-{txHash}-{batcher}` """ id: String! """ Hash of the Ethereum transaction that created this batch. """ hash: Bytes """ Timestamp of the transaction that created this batch. """ timestamp: BigInt """ The sender address that created this batch. """ batcher: LockupBatcher """ Index of the batch based on the `batchCounter` in the `Batcher` entity. """ position: BigInt """ Number of streams part of this batch. """ size: BigInt! """ Streams part of this batch. """ streams: [LockupStream!]! @derivedFrom(field: "batch") } """ Sender address that created batches. """ type LockupBatcher @entity(immutable: false) { """ Unique identifier: `batcher-{chainId}-{sender}` """ id: String! """ Total number of batches started by this sender. """ batchCounter: BigInt! """ Batches started by this sender. """ batches: [LockupBatch!]! @derivedFrom(field: "batcher") } """ An address that has sent USDC to the Sablier treasury for the billing process. """ type Sponsor @entity(immutable: false) { """ Unique identifier: `{chainId}_{address}` """ id: String! """ Address of the sponsor. """ address: Bytes! """ The chain ID where the sponsorship was made (sponsorships are chain-specific). """ chainId: BigInt! """ Number of sponsorships made by this sponsor. """ sponsorshipCount: Int! """ List of individual sponsorship transfers made by this sponsor. """ sponsorships: [Sponsorship!]! @derivedFrom(field: "sponsor") """ Cumulative raw USDC amount paid by the sponsor. """ totalAmount: BigInt! """ Human-readable cumulative USDC amount paid by the sponsor (e.g., "1729.12"). """ totalAmountDisplay: String! } """ A single USDC transfer to the Sablier treasury. Immutable — one per Transfer event. """ type Sponsorship @entity(immutable: true) { """ Unique identifier: `{chainId}_{txHash}_{logIndex}` """ id: String! """ Raw USDC amount. """ amount: BigInt! """ Human-readable USDC amount (e.g., "1719.12"). """ amountDisplay: String! """ Block number of the transaction. """ block: BigInt! """ The ID of the chain where the transfer occurred (e.g., 1 for Ethereum). """ chainId: BigInt! """ Log index of the Transfer event within the transaction. """ logIndex: Int! """ Address of the transaction signer (may differ from `from` for contract wallets). """ sender: Bytes! """ The sponsor who made this transfer. """ sponsor: Sponsor! """ Unix timestamp of the transaction. """ timestamp: BigInt! """ Hash of the transaction. """ txHash: Bytes! } """ A Sablier protocol contract """ type Contract @entity(immutable: false) { """ Unique identifier: contract-{chainId}-{address} """ id: String! """ Contract address """ address: Bytes! """ Contract alias (e.g., "FL", "LK") """ alias: String! """ Contract category (e.g., "Flow", "Lockup") """ category: String! """ The chain ID where the contract exists """ chainId: BigInt! } """ Older streams are no longer indexed. See https://x.com/Sablier/status/1914326014995620114 """ type DeprecatedStream @entity(immutable: true) { """ Unique identifier: \`{contractAddress}-{chainId}-{tokenId}\` """ id: String! """ The chain ID where the stream was created (e.g., 137 for Polygon). """ chainId: BigInt! """ The address of the contract that created the stream. """ contractAddress: Bytes! """ The id provided by the NFT contract. It's the ERC-721 tokenId. """ tokenId: BigInt! """ Hash of the Ethereum transaction that created this stream. """ hash: Bytes! """ Unix timestamp of the Ethereum transaction that created this stream. """ timestamp: BigInt! } """ See https://docs.sablier.com/concepts/lockup/segments """ type Segment @entity(immutable: true) { """ Unique identifier: `segment-{streamId}-{position}` """ id: String! """ Amount distributed by this segment. """ amount: BigInt! """ Total amount distributed at `endTime`. This is the sum of this segment's amount and all previous segments' amounts. """ endAmount: BigInt! """ Unix timestamp indicating the end of the segment. """ endTime: BigInt! """ Exponent used for the streamed amount calculations. See https://github.com/sablier-labs/lockup/blob/v2.0/src/types/DataTypes.sol#L279-L288 """ exponent: BigInt! """ Position of the segment inside the array. """ position: BigInt! """ Total amount distributed by the stream at `startTime`. This is the sum of all previous segments' amounts. """ startAmount: BigInt! """ Unix timestamp indicating the start of the segment. This is also the end time of the previous segment or, if this is the first segment, it is the start time of the stream. """ startTime: BigInt! """ The stream in which this segment was created. """ stream: LockupStream! } """ See https://docs.sablier.com/concepts/lockup/tranches """ type Tranche @entity(immutable: true) { """ Unique identifier: `tranche-{streamId}-{position}` """ id: String! """ Amount distributed by this tranche. """ amount: BigInt! """ Total amount distributed at `endTime`. This is the sum of this tranche's amount and all previous tranches' amounts. """ endAmount: BigInt! """ Unix timestamp indicating the end of the tranche. """ endTime: BigInt! """ Position of the tranche inside the array. """ position: BigInt! """ Total amount distributed by the stream at `startTime`. This is the sum of all previous tranches' amounts. """ startAmount: BigInt! """ Unix timestamp indicating the start of the tranche. This is also the end time of the previous tranche or, if this is the first tranche, it is the start time of the stream. """ startTime: BigInt! """ The stream in which this tranche was created. """ stream: LockupStream! } ``` --- ## Sablier Streams Source: https://docs.sablier.com/api/streams/indexers # Sablier Streams This page documents the indexers for the Sablier Streams protocol, which covers both [Lockup](/concepts/lockup/overview) (powering the [Vesting](/apps/features/vesting) product) and [Flow](/concepts/flow/overview) (powering the [Payments](/apps/features/payments) product) in the Sablier Interface. :::info The Lockup and Flow protocols are served by a unified indexer. In the GraphQL schema, entities are prefixed by protocol: `FlowStream`, `LockupStream`, `FlowAction`, `LockupAction`, `FlowBatch`, `LockupBatch`, etc. ::: ## Envio ### Source Code [ Envio indexer for Sablier Streamsenvio/streams ](https://github.com/sablier-labs/indexers/blob/main/envio/streams) ### Endpoints Envio is a multi-chain indexer. There's a single GraphQL API endpoint that aggregates data across chains. This approach differs from other vendors like The Graph, which require a separate indexer for each chain where Sablier is available. The standard Envio endpoints require [Hasura](https://docs.envio.dev/docs/HyperIndex/navigating-hasura) GraphQL query syntax. :::tip [Migrating from The Graph?] Envio also exposes `/converter` endpoints (see the table below) that accept subgraph-compatible queries, convert them to HyperIndex (standard GraphQL), and return responses in the same format as a standard subgraph. ::: | Chain | Production URL | Converter URL | Playground URL | Explorer URL | | --- | --- | --- | --- | --- | | Abstract | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Arbitrum | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Avalanche | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Base | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Blast | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Berachain | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | BNB Chain | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Chiliz | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Gnosis | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | HyperEVM | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Linea Mainnet | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Ethereum | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Mode | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Monad | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Morph | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | OP Mainnet | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Polygon | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Sonic | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Scroll | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Sei Network | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Sophon | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Superseed | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Unichain | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | XDC | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | ZKsync Era | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Base Sepolia | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | | Sepolia | [Production](https://indexer.hyperindex.xyz/53b7e25/v1/graphql) | [Converter](https://indexer.hyperindex.xyz/53b7e25/v1/graphql/converter) | [Playground](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.hyperindex.xyz%2F53b7e25%2Fv1%2Fgraphql) | [Explorer](https://envio.dev/app/sablier-labs/lockup-envio) | ## The Graph ### Source Code [ Graph indexer for Sablier Streamsgraph/streams ](https://github.com/sablier-labs/indexers/blob/main/graph/streams) ### Endpoints In the table below, you will see three URLs: - `Production URL`: requires a Studio API key for making queries. - `Testing URL`: doesn't require an API key but it's rate-limited to 3000 queries per day. Opening this URL in the browser opens a GraphiQL playground. - `Explorer URL`: The explorer URL for the indexer, if available. To learn how to create a Studio API key, check out [this guide](https://thegraph.com/docs/en/studio/managing-api-keys). The API key has to be provided via an `Authorization: Bearer ` header. Here are two examples in curl and JavaScript: **curl** ```bash curl -X POST \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"query": "{ _meta: { block { number } } }"' \ ``` **JavaScript** ```js import { GraphQLClient } from "graphql-request"; async function getBlockNumber() { const client = new GraphQLClient("", { headers: { Authorization: `Bearer `, }, }); const query = `query { _meta: { block { number } } }`; const result = await client.request(query); console.log(result); } ``` | Chain | Production URL | Testing URL | Explorer URL | | --- | --- | --- | --- | | Abstract | [Production](https://gateway.thegraph.com/api/subgraphs/id/2QjTdDFY233faXksUruMERMiDoQDdtGG5hBLC27aT1Pw) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-abstract/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/2QjTdDFY233faXksUruMERMiDoQDdtGG5hBLC27aT1Pw) | | Arbitrum | [Production](https://gateway.thegraph.com/api/subgraphs/id/yvDXXHSyv6rGPSzfpbBcbQmMFrECac3Q2zADkYsMxam) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-arbitrum/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/yvDXXHSyv6rGPSzfpbBcbQmMFrECac3Q2zADkYsMxam) | | Avalanche | [Production](https://gateway.thegraph.com/api/subgraphs/id/FTDmonvFEm1VGkzECcnDY2CPHcW5dSmHRurSjEEfTkCX) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-avalanche/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/FTDmonvFEm1VGkzECcnDY2CPHcW5dSmHRurSjEEfTkCX) | | Base | [Production](https://gateway.thegraph.com/api/subgraphs/id/778GfecD9tsyB4xNnz4wfuAyfHU6rqGr79VCPZKu3t2F) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-base/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/778GfecD9tsyB4xNnz4wfuAyfHU6rqGr79VCPZKu3t2F) | | Base Sepolia | [Production](https://gateway.thegraph.com/api/subgraphs/id/DdiYENuyh5ztSybRJnBnCZuUgESkFasjGFHZUbURpKHz) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-base-sepolia/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/DdiYENuyh5ztSybRJnBnCZuUgESkFasjGFHZUbURpKHz) | | Berachain | [Production](https://gateway.thegraph.com/api/subgraphs/id/C2r13APcUemQtVdPFm7p7T3aJkU2rH2EvdZzrQ53zi14) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-berachain/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/C2r13APcUemQtVdPFm7p7T3aJkU2rH2EvdZzrQ53zi14) | | BNB Chain | [Production](https://gateway.thegraph.com/api/subgraphs/id/A8Vc9hi7j45u7P8Uw5dg4uqYJgPo4x1rB4oZtTVaiccK) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-bsc/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/A8Vc9hi7j45u7P8Uw5dg4uqYJgPo4x1rB4oZtTVaiccK) | | Chiliz | [Production](https://gateway.thegraph.com/api/subgraphs/id/4KsXUFvsKFHH7Q8k3BPgEv2NhCJJGwG78gCPAUpncYb) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-chiliz/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/4KsXUFvsKFHH7Q8k3BPgEv2NhCJJGwG78gCPAUpncYb) | | Denergy | [Production](https://thegraph.denergychain.com/subgraphs/name/denergychain/sablier-lockup-denergychain) | N/A | [Explorer](https://thegraph.denergychain.com/subgraphs/name/denergychain/sablier-lockup-denergychain/graphql) | | Ethereum | [Production](https://gateway.thegraph.com/api/subgraphs/id/AvDAMYYHGaEwn9F9585uqq6MM5CfvRtYcb7KjK7LKPCt) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-ethereum/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/AvDAMYYHGaEwn9F9585uqq6MM5CfvRtYcb7KjK7LKPCt) | | Gnosis | [Production](https://gateway.thegraph.com/api/subgraphs/id/DtKniy1RvB19q1r2g1WLN4reMNKDacEnuAjh284rW2iK) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-gnosis/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/DtKniy1RvB19q1r2g1WLN4reMNKDacEnuAjh284rW2iK) | | Lightlink | [Production](https://graph.phoenix.lightlink.io/query/subgraphs/name/lightlink/sablier-lockup-lightlink) | N/A | [Explorer](https://graph.phoenix.lightlink.io/query/subgraphs/name/lightlink/sablier-lockup-lightlink/graphql) | | Linea Mainnet | [Production](https://gateway.thegraph.com/api/subgraphs/id/GvpecytqVzLzuwuQB3enozXoaZRFoVx8Kr7qrfMiE9bs) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-linea/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/GvpecytqVzLzuwuQB3enozXoaZRFoVx8Kr7qrfMiE9bs) | | OP Mainnet | [Production](https://gateway.thegraph.com/api/subgraphs/id/NZHzd2JNFKhHP5EWUiDxa5TaxGCFbSD4g6YnYr8JGi6) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-optimism/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/NZHzd2JNFKhHP5EWUiDxa5TaxGCFbSD4g6YnYr8JGi6) | | Polygon | [Production](https://gateway.thegraph.com/api/subgraphs/id/8fgeQMEQ8sskVeWE5nvtsVL2VpezDrAkx2d1VeiHiheu) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-polygon/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/8fgeQMEQ8sskVeWE5nvtsVL2VpezDrAkx2d1VeiHiheu) | | Scroll | [Production](https://gateway.thegraph.com/api/subgraphs/id/GycpYx8c9eRqxvEAfqnpNd1ZfXeuLzjRhnG7vvYaqEE1) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-scroll/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/GycpYx8c9eRqxvEAfqnpNd1ZfXeuLzjRhnG7vvYaqEE1) | | Sepolia | [Production](https://gateway.thegraph.com/api/subgraphs/id/5yDtFSxyRuqyjvGJyyuQhMEW3Uah7Ddy2KFSKVhy9VMa) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-sepolia/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/5yDtFSxyRuqyjvGJyyuQhMEW3Uah7Ddy2KFSKVhy9VMa) | | Sonic | [Production](https://gateway.thegraph.com/api/subgraphs/id/GnaSPX9XLkPn219CqbGFU1NgveuQk2Hh3c8WxjtesaEh) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-sonic/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/GnaSPX9XLkPn219CqbGFU1NgveuQk2Hh3c8WxjtesaEh) | | Unichain | [Production](https://gateway.thegraph.com/api/subgraphs/id/3MUG4H3gZcp9fpGLiJMTMeUFcQQ6QdT317P4wYKyns9M) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-unichain/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/3MUG4H3gZcp9fpGLiJMTMeUFcQQ6QdT317P4wYKyns9M) | | ZKsync Era | [Production](https://gateway.thegraph.com/api/subgraphs/id/7SuEYGYwZ835LjVGB85ZE8z5zmqdKgmRh8kAEeJefWQN) | [Testing](https://api.studio.thegraph.com/query/112500/sablier-lockup-zksync/version/latest) | [Explorer](https://thegraph.com/explorer/subgraphs/7SuEYGYwZ835LjVGB85ZE8z5zmqdKgmRh8kAEeJefWQN) | --- ## Branding Source: https://docs.sablier.com/apps/branding # Branding ## Brand Guidelines Want to make an integration with Sablier or just spread the word about it? We've put together a repository with all the branding assets you need to get started. [ Github - sablier-labs/branding: Sablier brand guidelines and assetsGitHub ](https://github.com/sablier-labs/branding) --- ## Airdrops Source: https://docs.sablier.com/apps/features/airdrops # Airdrops Sablier provides a solution for launching airdrops with up to a million recipients. This is designed to help projects distribute tokens to a large number of users in a fair and efficient manner. Start exploring at [app.sablier.com](https://app.sablier.com/airdrops/) or read more about it on [sablier.com/airdrops](https://sablier.com/airdrops). ## Airstreams (Vested Airdrops)") ![Banner Airdrops Vesting](/assets/images/docs-airdrops-airstreams-a54a0f77f888b2fa8099f1f47cd02b0d.webp) **Airdrops should be vested!** At Sablier, we believe in long-term distributions with aligned incentives. That's why we engineered Airstreams, a solution which allows you to airdrop streams with a vesting schedule. Pick a vesting curve (e.g., linear), define the rules (e.g. duration, clawback window), and allow recipients to claim their airdrops as vesting streams. ## Instant Airdrops Sablier also offers an instant airdrop solution, meaning the tokens are immediately released to the recipients upon claiming. ## Features ### Create with CSV Generate your list of recipients and put it into a CSV file, upload it to our app, and we'll take care of the rest. We will sanitize, validate and triple-check the data to ensure everything is formatted correctly. :::caution Timezone Caveat All times in the CSV are considered to be in the same timezone as the airdrop creator's device. Visit our [CSV guide](/apps/guides/csv-support) to read more about the format. ::: ### Easy 3-step process Creating campaigns involves a simple 3-step process: 1. Configure the initial details (e.g., token, campaign name, etc.) 2. Upload the CSV containing the recipient data 3. Deploy the Airdrop campaign contract | | | --- | | ![Airdrop Create: Step 1](/assets/images/airdrop-create-1-11543e38d979adef2a09216baec8f393.webp) | | ![Airdrop Create: Step 2](/assets/images/airdrop-create-2-a4acb86a8c7cf35670e136296754fed8.webp) | ### Open source If you'd like to support Airdrops in your UI, you can do so by using a self-hosted [Merkle service](/api/airdrops/merkle-api/overview). ### Explore the dashboard Enter the Dashboard and discover a detailed overview of the Airdrops you may be eligible for. Take advantage of the Search functionality to explore the chain and gain more insight into how others are using Sablier. | | | --- | | ![Airdrop Search](/assets/images/airdrop-search-cbce2ac922dbfa76b0dc61df7c41719a.webp) | ### Support for any ERC-20 token You can drop your own token! Thanks to our integration of Token Lists, any ERC-20 token can be airdropped on Sablier. For your token logo to show up in the Sablier app, add it to our [token list](https://github.com/sablier-labs/community-token-list/issues/new?template=token-request.md) | | | --- | | ![List of chains](/assets/images/general-chains-6b88571266d34394375000a804f11399.webp) | ### Oversight Have a clear view of how your campaign is performing. Check eligibility or manage your own campaign from a dedicated interface. | | | --- | | ![Airdrop Profile](/assets/images/airdrop-profile-a83c872b319d1ff5205fd6eb4c73cd04.webp) | :::info To integrate this functionality into your own products/apps, have a look at the [API guides](/api/overview), especially the [Merkle API](/api/airdrops/merkle-api/overview) and the Merkle subgraphs. ::: ### Advanced Settings For campaign admins, the interface enables advanced settings like in-app visibility, in-app geographical restrictions, and campaign-specific items like an eligibility criteria link. | | | --- | | ![Airdrop Settings](/assets/images/airdrop-settings-1a592acbe43ae94e430c4001d42e6014.webp) | ### Geographical Restrictions As shown in the image above, you can specify a list of countries where access to the campaign will be restricted on the Sablier Interface at [app.sablier.com](https://app.sablier.com). Note that this restriction does not apply to the Sablier Protocol, which runs permissionlessly on the blockchain. Additionally, some jurisdictions may already be restricted by default — either by your ISP or Vercel, our hosting provider. --- ## Other Features Source: https://docs.sablier.com/apps/features/other # Other Features The Sablier Interface comes with many other smaller (but still cool) features, from aesthetic easter eggs to integrations with popular services. ## Social Media Previews For the socialites among our users, we've added a feature that generates social media preview images based on your onchain activity in Sablier. To see what your preview looks like, paste your stream URL (e.g. `app.sablier.com/stream/...`) on socials. Here's an example: ![Stream Preview](/assets/images/stream-preview-c44df800fa204d995522199d971dc703.webp) ## Farcaster Frames ![Farcaster Frame](/assets/images/farcaster-bc0d7fc3f0c7e17d4b1b88fb012925ca.webp) You can share our [Frames](https://x.com/razgraf/status/1779208294264955316) to interact directly with Sablier from your favorite Farcaster client. | Latest | Stream by ID | | --- | --- | | ![Farcaster Frame Latest](/assets/images/frame-latest-555aa98dd7e68610b965613a7745822e.webp) | ![Farcaster Frame Stream](/assets/images/frame-stream-1550313394288e3273fe945cdd182c0f.webp) | | Keep tabs on the latest streams | Share stream previews using their ID | | [app.sablier.com/api/frame/latest/home](https://app.sablier.com/api/frame/latest/home) | [app.sablier.com/api/frame/stream/LL2-11155111-3](https://app.sablier.com/api/frame/stream/LL2-11155111-3) | :::note When pasted in the browser, the links will redirect to show the final images. Makes sure to use them in Farcaster in this original form. ::: --- ## The Sablier Interface Source: https://docs.sablier.com/apps/features/overview # The Sablier Interface The Sablier Interface is a web application that allows users to interact with the Sablier Protocol. It is a user-friendly interface that enables users to create, manage, and interact with streams. Start exploring at [app.sablier.com](https://app.sablier.com) or continue reading below to learn more about the available features. ## Use-case centric ![Banner App Navigation](/assets/images/docs-app-navigation-bd0ea1b1ec754ff7225fbe4c4c65eaf7.webp) The app is split among three use cases: | Use Case | Smart Contracts Involved | Description | | --- | --- | --- | | Vesting | Sablier Lockup | Closed-ended streams with and an end time and funds deposited upfront | | Payments | Sablier Flow | Open-ended streams that can be topped up over time | | Airdrops | Sablier MerkleFactory, Sablier Lockup | Instant or vested over time, with pre-configured recipients and amounts | These features are enabled by two types of streams: - Lockup streams - _fixed duration, amount required upfront, strict distribution curve_ - Flow streams - _no end time, amount can be topped-up, rate-per-second can be adjusted_ The Sablier Interface has adapted the Sablier Protocol for the use cases mentioned above, but we encourage you to explore other applications. ## Vesting Read about the business use-case on our [dedicated landing page](https://sablier.com/vesting) or dive deeper into the vesting-related features documented in [this section](/apps/features/vesting). ## Payments Read about the business use-case on our [dedicated landing page](https://sablier.com/payroll) or dive deeper into the vesting-related features documented in [this section](/apps/features/payments). ## Airdrop Read more about the business use-case on our [dedicated landing page](https://sablier.com/airdrops) or dive deeper into the airdrop-related features documented in [this section](/apps/features/airdrops). ## Grants Given how flexible Sablier is in adapting to any processes involving token distribution, we've also documented the business case for [onchain grants](https://sablier.com/grants) --- ## Payments Source: https://docs.sablier.com/apps/features/payments # Payments The Sablier Interface displays [Flow](/concepts/flow/overview) streams under the Payments tab. These are token streams with no end time, an ever-increasing amount (meaning the streams can be topped up), and a flexible rate per second. ![Banner Payments](/assets/images/docs-payments-e60c94c256d135173aeb444b1edb5ddc.webp) ## Features ### Flexible terms Increase the rate/second, fund the stream with more tokens and keep it alive indefinitely! With Flow streams in the Payments tab you have the freedom to adapt your distribution schedule based on external KPIs, pivots or executive decisions. ### Explore the dashboard Enter the Dashboard and discover a detailed overview of your incoming and outgoing flow streams. Take advantage of the Search functionality to explore the chain and gain more insight into how others are using Flow for continuous payments, grants, salaries and more. | | | --- | | ![All](/assets/images/payments-dashboard-split-43e04c88575b87d42fe56987b8020ebd.webp) | ### Top up multiple streams Select the streams you want to top up, provide the deposit amount for each stream, and confirm the batched top-up with only one transaction. You can specify custom amounts for each stream, or top up all streams with the same amount. | | | --- | | ![Top up multiple streams](/assets/images/topup-multiple-streams-eb50e1706c28c4eba13ff80764cbe7bc.gif) | ### Preview any stream Gain insight into any stream. Track progress using our very own stream circle. Share the unique URL with recipients or anyone really to increase transparency of your day to day token distribution. | | | --- | | ![All](/assets/images/payments-profile-fd122b125464b5c737ec94132d19501e.webp) | ### Save URL and top up later Create a unique URL to easily search the selected group of streams. Use this URL or share it with your partners to top up the streams at a later time. | | | --- | | ![URL for batch streams](/assets/images/batch-streams-url-fd395e71142881779aa1c58cf8b9424d.gif) | ### NFT representation Each stream in wrapped in an ERC-721 non-fungible token (NFT), making the stream recipient the owner of the NFT. Thus streams can be transferred, traded, and used as collateral in NFT lending protocols. ### Multi-chain experience Streaming, everywhere. We enable payments on 11+ EVM chains and testnets where you can stream or get paid using Sablier. | | | --- | | ![ChainList](/assets/images/general-chains-6b88571266d34394375000a804f11399.webp) | ### Create in bulk Save time by creating up to 280 streams in bulk for your employees, investors, or community members. Use the forms for a clean and straightforward UX. ### Create with CSV As an alternative to manually filling out the form, you can upload a CSV file with your user data. ### Mobile-ready layout Token streams on the go! Yes, the Sablier app works on mobile. And yes, we support dark mode by default (light mode coming soon). ### Permissions We've mapped the most important utilities from the Flow contracts into in-app features. Interact with streams that reference you as a key participant (e.g. sender, recipient) directly from the interfaces. | Feature | Sender | Recipient | Public | | --- | :---: | :---: | :---: | | Create Streams | ✅ | ❌ | \- | | Deposit / Top-up | ✅ | \- | ✅ | | Adjust rate | ✅ | ❌ | \- | | Refund | ✅ | ❌ | \- | | Void | ✅ | ✅ | \- | | Pause | ✅ | ❌ | \- | | Restart | ✅ | ❌ | \- | | Transfer | ❌ | ✅ | \- | | Withdraw | ✅ | ✅ | ✅ | ### Safe multisig support Payments are fully integrated with [Safe](https://safe.global). Start streaming from the safety and comfort of your multisig wallet. --- ## Vesting Source: https://docs.sablier.com/apps/features/vesting # Vesting The Sablier Interface will showcase [Lockup](/concepts/lockup/overview) streams under the Vesting tab. These are token streams with a fixed duration, predefined amount and strict distribution curve. ![Banner Vesting](/assets/images/docs-vesting-b9c77780752ca1f1966024599b2c2a27.webp) In Q4 2024 the app has undergone a use-case centric redesign. For past users, all streams created before this update will show up in the Vesting page. | [Vesting streams](https://app.sablier.com/vesting/gallery) | | --- | | ![Vesting stream profile](/screenshots/vesting-gallery.webp) | ## Features ### Flexible curves Almost any vesting schedule can be expressed as a stream. Sablier supports multiple options out of the box, including: - Linear - Cliff-Linear - Unlock in Steps - Unlock Monthly - Backweighted - Timelock - Unlock-Linear - Unlock-Dynamic - Exponential - Cliff-Exponential But programmatically, you can create any schedule you want. See the [Stream Shapes](/concepts/lockup/stream-shapes) for more details on how we design these shapes. ### Explore the dashboard Enter the Dashboard and discover a detailed overview of your incoming and outgoing streams. Take advantage of the Search functionality to explore the chain and gain more insight into how others are using Lockup for vesting. | | | --- | | ![All](/assets/images/vesting-dashboard-e5c79efba4315eb9cb571ef91e8b326f.webp) | ### Preview any stream Gain insight into any stream. Track progress using our very own stream circle. Share the unique URL with recipients or anyone really to increase transparency of your day to day token distribution. | | | --- | | ![All](/assets/images/vesting-profile-540367bd246535251c934699a0e95102.webp) | ### NFT representation Each stream in wrapped in an ERC-721 non-fungible token (NFT), making the stream recipient the owner of the NFT. Thus streams can be transferred, traded, and used as collateral in NFT lending protocols. | | | --- | | | ### Multi-chain experience Streaming, everywhere. We support 11+ EVM chains and testnets where you can stream or get paid using Sablier. | | | --- | | ![ChainList](/assets/images/general-chains-6b88571266d34394375000a804f11399.webp) | ### Create in bulk Save time by creating up to 280 streams in bulk for your employees, investors, or community members. Use the forms for a clean and straightforward UX. | | | --- | | ![Create in bulk](/assets/images/vesting-create-80ca35f0145fd0f845006b5575f20ed2.webp) | ### Create with CSV As an alternative to manually filling out the form, you can upload a CSV file with your user data. | | | --- | | ![Create with CSV](/assets/images/vesting-create-csv-2ac43351a4ad0a44b1c6e6aa0fef1ae6.webp) | ### Simulations Eager to see what your token distribution will look like? Use our simulation tool right from the stream creation forms (or later, from the stream profile). ![Simulate](/assets/images/simulation-4564c55be55624a63dc47960d3714f1a.webp) ### Detailed panels Explore each available stream in detail. Simulate future payouts, understand historical events, or simply enjoy cool representations of a monetized passage of time (NFTs 🔥). ### Granular management Manage your streams 24/7 as you see fit. The app will guide you through every supported process and help you keep an eye on your payouts. Remember, you can always be both a sender and a recipient. | | | | --- | --- | | ![Withdraw](/assets/images/cover-withdraw-7ba642af2360a98b7abfaa7c704f5fd8.webp) | ![Cancel](/assets/images/cover-cancel-35581d48504a874e6ad2367d869f0b88.webp) | ### Any ERC-20 token Thanks to our integration of Token Lists, any ERC-20 token can be distributed via Sablier Lockup. :::warning The only exception is rebasing tokens like Aave's aTokens. Tokens that dynamically rebase their balance are not supported by Sablier. ::: | | | | --- | --- | | ![Token List](/assets/images/tokenlist-home-4cadcae6572e235d8fde6e2606896bd5.webp) | ![Token List Search](/assets/images/tokenlist-search-3fa398cc7c3234a208ae0340294ba47f.webp) | ### Mobile-ready layout Token streams on the go! Yes, the Sablier app works on mobile. And yes, we support dark mode by default (light mode coming soon). ### Permissions We've mapped the most important utilities from the Lockup contracts into in-app features. Interact with streams that reference you as a key participant (e.g. sender, recipient) directly from the interfaces. | Feature | Sender | Recipient | Public | | --- | :---: | :---: | :---: | | Create Streams | ✅ | \- | \- | | Renounce Cancelability | ✅ | ❌ | \- | | Cancel | ✅ | ❌ | \- | | Transfer | ❌ | ✅ | \- | | Withdraw | ✅ | ✅ | ✅ | ### Safe multisig support Vesting is fully integrated with [Safe](https://safe.global). Start streaming from the safety and comfort of your multisig wallet. --- ## CSV Support Source: https://docs.sablier.com/apps/guides/csv-support # CSV Support The Sablier Interfaces supports CSV files for faster processing and automating large-scale operations. This feature is available for both airdrops and streams. :::warning Formatting Caveats **Dates**: All columns with the "date" type should have the following format: "YYYY-MM-DD HH:mm". **Durations**: All columns with the "duration" type should have the following format: "**x** years **y** days **z** hours". Note that each particle is optional, e.g., you can skip the days. **Timezones**: The dates and times extracted from the CSV are processed using the same timezone used by the user's browser. **Amounts**: All token amounts should be expressed in humanized form, e.g., 10 USDC should be written as `10`, not `10000000`. The Sablier app will multiply the amounts by the token's number of decimals in the processing step. ::: ## Airdrops With Sablier, you can create airdrop campaigns with up to a million recipients. To do so, you must upload a CSV file containing all recipient addresses and the airdrop amounts. Use the provided template and fill in the rows with recipient addresses and airdrop amounts. ### CSV Template For your convenience, here's a download link for the CSV template: [ Sablier - Airdrop CSV TemplateCSV template for creating airdrops on Sablier ](https://files.sablier.com/templates/airdrop-template.csv) ### Navigation To use this feature: 1. Access the [create airdrop](https://app.sablier.com/airdrops/create) page 2. Fill out the details for your airdrop campaign in the 1st step 3. Continue to the 2nd step, where you can upload the CSV | | | --- | | ![Airdrop Create](/assets/images/airdrop-create-2-a4acb86a8c7cf35670e136296754fed8.webp) | ## Streams | | | --- | | ![Create with CSV](/assets/images/create-with-csv-d8919d393e2f8db086dc7b47d7ed6bd0.webp) | Using a CSV, you can deploy up to 280 streams all at once. Start from the suggested template, and fill in the rows with addresses, amounts, and other details. ### CSV Template Here's table with all the available CSV templates. [Sablier Flow](/concepts/use-cases#sablier-flow) (the first row in the below table) is a great fit for use cases like payroll, grants, and subscriptions. The other streaming curves in the table rely on [Sablier Lockup](/concepts/use-cases#sablier-lockup) and are a better fit for vesting and airdrops. | URL | Description | | --- | --- | | [Flow](https://files.sablier.com/templates/flow-template.csv) | [Open-ended streams](/concepts/flow/overview#total-debt) that can be topped up. | | [Linear with duration](https://files.sablier.com/templates/linear-duration-template.csv) | [Linear streams](/concepts/lockup/stream-shapes#linear) with the duration timing. | | [Linear with range](https://files.sablier.com/templates/linear-range-template.csv) | [Linear streams](/concepts/lockup/stream-shapes#linear) with the range timing. | | [Cliff with duration](https://files.sablier.com/templates/2025-02/cliff-duration-template.csv) | [Cliff streams](/concepts/lockup/stream-shapes#unlock-cliff) with the duration timing. | | [Cliff with range](https://files.sablier.com/templates/2025-02/cliff-range-template.csv) | [Cliff streams](/concepts/lockup/stream-shapes#unlock-cliff) with the range timing. | | [Monthly with range](https://files.sablier.com/templates/monthly-range-template.csv) | [Unlock Each Month streams](/concepts/lockup/stream-shapes#unlock-monthly) with the range timing. | | [Stepper with duration](https://files.sablier.com/templates/2025-02/unlockSteps-duration-template.csv) | [Unlock In Steps streams](/concepts/lockup/stream-shapes#unlock-in-steps) with the duration timing. | | [Stepper with range](https://files.sablier.com/templates/2025-02/unlockSteps-range-template.csv) | [Unlock In Steps streams](/concepts/lockup/stream-shapes#unlock-in-steps) with the range timing. | | [Timelock with duration](https://files.sablier.com/templates/timelock-duration-template.csv) | [Timelock streams](/concepts/lockup/stream-shapes#timelock) with the duration timing. | | [Timelock with range](https://files.sablier.com/templates/timelock-range-template.csv) | [Timelock streams](/concepts/lockup/stream-shapes#timelock) with the range timing. | | [BackWeighted with range](https://files.sablier.com/templates/backWeighted-range-template.csv) | [BackWeighted streams](/concepts/lockup/stream-shapes) with the range timing. | | [Unlock linear with duration](https://files.sablier.com/templates/unlockLinear-duration-template.csv) | [Unlock-Linear streams](/concepts/lockup/stream-shapes#unlock-linear) with the duration timing. | | [Unlock linear with range](https://files.sablier.com/templates/unlockLinear-range-template.csv) | [Unlock-Linear streams](/concepts/lockup/stream-shapes#unlock-linear) with the range timing. | | [Unlock cliff with duration](https://files.sablier.com/templates/2025-02/unlockCliff-duration-template.csv) | [Unlock-Cliff streams](/concepts/lockup/stream-shapes#unlock-cliff) with the duration timing. | | [Unlock cliff with range](https://files.sablier.com/templates/2025-02/unlockCliff-range-template.csv) | [Unlock-Cliff streams](/concepts/lockup/stream-shapes#unlock-cliff) with the range timing. | | [Exponential with duration](https://files.sablier.com/templates/exponential-duration-template.csv) | [Exponential streams](/concepts/lockup/stream-shapes#exponential) with the duration timing. | | [Exponential with range](https://files.sablier.com/templates/exponential-range-template.csv) | [Exponential streams](/concepts/lockup/stream-shapes#exponential) with the range timing. | | [Cliff exponential with duration](https://files.sablier.com/templates/exponentialCliff-duration-template.csv) | [Cliff-Exponential streams](/concepts/lockup/stream-shapes#cliff-exponential) with the duration timing. | | [Cliff exponential with range](https://files.sablier.com/templates/exponentialCliff-range-template.csv) | [Cliff-Exponential streams](/concepts/lockup/stream-shapes#cliff-exponential) with the range timing. | ### Navigation To use this feature: 1. Access the [vesting gallery](https://app.sablier.com/vesting/gallery/) page in the Sablier app 2. Select the desired vesting shape 3. In the top right corner, you will find a button guiding you toward the CSV feature | | | --- | | | ### Column Formats To use the CSV feature, the data you provide must be formatted correctly. Below is a list with the format expected for all column types supported by Sablier. :::warning Make sure that your CSV editing software (e.g. Microsoft Excel) does not override the cell format. We suggest double-checking in the Sablier app that the dates have been parsed as expected. ::: | Column | Type | Description | Examples | | --- | --- | --- | --- | | address | String | Recipient address | `0x12...AB` | | amount | Number | Deposit amount | `100`, `42161` or any other valid amount | | duration | String | Total duration | `1 year 20 days`, `3 years 20 days 4 hours` | | start | Date | Start date in `YYYY-MM-DD HH:mm` format | `2024-02-24 16:15`, `2026-02-14 17:25` | | end | Date | End date in `YYYY-MM-DD HH:mm` format | `2024-02-24 16:15`, `2026-02-14 17:25` | | cliffDuration | String | Cliff duration | `2 years 20 days`, `3 years 20 days 4 hours` | | cliffEnd | Date | Cliff date in `YYYY-MM-DD HH:mm` format | `2024-02-24 16:15`, `2026-02-14 17:25` | | months | Number | Number of months for the unlock monthly | `5`, `12` or any other valid integer | | steps | Number | Number of steps for the unlock in steps | `5`, `12` or any other valid integer | | unlock | Number | Amount that will be initially unlocked | `100`, `42161` or any other valid amount | | initial | String | Whether the first unlock should occur at the start date or at the end of the first month | `at start` or `end of first month` | --- ## How-to Videos Source: https://docs.sablier.com/apps/guides/how-to # How-to Videos For an extensive set of video explanations please check out the [Support](/support/how-to) section. --- ## URL Schemes Source: https://docs.sablier.com/apps/guides/url-schemes # URL Schemes The Sablier Interface makes it easy for integrators to deep-link into specific streams, campaigns, or filtered searches. The portal is split into three products — **vesting**, **payments**, and **airdrops** — and each one has a profile page and a search page reachable through composable URLs. This guide documents the URL schemes you can build against. ## Stream Page | | | --- | | ![Stream profile](/assets/images/stream-share-f4d859daf33b741099adbfc7632e4423.webp) | ### Elements Every stream URL is built from one product namespace plus three identifier parts: - a **kind** namespace — `vesting` (Lockup) or `payments` (Flow), used as the URL prefix - a **source** — either an uppercase contract **alias** (e.g. `LL3`, `FL4`) or a lowercase contract **address** (e.g. `0xb10d…f95`) - a **chainId** (e.g., `1` for Ethereum, `137` for Polygon, `8453` for Base) - a **streamId** — the ERC-721 token ID assigned at stream creation See [Identifiers](/api/ids) for the full alias table covering every released Lockup and Flow contract. ### Building the URL Concatenate the three identifier parts with hyphens (`source-chainId-streamId`) and append the result to the product-namespaced base URL `app.sablier.com/{kind}/stream/`: | URL | Description | | --- | --- | | [app.sablier.com/vesting/stream/LL3-137-29](https://app.sablier.com/vesting/stream/LL3-137-29) | Lockup v1.2 Linear vesting stream #29 on Polygon | | [app.sablier.com/vesting/stream/LK3-1-12](https://app.sablier.com/vesting/stream/LK3-1-12) | Lockup v4.0 vesting stream #12 on Ethereum | | [app.sablier.com/vesting/stream/0xB10…f95-1-6](https://app.sablier.com/vesting/stream/0xB10daee1FCF62243aE27776D7a92D39dC8740f95-1-6) | Vesting stream #6 on Ethereum, addressed by contract address | | [app.sablier.com/payments/stream/FL4-8453-100](https://app.sablier.com/payments/stream/FL4-8453-100) | Flow v3.0 payment stream #100 on Base | | [app.sablier.com/payments/stream/FL2-11155111-40](https://app.sablier.com/payments/stream/FL2-11155111-40) | Flow v1.1 payment stream #40 on Ethereum Sepolia | The portal normalizes the alias to uppercase and the contract address to lowercase before resolving the stream. #### Sub-routes Append a sub-route to land on a specific tab of the stream page: | Sub-route | Effect | Example | | --- | --- | --- | | `/details` | Opens the **Details** modal view | `app.sablier.com/vesting/stream/LL3-137-29/details` | | `/history` | Opens the **History** modal view | `app.sablier.com/payments/stream/FL4-8453-100/history` | --- ## Search Streams ### Route Shape Stream search is **path-based**. The chain lives in the path for vesting, and the asset (when present) is always the last path segment: - Vesting: `app.sablier.com/vesting/search/{chain}/{token?}` - Payments: `app.sablier.com/payments/search/{token?}` `{chain}` is the lowercase chain slug — `ethereum` for chain ID `1`, otherwise the chain's slug (`polygon`, `base`, `arbitrum`, `optimism`, `sepolia`, …). `{token?}` is the optional ERC-20 contract address to filter by. To land on the dashboard with a specific tab instead of the search view, use `/vesting` or `/payments` with `t=`. ### Query Parameters These parameters apply to both vesting and payments search: | Parameter | Code | Type | Description | Example | | --- | --- | --- | --- | --- | | chainId | `c` | Number | Chain to filter by. On `/vesting/search` the chain lives in the path; on `/payments/search` it stays as a query param. | `c=137` | | sender | `s` | Address / ENS | Stream sender (creator). | `s=vitalik.eth` | | recipient | `r` | Address / ENS | Stream recipient. | `r=0x12…AB` | | ids | `i` | Comma-separated | Filter by stream IDs or aliases. | `i=LL2-1-2,LL3-1-29` | | filter | `f` | Comma-separated | Status filter (e.g., `streaming`, `paused`, `cancelled`, `settled`). | `f=streaming,cancelled` | | sortField | `so` | Enum | `default`, `value`, or `timeline`. Omitted when `default`. | `so=value` | | sortDir | `sd` | Enum | `asc` or `desc`. Omitted when `desc`. | `sd=asc` | | page | `p` | Number | 1-indexed page number. Omitted on page 1. | `p=2` | | tab | `t` | Enum | Dashboard tab: `created`, `recipient`, or `sender`. Used on `/vesting` and `/payments`, not on `/search`. | `t=recipient` | Address values for `s` and `r` are case-insensitive on input — the portal accepts checksummed (`0xB10d…F95`) and lowercase (`0xb10d…f95`) forms equally — but it lowercases them when emitting URLs of its own. ENS names are passed through as-is. ### Examples | URL | Description | Preview | | --- | --- | --- | | [vesting/search/ethereum?s=0x0aAe…72cC](https://app.sablier.com/vesting/search/ethereum?s=0x0aAeF7BbC21c627f14caD904E283e199cA2b72cC) | Vesting streams on Ethereum created by a particular sender | ![Form](/assets/images/search-with-sender-31f311469512bd73c175727d36e628d0.webp) | | [vesting/search/ethereum?i=LL2-1-2,LL3-1-29](https://app.sablier.com/vesting/search/ethereum?i=LL2-1-2,LL3-1-29) | Vesting streams matching specific IDs on Ethereum | ![Form](/assets/images/search-with-ids-27542b27ceb4e7fcea2f492ba3dbc42c.webp) | | [payments/search/0xa0b8…eB48?c=8453](https://app.sablier.com/payments/search/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48?c=8453) | Payments streams in USDC on Base | — | | [vesting?t=sender&f=streaming](https://app.sablier.com/vesting?t=sender&f=streaming) | Vesting dashboard, "sender" tab, only currently streaming | — | :::info The legacy `?t=search` query is no longer emitted by the portal. New integrations should use the path-based search routes above. ::: --- ## Airdrop Page | | | --- | | ![Airdrop Profile](/assets/images/airdrop-profile-a83c872b319d1ff5205fd6eb4c73cd04.webp) | ### Elements Every airdrop campaign is identified by two parts: - a **chainId** (e.g. `1` for Ethereum, `137` for Polygon) - a **contract address** (e.g. `0x12…AB`) :::info Airdrops do not use aliases — campaigns are referenced by their contract address. See [Identifiers](/api/ids) for details. ::: ### Building the URL Concatenate the address and chain ID with a hyphen and append to `app.sablier.com/airdrops/campaign/`. The address is lowercased. | URL | Description | | --- | --- | | [app.sablier.com/airdrops/campaign/0xe721…bbabc-11155111](https://app.sablier.com/airdrops/campaign/0xe72175dd12ac7efca6b7d12dfc913a5f661bbabc-11155111) | Campaign on Ethereum Sepolia | #### Sub-route Append `/manage` to land directly on the admin manage view (only useful to the campaign admin): `app.sablier.com/airdrops/campaign/0xe721…bbabc-11155111/manage` --- ## Search Airdrops ### Route Shape `app.sablier.com/airdrops/search/{token?}` `{token?}` is the optional ERC-20 contract address of the airdrop's token. ### Query Parameters | Parameter | Code | Type | Description | Example | | --- | --- | --- | --- | --- | | chainId | `c` | Number | Chain to filter by. | `c=1` | | admin | `m` | Address / ENS | Campaign admin (creator). | `m=vitalik.eth` | | isAlive | `o` | Boolean | `true` for ongoing campaigns, `false` for expired. | `o=true` | | name | `n` | String | Campaign name substring. | `n=Q1+Drop` | Like the stream search params, `m` is case-insensitive on input and lowercased on output; ENS is passed through as-is. ### Example ```text app.sablier.com/airdrops/search/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48?c=1&m=0x..1&o=true ``` --- ## Global Parameter: Connected Chain Any portal URL can carry a `cc` (connected chain) parameter. When present, the portal prompts the connected wallet to switch to that chain ID before rendering — useful when deep-linking into a stream or campaign that lives on a chain different from the user's current wallet network. | Parameter | Code | Type | Description | Example | | --- | --- | --- | --- | --- | | connectedChain | `cc` | Number | Chain ID the wallet should switch to before load. | `app.sablier.com/vesting/stream/LL3-137-29?cc=137` | --- ## Migrating from the Legacy Interface If you previously linked into the older interface, here is the mapping: | Legacy URL | New URL | | --- | --- | | `app.sablier.com/stream/{id}` | `app.sablier.com/vesting/stream/{id}` or `app.sablier.com/payments/stream/{id}` | | `app.sablier.com/airstream/{id}` | `app.sablier.com/airdrops/campaign/{id}` | | `app.sablier.com/?t=search&c=…&s=…` | `app.sablier.com/vesting/search/{chain}?s=…` or `app.sablier.com/payments/search?c=…&s=…` | | `app.sablier.com/airdrops/?t=search&…` | `app.sablier.com/airdrops/search?…` | Legacy URLs still resolve through redirects, but new integrations should use the product-namespaced format. Note that vesting search now expects the chain as a path slug (`ethereum`, `polygon`, `base`, …) rather than the numeric `c=` query — convert the chain ID before building the URL. --- ## Legacy Source: https://docs.sablier.com/apps/legacy # Legacy ## Sablier Legacy The first version of the Sablier protocol will keep running in perpetuity thanks to the Ethereum blockchain, but the current Sablier Interfaces are not compatible with the Legacy protocol. We will keep hosting the legacy apps so that you can manage any streams previously created on V1. - Sender interface: [legacy-sender.sablier.com](https://legacy-sender.sablier.com) - Recipient interface: [legacy-recipient.sablier.com](https://legacy-recipient.sablier.com) --- ## Airdrops Source: https://docs.sablier.com/concepts/airdrops # Airdrops :::note You can refer to the [airdrop section](/concepts/use-cases#merkle-airdrops) of our use-cases page to learn more about the benefits of streaming airdrops. ::: There are three types of airdrop campaigns that you can setup using Sablier Merkle Airdrops. ## Instant Airdrop Instant airdrops is the traditional way of running airdrops where there's no vesting period and tokens can instantly be claimed by the recipients. Eligible users receive airdrop tokens directly into their wallets at the claim time. ## Vested Airdrops Vested airdrops are campaigns in which instead of airdropping the entirety of the token allocation all at once, airdrop recipients receive a fraction of the tokens every second through a Sablier stream. The gist of vested airdrops is that instead of airdropping the entirety of the tokens all at once, airdrop recipients receive a fraction of the tokens every second through a token stream. The beauty of it is that airdrop recipients are forced to think long-term and keep the project's future as their first and foremost priority. They are forced to, because instead of receiving all the tokens at once, they receive them over time in our user-friendly [vested airdrop interface](https://app.sablier.com/airdrops). :::info An airdrop campaign can have a claim window of a few days, months, or even years. Alternatively, they can have no expiration. In case of vested airdrops, you could, for example, configure the airdrop of your new token to vest over years, but the recipients get the streamed tokens only if a claim is made within that period of time. ::: Vested airdrops not only create the right incentives for token holders, but also prevent them from dumping their tokens on day one, as has been the case for many airdrops in the past. There are two types of vested airdrop campaigns that you can create using Merkle Airdrop. ### Ranged This either uses Lockup Linear model or Lockup Tranched model depending on whether you use `MerkleLL` or `MerkleLT` to create the campaign. In ranged vested airdrop campaigns, the vesting begins for all the recipients at the same time. This time had to be provided while creating the campaign. ### Non-Ranged In non-ranged vested airdrop campaigns, the vesting begins at the time of claiming. In this case, all recipients can have different start time for vesting depending on when they claim. ## Variable Claim Airdrops This is a special type of vested airdrop in which user can choose to forfeit the unvested tokens back to the campaign, by claiming early. Campaign admin can then clawback the forfeited tokens after the campaign expires. A typical variable claim airdrop, created to vest tokens over 1 year, may look like the following: - A user claims after 3 months and receive 25% of his total allocation. 75% of the tokens are then forfeited and returned to the campaign. - Another user claims after 6 months and receive 50% of his total allocation. 50% of the tokens are then forfeited and returned to the campaign. - A third user claims after 12 months and receive 100% of his total allocation. This type of airdrop is particularly useful for campaigns that want to reward users for their patience and loyalty. ## White Label Solution Sablier Labs does not currently offer any white label solutions for Merkle Airdrops. This means that you cannot have your logo displayed in the claim page seen by the airdrop recipients. ## How it Works Thanks to our battle-tested token distribution protocol, you can create Airdrop campaigns for thousands of recipients in a few clicks using our interface. Recipients and their airdropped allocations can be set by uploading a simple CSV spreadsheet in the [user interface](https://app.sablier.com/airdrops). The spreadsheet feature is the perfect fit for merkle airdrop campaigns: it allows you to upload a CSV file with tens of thousands of recipients and the interface will let each of these recipients claim with ease. You can download a template of the CSV file [here](https://files.sablier.com/templates/airstream-template.csv). Another great advantage is that creating an airdrop campaign with thousands of recipients won't ruin you in terms of gas fees. When launching a campaign, a contract is deployed only storing the merkle root. Thus, users pay the gas fee to claim their airdrop. This is made possible by a data structure called Merkle Tree, which efficiently summarizes and verifies the integrity of large sets of data. :::info The contracts that implement vested airdrop campaigns are called [`MerkleLL`](/reference/airdrops/contracts/contract.SablierMerkleLL) and [`MerkleLT`](/reference/airdrops/contracts/contract.SablierMerkleLT). If you are interested in instant airdrop campaigns, the contract code can be found [here](/reference/airdrops/contracts/contract.SablierMerkleInstant). ::: When you create an airdrop campaign, all you are doing is deploying a contract that allows for the recipients you put in to prove that they are eligible, and create a stream if they are. That's all it is. Additionally, you don't have to immediately fund the campaign contract. You can just create the contract and at a later date fund it with the airdropped tokens. **This has three great implications:** 1. **Recipients pay for the gas fees themselves to create the stream**, when they claim (the claim action creates the stream). Creating a campaign with thousands of recipients would be incredibly costly if you had to pay for all the gas fees. 2. **You keep full control over unclaimed Tokens**. If a recipient doesn't claim their airdrop, it's not created, and you remain in full control over their allocation. - This applies only if the campaign has an expiration date. If there is no expiration date, you can only clawback during the grace period, and the recipients can claim their airdrop at any time in the future. 3. **You can retrieve your funds in case of misconfiguration**. There is a grace period during which you can retrieve unclaimed funds. The grace period ends 7 days after the first claim is made. This is useful in case where you have incorrectly configured the campaign or deposited more tokens than required. Once the campaign is launched, recipients can claim their airdrop and withdraw the underlying tokens that have already been streamed at any time using the Sablier Interface at [app.sablier.com](https://app.sablier.com). ## Diagram If you want to have a detailed look into how these campaigns work at the contract level, you can head over to the [Diagrams page](/reference/airdrops/diagrams). --- ## Lido Adapter Source: https://docs.sablier.com/concepts/bob/lido-adapter # Lido Adapter The Lido adapter is a smart contract that is used to generate yield on the locked tokens in a vault. Only the vaults with the following tokens on Ethereum mainnet can generate yield using the Lido adapter: - ETH - WETH :::info Once a vault is created, the adapter cannot be changed. Any change to the adapter only impacts the new vaults. ::: ## Deposit Flow When a user deposits into such vaults, the adapter transfers the tokens into the Lido protocol to earn yield. The adapter then tracks the yield generated per user. When a user deposits WETH, the adapter transfers it to the Lido protocol and receives stETH in return. It then wraps the stETH into wstETH which is stored on the adapter contract address. The wstETH token represents the principal amount as well as the yield generated from Lido. ## Redeem Flow When a vault settles or expires, users can redeem their shares for the underlying tokens. The adapter will then calculate the user's proportional share of the yield generated from Lido and transfer it to the user along with the original deposit amount. During the first redemption, the adapter converts wstETH back into WETH and transfers them to the Bob contract. There are two pathways implemented in the adapter to convert wstETH back into WETH: ### Curve Swap (Default)") The default path follows wstETH → stETH → ETH via the [Curve stETH/ETH pool](https://curve.fi/dex/ethereum/pools/steth/deposit/), then wraps to WETH. This path: - Is immediate. - Uses Chainlink's stETH/ETH oracle for slippage protection. - Has a tight slippage tolerance. ### Lido Withdrawal Queue In case the Curve exchange fails due to low liquidity, the Comptroller can request a withdrawal using the Lido withdrawal queue. This path: - Is slower due to the waiting period of the Lido withdrawal queue. - Has no slippage. - Takes precedence over the Curve path once initiated. ## Yield Tracking The adapter tracks each user's balance using the following formula: $$ \text{user WETH share} = \frac{\text{user wstETH}}{\text{vault total wstETH}} \times \text{total WETH from unstaking} $$ When the yield is positive, a [fee](/concepts/fees#bob-vaults) is applied to it. For example, if a user deposits 1 WETH and receives 1.05 WETH after redemption, the yield is 0.05 WETH. If the vault yield fee is 10%, the fee is 0.005 WETH. The user will receive 1.045 WETH. ## Vaults Without Adapters Vaults created for tokens without any adapter hold the deposited tokens directly in the [`SablierBob`](/reference/bob/contracts/contract.SablierBob) contract. In this case, a flat fee in the chain's native token is charged at redemption. ```mermaid flowchart LR User -- "deposit WETH" --> SablierBob SablierBob -- "stake" --> LidoAdapter LidoAdapter -- "submit ETH" --> Lido Lido -- "stETH" --> LidoAdapter LidoAdapter -- "wrap" --> wstETH[(wstETH)] ``` ```mermaid flowchart LR User -- "redeem shares" --> SablierBob SablierBob <-- "calculate yield" --> LidoAdapter SablierBob -- "transfer yield + original deposit" --> User ``` --- ## Overview Source: https://docs.sablier.com/concepts/bob/overview # Overview Bob is designed to unlock tokens when a specific price is reached. It allows you to deposit ERC-20 tokens into vaults that unlock when either a target price is reached or a specified time period elapses. In some cases such as ETH, the locked tokens also earn yield via Lido. Let's take an example. Imagine Alice believes ETH will reach $5,000 within the next 12 months. 1. Alice finds an existing vault with WETH as the underlying token that has a target price set to $5,000, and an expiry 12 months from now. 2. Alice deposits 10 ETH into this vault and receives 10 [shares](/concepts/bob/shares) in return. 3. When ETH reaches $5,000, the vault enters into the settled state which means Alice can redeem her shares for ETH. 4. If ETH does not reach $5,000 in the next 12 months, Alice can still redeem her shares because the vault has expired. 5. For some tokens such as ETH, because of the [yield adapters](/concepts/bob/lido-adapter), Alice's ETH would also earn while they are locked in the vault. ## Key Features 1. **Price-gated unlocking:** Unlock tokens when the target price is reached or the expiry is reached (note that a vault can be set to never expire). 2. **Yield generation:** Some tokens such as ETH and WETH allow locked tokens to earn yield automatically. 3. **ERC-20 share tokens:** Each vault issues transferable share tokens that represent a depositor's claim on the vault's underlying tokens including any accrued yield. 4. **Immutability:** A vault's target price and expiry cannot be changed once the vault is created. If you are looking for token vesting or airdrop distributions, please refer to our [Lockup](/concepts/lockup/overview) and [Airdrops](/concepts/airdrops) protocols. --- ## Shares Source: https://docs.sablier.com/concepts/bob/shares # Shares Every Bob vault has its own ERC-20 token called a [`BobVaultShare`](/reference/bob/contracts/contract.BobVaultShare). Unlike Lockup and Flow which use ERC-721 NFTs to represent positions, Bob uses ERC-20 tokens. Shares represent a user's claim on the vault's underlying tokens and any accrued yield. ## Minting and Burning When a user deposits tokens via [`enter`](/reference/bob/contracts/contract.SablierBob#enter), shares are minted **1:1** with the deposit amount. When a user calls [`redeem`](/reference/bob/contracts/contract.SablierBob#redeem), their shares are burned and the underlying tokens are returned. These shares are fully transferable ERC-20 tokens. You can transfer them to other wallets. You can only redeem as long as you hold these shares. :::important When shares are transferred between users, any yield generated by the vault can only be claimed by the new recipient of the shares. ::: ## Token Metadata Each vault's share token uses the following algorithm to generate its name and symbol: | Field | Format | Example | | --- | --- | --- | | Name | `Sablier Bob {TOKEN} Vault #{vaultId}` | `Sablier Bob WETH Vault #1` | | Symbol | `{TOKEN}-{targetPrice}-{expiry}-{vaultId}` | `WETH-500000000000-1735689600-1` | | Decimals | Same as the underlying token | `18` (for WETH) | This can help users identify what vault a share token represents by simply looking at them on Etherscan. For example, `WETH-500000000000-1735689600-1` represents a WETH vault with ID 1, a target price of $5,000, and an expiry of 2025-01-01. --- ## Vault Statuses Source: https://docs.sablier.com/concepts/bob/statuses # Vault Statuses A Bob vault can have one of three distinct statuses: | Status | Description | | --- | --- | | `ACTIVE` | Tokens can be deposited into the vault. | | `SETTLED` | The target price has been reached and tokens can be withdrawn. | | `EXPIRED` | The expiry timestamp has been reached and tokens can be withdrawn. | The vault status is computed on-the-fly as the following: $$ \text{status} = \begin{cases} \text{EXPIRED} & \text{if } \text{block.timestamp} \geq \text{expiry} \\ \text{SETTLED} & \text{if } \text{lastSyncedPrice} \geq \text{targetPrice} \\ \text{ACTIVE} & \text{otherwise} \end{cases} $$ :::info - Expiry takes precedence over settlement. If the expiry has passed, the vault is always `EXPIRED` regardless of the last synced price. - A vault does not become `SETTLED` automatically when the target price is reached. An on-chain transaction must be triggered manually — either by calling `redeem` or `syncPriceFromOracle` — to update the last synced price and transition the vault to `SETTLED`. ::: ## State Transitions ## Q&A ### Q: What is a null vault? A: A vault that does not exist. Trying to interact with a null vault will always revert. ### Q: Can a vault be canceled or modified after creation? A: No. Vault parameters are immutable once set. ### Q: Market price of the token is already over the target price, but the vault is still in the active state. What should I do? A: You can call `redeem` which will automatically use the latest price from the vault oracle and change the vault status. Alternatively, you can also call `syncPriceFromOracle` to update the latest price in the vault and change its status. ```mermaid stateDiagram-v2 direction LR Null --> Active Active --> Settled Active --> Expired : time Settled --> Expired : time ``` --- ## Cancelability Source: https://docs.sablier.com/concepts/cancelability # Cancelability ## Lockup Streams When creating a Lockup stream, users have the ability to set the stream as cancelable, or uncancelable. If **cancelable**, the stream can be stopped at any time by the stream creator, with the unstreamed funds being returned over to the stream creator. **Example:** If you are using Sablier Lockup for Employee Token Vesting, and one of your employees leaves your company, you can cancel the stream and get the tokens back which haven't yet been vested to the ex-employee at that specific time. Any tokens vested out before that are obviously now owned by the recipient. :::info When a stream is canceled (stopped), there is no way to uncancel it (start it again). You will need to create a new stream in that case. ::: If **uncancelable**, there is no way to cancel the stream, and the recipient is guaranteed to receive the funds. A cancelable stream can be set as uncancelable at any point in time by the stream creator. On the other hand, an uncancelable stream cannot be set as cancelable by the stream creator. Recipients do not have the ability to cancel a stream. ## Merkle Airdrop campaigns If you have created a merkle campaign and made a mistake, you can recover the funds from the campaign that haven't yet been claimed in the following cases: - Before any user claimed from the campaign - Up until 7 days after the first claim took place - After the claim window ends (assuming it's enabled) If you haven't set up a claim window, you can only recover the funds you deposited into the campaign up until 7 days after the first claim took place, or as long as you like if there are no claims. Assuming there are claims, after the 7-day period passes, you will not have the ability anymore to recover the funds deposited into the campaign contract. They will stay there forever until claimed by eligible recipients. If you have set up a claim window, after the claim window ends, you have the ability to get the funds back which haven't yet been claimed by that time. :::important It's important to mention that the airstreams created through the merkle campaigns can be set as cancelable, the above points are related to the campaign itself, not the streams created through users claiming from the campaign. **Example:** you create an Airstream campaign. You deposit 1000 USDC in it. You set up the streams as cancelable, but there is no claim window. All eligible users claim from the campaign at the same time, and 8 days later, you realize you made a mistake. While you cannot recover the funds through the campaign as the 7-day period has passed, you can cancel the streams that were created and get the funds back which haven't been paid out at that specific time to the recipients. ::: ### Ranged Airstreams In case of Ranged Airstreams, the vesting begins and ends at the same time for all the recipients, provided by the campaign creator. If campaign creator wishes to cancel unclaimed Ranged Airstreams before the campaign expiry, the creator would have to trigger claims on behalf of the recipients and then cancel the Lockup streams. On the other hand, to cancel claimed Ranged Airstreams, campaign creator can simply cancel them through the Sablier interface, provided they were set as cancelable during campaign launch. ### Non-Ranged Airstreams For all the claimed Non-Ranged Airstreams, campaign creator can cancel them through the Sablier interface, provided they were set as cancelable during campaign launch. For all non-claimed Non-Ranged Airstreams, because the vesting does not begin until the user claims, campaign creator can wait until the campaign expiry to recover the unclaimed funds. ## Flow Streams Flow Streams do not offer the ability to cancel the stream. However, the stream creator can pause a Flow stream at any time and resume it at any time making it a more flexible option for payments. ## Bob Vaults Bob vaults are not cancelable. Once a vault is created, its parameters (token, oracle, target price, expiry) are immutable. Depositors can only redeem their shares after the vault has [settled or expired](/concepts/bob/statuses). --- ## Supported Chains Source: https://docs.sablier.com/concepts/chains # Supported Chains The Sablier Protocol is deployed on 24 mainnets and 5 testnet EVM chains, although not all of these are supported by the [Sablier Interface](https://app.sablier.com/). :::note [Bob] Bob is currently deployed only on **Ethereum Mainnet** and **Sepolia**. See [Bob Deployments](/guides/bob/deployments). ::: ## Mainnets | Name | Chain ID | In UI? | Native Token | Explorer | | --- | --- | --- | --- | --- | | Abstract | 2741 | ✅ | ETH | [abscan.org](https://abscan.org) | | Arbitrum | 42161 | ✅ | ETH | [arbiscan.io](https://arbiscan.io) | | Avalanche | 43114 | ✅ | AVAX | [snowscan.xyz](https://snowscan.xyz) | | Base | 8453 | ✅ | ETH | [basescan.org](https://basescan.org) | | Berachain | 80094 | ✅ | BERA | [berascan.com](https://berascan.com) | | BNB Chain | 56 | ✅ | BNB | [bscscan.com](https://bscscan.com) | | Chiliz | 88888 | ✅ | CHZ | [chiliscan.com](https://chiliscan.com) | | Denergy | 369369 | ✅ | WATT | [explorer.denergychain.com](https://explorer.denergychain.com) | | Ethereum | 1 | ✅ | ETH | [etherscan.io](https://etherscan.io) | | Gnosis | 100 | ✅ | XDAI | [gnosisscan.io](https://gnosisscan.io) | | HyperEVM | 999 | ✅ | HYPE | [hyperevmscan.io](https://hyperevmscan.io) | | Lightlink | 1890 | ✅ | ETH | [phoenix.lightlink.io](https://phoenix.lightlink.io) | | Linea Mainnet | 59144 | ✅ | ETH | [lineascan.build](https://lineascan.build) | | Mode | 34443 | ✅ | ETH | [modescan.io](https://modescan.io) | | Monad | 143 | ✅ | MON | [monadscan.com](https://monadscan.com) | | Morph | 2818 | ✅ | ETH | [explorer.morphl2.io](https://explorer.morphl2.io) | | OP Mainnet | 10 | ✅ | ETH | [optimistic.etherscan.io](https://optimistic.etherscan.io) | | Polygon | 137 | ✅ | POL | [polygonscan.com](https://polygonscan.com) | | Scroll | 534352 | ✅ | ETH | [scrollscan.com](https://scrollscan.com) | | Sonic | 146 | ✅ | S | [sonicscan.org](https://sonicscan.org) | | Superseed | 5330 | ✅ | ETH | [explorer.superseed.xyz](https://explorer.superseed.xyz) | | Unichain | 130 | ✅ | ETH | [uniscan.xyz](https://uniscan.xyz) | | XDC | 50 | ✅ | XDC | [xdcscan.com](https://xdcscan.com) | | ZKsync Era | 324 | ✅ | ETH | [explorer.zksync.io](https://explorer.zksync.io) | ## Testnets | Name | Chain ID | In UI? | Native Token | Explorer | | --- | --- | --- | --- | --- | | Arbitrum Sepolia | 421614 | ❌ | ETH | [sepolia.arbiscan.io](https://sepolia.arbiscan.io) | | Base Sepolia | 84532 | ✅ | ETH | [sepolia.basescan.org](https://sepolia.basescan.org) | | BattleChain Testnet | 627 | ❌ | ETH | [explorer.testnet.battlechain.com](https://explorer.testnet.battlechain.com) | | OP Sepolia | 11155420 | ❌ | ETH | [optimism-sepolia.blockscout.com](https://optimism-sepolia.blockscout.com) | | Sepolia | 11155111 | ✅ | ETH | [sepolia.etherscan.io](https://sepolia.etherscan.io) | --- ## Fees Source: https://docs.sablier.com/concepts/fees # Fees During the course of using Sablier, you may encounter two different types of fees: [Interface Fees](#interface-fees) and [Gas Fees](#gas-fees). Below you can see a breakdown of how each fee is calculated and who receives it. ## Interface Fees The Sablier Interface charges a flat fee in USD for certain operations. However, this fee is paid in the native gas token, i.e. in ETH for streams on Ethereum, and in POL for streams on Polygon. The fee amount is calculated based on market prices. For example, when ETH is $4000, a $1 fee is 0.00025 ETH. :::tip [For Senders] Interface Fees can be subsidized - the person creating the stream or airdrop can choose to cover the fees that would normally be charged to recipients. ::: ### Stream Withdrawals The Sablier Interface charges a flat fee each time you withdraw from a stream. This applies to both Lockup and Flow streams. :::info [How much is the withdraw fee?] The fee is **$0.99** (in the gas token) regardless of the withdraw amount. ::: The fee applies only to streams created via [Lockup v2.0 (or later)](/guides/lockup/deployments#versions) and [Flow v1.1 (or later)](/guides/flow/deployments#versions). You do not pay this fee for withdrawing from streams created using earlier releases. ### Airdrop Claims The Sablier Interface charges a dynamic fee when you claim an airdrop. The fee applies only to airdrops created via Merkle [Airdrops v1.3 (or later)](/guides/airdrops/deployments#versions). Claims from airdrops created with earlier versions do not incur any claim fee. ### Current Interface Fees The current Interface Fees are **$0.99** for withdrawing from streams and up to **$1.99** for claiming airdrops. For the most up-to-date pricing, see [sablier.com/pricing](https://sablier.com/pricing). ## Gas Fees [Gas fees](https://investopedia.com/terms/g/gas-ethereum.asp) are transaction fees paid to the blockchain validators in the native token of the network, e.g., ETH for Ethereum Mainnet. Gas is paid only when streams are created, canceled, transferred, or withdrawn from. Gas does not accrue in real-time. Importantly, Sablier Labs does not take any cut from the gas fee. 100% of the gas fee goes to the blockchain network validators, which are not affiliated with Sablier Labs. ## Bob Vaults Sablier Bob vaults charge a yield fee on positive yield generated by the [Lido adapter](/concepts/bob/lido-adapter), and a flat fee in the native gas token on vaults without an adapter. - **Yield fee:** a configurable percentage of the positive yield. For example, a 10% fee on 0.05 WETH of yield is 0.005 WETH; the depositor keeps the remaining 0.045 WETH. - **Native token fee:** charged when withdrawing from vaults that hold deposited tokens directly (no yield-generating adapter). Both fees are governed by the [`SablierComptroller`](/concepts/governance) and are independent of Interface Fees and Gas Fees. ## Fee Subsidization Interface Fees can be subsidized, partially or fully, by the sender in certain cases. This means that the person creating the stream or airdrop can choose to cover the fees that would normally be charged to the recipient. Note that the gas fees cannot be subsidized as they are paid to the blockchain network validators on every transaction. See the [Interface Fees](#interface-fees) section above for details on how subsidization works. ## FAQ ### Q: How are the Interface Fees charged? **A:** They are added to the gas fee. For example, if the gas fee is $10 and the Interface Fee is $1, you would pay $11 in total (the payment is taken in the gas token, e.g., ETH). ### Q: Does the Stream Withdrawal Fee apply to each withdrawal? **A:** Yes, unless they are subsidized by the sender. The fee is charged each time you withdraw from a stream. For example, if you withdraw from a stream 10 times, you will pay $10 in fees. ### Q: Can the Interface Fees be subsidized? **A:** Yes, they can be subsidized by the stream sender or the airdrop creator. See the [Fee Subsidization](#fee-subsidization) section for details. :::tip We provide gas benchmarks for the [Lockup](/guides/lockup/gas-benchmarks) and [Flow](/guides/flow/gas-benchmarks) contracts. ::: --- ## Overview Source: https://docs.sablier.com/concepts/flow/overview # Overview Flow is a debt tracking protocol that tracks tokens owed between two parties, enabling indefinite token streaming. A Flow stream is characterized by its rate per second (rps). The relationship between the amount owed and time elapsed is linear and can be defined as: $$ \text{amount owed} = rps \cdot \text{elapsed time} $$ The Flow protocol can be used in several areas of everyday finance, such as payroll, distributing grants, insurance premiums, loans interest, token ESOPs etc. If you are looking for vesting and airdrops, please refer to our [Lockup](/concepts/lockup/overview) protocol. ## Features 1. **Flexible deposit:** A stream can be funded with any amount, at any time, by anyone, in full or in parts. 2. **Flexible start time:** A stream can be created with a start time in past, present or in future. 3. **Flexible end time:** A stream can be created without specifying any end time so that it can run indefinitely. 4. **Pause:** A stream can be paused by the sender and can later be restarted without losing track of previously accrued debt. 5. **Refund:** Unstreamed amount can be refunded back to the sender at any time. 6. **Void:** Voiding a stream implies it cannot be restarted anymore. Voiding an insolvent stream forfeits the uncovered debt. Either party can void a stream at any time. 7. **Withdraw:** it is publicly callable as long as `to` is set to the recipient. However, a stream’s recipient is allowed to withdraw funds to any address. ## Key Definitions The definitions below will help you understand some terms used in Sablier Flow: ## Open-Ended Streams Open-ended streams on Sablier are token streams without a predefined end date. They continue indefinitely until voided by the sender. Key traits: - No end time: the stream has a start time but no fixed end time. - Funds are streamed continuously at a fixed rate per second. - Stream must be topped up periodically to maintain solvency, [debt is otherwise accumulated](/concepts/flow/overview#total-debt). - Useful for ongoing payments like salaries, grants, or subscriptions. ### Stream balance Stream balance is the token balance of a stream. It increases when funds are deposited into a stream, and decreases when the sender refunds from it or when a withdrawal happens. ### Total debt Total debt is the amount of tokens owed to the recipient. This value is further divided into two sub-categories: - **Covered debt:** The part of the total debt that is covered by the stream balance. This is the same as the **withdrawable amount**, which is an alias. - **Uncovered debt:** The part of the total debt that is not covered by the stream balance. This is what the sender owes to the stream. $$ \text{total debt} = \text{covered debt} + \text{uncovered debt} $$ ### Snapshot debt and Snapshot time A snapshot is an event during which snapshot debt and snapshot time of a Flow stream are updated. **Snapshot debt** is the debt accumulated until the previous snapshot. The UNIX timestamp at which snapshot debt is updated is called **Snapshot time**. At snapshot, the following operations are taking place: $$ \text{snapshot debt} = \text{previous snapshot debt} + \underbrace{ rps \cdot (\text{block.timestamp} - \text{snapshot time})}_\text{ongoing debt} $$ $$ \text{snapshot time} = \text{block.timestamp} $$ ### Ongoing debt Ongoing debt is the debt accumulated since the previous snapshot. It is calculated as the following: $$ \text{ongoing debt} = rps \cdot (\text{block.timestamp} - \text{snapshot time}) $$ Therefore, at any point in time, total debt can also be defined as: $$ \text{total debt} = \text{snapshot debt} + \text{ongoing debt} $$ --- ## Stream Statuses Source: https://docs.sablier.com/concepts/flow/statuses # Stream Statuses A Flow stream can have one of five distinct statuses: | Status | Description | | --- | --- | | `PENDING` | Stream created but not started. | | `STREAMING_SOLVENT` | Active stream with total debt not exceeding stream balance. | | `STREAMING_INSOLVENT` | Active stream with total debt exceeding stream balance. | | `PAUSED_SOLVENT` | Paused stream with total debt not exceeding stream balance. | | `PAUSED_INSOLVENT` | Paused stream with total debt exceeding stream balance. | | `VOIDED` | Paused stream that can no longer be restarted and has forfeited its uncovered debt. | ## Stream characteristics A stream can have the following characteristics: | Characteristic | Statuses | Description | | --- | --- | --- | | Pending | `PENDING` | Snapshot time in future, and non-zero rps. | | Streaming | `STREAMING_SOLVENT`, `STREAMING_INSOLVENT` | Non-zero rps. | | Paused | `PAUSED_SOLVENT`, `PAUSED_INSOLVENT`, `VOIDED` | Zero rps. | | Solvent | `STREAMING_SOLVENT`, `PAUSED_SOLVENT`, `VOIDED` | Total debt not exceeding the stream balance. | | Insolvent | `STREAMING_INSOLVENT`, `PAUSED_INSOLVENT` | Total debt exceeding the stream balance. | ## State transitions The following diagram illustrates the statuses and the allowed transitions between them: ## Functions Statuses Interaction ### NULL stream ### PENDING stream ### STREAMING stream ### PAUSED stream ### VOIDED stream ## Q&A ### Q: What is a null stream? A: An ID that does not reference a created stream. Trying to interact with a null stream will result in a revert. ### Q: What to do with a stream status? A: Knowing the status of a stream can inform your decision making. For example, if a stream is `STREAMING_INSOLVENT`, that means the stream is active but has insufficient balance. As a sender, you should deposit into the stream so that your recipient can withdraw the streamed amount without any hiccups. And if you don't want to continue the stream, you can pause it. ### Q: Who can make a stream `VOIDED`? A: Both sender and recipient can void the stream. This is especially useful when either party wants to stop the stream immediately. Once a stream is voided, it cannot be restarted. If there is uncovered debt, it will be reset to 0. So to ensure that your recipient does not lose on any streamed amount, you can deposit into the stream before voiding it. ```mermaid flowchart LR subgraph PAUSED direction RL PS(SOLVENT) PI(INSOLVENT) PI -- "deposit" --> PS end subgraph STREAMING direction LR SS(SOLVENT) SI(INSOLVENT) SI -- "deposit" --> SS SS -- "time" --> SI end PENDING -- "time" --> STREAMING STREAMING -- pause --> PAUSED STREAMING -- void --> VOIDED PAUSED -- restart --> STREAMING PAUSED -- void --> VOIDED PENDING -- void --> VOIDED NULL -- create (rps > 0, startTime > blockTime) --> PENDING NULL -- create (rps > 0, startTime <= blockTime) --> STREAMING NULL -- create (rps = 0, startTime <= blockTime) --> PAUSED ``` ```mermaid flowchart LR CR([CREATE]) --> NULL((NULL)) ``` ```mermaid flowchart TD P((PENDING)) ADJRPS([ADJUST_RPS]) --> P DP([DEPOSIT]) --> P RFD([REFUND]) --> P VD([VOID]) --> P ``` ```mermaid flowchart TD STR((STREAMING)) ADJRPS([ADJUST_RPS]) --> STR DP([DEPOSIT]) --> STR RFD([REFUND]) --> STR PS([PAUSE]) --> STR VD([VOID]) --> STR WTD([WITHDRAW]) --> STR ``` ```mermaid flowchart TD PSED((PAUSED)) DP([DEPOSIT]) --> PSED RFD([REFUND]) --> PSED RST([RESTART]) --> PSED VD([VOID]) --> PSED WTD([WITHDRAW]) --> PSED ``` ```mermaid flowchart LR VOID((VOIDED)) RFD([REFUND]) --> VOID WTD([WITHDRAW]) --> VOID ``` --- ## Glossary Source: https://docs.sablier.com/concepts/glossary # Glossary ## Bob A price-gated vault protocol where deposited tokens unlock when the token price reaches a specific price or when the vault expires. ### Bob Vault Share A fungible [ERC-20](/concepts/glossary#erc-20) token representing a user's claim on a vault's underlying tokens and any accrued yield. Each vault deploys its own share token. ## Claim Fee The minimum fee, in native tokens, required for claiming an airdrop from a Merkle Airdrop campaign. ## Closed-Ended Stream Closed-ended streams have a fixed deposit amount and a fixed duration. Once the sender creates the stream, a specific start and end time are recorded on the blockchain. They contrast with [open-ended streams](/concepts/glossary#open-ended-stream). ## Comptroller A smart contract with exclusive access to specific functions of the protocol. ## DeFi Short for Decentralized Finance: an ecosystem of financial applications and services built on blockchain networks, primarily Ethereum, that leverage smart contracts to enable trustless, permissionless, and transparent financial transactions without relying on traditional intermediaries like banks or financial institutions. ## ERC-20 [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens are fungible tokens on Ethereum. Sablier supports all standard ERC-20 implementations, including those with the [missing return value bug](https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca). :::important If your ERC-20 token does not follow the standard implementation, it may be supported as long as the `transfer` and `transferFrom` methods strictly reduce the sender's balance by the transfer amount and increase the recipient's balance by the same amount. That means tokens that charge fees (or tax) on transfer, rebase tokens, interest-bearing tokens, and permissioned tokens where the admin can arbitrarily change balances are not supported. ::: ## ERC-721 [ERC-721](https://eips.ethereum.org/EIPS/eip-721) tokens are non-fungible tokens ("NFTs") on Ethereum. Both Lockup and Flow streams are represented as NFTs. ## Ethereum A global, open-source platform for decentralized applications. ## Ethereum Virtual Machine The technical architecture of Ethereum, which many other blockchains have appropriated. ## Flow A term coined by us for our debt tracking protocol that tracks tokens owed between two parties, enabling open-ended token streaming. ### Covered debt The amount of tokens that can be withdrawn by the recipient. ### Flow Protocol Smart contracts that are considered foundational, and are essential for Flow streams to exist. Upgrading to a new version of Flow would require deploying an entirely new set of smart contracts, and would be considered a new version of the Flow protocol. ### Rate Per Second The amount of tokens that are streamed per second in a Flow stream. ### Status A Flow stream can have one out of seven possible statuses: 1. NULL 2. PENDING 3. PAUSED\_INSOLVENT 4. PAUSED\_SOLVENT 5. STREAMING\_INSOLVENT 6. STREAMING\_SOLVENT 7. VOIDED ### Uncovered debt The amount of tokens that sender owes to the stream. ## Foundry [Foundry](https://github.com/foundry-rs/foundry) is the application development toolkit that has been used to develop the Sablier Protocol. ## Gas Fee [Gas fees](https://www.investopedia.com/terms/g/gas-ethereum.asp) are transaction fees paid to the blockchain validators in native tokens such as ETH. Sablier Labs does not take any cut from this. Gas is paid only when streams are created, canceled, transferred, or withdrawn from. It does not accrue in real-time. ## Lockup A term coined by us to refer to the requirement of locking up tokens in order to create a stream. ### Allow List A list of smart contract recipients that are authorized to be run by the Lockup protocol upon withdraw and cancel. The stream itself is represented as an NFT (ERC-721). ### Cliff Time The cut-off point for releasing tokens. Prior to the cliff, the recipient cannot withdraw, though tokens continue to accrue in the stream. ### Distribution Model A distribution model represents the streaming function used to create lockup streams. There are three types of models supported by the Lockup protocol: 1. Linear 2. Dynamic 3. Tranched ### Dynamic Model A Lockup [distribution model](#distribution-model) with a streaming rate per second that can vary over time. ### End Time The time when a stream is scheduled to end. ### Linear Model A Lockup [distribution model](#distribution-model) with a constant streaming rate per second. ### Lockup Math [A public library](/reference/lockup/contracts/libraries/library.LockupMath) used by the Lockup protocol to calculate the amount of vested tokens at any given time. ### Lockup Protocol Smart contracts that are considered foundational, and are essential for Lockup streams to exist. Upgrading to a new version of Lockup would require deploying an entirely new set of smart contracts, and would be considered a new version of the Lockup protocol. ### Monotonicity A protocol invariant that states that the total amount of tokens released by the stream can only increase over time and never decrease. ### Renounce A renounced stream is a stream that cannot be canceled anymore. ### Segment A data object that encapsulates these three properties: 1. Amount 2. Exponent 3. Timestamp Segments are an essential component of Dynamic [distribution model](#distribution-model), as they facilitate the calculation of the custom streaming curve. ### Start time The time when a stream is scheduled to start. ### Status A Lockup stream can have one out of six possible statuses: 1. NULL 2. PENDING 3. STREAMING 4. SETTLED 5. CANCELED 6. DEPLETED ### Timestamp Lockup timestamp represents start time and end time. ### Tranche A data object that encapsulates these two properties: 1. Amount 2. Timestamp Tranches are an essential component of Tranched [distribution model](#distribution-model), as they facilitate the calculation of the custom streaming curve. ### Tranched Model A Lockup [distribution model](#distribution-model) with streaming in discrete tranches. ### Unlock Amounts A data object that encapsulates amounts to be unlocked at the start of the stream and at the cliff of the stream. ## Merkle Airdrop An onchain distribution of tokens that employs a Merkle tree data structure to perform airdrop eligibility checks. ## Merkle Tree A data structure used to store hashes of the recipients airdrop allocations in Merkle Airdrop Campaigns. ## Open-Ended Stream Open-ended streams are streams that don't have an end time and are not tethered to a specific deposit amount. They may also be called "indefinite streams". They contrast with [closed-ended streams](/concepts/glossary#closed-ended-stream). ## PRBMath [PRBMath](https://github.com/PaulRBerg/prb-math) is fixed-point arithmetic library used by the Sablier Protocol for precise calculations. ## Real-time finance A term coined by us in 2019 to emphasize the wide-ranging use cases for the Sablier Protocol. Since the withdrawable amounts in streams are updated every second, they embody the concept of real-time financial transactions. ## Stream A new financial primitive that permits by-the-second payments. Currently, Sablier offers two distribution protocols called Lockup and Flow. In Lockup, the creator has to lock up a specified amount of tokens, whereas in Flow, creator is not required to lock up any amount of tokens. ## Streaming By-the-second payments. ## Token Digital tokens can exist in various forms, but the Sablier Protocol exclusively supports the streaming of ERC-20 tokens. ## Vesting One of the most popular use cases for streaming today. The purpose of vesting is to delay gratification. Founders, investors, and employees must wait a certain amount of time before being able to access tokens. ## Withdraw Fee The fee collected, in native tokens, by the Sablier interface for withdrawing from streams. --- ## Governance Source: https://docs.sablier.com/concepts/governance # Governance ## Comptroller and Admins The Sablier Comptroller is a smart contract that acts as an intermediary between the protocols and the Sablier admin addresses. It has exclusive access to specific protocol functions. This design provides a more flexible approach to access control across all protocols while maintaining security. ### Current Used in Lockup v2.0, v3.0 and v4.0, Flow v1.1, v2.0 and v3.0, and Airdrops v1.3, v2.0 and v3.0. The table below lists the deployed Comptroller and the admin that controls it on each chain. | Chain | Comptroller | Comptroller Admin | | --- | --- | --- | | Abstract | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://abscan.org/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://abscan.org/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Arbitrum | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://arbiscan.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://arbiscan.io/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Avalanche | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://snowscan.xyz/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://snowscan.xyz/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Base | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://basescan.org/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://basescan.org/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Berachain | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://berascan.com/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://berascan.com/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Blast | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://blastscan.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://blastscan.io/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | BNB Chain | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://bscscan.com/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://bscscan.com/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Chiliz | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://chiliscan.com/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://chiliscan.com/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Denergy | [0x946654AB30Dd6eD10236C89f2C8B2719df653691](https://explorer.denergychain.com/address/0x946654AB30Dd6eD10236C89f2C8B2719df653691) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://explorer.denergychain.com/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Ethereum | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://etherscan.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://etherscan.io/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Gnosis | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://gnosisscan.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://gnosisscan.io/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | HyperEVM | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://hyperevmscan.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://hyperevmscan.io/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Lightlink | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://phoenix.lightlink.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://phoenix.lightlink.io/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Linea Mainnet | [0xF21b304A08993f98A79C7Eb841f812CCeab49B8b](https://lineascan.build/address/0xF21b304A08993f98A79C7Eb841f812CCeab49B8b) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://lineascan.build/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Mode | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://modescan.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://modescan.io/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Monad | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://monadscan.com/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://monadscan.com/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Morph | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://explorer.morphl2.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://explorer.morphl2.io/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | OP Mainnet | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://optimistic.etherscan.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://optimistic.etherscan.io/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Polygon | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://polygonscan.com/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://polygonscan.com/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | | Scroll | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://scrollscan.com/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://scrollscan.com/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Sonic | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://sonicscan.org/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://sonicscan.org/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Superseed | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://explorer.superseed.xyz/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://explorer.superseed.xyz/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | Unichain | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://uniscan.xyz/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://uniscan.xyz/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | XDC | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://xdcscan.com/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0xcB88fBf459000853F22a7296b23d163901BB385E](https://xdcscan.com/address/0xcB88fBf459000853F22a7296b23d163901BB385E) | | ZKsync Era | [0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399](https://explorer.zksync.io/address/0x0000008ABbFf7a84a2fE09f9A9b74D3BC2072399) | [0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd](https://explorer.zksync.io/address/0x58290bbdb51A4c6B022A81e9cDeD24BE19Ca57fd) | ### Old Used in all previous versions: Lockup v1.0, v1.1 and v1.2; Flow v1.0; and Airdrops v1.1 and v1.2. Each of these addresses had direct admin authority over the corresponding protocol contracts before the Comptroller was introduced. | Chain | Address | | --- | --- | | Abstract | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://abscan.org/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Arbitrum | [0xF34E41a6f6Ce5A45559B1D3Ee92E141a3De96376](https://arbiscan.io/address/0xF34E41a6f6Ce5A45559B1D3Ee92E141a3De96376) | | Avalanche | [0x4735517616373c5137dE8bcCDc887637B8ac85Ce](https://snowscan.xyz/address/0x4735517616373c5137dE8bcCDc887637B8ac85Ce) | | Base | [0x83A6fA8c04420B3F9C7A4CF1c040b63Fbbc89B66](https://basescan.org/address/0x83A6fA8c04420B3F9C7A4CF1c040b63Fbbc89B66) | | Berachain | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://berascan.com/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Blast | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://blastscan.io/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | BNB Chain | [0x6666cA940D2f4B65883b454b7Bc7EEB039f64fa3](https://bscscan.com/address/0x6666cA940D2f4B65883b454b7Bc7EEB039f64fa3) | | Chiliz | [0x74A234DcAdFCB395b37C8c2B3Edf7A13Be78c935](https://chiliscan.com/address/0x74A234DcAdFCB395b37C8c2B3Edf7A13Be78c935) | | Ethereum | [0x79Fb3e81aAc012c08501f41296CCC145a1E15844](https://etherscan.io/address/0x79Fb3e81aAc012c08501f41296CCC145a1E15844) | | Gnosis | [0x72ACB57fa6a8fa768bE44Db453B1CDBa8B12A399](https://gnosisscan.io/address/0x72ACB57fa6a8fa768bE44Db453B1CDBa8B12A399) | | HyperEVM | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://hyperevmscan.io/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Lightlink | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://phoenix.lightlink.io/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Linea Mainnet | [0x72dCfa0483d5Ef91562817C6f20E8Ce07A81319D](https://lineascan.build/address/0x72dCfa0483d5Ef91562817C6f20E8Ce07A81319D) | | Mode | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://modescan.io/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Morph | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://explorer.morphl2.io/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | OP Mainnet | [0x43c76FE8Aec91F63EbEfb4f5d2a4ba88ef880350](https://optimistic.etherscan.io/address/0x43c76FE8Aec91F63EbEfb4f5d2a4ba88ef880350) | | Polygon | [0x40A518C5B9c1d3D6d62Ba789501CE4D526C9d9C6](https://polygonscan.com/address/0x40A518C5B9c1d3D6d62Ba789501CE4D526C9d9C6) | | Scroll | [0x0F7Ad835235Ede685180A5c611111610813457a9](https://scrollscan.com/address/0x0F7Ad835235Ede685180A5c611111610813457a9) | | Sonic | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://sonicscan.org/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Superseed | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://explorer.superseed.xyz/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Tangle | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://explorer.tangle.tools/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | Unichain | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://uniscan.xyz/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | XDC | [0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F](https://xdcscan.com/address/0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F) | | ZKsync Era | [0xaFeA787Ef04E280ad5Bb907363f214E4BAB9e288](https://explorer.zksync.io/address/0xaFeA787Ef04E280ad5Bb907363f214E4BAB9e288) | ## Contract Permissions ### Comptroller The Comptroller itself has admin- and role-gated functions. Roles are managed by the admin via `grantRole` / `revokeRole`; the admin can also call any role-gated function directly. #### Admin Only | Permission | Function | | --- | --- | | Execute | [`execute`](/reference/utils/contracts/contract.SablierComptroller#execute) | | Grant Role | `grantRole` (inherited) | | Revoke Role | `revokeRole` (inherited) | | Set Oracle | [`setOracle`](/reference/utils/contracts/contract.SablierComptroller#setoracle) | | Transfer Admin | `transferAdmin` (inherited) | | Upgrade To And Call | `upgradeToAndCall` (inherited) | | Withdraw ERC-20 | [`withdrawERC20Token`](/reference/utils/contracts/contract.SablierComptroller#withdrawerc20token) | #### Fee Management Role | Permission | Function | | --- | --- | | Disable Custom Fee USD For | [`disableCustomFeeUSDFor`](/reference/utils/contracts/contract.SablierComptroller#disablecustomfeeusdfor) | | Lower Min Fee USD Campaign | [`lowerMinFeeUSDForCampaign`](/reference/utils/contracts/contract.SablierComptroller#lowerminfeeusdforcampaign) | | Set Custom Fee USD For | [`setCustomFeeUSDFor`](/reference/utils/contracts/contract.SablierComptroller#setcustomfeeusdfor) | | Set Min Fee USD | [`setMinFeeUSD`](/reference/utils/contracts/contract.SablierComptroller#setminfeeusd) | #### Attestor Manager Role | Permission | Function | | --- | --- | | Set Attestor | [`setAttestor`](/reference/utils/contracts/contract.SablierComptroller#setattestor) | | Set Attestor For Campaign | [`setAttestorForCampaign`](/reference/utils/contracts/contract.SablierComptroller#setattestorforcampaign) | #### Fee Collector Role | Permission | Function | | --- | --- | | Transfer Fees | [`transferFees`](/reference/utils/contracts/contract.SablierComptroller#transferfees) | ### Lockup Comptroller has the following permissions on each chain where Lockup is deployed: | Permission | Function | | --- | --- | | Allow to Hook | [`allowToHook`](/reference/lockup/contracts/contract.SablierLockup#allowtohook) | | Recover | [`recover`](/reference/lockup/contracts/contract.SablierLockup#recover) | | Set Native Token | [`setNativeToken`](/reference/lockup/contracts/contract.SablierLockup#setnativetoken) | | Set NFT Descriptor | [`setNFTDescriptor`](/reference/lockup/contracts/contract.SablierLockup#setnftdescriptor) | ### Merkle Factory Comptroller has the following permission on each chain where the Merkle Factories are deployed: | Permission | Function | | --- | --- | | Set Native Token | [`setNativeToken`](/reference/airdrops/contracts/abstracts/abstract.SablierFactoryMerkleBase#setnativetoken) | ### Flow Comptroller has the following permissions on each chain where Flow is deployed: | Permission | Function | | --- | --- | | Recover | [`recover`](/reference/flow/contracts/contract.SablierFlow#recover) | | Set Native Token | [`setNativeToken`](/reference/flow/contracts/contract.SablierFlow#setnativetoken) | | Set NFT Descriptor | [`setNFTDescriptor`](/reference/flow/contracts/contract.SablierFlow#setnftdescriptor) | ### Bob Comptroller has the following permissions on each chain where Bob is deployed: | Permission | Function | | --- | --- | | Set Default Adapter | [`setDefaultAdapter`](/reference/bob/contracts/contract.SablierBob#setdefaultadapter) | | Set Native Token | [`setNativeToken`](/reference/bob/contracts/contract.SablierBob#setnativetoken) | ### Lido Adapter Comptroller has the following permissions on the Lido Adapter: | Permission | Function | | --- | --- | | Request Lido Withdrawal | [`requestLidoWithdrawal`](/reference/bob/contracts/contract.SablierLidoAdapter#requestlidowithdrawal) | | Set Slippage Tolerance | [`setSlippageTolerance`](/reference/bob/contracts/contract.SablierLidoAdapter#setslippagetolerance) | | Set Yield Fee | [`setYieldFee`](/reference/bob/contracts/contract.SablierLidoAdapter#setyieldfee) | ## Trustlessness Despite having an admin, the Sablier Protocol remains trustless. Here's why: 1. The protocol is permissionless, i.e. it can be freely accessed by anyone with an Internet connection. 2. The protocol is persistent, i.e. the admin cannot pause it. 3. The streaming logic is non-upgradeable, i.e. the admin cannot tamper with the streams created by users. 4. There are no escape hatches that allow the admin to claim user funds. ## Timelocks The parameter changes that can be effected are NOT subject to a timelock. This means that the admin can execute any of the functions listed above at any time. ## Governance As a startup, Sablier has to deal with uncertainty regarding: 1. Protocol-market fit 2. Smart contract security Attaining success in these areas is no easy feat, and as such, decentralizing the protocol's governance will not be an initial priority. Nonetheless, we believe that progressive decentralization is the most effective approach to scaling a smart contract protocol. As the protocol matures, we will decentralize its governance incrementally. --- ## Hooks Source: https://docs.sablier.com/concepts/lockup/hooks # Hooks Hooks are arbitrary third-party functions that get automatically executed by the Sablier Protocol in response to `cancel` and `withdraw` events. They are similar to callback functions in web2. :::info Important Hooks have to be allowlisted before they can be run. Currently, only the [Comptroller](/concepts/governance) has permission to do this. In the future, we may decentralize this process through governance. ::: Hooks are a powerful feature that enable Sablier streams to interact with other DeFi protocols. Let's consider an example: You own a Sablier stream that expires in two years. You are interested in taking a loan against it with the intention to pay it all back after it expires. Hooks are what enable you to do that. With the help of Hooks, we can create an ecosystem of varied use cases for Sablier streams. This can range from lending, staking, credit, and more. It is worth noting that once a hook has been allowlisted, it can never be removed. This is to ensure stronger immutability and decentralization guarantees. Once a recipient contract is allowlisted, integrators do NOT have to trust us to keep their contract on the allowlist. ## Checklist The requirements a hook contract must meet: 1. The contract is not upgradeable. 2. The contract was audited by a third-party security researcher. 3. The contract implements `supportsInterface` and returns `true` for `0xf8ee98d3`, i.e., `type(ISablierLockupRecipient).interfaceId`. 4. If it implements `onSablierLockupCancel`: 1. It returns `ISablierLockupRecipient.onSablierLockupCancel.selector`. 2. It reverts if `msg.sender` is not the Lockup contract. 3. It uses input parameters correctly: `streamId`, `sender`, `senderAmount`, `recipientAmount`. 4. Be aware that if the call reverts, the entire `cancel` execution would revert too. 5. If it implements `onSablierLockupWithdraw`: 1. It returns `ISablierLockupRecipient.onSablierLockupWithdraw.selector`. 2. It reverts if `msg.sender` is not the Lockup contract. 3. It uses input parameters correctly: `streamId`, `caller`, `to`, `amount`. 4. Be aware that if the call reverts, the entire `withdraw` execution would revert too. ## Visual representation :::note If the recipient contract is not on the Sablier allowlist, the hooks will not be executed. ::: ### Cancel hook ### Withdraw hook ## Next steps If you are interested in using Sablier hooks in your protocol, please check the [Hook guide](/guides/lockup/examples/hooks). ```mermaid sequenceDiagram actor Sender Sender ->> SablierLockup: cancel() SablierLockup -->> Recipient: onSablierLockupCancel() Recipient -->> SablierLockup: return selector SablierLockup -->> Sender: transfer unstreamed tokens break if hook reverts Recipient -->> SablierLockup: ❌ tx fail end ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> SablierLockup: withdraw() SablierLockup -->> Recipient: onSablierLockupWithdraw() Recipient -->> SablierLockup: return selector SablierLockup -->> Recipient: transfer streamed tokens break if hook reverts Recipient -->> SablierLockup: ❌ tx fail end ``` --- ## Overview Source: https://docs.sablier.com/concepts/lockup/overview # Overview Lockup is a token streaming protocol that refers to the requirement that the creator of a stream must lock up a certain amount of tokens in a smart contract. A Lockup stream, therefore, is characterized by the start time, end time, amount of tokens to be streamed and a [stream shape](/concepts/lockup/stream-shapes). Let's take an example. Imagine Alice wants to stream 3000 DAI to Bob during the whole month of April. 1. Alice deposits the 3000 DAI in Lockup before Apr 1, setting the end time to May 1. 2. Bob's allocation of the DAI deposit increases every second beginning Apr 1. 3. On Apr 10, Bob will have earned approximately 1000 DAI. He can send a transaction to Lockup to withdraw the tokens. 4. If at any point during April Alice wishes to get back her tokens, she can cancel the stream and recover what has not been streamed yet. This streaming model is especially useful for use cases like vesting and airdrops. If you are looking to create an indefinite stream of tokens, please refer to our [Flow](/concepts/flow/overview) protocol. Lockup enables multiple distribution models, a feature that is discussed in the next section. --- ## Segments Source: https://docs.sablier.com/concepts/lockup/segments # Segments ## Definition A Dynamic stream is composed of multiple segments, which are separate partitions with different streaming amount and rates. The protocol uses these segments to enable custom streaming curves, which power exponential streams, cliff streams, etc. Technically, a segment is a [struct](/reference/lockup/contracts/types/library.LockupDynamic#segment) with three fields: | Field | Type | Description | | --- | --- | --- | | Amount | `uint128` | The amount of tokens to be streamed in this segment, denoted in units of the token's decimals. | | Exponent | `UD2x18` | The exponent of this segment, denoted as a fixed-point number. | | Timestamp | `uint40` | The Unix timestamp indicating this segment's end. | Each segment has its own streaming function: $$ f(x) = x^{exp} * csa $$ Therefore, the distribution function of a dynamic stream becomes: $$ f(x) = x^{exp} * csa + \Sigma(esa) $$ Where: - $x$ is the elapsed time divided by the total time in the current segment. - $exp$ is the current segment exponent. - $csa$ is the current segment amount. - $\Sigma(esa)$ is the sum of all elapsed segments' amounts. :::info Segments can be used to represent any monotonic increasing function. ::: :::caution Because x is a percentage, the payment rate is inversely proportional to the exponent. For example, if the exponent is 0.5, the rate is quadratically faster compared to the baseline when the exponent is 1. Conversely, if the exponent is 2, the rate is quadratically slower compared to baseline. ::: ## Requirements - The sum of all segment amounts must equal the deposit amount. - There is a limit to how many segments there can be in a stream as enforced by the block gas limit. - If someone creates a stream with an excessively large number of segments, the transaction would revert as it wouldn't fit within a block. - The timestamps must be sorted in ascending order. It's not possible for the $(i-1)^{th}$ timestamp to be greater than $i^{th}$ timestamp (given that we're dealing with an increasing monotonic function). ## Examples A segment can be used to represent any monotonic increasing function. A few popular examples are highlighted below: ### Constant Curve (exp = 0)") A constant segment follows the function $f(x) = c$. This is achieved with an exponent of 0. ```solidity LockupDynamic.Segment({ amount: amount, // Total amount in this segment exponent: ud2x18(0e18), // Exponent = 0 (constant) timestamp: endTime // End time of the segment }); ``` A constant curve can be used to unlock amount in tranches where the entire segment amount unlocks at the beginning or the end of the segment. ### Linear Curve (exp = 1)") A linear segment follows the function $f(x) = cx$. This is achieved with an exponent of 1. ```solidity LockupDynamic.Segment({ amount: amount, // Total amount in this segment exponent: ud2x18(1e18), // Exponent = 1 (linear) timestamp: endTime // End time of the segment }); ``` A linear curve unlocks amount linearly over time. ### Accelerating Curve (exp > 1)") A function $f(x) = cx^{exp} \mid exp > 1$ can be used to create a segment that unlocks slowly at the beginning and then quickly at the end. For example, an exponent of 2 unlocks 25% of the segment amount in the first 50% of the time and the remaining 75% in the last 50% of the time. ```solidity segments[0] = LockupDynamic.Segment({ amount: totalAmount, // Total amount in this segment exponent: ud2x18(2e18), // Exponent = 2 (Quadratic) timestamp: endTime // End time of the stream }); ``` As you may have realized, the higher the exponent, the steeper the curve gets at the end. For example, an exponent of 4 unlocks 6% of the segment amount in the first 50% of the time and the remaining 94% in the last 50% of the time. ### Decelerating Curve (exp < 1)") A function $f(x) = cx^{exp} \mid 0 < exp < 1$ can be used to create a segment that unlocks quickly at the beginning and then slowly at the end. For example, an exponent of 0.2 unlocks 80% of the segment amount in the first 50% of the time and the remaining 20% in the last 50% of the time. ```solidity segments[0] = LockupDynamic.Segment({ amount: totalAmount, // Total amount in this segment exponent: ud2x18(0.3e18), // Exponent = 0.3 timestamp: endTime // End time of the stream }); ``` --- ## Stream Statuses Source: https://docs.sablier.com/concepts/lockup/statuses # Stream Statuses A Lockup stream can have one of five distinct statuses: | Status | Description | | --- | --- | | `PENDING` | Stream created but not started; tokens are in a pending state. | | `STREAMING` | Active stream where tokens are currently being streamed. | | `SETTLED` | All tokens have been streamed; recipient is due to withdraw them. | | `CANCELED` | Canceled stream; remaining tokens await recipient's withdrawal. | | `DEPLETED` | Depleted stream; all tokens have been withdrawn and/or refunded. | ## Temperature A stream status can have one out of two "temperatures": | Temperature | Statuses | Description | | --- | --- | --- | | Warm | Pending, Streaming | The passage of time alone can change the status. | | Cold | Settled, Canceled, Depleted | The passage of time won’t affect the status anymore. | ## State transitions The following diagram illustrates the statuses and the allowed transitions between them: ## Q&A ### Q: What is a null stream? A: An id that does not reference a created stream. Trying to interact with a null stream will result in a revert. ### Q: What to do with a stream status? A: Knowing the status of a stream can inform your decision making. For example, if a stream is canceled, you know that you can't cancel it again. Or, if a stream is depleted, you know that you can't withdraw any more tokens from it. ### Q: How can a stream enter the `SETTLED` status directly? A: This is a peculiarity of the [Lockup Dynamic](/concepts/lockup/stream-shapes#lockup-dynamic) streams. Segment amounts can be zero, and the segment milestones can be set in such a way that all non-zero segments are in the past. This will cause the stream to enter the `SETTLED` status directly. ```mermaid stateDiagram-v2 direction LR state Warm { Pending Streaming } state Cold { Settled Canceled Depleted } Null --> Pending Null --> Settled Pending --> Streaming Pending --> Settled Pending --> Canceled Streaming --> Settled Streaming --> Canceled Streaming --> Depleted Settled --> Depleted Canceled --> Depleted ``` --- ## Stream Shapes Source: https://docs.sablier.com/concepts/lockup/stream-shapes # Stream Shapes The stream shapes below are examples. Not an exhaustive list. The model for each shape is not a requirement, but a recommendation - for example, the Timelock shape can also be implemented via Linear or Dynamic models, but is most efficient with the Tranched model. :::tip When creating streams programmatically, pass the corresponding **Shape ID** (shown under each shape below) as the `shape` field. The Sablier app uses this ID to render the matching curve in its [UI gallery](https://app.sablier.com/vesting/gallery/). Install the [`sablier`](https://github.com/sablier-labs/sdk) SDK to import the IDs as a typed enum (`Shape.Lockup`). ::: :::note - The code used to generate the gas benchmarks for the different stream curves can be found [here](https://github.com/sablier-labs/evm-monorepo/tree/main/misc/benchmarks). - If you are interested in learning how to programmatically create the curves shown below in Solidity, check out the [examples](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/lockup/) repository and the "CurvesCreator" files. ::: ## Lockup Linear ### Linear **Shape ID:** `linear` Lockup Linear streams are the simplest token distribution curve in Sablier. The streamed amount over time follows a straight line that goes up and to the right on a graph, which corresponds to the identity function $f(x) = x$: With this shape of stream, the payment rate remains constant, meaning that the same fraction of the deposit amount is streamed to the recipient every second. This provides greater predictability and is easy to understand because of how intuitive it is. Imagine a diagonal line going up and to the right – that's how simple it is. :::info The gas cost to create this shape is approximately _168,923_ on Mainnet. This may vary due to multiple factors. ::: ### Unlock Linear **Shape ID:** `linearUnlockLinear` The Unlock Linear shape combines an initial immediate release of tokens with a steady, linear payout over time. This shape is ideal for employment contracts that include a signing bonus along with a regular payout schedule. At the beginning of the stream, a fixed amount of tokens is instantly available to the recipient — this is your "signing bonus". Following this, the remaining tokens begin to stream continuously at a consistent rate until the end of the contract term — this is your "salary". Another use case is a token distribution to investors where a particular amount gets unlocked immediately followed by a linear vesting plan. :::info The gas cost to create this shape is approximately _191,182_ on Mainnet. This may vary due to multiple factors. ::: ### Cliff **Shape ID:** `cliff` It is possible to attach a "cliff" to a Lockup Linear stream, which sets a cut-off point for releasing tokens. Prior to the cliff, the recipient cannot withdraw any tokens, but the stream continues to accrue them. After the cliff, the constant payment rate per second kicks in. This feature is especially useful for vesting ERC-20 tokens as it allows you to have, for example, a 1-year cliff, and then 3 additional years of linear streaming. If the stream is for an employee, you can make it cancellable so that if the employee leaves your company during the stream, you can cancel it and recover the tokens that have not yet been streamed. :::info The gas cost to create this shape is approximately _213,708_ on Mainnet. This may vary due to multiple factors. ::: ### Unlock Cliff **Shape ID:** `linearUnlockCliff` This shape is useful for companies who want to distribute tokens to their investors using a cliff followed by linear vesting but also want to unlock some liquidity at the beginning to be able to allow investors to bootstrap AMM pools. Initially, a set amount of tokens are made available to the recipient as an upfront payment. After this initial unlock, the tokens are held during the cliff period until the moment of time set. The release resumes in a linearly post-cliff. :::info The gas cost to create this shape is approximately _214,067_ on Mainnet. This may vary due to multiple factors. ::: ## Lockup Dynamic Lockup Dynamic streams are what makes Sablier so unique, since they enable the creation of an arbitrary streaming curve, including non-linear curves. On the Sablier Interface, we support only some distribution shapes (the ones enumerated below), but the potential for innovation is limitless when you interact programmatically with the contracts. For example, one could design a logarithmic stream that emulates the $f(x) = log(x)$ function. These streams are powered by a number of user-provided [segments](/concepts/lockup/segments), which we will cover in the next article. What is important to note here is that with Lockup Dynamic, Sablier has evolved into a universal streaming engine, capable of supporting any custom streaming curve. ### Exponential **Shape ID:** `dynamicExponential` A fantastic use case for Lockup Dynamic is Exponential streams, a shape through which the recipient receives increasingly more tokens as time passes. Exponentials are a great fit if you are looking to airdrop tokens, because your community members will receive the majority of the tokens towards the end of the stream instead of receiving the tokens all at once (no streaming) or in a linear fashion (Lockup Linear). This incentivizes long-term behavior and a constructive attitude. :::info The gas cost to create this shape is approximately _219,629_ on Mainnet. This may vary due to multiple factors. ::: ### Cliff Exponential **Shape ID:** `dynamicCliffExponential` Another use case for Lockup Dynamic is a variation of the previous design: an Cliff Exponential. The stream starts with a cliff (which can be how long you want), a specific amount instantly unlocked when the cliff ends, and then the rest of the stream is exponentially streamed. This is an excellent distribution if you are a company looking to set up a token vesting plan for your employees. Your employees will have an incentive to remain with your company in the long run, as they will receive an increasingly larger number of tokens. :::info The gas cost to create this shape is approximately _274,420_ on Mainnet. This may vary due to multiple factors. ::: ## Lockup Tranched Lockup Tranched streams are, as the name says, streams that have token unlocks in tranches. Even though you can use Lockup Dynamic to create a traditional vesting structure with periodic unlocks, Lockup Tranched is specifically designed for that use case. As a result, a stream with tranches created using Lockup Tranched is more gas efficient than the same stream created using Lockup Dynamic. These streams are powered by a number of user-provided [tranches](/concepts/lockup/tranches), which is covered in the tranches article. ### Unlock in Steps **Shape ID:** `linearStepper` _(the legacy `tranchedStepper` is deprecated)_ You can use Lockup Tranched to create a traditional vesting contract with periodic unlocks. In this case, the "streaming" rate would not be by the second, but by the week, month, or year. After each period, a specific amount becomes unlocked and available for the recipient to withdraw. Past unlocks accumulate, so if the recipient doesn't withdraw them, they will be able to withdraw them later. The advantage of using Unlock in Steps instead of a normal vesting contract is that Sablier automates the entire process. No more worries about setting up vesting contracts or creating a user interface for your investors to claim their tokens. :::info The gas cost to create this shape is approximately _299,268_ on Mainnet for four unlocks. This may vary as there are multiple factors to consider. ::: ### Unlock Monthly **Shape ID:** `tranchedMonthly` Unlock Monthly is a special case of Unlock in Steps where tokens are unlocked on the same day every month, e.g. the 1st of every month. This is suited for use cases like traditional monthly salaries or ESOPs plans. Each month, on a particular day, a specific amount of tokens becomes unlocked and available for withdrawal. Like Unlock in Steps, unwithdrawn tokens will carry over to the next period, providing flexibility and control to the recipient. This shape is ideal for employers who wish to set up advanced payment schedules for their employees, offering them access to funds on a predictable, monthly basis. :::info The gas cost to create this shape is approximately _511,476_ on Mainnet for a period of exactly **one year**. This may vary as there are multiple factors to consider. ::: ### Backweighted **Shape ID:** `tranchedBackweighted` Backweighted is a type of tranched vesting curve where the tokens are vested in a backweighted way, meaning little vests early on, and large chunks vest towards the end. Example for a 4-year vesting schedule: - Year 1: 10% vests - Year 2: 20% vests - Year 3: 30% vests - Year 4: 40% vests This makes it a particularly good fit for recipients that need to be particularly incentivized on a long-term perspective, as they receive progressively more and more tokens as the stream gets closer to its end date. :::info The gas cost to create this shape is approximately _342,999_ on Mainnet for a period of exactly **four years**. This may vary as there are multiple factors to consider. ::: ### Timelock **Shape ID:** `tranchedTimelock` The Timelock shape locks all tokens for a specified period. Users cannot access the tokens until the set period elapses. Once the set period elapses, the full amount becomes accessible to the recipient. This setup is particularly advantageous for projects seeking to stabilize token pricing and minimize market volatility, encouraging investors to maintain their stake over a more extended period. :::info The gas cost to create this shape is approximately _219,700_ on Mainnet. This may vary due to multiple factors. ::: --- ## Tranches Source: https://docs.sablier.com/concepts/lockup/tranches # Tranches ## Definition Analogous to the segments in Dynamic streams, a Tranched stream is composed of multiple tranches with different amounts and durations. The protocol uses these tranches to enable traditional vesting curves with regular unlocks. Technically, a tranche is a [struct](/reference/lockup/contracts/types/library.LockupTranched#tranche) with two fields: | Field | Type | Description | | --- | --- | --- | | Amount | `uint128` | The amount of tokens to be unlocked in a tranche, denoted in units of the token's decimals. | | Timestamp | `uint40` | The Unix timestamp indicating the tranche's end. | The distribution function of a Lockup tranched stream: $$ f(x) = \Sigma(eta) $$ Where: - $\Sigma(eta)$ is the sum of all vested tranches' amounts. ## Requirements - The sum of all tranche amounts must equal the deposit amount. - The block gas limit enforces a limit to how many tranches there can be in a stream. - If someone creates a stream with an excessively large number of tranches, the transaction would revert as it wouldn't fit within a block. In such cases, make sure to simulate the transaction first. - The timestamps must be sorted in ascending order. It's not possible for the $(i-1)^{th}$ timestamp to be greater than $i^{th}$ timestamp (given that we're dealing with an increasing monotonic function). --- ## NFTs Source: https://docs.sablier.com/concepts/nft # NFTs Both Lockup and Flow Protocols wrap every stream in an ERC-721 non-fungible token (NFT), making the stream recipient the owner of the NFT. The recipient can transfer the NFT to another address, and this also transfers the right to withdraw funds from the stream, including any funds already streamed. ## Lockup NFT Sablier Lockup streams are represented as unique onchain generated hourglass SVGs, which change their color and content based on user data. Here's an example for a stream that is 42.35% way through: ### Gallery of Multiple Sablier NFT SVGs ![](/assets/images/gallery-1bc5ee87aa9fa6c9ec9b8137c14d5b9d.webp) If you prefer the granularity of a blockchain explorer, you can also view the stream NFTs on [Etherscan](https://etherscan.io/token/0xB10daee1FCF62243aE27776D7a92D39dC8740f95). See the [Deployments](/guides/lockup/deployments) page for the full list of addresses. ## Flow NFT Unlike Lockup streams, the first release of Flow streams are represented by Sablier Logo. ## Integrations The transferability of the NFT makes Sablier streams tradable and usable as collateral in DeFi. Imagine an NFT lending marketplace that allows users to borrow funds by locking their streams as collateral (effectively borrowing against their future income). Or a decentralized exchange that allows users to trade streams for other tokens. :::note Not all Sablier streams are transferable. The stream creator can choose to make the stream non-transferable. You can find more details on it in the [Transferability](/concepts/transferability) section. ::: ## Marketplaces :::caution Be careful when buying Lockup NFTs that represent cancelable stream. When these streams are canceled, the unstreamed amount is returned to the sender. ::: Thanks to adhering to the ERC-721 standard, Sablier streams can be traded and viewed on any NFT marketplace. Here are some of the marketplaces that support Sablier streams: - [OpenSea](https://opensea.io) - [Blur](https://blur.io) - [Rarible](https://rarible.com) - [SuperRare](https://superrare.com) - [LooksRare](https://looksrare.org) ## Caching The SVG artwork is generated using certain real-time values, such as the current time on the blockchain. However, NFT marketplaces cache the NFT metadata, and this may cause the SVGs might not always be up to date. The Sablier Protocol triggers [ERC-4906](https://eips.ethereum.org/EIPS/eip-4906) events whenever there's an update in a stream (for instance, when a withdrawal is made). However, some streams might remain unchanged for an extended period. To ensure you're viewing the most recent version of the NFT SVG, it's recommended to check the stream directly via the [Sablier Interface](https://app.sablier.com). --- ## Security Source: https://docs.sablier.com/concepts/security # Security Ensuring the security of the Sablier Protocol is our utmost priority. We have dedicated significant efforts towards the design and testing of the protocol to guarantee its safety and reliability. The Sablier contracts have undergone rigorous audits by leading security experts from [Cantina](https://cantina.xyz/), [CodeHawks](https://codehawks.cyfrin.io/), and many independent auditors. For a comprehensive list of all audits conducted, check out [the audit repo](https://github.com/sablier-labs/audits/). ## Comptroller and Utility Contracts Audits All the audits of Comptroller and other utility contracts can be found [here](https://github.com/sablier-labs/audits/blob/main/evm-utils). ## Lockup Audits All the audits of Lockup contracts can be found [here](https://github.com/sablier-labs/audits/blob/main/lockup). ## Merkle Airdrops Audits All the audits of Merkle Airdrops contracts can be found [here](https://github.com/sablier-labs/audits/tree/main/airdrops). ## Flow Audits All the audits of Flow contracts can be found [here](https://github.com/sablier-labs/audits/blob/main/flow). ## Bob Audits All the audits of Bob contracts can be found [here](https://github.com/sablier-labs/audits/blob/main/bob). ## Bug Bounty The details of the now deprecated bug bounty program can be found [here](https://sablier.notion.site/bug-bounty). :::important [This bug bounty program has been deprecated] Effective May 1, 2026, the Sablier bug bounty program is no longer applicable. We thank all researchers who contributed to the security of the protocol. ::: --- ## Streaming Source: https://docs.sablier.com/concepts/streaming # Streaming Token streaming means the ability to make continuous, real-time payments on a per-second basis. This novel approach to making payments is the core concept of Sablier. ## Brief history Andreas Antonopoulos introduced the concept of money streaming in his keynote talk [Bitcoin, Lightning, and Streaming Money](https://www.youtube.com/watch?v=gF_ZQ_eijPs), held at a Bitcoin meetup in 2016. He discussed it within the context of the Lightning Network, but the idea can be applied to any cryptocurrency platform. Inspired by Antonopoulos' presentation, Sablier co-founder Paul Berg published [ERC-1620](https://eips.ethereum.org/EIPS/eip-1620) in November 2018, proposing a standard for streaming payments on the Ethereum blockchain. The standard required users to lock a specified amount of funds in a smart contract, which would then be released to a recipient at a predetermined rate per second. Sablier was born in 2019 when Paul and his co-founder, Gabriel Apostu, decided to build a protocol implementing the ERC-1620 standard. The first iteration of Sablier was successfully deployed on Ethereum Mainnet in December 2019. In 2024, Sablier protocol got renamed to Lockup when Flow was introduced. ## What are the benefits? Conventional lump-sum payments come with inherent challenges such as the need for trust between parties, slow processing times, and susceptibility to errors. Token streaming, or continuous by-the-second payments, addresses these issues and offers additional benefits (see [Use Cases](/concepts/use-cases)). First, streaming involves a significantly smaller degree of trust compared to lump-sum payments, as it eliminates the need for advance payments. For instance, suppose you hire a remote worker to build a website for you, and they ask you for an advance payment. A lump-sum payment is risky because there is no guarantee the worker will deliver the website as promised. By streaming money to the worker instead, your potential loss is limited to the small amount streamed in the short term. If the remote worker disappears, you can simply cancel the stream and reclaim any unstreamed funds. Secondly, money streaming is substantially faster than lump-sum payments for evident reasons. Streaming transactions settle in real-time, with small amounts of tokens being released from the sender to the recipient every second. This automates the payment process and ensures a continuous flow of funds. Lastly, streaming is more secure than lump-sum payments, because it makes it possible to correct errors. Suppose you accidentally started a stream worth 10 ETH to the wrong address. For example, if you accidentally initiated a stream worth 10 ETH to an incorrect address, you can cancel the stream and reclaim the unstreamed Ether (e.g., recovering 9.99 of the 10 ETH). Conversely, recovering a lump-sum payment sent to the wrong address is not possible, unless the recipient is willing to return it. ## Diversity of streams Over time, we have come to realize that there is no one-size-fits-all streaming model that can address the diverse range of use cases. In the upcoming section, we will explore the various token distribution curves supported by Lockup and Flow. But first, let's dive into the use cases. --- ## Transferability Source: https://docs.sablier.com/concepts/transferability # Transferability ## Lockup & Flow Streams As you may know, all Sablier streams [are represented by NFTs](/concepts/nft). The NFT representing a stream is owned by the stream recipient. Whoever owns the stream NFT becomes the stream recipient. When creating the stream, users have the ability to set it as transferable or untransferable. If transferable, recipients have the ability to transfer the NFT (meaning the stream) to another wallet. They have, by extension, also the ability to, for example, sell it on an NFT marketplace, or borrow against it by using it as collateral in an NFT lending protocol. If untransferable, recipients do not have the ability to transfer the NFT (meaning the stream) to another wallet, and by extension, do not have the ability to sell it on an NFT marketplace or borrow against it in an NFT lending protocol. :::info If you are using Sablier for vesting, and do not want your investors and/or employees to be able to exit their position without the vesting being complete, you should set up the streams as untransferable. ::: ## Bob Vault Shares Unlike Lockup and Flow which use ERC-721 NFTs, Bob vaults use fungible ERC-20 [share tokens](/concepts/bob/shares). These shares are always transferable and grant the right to redeem the underlying tokens. --- ## Use Cases Source: https://docs.sablier.com/concepts/use-cases # Use Cases While Lockup and Flow both are general-purpose protocols that can be used for a wide variety of applications, some use cases are more popular than others. In this article, we will cover the primary reasons people are using Sablier Lockup and Sablier Flow. ## Sablier Lockup Sablier Lockup requires fixed dates and fixed amounts. When creating a stream, the total amount of tokens to be distributed needs to be deposited into the stream and you cannot add funds to an existing stream. Lockup streams have a fixed start date and end date, and cannot be extended. These properties make for an excellent user experience for vesting, as the terms are clear, defined and transparent. ### Vesting #### 1\. Efficiency Traditional vesting schemes require a lot of manual input. Payments must be processed manually over an extended period, demanding continuous dedication from the treasury management team. The treasury admin has to initiate numerous transactions each month to compensate contributors and oversee the vesting of employees and investors As a result, traditional vesting proves to be labor-intensive, prone to errors, and ultimately delivers a subpar user experience for everyone involved. Organizations need to devote considerable time to administer funds, while recipients wait months, quarters, or sometimes even longer to obtain their compensation. Sablier solves these problems by automating the entire vesting process. The initial setup involves creating the streams, which only needs to be done once. You simply specify the total duration of the stream (e.g., two years), and that's all - no further actions are required from you. With Sablier, vesting is a "set it and forget it" process. Then, recipients receive their compensation gradually over time: with every second that passes, they receive a fraction of their allocated compensation. This model aligns with the incentives of both parties. The organization only needs to spend time once, to create the streams, while recipients receive the funds gradually over time, allowing them to manage their finances as they wish. #### 2\. Schelling points :::info In game theory, a "focal point" (also called Schelling point) is a solution that people tend to choose by default in the absence of communication. ::: Since traditional vesting contracts have a predictable release schedule, the day on which a vesting period unlocks may be used as a Schelling point for speculation. As a result, some token holders may dump their tokens as soon as they receive them. By contrast, Sablier streams distribute a fraction of the total payment every second to recipients, enabling them to withdraw a portion of funds at any time. This effectively addresses the problem of coordinated dumping. #### 3\. Transparency It's hard to aggregate discrete payments, which is why they typically lack transparency. Just by looking at a transaction on Etherscan, it's difficult to pin down to whom it was made, or for what purpose. This issue is particularly relevant to DAOs, where transparency is critical to enabling contributors to understand how the treasury funds are allocated and for what purposes. With Sablier streams, the issues mentioned above are avoided. Anyone can use the Sablier interface to monitor all streams created by a particular address, as well as all transactions associated with each stream. To illustrate this, here's an example of a stream as viewed on the Sablier Interface. ## Merkle Airdrops This section explores the use cases enabled by Merkle Airdrops. ### Instant airdrops Instant airdrops is the traditional way of running airdrops where there's no vesting period and tokens can instantly be claimed by the recipients. Running an airdrop campaign can become very expensive if you are storing airdrop data on-chain, however. This is where Merkle Airdrops come into the picture. When you run a campaign with Sablier, the airdrop information is stored in a merkle tree, hosted on IPFS. The EVM storage only stores the root of the Merkle tree. At the time of claim, eligible users can provide a merkle proof of their claim which is verified on-chain. This not only reduces the cost of running airdrop campaigns but also inherits the security of the Sablier protocol. ### Streaming airdrops With vested airdrops, also called Airstreams, instead of airdropping the entirety of the token allocation all at once, airdrop recipients receive a fraction of the tokens every second through a Sablier stream. A stream can have any duration you want. You can choose to vest your new token over a period of 6 months, 2 years, or any other duration you prefer. This way, airdrop recipients are forced to think about the project's long-term development and stay engaged with it. 1. The token price may fluctuate over time, which motivates recipients to do whatever they can to increase the price. 2. In cases where a recipient fails to remain involved with the project, the creator of the airdrop has the ability to cancel the recipient's stream. Canceling a stream will not undo any tokens that have already been streamed, but it will prevent the recipient from receiving any more tokens. ### Variable Claim airdrops While both Instant and Streaming airdrops have their own benefits, neither of them rewards user loyalty. To address this, we introduced Variable Claim airdrops. In a Variable Claim airdrop, the claim amount depends on when the user claims. The longer a user waits, the more tokens they receive. This creates an incentive for users to delay their claim until the end of the airdrop campaign, rewarding patience and long-term commitment. When users claim early, the remaining unclaimed tokens are forfeited and returned to the campaign. As the campaign creator, you can clawback these forfeited tokens after the airdrop campaign expires. Here are some examples of what you can do with these reclaimed tokens: - Create a second campaign and airdrop them to users who waited until the end of the airdrop campaign as a loyalty bonus. - Create a staking campaign and use them as staking rewards. - Send them to your treasury and use them as a future fund. - Use them to buyback your tokens from the market. Only linear unlocks are supported in variable claim airdrops. :::tip If you are interested in airdropping your token, check out [Sablier website](https://app.sablier.com/airdrops/). ::: ## Sablier Flow Unlike Lockup, Sablier Flow streams do not require upfront deposits, nor do they have fixed start and end dates. Flow is all about flexibility, making it ideal for use cases like payroll and grants. ### Payroll What if your salary could be streamed to you in real time? Why should you wait for two weeks or a month when you can get it every second? Streaming salaries through Sablier significantly enhances employee satisfaction and retention. Your employer can create a Flow stream and keep funding it at the end of every month or in advance, for you to withdraw your money. The benefits outlined in the "Efficiency" section earlier apply equally to this use case, since traditional payroll solutions, like vesting schemes, can be both labor-intensive and prone to errors. ### Grants Grants are a powerful use case for Sablier, allowing for efficient, transparent, and flexible distribution of funds to grant recipients. :::info Uniswap Governance used Sablier to distribute grant to DeFi Education Fund. You can find more details on it [here](https://x.com/Sablier/status/1798010170133692730). ::: #### 1\. Transparency Sablier enables real-time streaming of funds, ensuring transparency in how grants are distributed. Your stakeholders can monitor the flow of funds, providing assurance that the money is being used as intended. #### 2\. Pay as they deliver Instead of lump-sum payments, you can use Sablier to stream funds continuously over a specified period. This ensures recipients have a steady cash flow and reduces the risk of mismanagement of funds. If a grant recipient stops working on their project, you can cancel the stream and retrieve back the remaining funds. #### 3\. No Administrative Overhead All streams through Sablier are automated, which means, you don't have to send funds manually at the end of every month. --- ## What Is Sablier? Source: https://docs.sablier.com/concepts/what-is-sablier # What Is Sablier? Sablier is a powerful onchain token distribution protocol. Here are some key definitions: - **The Sablier Protocol**: A collection of persistent, non-upgradeable smart contracts to facilitate streaming and distribution of ERC-20 tokens on Ethereum and other EVM blockchains. - **The Sablier Interface**: A web interface that allows for easy interaction with the Sablier Protocol. The interface is only one of many ways to interact with the Sablier Protocol. - **Sablier Labs**: The company that develops the Sablier Protocol, the Sablier Interface, and the documentation website you are reading right now. :::info Fun fact: "sablier" means "hourglass" in French. ::: ## Sablier Protocol A software protocol built with [Ethereum](https://ethereum.org/) smart contracts, designed to facilitate distribution of [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens. The protocol employs a set of persistent and non-upgradable smart contracts that prioritize security, censorship resistance, self-custody, and functionality without the need for trusted intermediaries who may selectively restrict access. Currently, Sablier consists of four separate systems: - **Lockup**: facilitates vesting and vested airdrops - **Merkle Airdrops**: enables on-chain airdrops - **Flow**: for payroll, grants etc. - **Bob**: allows locking tokens until a specific price is reached While most of these are licensed under BUSL-1.1, there are some components licensed under GPL v3. The source code can be accessed on Sablier's [GitHub account](https://github.com/sablier-labs), and a detailed technical reference can be found in the [Technical References](/reference/overview) section of this website. As long as Ethereum and the other EVM chains continue to exist, every version of the Sablier Protocol that gets deployed will operate continuously and without interruption, with a guarantee of 100% uptime. :::info Sablier is the first project to enable token streaming in the Ethereum ecosystem, tracing its roots [back to 2019](https://x.com/Sablier/status/1205533344886411264). ::: ## How does Sablier differ from traditional payment systems? To understand the unique characteristics of Sablier, it is helpful to examine two aspects: the permissionless nature of the protocol compared to traditional payment systems, the concept of streaming as an alternative to conventional payment methods. ### Permissionless systems Sablier is rooted in the essential ideas of open access and immutability, deriving inspiration from Ethereum's foundational principles and the core values of the DeFi[1](#user-content-fn-1) movement. These concepts are crucial in shaping a future where financial services are accessible to everyone, irrespective of their geographical location or economic standing, without prejudice or exposure to counterparty risks. The permissionless design ensures that the protocol's services are open to the public, without any restrictions on who can use them. Users have the liberty to establish new streams with any ERC-20 token, or interact with existing streams as they wish. This feature stands in sharp contrast to conventional financial services that frequently impose restrictions based on factors such as location, financial status, or age. As an immutable system, the Sablier Protocol is non-upgradeable, meaning that no party can pause the contracts, reverse transactions, or alter the users' streams in any way. This ensures the system remains transparent, secure, and resistant to manipulation or abuse. ### Streaming vs conventional payments Traditional payment systems generally involve lump-sum transfers, which rely on trust between parties, have slow processing times, and are prone to errors. In the context of bank transfers, payments are also subject to substantial fees and can face delays due to intermediaries. By contrast, Sablier introduces the concept of token streaming, enabling users to make continuous, real-time payments on a per-second basis. This innovative approach enables seamless, frictionless transactions and promotes increased financial flexibility for users, businesses, and other entities. Sablier makes the passage of time itself the trust-binding mechanism, unlocking business opportunities that were previously unavailable. A good mental model to contrast streaming with conventional payment models is to view the former as "real-time finance" or "continuous finance", and the latter as of "discrete finance". ## Where can I find more information? For more details on the Sablier Protocol, its features, and potential use cases, explore this documentation site and visit the official [Sablier website](https://sablier.com) as well. :::tip If you have any questions along the way, please join the #dev channel in our [Discord server](https://discord.sablier.com). Our team and members of the community are looking forward to help you.‌ ::: ## Release history ### Lockup For more details on the UI alias, see the guide on [URL schemes](/apps/guides/url-schemes). | Version | Release Date | UI Aliases | | --- | --- | --- | | [v4.0](/guides/lockup/deployments) (latest) | March 2026 | - `LK3` (Lockup) | | [v3.0](/guides/lockup/previous-deployments/v3.0) | October 2025 | - `LK2` (Lockup) | | [v2.0](/guides/lockup/previous-deployments/v2.0) | February 2025 | - `LK` (Lockup): all models have been merged into a single contract | | [v1.2](/guides/lockup/previous-deployments/v1.2) | July 2024 | - `LD3` (Lockup Dynamic) - `LL3` (Lockup Linear) - `LT3` (Lockup Tranched) | | [v1.1](/guides/lockup/previous-deployments/v1.1) | December 2023 | - `LD2` (Lockup Dynamic) - `LL2` (Lockup Linear) | | [v1.0](/guides/lockup/previous-deployments/v1.0) | July 2023 | - `LD` (Lockup Dynamic) - `LL` (Lockup Linear) | ### Merkle Airdrops | Version | Release Date | UI Aliases | | --- | --- | --- | | [v3.0](/guides/airdrops/deployments) (latest) | March 2026 | - `MF2_EXEC` (Merkle Factory Execute) - `MF2_INST` (Merkle Factory Instant) - `MF2_LL` (Merkle Factory Linear) - `MF2_LT` (Merkle Factory Tranched) - `MF2_VCA` (Merkle Factory VCA) | | [v2.0](/guides/airdrops/previous-deployments/v2.0) | October 2025 | - `MF_INST` (Merkle Factory Instant) - `MF_LL` (Merkle Factory Linear) - `MF_LT` (Merkle Factory Tranched) - `MF_VCA` (Merkle Factory VCA) | | [v1.3](/guides/airdrops/previous-deployments/v1.3) | February 2025 | - `MSF4` (Merkle Factory): all factories are in a single contract | Before v1.3, Merkle Airdrops contracts were part of the Sablier Lockup [periphery repository](https://github.com/sablier-labs/v2-periphery). ### Flow For more details on the UI alias, see the guide on [URL schemes](/apps/guides/url-schemes). | Version | Release Date | UI Aliases | | --- | --- | --- | | [v3.0](/guides/flow/deployments) (latest) | March 2026 | - `FL4` | | [v2.0](/guides/flow/previous-deployments/v2.0) | October 2025 | - `FL3` | | [v1.1](/guides/flow/previous-deployments/v1.1) | February 2025 | - `FL2` | | [v1.0](/guides/flow/previous-deployments/v1.0) | December 2024 | - `FL` | ### Bob | Version | Release Date | UI Aliases | | --- | --- | --- | | [v1.0](/guides/bob/deployments) (latest) | March 2026 | - `BOB` | ### Legacy The Legacy contracts have been superseded by Lockup. | Version | Release Date | | --- | --- | | [v1.1](/guides/legacy/deployments) | July 2021 | | [v1.0](/guides/legacy/deployments) | November 2019 | ## Footnotes 1. Short for Decentralized Finance: an ecosystem of financial applications and services built on blockchain networks, primarily Ethereum, that leverage smart contracts to enable trustless, permissionless, and transparent financial transactions without relying on traditional intermediaries like banks or financial institutions. [↩](#user-content-fnref-1) --- ## AI Agents Source: https://docs.sablier.com/guides/ai-agents # AI Agents Sablier provides [skills](https://github.com/sablier-labs/sablier-skills) for AI coding agents like Claude Code and Codex. These skills let agents create streams, vesting schedules, and airdrops directly from natural language instructions. ## Installation Install all skills at once: ```bash npx skills add sablier-labs/sablier-skills ``` Or install a specific skill: ```bash npx skills add sablier-labs/sablier-skills --skill sablier-create-vesting ``` ## Available skills | Skill | Protocol | Description | | --- | --- | --- | | `sablier-create-vesting` | Lockup | Create fixed-schedule vesting streams with cliff periods and custom unlocks | | `sablier-create-open-ended-stream` | Flow | Create open-ended payment streams with configurable per-second rates | | `sablier-create-airdrop` | Airdrops | Create Merkle-based airdrop campaigns (instant, linear, or tranched) | | `sablier-protocol` | \-- | Advisory skill explaining Sablier concepts and routing to the right tool | ## Usage examples Once installed, you can ask your AI agent to perform tasks using natural language: - _"Create a 4-year vesting stream with a 12-month cliff on Arbitrum for 0xABCD..."_ - _"Stream 1 USDC per day on Base to 0x1234..."_ - _"Create an instant Merkle airdrop on Ethereum from this CSV"_ - _"Explain the difference between Lockup, Flow, and Airdrops"_ ## LLM-friendly documentation Beyond skills, we make our docs easy for any LLM to consume -- whether you're pasting context into a chat or building automated integrations. ### Plain text markdown Append `.md` to any docs URL to get a plain text version (e.g. [docs.sablier.com/guides/ai-agents.md](https://docs.sablier.com/guides/ai-agents.md)). This format uses fewer tokens, includes content hidden behind tabs, and preserves markdown hierarchy that LLMs parse well. ### llms.txt Our [/llms.txt file](https://docs.sablier.com/llms.txt) lists all pages in plain text format, following the [llmstxt.org](https://llmstxt.org/) standard. ### Protocol-specific files Pre-built markdown files scoped to individual protocols: - **[llms-lockup.txt](https://docs.sablier.com/llms-lockup.txt)** -- Lockup documentation - **[llms-flow.txt](https://docs.sablier.com/llms-flow.txt)** -- Flow documentation - **[llms-airdrops.txt](https://docs.sablier.com/llms-airdrops.txt)** -- Merkle Airdrops documentation - **[llms-full.txt](https://docs.sablier.com/llms-full.txt)** -- Complete documentation bundle --- ## Merkle Airdrops Deployments Source: https://docs.sablier.com/guides/airdrops/deployments # Merkle Airdrops Deployments This section contains the deployment addresses for the v3.0 release of [@sablier/airdrops](https://npmjs.com/package/@sablier/airdrops). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains ## Versions Any updates or additional features will require a new deployment of the protocol, due to its immutable nature. Came here looking for the previous deployments? Click below to see other versions. | Version | Release Date | UI Aliases | | --- | --- | --- | | [v3.0](/guides/airdrops/deployments) (latest) | March 2026 | - `MF2_EXEC` (Merkle Factory Execute) - `MF2_INST` (Merkle Factory Instant) - `MF2_LL` (Merkle Factory Linear) - `MF2_LT` (Merkle Factory Tranched) - `MF2_VCA` (Merkle Factory VCA) | | [v2.0](/guides/airdrops/previous-deployments/v2.0) | October 2025 | - `MF_INST` (Merkle Factory Instant) - `MF_LL` (Merkle Factory Linear) - `MF_LT` (Merkle Factory Tranched) - `MF_VCA` (Merkle Factory VCA) | | [v1.3](/guides/airdrops/previous-deployments/v1.3) | February 2025 | - `MSF4` (Merkle Factory): all factories are in a single contract | This repository is the successor of [Lockup Periphery](https://github.com/sablier-labs/v2-periphery), which has been discontinued. For deployments earlier than v1.3, please refer to the [Lockup v1.2 deployments](/guides/lockup/previous-deployments/v1.2) page. :::info Stay up to date with any new releases by [subscribing](https://x.com/Sablier/status/1821220784661995627) to the official Sablier repositories on Github. ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x75ca3677966737E70649336ee8f9be57AC9f74bA`](https://etherscan.io/address/0x75ca3677966737E70649336ee8f9be57AC9f74bA) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xb2855845067e126207DE2155Ad1c8AD5C495cb3F`](https://etherscan.io/address/0xb2855845067e126207DE2155Ad1c8AD5C495cb3F) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x3210E9b8ed75f9E2Db00ef17167C775e658c2221`](https://etherscan.io/address/0x3210E9b8ed75f9E2Db00ef17167C775e658c2221) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x239BD5431aDa12F09cA95d0a5d4388A5644268e9`](https://etherscan.io/address/0x239BD5431aDa12F09cA95d0a5d4388A5644268e9) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xe60Df8e04cE1616a06db8AD11ce71c05dDcB5D88`](https://etherscan.io/address/0xe60Df8e04cE1616a06db8AD11ce71c05dDcB5D88) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x8D9C6a53893372E95D61decec80AFD696bb36D4D`](https://abscan.org/address/0x8D9C6a53893372E95D61decec80AFD696bb36D4D) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x3a310776ABF7741719d2d61d0937004021575469`](https://abscan.org/address/0x3a310776ABF7741719d2d61d0937004021575469) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xf0340224A37146F59901BD8C5e8632E3519a1E16`](https://abscan.org/address/0xf0340224A37146F59901BD8C5e8632E3519a1E16) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x8B5BC2d3FE5EB515594aCEC7E30E5009dc699Bff`](https://abscan.org/address/0x8B5BC2d3FE5EB515594aCEC7E30E5009dc699Bff) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xEBd2e17C86a6eA828B6FE6e6ce665fF118746Aec`](https://abscan.org/address/0xEBd2e17C86a6eA828B6FE6e6ce665fF118746Aec) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xb697A544775ddA92216E2B59130DA2896bc15549`](https://arbiscan.io/address/0xb697A544775ddA92216E2B59130DA2896bc15549) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xEdA6c634c556897C1A04779E4C6331591801d266`](https://arbiscan.io/address/0xEdA6c634c556897C1A04779E4C6331591801d266) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x5Fc7d1693E050D63F80fdAE3061206fCa32C0825`](https://arbiscan.io/address/0x5Fc7d1693E050D63F80fdAE3061206fCa32C0825) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xfF39df1AA44E1C0010ec6843746c1B2795a7Bac5`](https://arbiscan.io/address/0xfF39df1AA44E1C0010ec6843746c1B2795a7Bac5) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x77a8Ac18d4429Cd1Ff19592EC4c9E4037EA16e98`](https://arbiscan.io/address/0x77a8Ac18d4429Cd1Ff19592EC4c9E4037EA16e98) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x744cf052c8BB4bb4468D602FBf400FE447398bd3`](https://snowscan.xyz/address/0x744cf052c8BB4bb4468D602FBf400FE447398bd3) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x97b3AbE13960B05D527938c7D5b75806827A32Dd`](https://snowscan.xyz/address/0x97b3AbE13960B05D527938c7D5b75806827A32Dd) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x1bd887156fFBC602F891c3e3099b37a6dFfb2f1d`](https://snowscan.xyz/address/0x1bd887156fFBC602F891c3e3099b37a6dFfb2f1d) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x56D3E0A59Cfc9AF1528779f563664eB5e436897e`](https://snowscan.xyz/address/0x56D3E0A59Cfc9AF1528779f563664eB5e436897e) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xC087410c9d0D1D73326534d551Ab9a43497d336B`](https://snowscan.xyz/address/0xC087410c9d0D1D73326534d551Ab9a43497d336B) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xF2b9aa7600f93a40fA42D8639e85BDD634F2a036`](https://basescan.org/address/0xF2b9aa7600f93a40fA42D8639e85BDD634F2a036) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xD5a361519F6c417Cc4bCfcC15501191C816705a6`](https://basescan.org/address/0xD5a361519F6c417Cc4bCfcC15501191C816705a6) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x417b374a774D9Ba2F32Dc1D8c0b1BDd90E3538a6`](https://basescan.org/address/0x417b374a774D9Ba2F32Dc1D8c0b1BDd90E3538a6) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xE88dff96832454a0F075698d501889A59D4145DA`](https://basescan.org/address/0xE88dff96832454a0F075698d501889A59D4145DA) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xe595d4Df663a63B963edA8DAeE77f31D46B75d4b`](https://basescan.org/address/0xe595d4Df663a63B963edA8DAeE77f31D46B75d4b) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xe2ffc214d23D745560542AE6BC7C997Ec28b006F`](https://berascan.com/address/0xe2ffc214d23D745560542AE6BC7C997Ec28b006F) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xf2C3741C4E982Ca4523709Faa2Dff725d3D8f144`](https://berascan.com/address/0xf2C3741C4E982Ca4523709Faa2Dff725d3D8f144) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x8c1E55013DA152DBc5401773fc1c488a22Ee8c93`](https://berascan.com/address/0x8c1E55013DA152DBc5401773fc1c488a22Ee8c93) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x244F45342248f8eBe7Dce39510BE427895F6E1d4`](https://berascan.com/address/0x244F45342248f8eBe7Dce39510BE427895F6E1d4) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xA399a66D9172ae5D81e7e455CB774018e93eb002`](https://berascan.com/address/0xA399a66D9172ae5D81e7e455CB774018e93eb002) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x23Ee0Ff8217d27813d47C98257EBc90F1cb55F61`](https://bscscan.com/address/0x23Ee0Ff8217d27813d47C98257EBc90F1cb55F61) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x34187693b957C055da3fF0Af9DcE46E981d55225`](https://bscscan.com/address/0x34187693b957C055da3fF0Af9DcE46E981d55225) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x17AC0d83FF9b9f8A271Bd6f9C36A469089b45e1C`](https://bscscan.com/address/0x17AC0d83FF9b9f8A271Bd6f9C36A469089b45e1C) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xB295B2F76268CCE9a9d48875158F0d6457Deb39E`](https://bscscan.com/address/0xB295B2F76268CCE9a9d48875158F0d6457Deb39E) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x6f8DF5B18D4FA0D54cb40d386D6f1a84Bd350359`](https://bscscan.com/address/0x6f8DF5B18D4FA0D54cb40d386D6f1a84Bd350359) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x9ce2201f7D731cd37ffc7c4Dde522846c68f5b60`](https://chiliscan.com/address/0x9ce2201f7D731cd37ffc7c4Dde522846c68f5b60) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x9eB917d6709a0C974a6ddeB82724Fef175c49165`](https://chiliscan.com/address/0x9eB917d6709a0C974a6ddeB82724Fef175c49165) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x5F2874d710a2441586C239e8bD8cF0BBb22b19CA`](https://chiliscan.com/address/0x5F2874d710a2441586C239e8bD8cF0BBb22b19CA) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xFB600862fdA55A30239c3741762Cc5A9f5bFDa54`](https://chiliscan.com/address/0xFB600862fdA55A30239c3741762Cc5A9f5bFDa54) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xdAa4E52fa675E8dD6E04437Ba9887462561047bE`](https://chiliscan.com/address/0xdAa4E52fa675E8dD6E04437Ba9887462561047bE) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Denergy ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x075765fE96d9a445C5C792FfA0Ce64A1647f0821`](https://explorer.denergychain.com/address/0x075765fE96d9a445C5C792FfA0Ce64A1647f0821) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xf6843cDBA4bd27c59C008f4e5a9572350Ca2BACA`](https://explorer.denergychain.com/address/0xf6843cDBA4bd27c59C008f4e5a9572350Ca2BACA) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xa089d1179C09B5E1c07B0a03A2D8eb181E20aA43`](https://explorer.denergychain.com/address/0xa089d1179C09B5E1c07B0a03A2D8eb181E20aA43) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x80Ffa31C7D1762748366C5E34D932369deB11B54`](https://explorer.denergychain.com/address/0x80Ffa31C7D1762748366C5E34D932369deB11B54) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x17409C2DDFd247F8adC4e1035eF4CDD2F5C5E700`](https://explorer.denergychain.com/address/0x17409C2DDFd247F8adC4e1035eF4CDD2F5C5E700) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x90D02b8FeB69a127e8137885dFB60f4Cc5e4De0e`](https://gnosisscan.io/address/0x90D02b8FeB69a127e8137885dFB60f4Cc5e4De0e) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xaef7BFC6B9b8B7e0Bfa6EE994F47ed6b03DfdE60`](https://gnosisscan.io/address/0xaef7BFC6B9b8B7e0Bfa6EE994F47ed6b03DfdE60) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x703315dCE1b017199dBb5ae38960c681F9F642dA`](https://gnosisscan.io/address/0x703315dCE1b017199dBb5ae38960c681F9F642dA) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x803cBAB11B9Fd0e252A603523958a81f9b4A582B`](https://gnosisscan.io/address/0x803cBAB11B9Fd0e252A603523958a81f9b4A582B) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x697c180776FF9B9693363B9fC028B0275CE3d202`](https://gnosisscan.io/address/0x697c180776FF9B9693363B9fC028B0275CE3d202) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x4757bAabcDD572a9574C6335570DAaeAA847c033`](https://hyperevmscan.io/address/0x4757bAabcDD572a9574C6335570DAaeAA847c033) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x45C11bbF49eF2C14AD0D5BDfCa80DE55C40523aa`](https://hyperevmscan.io/address/0x45C11bbF49eF2C14AD0D5BDfCa80DE55C40523aa) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x89390675C2F197C2C41654dCdDa8bd4ee5a8A1fd`](https://hyperevmscan.io/address/0x89390675C2F197C2C41654dCdDa8bd4ee5a8A1fd) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x360c237DF86F9e6F1F265EFA560d81fbed63ec4f`](https://hyperevmscan.io/address/0x360c237DF86F9e6F1F265EFA560d81fbed63ec4f) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x5A7B2AA82C658f03726dF4029b7368932B7C1124`](https://hyperevmscan.io/address/0x5A7B2AA82C658f03726dF4029b7368932B7C1124) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xcbcd885258ab33261bF1c626FC0181739Bfaf32E`](https://phoenix.lightlink.io/address/0xcbcd885258ab33261bF1c626FC0181739Bfaf32E) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x2DD8D43045417324d218f937698F9B7905D73E85`](https://phoenix.lightlink.io/address/0x2DD8D43045417324d218f937698F9B7905D73E85) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x2B24f16991D67C8753658664Cd806dbb767DC479`](https://phoenix.lightlink.io/address/0x2B24f16991D67C8753658664Cd806dbb767DC479) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x199822A2d20D125c41d4c0b7bd4cc92904735e9f`](https://phoenix.lightlink.io/address/0x199822A2d20D125c41d4c0b7bd4cc92904735e9f) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x28926d9471DD4E4Fdec929CE7027bcF7235eE603`](https://phoenix.lightlink.io/address/0x28926d9471DD4E4Fdec929CE7027bcF7235eE603) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x1BA93dfacb5D776E42fDeddd7A1174083bA5bf72`](https://lineascan.build/address/0x1BA93dfacb5D776E42fDeddd7A1174083bA5bf72) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xCd8c670C191eF5cE0bC1B5F11D7c0544cBB11Ccb`](https://lineascan.build/address/0xCd8c670C191eF5cE0bC1B5F11D7c0544cBB11Ccb) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x5E28690AE0b74d9dB25AB95594eF2e3BD9b3A677`](https://lineascan.build/address/0x5E28690AE0b74d9dB25AB95594eF2e3BD9b3A677) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xbC94aBe081BFEDE4c4Fbd5c5Abe50Edca238e35f`](https://lineascan.build/address/0xbC94aBe081BFEDE4c4Fbd5c5Abe50Edca238e35f) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x36f2F0db9100d8a0d42f24eA1e5f572932f96D87`](https://lineascan.build/address/0x36f2F0db9100d8a0d42f24eA1e5f572932f96D87) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x454A43a29a9f33F02acCC7F5fFf206d9E74C3892`](https://modescan.io/address/0x454A43a29a9f33F02acCC7F5fFf206d9E74C3892) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x9E70fa6d6EC8b63F31F3229a36c5f996A0674D7D`](https://modescan.io/address/0x9E70fa6d6EC8b63F31F3229a36c5f996A0674D7D) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xFA3Dcb7cA02B5A5F0392Fb7fD56f297969495657`](https://modescan.io/address/0xFA3Dcb7cA02B5A5F0392Fb7fD56f297969495657) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x56a3221660aa342411AAA2a1D292478021E8716c`](https://modescan.io/address/0x56a3221660aa342411AAA2a1D292478021E8716c) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x3cC064358252353068a124013Fa141414a4C0c17`](https://modescan.io/address/0x3cC064358252353068a124013Fa141414a4C0c17) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Monad ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x44a17c7F546ed6c360CcDc69B87B054801b4B6c8`](https://monadscan.com/address/0x44a17c7F546ed6c360CcDc69B87B054801b4B6c8) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xEFaC414CDAB5A042D9443e7916dE02D06d64ecc3`](https://monadscan.com/address/0xEFaC414CDAB5A042D9443e7916dE02D06d64ecc3) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xBE5Ba6bBFB497433522622Dc99a43346eFFdf53c`](https://monadscan.com/address/0xBE5Ba6bBFB497433522622Dc99a43346eFFdf53c) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x169579acb65698A56cf358B55a668Ff880E83078`](https://monadscan.com/address/0x169579acb65698A56cf358B55a668Ff880E83078) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x5238d6cE52E9c69FC89FEB19F249FE515eDd85b0`](https://monadscan.com/address/0x5238d6cE52E9c69FC89FEB19F249FE515eDd85b0) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x63Ca542C5A52c3Ad664083AecA74003E1F719CA8`](https://explorer.morphl2.io/address/0x63Ca542C5A52c3Ad664083AecA74003E1F719CA8) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x752A62f7750ddcA07Fc7D7cb42D0652457C36feF`](https://explorer.morphl2.io/address/0x752A62f7750ddcA07Fc7D7cb42D0652457C36feF) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xc3974B6DFa98AB07Ecf004C42b2bdb4A9617bd39`](https://explorer.morphl2.io/address/0xc3974B6DFa98AB07Ecf004C42b2bdb4A9617bd39) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xE87ebfFB866c71388F53bDc05C945A88d41Fd628`](https://explorer.morphl2.io/address/0xE87ebfFB866c71388F53bDc05C945A88d41Fd628) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xfb180bB0c67554F858A5d0c47b7849103d0F456d`](https://explorer.morphl2.io/address/0xfb180bB0c67554F858A5d0c47b7849103d0F456d) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xBA25926A37Ea36BcE117DDC9524565B02C7A4232`](https://optimistic.etherscan.io/address/0xBA25926A37Ea36BcE117DDC9524565B02C7A4232) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xFAb60A7B98D3c2357E99D5B67beD8cE418CEAc32`](https://optimistic.etherscan.io/address/0xFAb60A7B98D3c2357E99D5B67beD8cE418CEAc32) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x178eCB817585ef37Fa13b91600800053029F1ddd`](https://optimistic.etherscan.io/address/0x178eCB817585ef37Fa13b91600800053029F1ddd) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x978Ff13F638326E8cE8c1f49e22066d437798630`](https://optimistic.etherscan.io/address/0x978Ff13F638326E8cE8c1f49e22066d437798630) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x8a1b5A122cc01d2ADDFFD1359a5B1ce82F5Cad8b`](https://optimistic.etherscan.io/address/0x8a1b5A122cc01d2ADDFFD1359a5B1ce82F5Cad8b) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xc6d9039603320d253526D25Cfa6572bC450D997C`](https://polygonscan.com/address/0xc6d9039603320d253526D25Cfa6572bC450D997C) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x372a591B311d05dD51749F1c5F8074989a6d831c`](https://polygonscan.com/address/0x372a591B311d05dD51749F1c5F8074989a6d831c) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xe6843bB4ED3BdCab6415610bFA82f32e1dd48b31`](https://polygonscan.com/address/0xe6843bB4ED3BdCab6415610bFA82f32e1dd48b31) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x00940E58a0Ca942b5A100820B10758c655bcB78A`](https://polygonscan.com/address/0x00940E58a0Ca942b5A100820B10758c655bcB78A) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x9577143ad5A95391d78ECeB0C76D836bE7075592`](https://polygonscan.com/address/0x9577143ad5A95391d78ECeB0C76D836bE7075592) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xbc77E627b176087c446F15f192D631CB7BEC5381`](https://scrollscan.com/address/0xbc77E627b176087c446F15f192D631CB7BEC5381) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x536C7e834e746F1365DaF381776d70eC855D1bcD`](https://scrollscan.com/address/0x536C7e834e746F1365DaF381776d70eC855D1bcD) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xF11DdEBC9BfbA0B803e2ab201EdfbaE9a8557c03`](https://scrollscan.com/address/0xF11DdEBC9BfbA0B803e2ab201EdfbaE9a8557c03) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x7216A9Dc9462354fD8707c5fdf0D29F2fBA86d97`](https://scrollscan.com/address/0x7216A9Dc9462354fD8707c5fdf0D29F2fBA86d97) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x4bE09E22eC5fa9BE267Ea955b3a54406023c67DD`](https://scrollscan.com/address/0x4bE09E22eC5fa9BE267Ea955b3a54406023c67DD) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x73f0d7C6D52876C22bC83c8Af39B44c98aAB8EBd`](https://sonicscan.org/address/0x73f0d7C6D52876C22bC83c8Af39B44c98aAB8EBd) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x90967cBfBCe9EC637BC17707AeE1849869A79b8e`](https://sonicscan.org/address/0x90967cBfBCe9EC637BC17707AeE1849869A79b8e) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x86B1F07ddF6d2Acf332743E0B72248C898ECB904`](https://sonicscan.org/address/0x86B1F07ddF6d2Acf332743E0B72248C898ECB904) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x3a43C37637C6594b724Fa221C989be66a33eE2e8`](https://sonicscan.org/address/0x3a43C37637C6594b724Fa221C989be66a33eE2e8) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x529b08972710636C7b602C879F23638b8caDd152`](https://sonicscan.org/address/0x529b08972710636C7b602C879F23638b8caDd152) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x82f9A5AB00a95f7D95D6F5204E8640482DE49EF2`](https://explorer.superseed.xyz/address/0x82f9A5AB00a95f7D95D6F5204E8640482DE49EF2) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x982D9a3C53e8c2162E42a2f8BaA4E3171Ed7B873`](https://explorer.superseed.xyz/address/0x982D9a3C53e8c2162E42a2f8BaA4E3171Ed7B873) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x05dEb48116442Bb93C0E411Aa095F45d6f29B92a`](https://explorer.superseed.xyz/address/0x05dEb48116442Bb93C0E411Aa095F45d6f29B92a) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x1e250B33B468a24003A4F32706125667BD953D7b`](https://explorer.superseed.xyz/address/0x1e250B33B468a24003A4F32706125667BD953D7b) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x8ffD310773C8840f4c4698BFA6D9850b44c2A7Fc`](https://explorer.superseed.xyz/address/0x8ffD310773C8840f4c4698BFA6D9850b44c2A7Fc) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xbC7D4Fe222Ee68BD63e8b65C3e9c9B4B61Cf56d2`](https://uniscan.xyz/address/0xbC7D4Fe222Ee68BD63e8b65C3e9c9B4B61Cf56d2) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0xb69510A4505f654412030A7D16ba164C72f653c5`](https://uniscan.xyz/address/0xb69510A4505f654412030A7D16ba164C72f653c5) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x2b76DF77Cd627603B108E4D017d0A22f379b5AEd`](https://uniscan.xyz/address/0x2b76DF77Cd627603B108E4D017d0A22f379b5AEd) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x5f264f649b51742201fe14164C088249DD10D208`](https://uniscan.xyz/address/0x5f264f649b51742201fe14164C088249DD10D208) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x3358471Ee9A7C7ddd115eD7800CA8D6b1dd5A5F8`](https://uniscan.xyz/address/0x3358471Ee9A7C7ddd115eD7800CA8D6b1dd5A5F8) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x1844A37e5D4C7e00B4f91DB41A78aeaF6A5B9CA8`](https://xdcscan.com/address/0x1844A37e5D4C7e00B4f91DB41A78aeaF6A5B9CA8) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x9CFA27760D3aE595e6829AaA11f31b16b04fdf1d`](https://xdcscan.com/address/0x9CFA27760D3aE595e6829AaA11f31b16b04fdf1d) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x65Bc0c39A6F33F7093555c24bb358Dc0ABd6eCa2`](https://xdcscan.com/address/0x65Bc0c39A6F33F7093555c24bb358Dc0ABd6eCa2) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xD5286D2cA959Bd75CFF66CD1aeDb76F2B61EEc29`](https://xdcscan.com/address/0xD5286D2cA959Bd75CFF66CD1aeDb76F2B61EEc29) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x6F5E5f039FA2446C58Ec514c0C18E0C2Cbd7059E`](https://xdcscan.com/address/0x6F5E5f039FA2446C58Ec514c0C18E0C2Cbd7059E) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x886E737c9Ad626f007a570D5866E3F7a9bA785a0`](https://explorer.zksync.io/address/0x886E737c9Ad626f007a570D5866E3F7a9bA785a0) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x5fdbD5a1B55C6a104E6A749d6805D1c05132694d`](https://explorer.zksync.io/address/0x5fdbD5a1B55C6a104E6A749d6805D1c05132694d) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x491f3aa4021abe8744EA94572Fa7f5A7a2810999`](https://explorer.zksync.io/address/0x491f3aa4021abe8744EA94572Fa7f5A7a2810999) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x565F1ec207328C585563B8Db647B66cFF275dF62`](https://explorer.zksync.io/address/0x565F1ec207328C585563B8Db647B66cFF275dF62) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x0B877D6523056B1eaaE38b89633E0eF6235AeE11`](https://explorer.zksync.io/address/0x0B877D6523056B1eaaE38b89633E0eF6235AeE11) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xdE22693D3E51188A71F081E3eA3C9705C667F125`](https://sepolia.arbiscan.io/address/0xdE22693D3E51188A71F081E3eA3C9705C667F125) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x0bbd202049835E9179BBE3c16d65E5b7CE0b6B19`](https://sepolia.arbiscan.io/address/0x0bbd202049835E9179BBE3c16d65E5b7CE0b6B19) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x4fAc09B6cc545646e2840F14d5BcC68C9DB6A5f9`](https://sepolia.arbiscan.io/address/0x4fAc09B6cc545646e2840F14d5BcC68C9DB6A5f9) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x18385F06f053899CaE3C66F68135435eA3607bEb`](https://sepolia.arbiscan.io/address/0x18385F06f053899CaE3C66F68135435eA3607bEb) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0xed41ca6Ee2B0f3C4E5A32d5daB2051C47349add6`](https://sepolia.arbiscan.io/address/0xed41ca6Ee2B0f3C4E5A32d5daB2051C47349add6) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xb15286F3CD1CC3003637fEB187EbB8b9702Ed707`](https://sepolia.basescan.org/address/0xb15286F3CD1CC3003637fEB187EbB8b9702Ed707) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x863b326Ef7Ce2B82d881931bE577Ba04dd4D58BE`](https://sepolia.basescan.org/address/0x863b326Ef7Ce2B82d881931bE577Ba04dd4D58BE) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x7eBF0f960415a477fBb46A280B5DcaA1A6b1D0f9`](https://sepolia.basescan.org/address/0x7eBF0f960415a477fBb46A280B5DcaA1A6b1D0f9) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0xFc7C08dFE26d461b0dE0c0BF1D787D019fCB8861`](https://sepolia.basescan.org/address/0xFc7C08dFE26d461b0dE0c0BF1D787D019fCB8861) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x13CBF2aC8EE2321a2e0F4DDFFDD0A2D7167967a7`](https://sepolia.basescan.org/address/0x13CBF2aC8EE2321a2e0F4DDFFDD0A2D7167967a7) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### BattleChain Testnet ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0x95EC0f86cB1f4FeeeB82924d70C6bba4fF49F2Aa`](https://explorer.testnet.battlechain.com/address/0x95EC0f86cB1f4FeeeB82924d70C6bba4fF49F2Aa) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x4259557F6665eCF5907c9019a30f3Cb009c20Ae7`](https://explorer.testnet.battlechain.com/address/0x4259557F6665eCF5907c9019a30f3Cb009c20Ae7) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8`](https://explorer.testnet.battlechain.com/address/0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x83Dd52FCA44E069020b58155b761A590F12B59d3`](https://explorer.testnet.battlechain.com/address/0x83Dd52FCA44E069020b58155b761A590F12B59d3) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x1DdC1c21CD39c2Fa16366E6036c95342A31831Ba`](https://explorer.testnet.battlechain.com/address/0x1DdC1c21CD39c2Fa16366E6036c95342A31831Ba) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xF330b56C17eC43071058700E65adB2874f661050`](https://optimism-sepolia.blockscout.com/address/0xF330b56C17eC43071058700E65adB2874f661050) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x6b2d32f2844632b6910C32e1FC8eAA2b902525fE`](https://optimism-sepolia.blockscout.com/address/0x6b2d32f2844632b6910C32e1FC8eAA2b902525fE) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0xca6BCdce07A2cc7f2eDAc34CC18CF2cE42F635D7`](https://optimism-sepolia.blockscout.com/address/0xca6BCdce07A2cc7f2eDAc34CC18CF2cE42F635D7) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x0F40cB83423faCd072855ED54368B765C5121693`](https://optimism-sepolia.blockscout.com/address/0x0F40cB83423faCd072855ED54368B765C5121693) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x56f5105cF557FEDb3078740E7d4B30e645e993EA`](https://optimism-sepolia.blockscout.com/address/0x56f5105cF557FEDb3078740E7d4B30e645e993EA) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleExecute | [`0xF1bf5DdE27c4C265AC5B86A26494BcdB9C0B84b8`](https://sepolia.etherscan.io/address/0xF1bf5DdE27c4C265AC5B86A26494BcdB9C0B84b8) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleInstant | [`0x857A37BE6536F5cb7470462e22D47d639F8f5aeD`](https://sepolia.etherscan.io/address/0x857A37BE6536F5cb7470462e22D47d639F8f5aeD) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLL | [`0x3279f698708A07e3DE1Bc89A5e64dC302F4Fd90C`](https://sepolia.etherscan.io/address/0x3279f698708A07e3DE1Bc89A5e64dC302F4Fd90C) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleLT | [`0x93891075D2eFbBa3A2dD359e348820CFc56A40d1`](https://sepolia.etherscan.io/address/0x93891075D2eFbBa3A2dD359e348820CFc56A40d1) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | | SablierFactoryMerkleVCA | [`0x4331C17fD94369E9605971B914bd23614d6A5526`](https://sepolia.etherscan.io/address/0x4331C17fD94369E9605971B914bd23614d6A5526) | [`airdrops-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v3.0) | --- ## Create Airdrop Campaigns Source: https://docs.sablier.com/guides/airdrops/examples/create-campaign # 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](/concepts/airdrops) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Now, import the relevant symbols from `@sablier/lockup` and `@sablier/airdrops`: ```solidity 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 { ISablierFactoryMerkleExecute } from "@sablier/airdrops/src/interfaces/ISablierFactoryMerkleExecute.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 { ISablierMerkleExecute } from "@sablier/airdrops/src/interfaces/ISablierMerkleExecute.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 { MerkleInstant, MerkleLL, MerkleLT, MerkleVCA } from "@sablier/airdrops/src/types/DataTypes.sol"; import { MerkleExecute } from "@sablier/airdrops/src/types/MerkleExecute.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`. ```solidity contract MerkleCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierFactoryMerkleExecute public constant EXECUTE_FACTORY = ISablierFactoryMerkleExecute(0x75ca3677966737E70649336ee8f9be57AC9f74bA); ISablierFactoryMerkleInstant public constant INSTANT_FACTORY = ISablierFactoryMerkleInstant(0xb2855845067e126207DE2155Ad1c8AD5C495cb3F); ISablierFactoryMerkleLL public constant LL_FACTORY = ISablierFactoryMerkleLL(0x3210E9b8ed75f9E2Db00ef17167C775e658c2221); ISablierFactoryMerkleLT public constant LT_FACTORY = ISablierFactoryMerkleLT(0x239BD5431aDa12F09cA95d0a5d4388A5644268e9); ISablierFactoryMerkleVCA public constant VCA_FACTORY = ISablierFactoryMerkleVCA(0xe60Df8e04cE1616a06db8AD11ce71c05dDcB5D88); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ``` 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](/guides/lockup/deployments) page. Factory address can be obtained from the [Merkle Airdrops Deployments](/guides/airdrops/deployments) page. ## Create function There are four create functions available through the factories: - [`createMerkleInstant`](/reference/airdrops/contracts/contract.SablierFactoryMerkleInstant#createmerkleinstant): creates campaign for instant distribution of tokens. - [`createMerkleLL`](/reference/airdrops/contracts/contract.SablierFactoryMerkleLL#createmerklell): creates campaign with a Lockup Linear distribution. - [`createMerkleLT`](/reference/airdrops/contracts/contract.SablierFactoryMerkleLT#createmerklelt): creates campaign with a Lockup Tranched distribution. - [`createMerkleVCA`](/reference/airdrops/contracts/contract.SablierFactoryMerkleVCA#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`. :::note 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. ```solidity function createMerkleLL() public returns (ISablierMerkleLL merkleLL) { // Declare the constructor parameters of MerkleLL. MerkleLL.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ## Parameters Merkle Factory uses [MerkleLL.ConstructorParams](/reference/airdrops/contracts/types/library.MerkleLL#constructorparams) as the struct for the `createMerkleLL` function. ```solidity MerkleLL.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` 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. ```solidity params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Campaign Start Time The unix timestamp indicating when the campaign starts. Users will be able to claim their airdrop after this time. ```solidity params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### 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](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleBase#clawback) any unclaimed tokens from the campaign. ```solidity params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### 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. ```solidity params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### 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. ```solidity params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### 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](/api/airdrops/merkle-api/overview). ```solidity params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Campaign Name The name of the campaign. ```solidity params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Shape A custom stream shape name for visualization in the UI. This helps users understand the vesting schedule visually. ```solidity params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Lockup The address of the Sablier Lockup contract that will be used to create the streams when recipients claim their airdrop. ```solidity params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Vesting Start Time The unix timestamp indicating when the vesting starts. If set to 0, the vesting will begin at the time of claim. ```solidity params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Cliff Duration The duration of the cliff period in seconds. During this period, no tokens will be unlocked. ```solidity params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Cliff Unlock Percentage The percentage of tokens to unlock after the cliff period ends. We want to unlock 0.01% after the cliff period. ```solidity params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Start Unlock Percentage The percentage of tokens to unlock at the vesting start time. We want to unlock 0.01% at the start. ```solidity params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Total Duration The total duration of vesting in seconds. This should be 90 days for this example. ```solidity params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Cancelable Boolean that indicates whether the stream will be cancelable or not after it has been claimed. ```solidity params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Transferable Boolean that indicates whether the stream will be transferable or not. This is the stream that users obtain when they claim their airstream. ```solidity params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` 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 $100m\times 10^{18}$. ```solidity uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ### Recipient Count The total number of recipient addresses. ```solidity uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` ## 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: ```solidity merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: 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](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/airdrops/MerkleCreator.sol). ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; 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 { ISablierFactoryMerkleExecute } from "@sablier/airdrops/src/interfaces/ISablierFactoryMerkleExecute.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 { ISablierMerkleExecute } from "@sablier/airdrops/src/interfaces/ISablierMerkleExecute.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 { MerkleInstant, MerkleLL, MerkleLT, MerkleVCA } from "@sablier/airdrops/src/types/DataTypes.sol"; import { MerkleExecute } from "@sablier/airdrops/src/types/MerkleExecute.sol"; /// @notice Example of how to create Merkle airdrop campaigns. /// @dev This code is referenced in the docs: https://docs.sablier.com/guides/airdrops/examples/create-campaign contract MerkleCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierFactoryMerkleExecute public constant EXECUTE_FACTORY = ISablierFactoryMerkleExecute(0x75ca3677966737E70649336ee8f9be57AC9f74bA); ISablierFactoryMerkleInstant public constant INSTANT_FACTORY = ISablierFactoryMerkleInstant(0xb2855845067e126207DE2155Ad1c8AD5C495cb3F); ISablierFactoryMerkleLL public constant LL_FACTORY = ISablierFactoryMerkleLL(0x3210E9b8ed75f9E2Db00ef17167C775e658c2221); ISablierFactoryMerkleLT public constant LT_FACTORY = ISablierFactoryMerkleLT(0x239BD5431aDa12F09cA95d0a5d4388A5644268e9); ISablierFactoryMerkleVCA public constant VCA_FACTORY = ISablierFactoryMerkleVCA(0xe60Df8e04cE1616a06db8AD11ce71c05dDcB5D88); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); function createMerkleExecute() public returns (ISablierMerkleExecute merkleExecute) { // Declare the constructor parameters of MerkleExecute. MerkleExecute.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.target = address(DAI); // Target contract to call on claim params.selector = bytes4(0x12345678); // Function selector to call on the target // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleExecute campaign contract. Recipients claim tokens and immediately execute a function on the // target contract (e.g., staking, lending). merkleExecute = EXECUTE_FACTORY.createMerkleExecute(params, aggregateAmount, recipientCount); } function createMerkleInstant() public virtual returns (ISablierMerkleInstant merkleInstant) { // Declare the constructor parameters of MerkleInstant. MerkleInstant.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleInstant campaign contract. The deployed contract will be completely owned by the campaign // admin. Recipients will interact with the deployed contract to claim their airdrop. merkleInstant = INSTANT_FACTORY.createMerkleInstant(params, aggregateAmount, recipientCount); } function createMerkleLL() public returns (ISablierMerkleLL merkleLL) { // Declare the constructor parameters of MerkleLL. MerkleLL.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cliffDuration = 30 days; params.cliffUnlockPercentage = ud60x18(0.01e18); params.granularity = 1 seconds; // Granularity for the linear stream params.startUnlockPercentage = ud60x18(0.01e18); params.totalDuration = 90 days; params.cancelable = false; params.transferable = true; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLL campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLL = LL_FACTORY.createMerkleLL({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleLT() public returns (ISablierMerkleLT merkleLT) { // Prepare the constructor parameters. MerkleLT.ConstructorParams memory params; // Set the parameters. params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.expiration = uint40(block.timestamp + 12 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.shape = "A custom stream shape"; // Stream shape name for visualization in the UI params.lockup = LOCKUP; params.vestingStartTime = uint40(block.timestamp); params.cancelable = false; params.transferable = true; // The tranches with their unlock percentages and durations. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = new MerkleLT.TrancheWithPercentage[](2); tranchesWithPercentages[0] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 30 days }); tranchesWithPercentages[1] = MerkleLT.TrancheWithPercentage({ unlockPercentage: ud2x18(0.5e18), duration: 60 days }); params.tranchesWithPercentages = tranchesWithPercentages; // The total amount of tokens you want to airdrop to your users. uint256 aggregateAmount = 100_000_000e18; // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleLT campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleLT = LT_FACTORY.createMerkleLT({ campaignParams: params, aggregateAmount: aggregateAmount, recipientCount: recipientCount }); } function createMerkleVCA() public returns (ISablierMerkleVCA merkleVCA) { // Prepare the constructor parameters. MerkleVCA.ConstructorParams memory params; // Set the parameters. params.aggregateAmount = 100_000_000e18; // The total amount of tokens to airdrop params.token = DAI; params.campaignStartTime = uint40(block.timestamp); params.enableRedistribution = false; params.expiration = uint40(block.timestamp + 14 weeks); // The expiration of the campaign params.initialAdmin = address(0xBeeF); // Admin of the merkle lockup contract params.ipfsCID = "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX"; // IPFS hash of the campaign metadata params.merkleRoot = 0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123; params.campaignName = "My First Campaign"; // Unique campaign name params.unlockPercentage = ud60x18(0.25e18); // 25% unlocked immediately params.vestingStartTime = uint40(block.timestamp); params.vestingEndTime = uint40(block.timestamp + 90 days); // 90 days vesting period // The total number of addresses you want to airdrop your tokens to. uint256 recipientCount = 10_000; // Deploy the MerkleVCA campaign contract. The deployed contract will be completely owned by the campaign admin. // Recipients will interact with the deployed contract to claim their airdrop. merkleVCA = VCA_FACTORY.createMerkleVCA({ campaignParams: params, recipientCount: recipientCount }); } } ``` --- ## Configure Your Local Environment Source: https://docs.sablier.com/guides/airdrops/examples/local-environment # Configure Your Local Environment In this guide, we will go through the steps to set up a local development environment for building onchain integrations with Airdrops. We will use Foundry to install Airdrops as a dependency. At the end, you’ll have a development environment set up that you can use to build the rest of the examples under "Guides", or start your own integration project. ## Pre-requisites You will need the following software on your machine: - [Git](https://git-scm.com/downloads) - [Foundry](https://github.com/foundry-rs/foundry) - [Node.js](https://nodejs.org/en/download) - [Bun](https://bun.sh) In addition, familiarity with [Ethereum](https://ethereum.org/) and [Solidity](https://soliditylang.org/) is requisite. ## Set up using Foundry template :::tip Make sure you are using the latest version of Foundry by running `foundryup`. ::: Foundry is a popular development toolkit for Ethereum projects, which we have used to build the Airdrops Protocol. For the purposes of this guide, Foundry will provide us with the tooling needed to compile and test our contracts. Let's use this command to spin up a new Foundry project: ```bash $ forge init my-project $ cd my-project ``` Once the initialization completes, take a look around at what got set up: ```bash ├── foundry.toml ├── script ├── src └── test ``` The folder structure should be intuitive: - `src` is where you'll write Solidity contracts - `test` is where you'll write tests (also in Solidity) - `script` is where you'll write scripts to perform actions like deploying contracts (you guessed it, in Solidity) - `foundry.toml` is where you can configure your Foundry settings, which we will leave as is in this guide :::note You might notice that the CLI is `forge` rather than `foundry`. This is because Foundry is a toolkit, and `forge` is just one of the tools that comes with it. ::: ## Install via npm package Let's install the Airdrops Node.js packages using Bun: ```bash $ bun add @sablier/airdrops ``` Bun will download the Airdrops contracts, along with their dependencies, and put them in the `node_modules` directory. That's it! You should now have a functional development environment to start building onchain Airdrops integrations. ## Next steps Congratulations! Your environment is now configured, and you are prepared to start building. Explore the guides section to discover various features available for Airdrops integration. Remember to include all contracts (`.sol` files) in the `src` folder and their corresponding tests in the `test` folder. As far as Foundry is concerned, there is much more to uncover. If you want to learn more about it, check out the [Foundry Book](https://book.getfoundry.sh/), which contains numerous examples and tutorials. A deep understanding of Foundry will enable you to create more sophisticated integrations with Airdrops protocol. --- ## Sablier Merkle Airdrops Source: https://docs.sablier.com/guides/airdrops/overview # Sablier Merkle Airdrops Welcome to the Sablier Merkle Airdrops documentation. This section contains detailed guides and technical references for the Merkle Airdrops contracts. These documents offer insight into the operational nuances of the contracts, providing a valuable resource for building onchain integrations. # Guides If you are new to Merkle Airdrops, we recommend you start with the [Airstreams](/concepts/airdrops) section first. If you want to setup your local environment, head over to [the guide](/guides/airdrops/examples/local-environment). # Reference For a deeper dive into the protocol specifications, read through the [technical reference](/reference/airdrops/diagrams). # Resources - [Source Code](https://github.com/sablier-labs/evm-monorepo/blob/main/airdrops) - [Examples](https://github.com/sablier-labs/evm-monorepo/tree/main/misc/examples/airdrops/) - [Foundry Book](https://book.getfoundry.sh/) --- ## Merkle Airdrops v1.3 Source: https://docs.sablier.com/guides/airdrops/previous-deployments/v1.3 # Merkle Airdrops v1.3 This section contains the deployment addresses for the v1.3 release of [@sablier/airdrops@1.3.0](https://npmjs.com/package/@sablier/airdrops/v/1.3.0). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Merkle Airdrops protocol. See the latest version [here](/guides/airdrops/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x71DD3Ca88E7564416E5C2E350090C12Bf8F6144a`](https://etherscan.io/address/0x71DD3Ca88E7564416E5C2E350090C12Bf8F6144a) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x0C72b957347B51285854f015e4D20641655B939A`](https://abscan.org/address/0x0C72b957347B51285854f015e4D20641655B939A) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x7efd170e3e32Dc1b4c17eb4cFFf92c81FF43a6cb`](https://arbiscan.io/address/0x7efd170e3e32Dc1b4c17eb4cFFf92c81FF43a6cb) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x6bCD2260825CFed440Bb765f7A92f6CDBDc90f43`](https://snowscan.xyz/address/0x6bCD2260825CFed440Bb765f7A92f6CDBDc90f43) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xD9e108f26fe104CE1058D48070438deDB3aD826A`](https://basescan.org/address/0xD9e108f26fe104CE1058D48070438deDB3aD826A) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x7868Af143cc5e6Cd03f9B4f5cdD2832695A85d6B`](https://berascan.com/address/0x7868Af143cc5e6Cd03f9B4f5cdD2832695A85d6B) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xDd40b4F5B216F524a55E2e8F75637E8b453E4bd2`](https://blastscan.io/address/0xDd40b4F5B216F524a55E2e8F75637E8b453E4bd2) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xf9f89d99fb702b06fba16a294b7614089defe068`](https://bscscan.com/address/0xf9f89d99fb702b06fba16a294b7614089defe068) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xf978034bb3CAB5fe88d23DB5Cb38D510485DaB90`](https://chiliscan.com/address/0xf978034bb3CAB5fe88d23DB5Cb38D510485DaB90) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x64ba580946985B4b87f4D9f7b6598C2156026775`](https://gnosisscan.io/address/0x64ba580946985B4b87f4D9f7b6598C2156026775) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xe0548364372f3b842e6f89e2DAC2E53b5eA0a35b`](https://hyperevmscan.io/address/0xe0548364372f3b842e6f89e2DAC2E53b5eA0a35b) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xC0107f368FBB50075d2190549055d9E6bf75c5c9`](https://phoenix.lightlink.io/address/0xC0107f368FBB50075d2190549055d9E6bf75c5c9) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xAa122611E0e3a0771127aA4cd4995A896BB2c20B`](https://lineascan.build/address/0xAa122611E0e3a0771127aA4cd4995A896BB2c20B) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xc472391DB89e7BE07170f18c4fdb010242507F2C`](https://modescan.io/address/0xc472391DB89e7BE07170f18c4fdb010242507F2C) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xBE64e8718D82C598EBCDA5149D10eB68b79632a4`](https://explorer.morphl2.io/address/0xBE64e8718D82C598EBCDA5149D10eB68b79632a4) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x2455bff7a71E6e441b2d0B1b1e480fe36EbF6D1E`](https://optimistic.etherscan.io/address/0x2455bff7a71E6e441b2d0B1b1e480fe36EbF6D1E) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xf0d61b42311C810dfdE191D58427d81E87c5d5F6`](https://polygonscan.com/address/0xf0d61b42311C810dfdE191D58427d81E87c5d5F6) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x6dF0bfFDb106b19d1e954853f4d14003E21B7854`](https://scrollscan.com/address/0x6dF0bfFDb106b19d1e954853f4d14003E21B7854) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Sei Network ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x0171A06878F7ff81c9955DEB5641f64f520d45E5`](https://seiscan.io/address/0x0171A06878F7ff81c9955DEB5641f64f520d45E5) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xbD73389Cbdd4f31F374F2815ecb7f9dEc0F124D3`](https://sonicscan.org/address/0xbD73389Cbdd4f31F374F2815ecb7f9dEc0F124D3) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Sophon ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x9D4923e2ff0b9DAdc447A89f528760928f84D0F7`](https://sophscan.xyz/address/0x9D4923e2ff0b9DAdc447A89f528760928f84D0F7) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x3df48bb93509D9a041C81F6670C37B1eEb3E154B`](https://explorer.superseed.xyz/address/0x3df48bb93509D9a041C81F6670C37B1eEb3E154B) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Taiko ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x39D4D8C60D3596B75bc09863605BBB4dcE8243F1`](https://taikoscan.io/address/0x39D4D8C60D3596B75bc09863605BBB4dcE8243F1) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Tangle ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xd641a0E4509Cced67cC24E7BDcDe2a31b7F7cF77`](https://explorer.tangle.tools/address/0xd641a0E4509Cced67cC24E7BDcDe2a31b7F7cF77) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xC6fC028E988D158C52Aa2e38CDd6f969AA14bdCd`](https://uniscan.xyz/address/0xC6fC028E988D158C52Aa2e38CDd6f969AA14bdCd) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xe41909f5623c3b78219D9a2Bb92bE95AEe5bbC30`](https://xdcscan.com/address/0xe41909f5623c3b78219D9a2Bb92bE95AEe5bbC30) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x8E7E78799F8cC87d4816112A758281dabc158452`](https://explorer.zksync.io/address/0x8E7E78799F8cC87d4816112A758281dabc158452) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x465E9218C1A8d36169e0c40C01b856A83CE44153`](https://sepolia.arbiscan.io/address/0x465E9218C1A8d36169e0c40C01b856A83CE44153) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x6a3466398A66c7Ce801989B45C390cdC8717102D`](https://sepolia.basescan.org/address/0x6a3466398A66c7Ce801989B45C390cdC8717102D) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Linea Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x5ADE5DF4FB42e353223DFF677cbfec812c6C4Da7`](https://sepolia.lineascan.build/address/0x5ADE5DF4FB42e353223DFF677cbfec812c6C4Da7) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0x2934A7aDDC3000D1625eD1E8D21C070a89073702`](https://optimism-sepolia.blockscout.com/address/0x2934A7aDDC3000D1625eD1E8D21C070a89073702) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xf642751d1271c88bBb8786067de808B32a016Fd4`](https://sepolia.etherscan.io/address/0xf642751d1271c88bBb8786067de808B32a016Fd4) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | ### Superseed Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierMerkleFactory | [`0xb5951501D416cb7326e5b9bEB6EF8840a8DF6910`](https://sepolia-explorer.superseed.xyz/address/0xb5951501D416cb7326e5b9bEB6EF8840a8DF6910) | [`airdrops-v1.3`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v1.3) | --- ## Merkle Airdrops v2.0 Source: https://docs.sablier.com/guides/airdrops/previous-deployments/v2.0 # Merkle Airdrops v2.0 This section contains the deployment addresses for the v2.0 release of [@sablier/airdrops@2.0.1](https://npmjs.com/package/@sablier/airdrops/v/2.0.1). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Merkle Airdrops protocol. See the latest version [here](/guides/airdrops/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x7f70ba7c7373baa4047ce450168cd24968321bda`](https://etherscan.io/address/0x7f70ba7c7373baa4047ce450168cd24968321bda) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x0781ad660a5ed0041b45d44d45009a163cc0b578`](https://etherscan.io/address/0x0781ad660a5ed0041b45d44d45009a163cc0b578) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x336d464276e2c7c76927d975ef866df8a7ecf8dd`](https://etherscan.io/address/0x336d464276e2c7c76927d975ef866df8a7ecf8dd) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x91fdbd7077d615f951a0defa81ec30bfd68dbd8d`](https://etherscan.io/address/0x91fdbd7077d615f951a0defa81ec30bfd68dbd8d) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x15C9d45FB210B11cF5fD16C35ed3C31D7ca475Cb`](https://abscan.org/address/0x15C9d45FB210B11cF5fD16C35ed3C31D7ca475Cb) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x9cD6c1Df185CFBa590AFcEEBe9fCFE8d273c9187`](https://abscan.org/address/0x9cD6c1Df185CFBa590AFcEEBe9fCFE8d273c9187) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x5f3A9E8e6979e9edC5738983D909617d4f2db882`](https://abscan.org/address/0x5f3A9E8e6979e9edC5738983D909617d4f2db882) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xAD49814f588085f5DE4fE5a210cA82054D0Ba693`](https://abscan.org/address/0xAD49814f588085f5DE4fE5a210cA82054D0Ba693) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x4273c32a668ee9d8b24c4d774635134c72974077`](https://arbiscan.io/address/0x4273c32a668ee9d8b24c4d774635134c72974077) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xf8224aa806510041aeab338d1667b152ed549983`](https://arbiscan.io/address/0xf8224aa806510041aeab338d1667b152ed549983) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x011c08d385c3df8d5da4edf804b4cf8b9f4a0187`](https://arbiscan.io/address/0x011c08d385c3df8d5da4edf804b4cf8b9f4a0187) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x199de153773ac81494978d7b313e7402d2fa2425`](https://arbiscan.io/address/0x199de153773ac81494978d7b313e7402d2fa2425) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x25df49158cd5d9fc9def1dae57cf891310d6bfa0`](https://snowscan.xyz/address/0x25df49158cd5d9fc9def1dae57cf891310d6bfa0) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x21617a5835b77fa06669402d1b60b26179c5dfc3`](https://snowscan.xyz/address/0x21617a5835b77fa06669402d1b60b26179c5dfc3) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x3d4da59b6ac3ad42840ecc3ae1988d3045f6f178`](https://snowscan.xyz/address/0x3d4da59b6ac3ad42840ecc3ae1988d3045f6f178) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x4489610db7937acdb975528f692011d324195763`](https://snowscan.xyz/address/0x4489610db7937acdb975528f692011d324195763) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x4b2aa7e2a47b5cfd67a4943de2952d0f4ca683cd`](https://basescan.org/address/0x4b2aa7e2a47b5cfd67a4943de2952d0f4ca683cd) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xc89936e84375b80d3ec435020b3ee07c84dc49e1`](https://basescan.org/address/0xc89936e84375b80d3ec435020b3ee07c84dc49e1) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0xd97e6e03693c817e583a5046bc22d457ec503d46`](https://basescan.org/address/0xd97e6e03693c817e583a5046bc22d457ec503d46) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xb1f20bbe05463e9a5de67d9ff8a0107aa3c9e143`](https://basescan.org/address/0xb1f20bbe05463e9a5de67d9ff8a0107aa3c9e143) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xc4ca662dce3a32ee3b592c0af35013d5ff8d6738`](https://berascan.com/address/0xc4ca662dce3a32ee3b592c0af35013d5ff8d6738) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xa7ffc4c7b7a333519a5b1f16617316e3545f535d`](https://berascan.com/address/0xa7ffc4c7b7a333519a5b1f16617316e3545f535d) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x6b108108b7a2fdf95743d7959e9bbc665aea24ad`](https://berascan.com/address/0x6b108108b7a2fdf95743d7959e9bbc665aea24ad) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x1ed18ffff2e36d0198053c1b9ceb8ec1806b0e0b`](https://berascan.com/address/0x1ed18ffff2e36d0198053c1b9ceb8ec1806b0e0b) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xc880032820f62d3fbc48bc56890ee774c3741b69`](https://blastscan.io/address/0xc880032820f62d3fbc48bc56890ee774c3741b69) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x51ab2f957e353b96ac631da75941cd18ce57900a`](https://blastscan.io/address/0x51ab2f957e353b96ac631da75941cd18ce57900a) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x8cd6cd223305ddb28432ce0f6bb129561a7e46af`](https://blastscan.io/address/0x8cd6cd223305ddb28432ce0f6bb129561a7e46af) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xa6e02ee60c7b7ee3e1806914f99f53551b3c9789`](https://blastscan.io/address/0xa6e02ee60c7b7ee3e1806914f99f53551b3c9789) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xd3195b706056349e8b371150220bf95cc6fb098b`](https://bscscan.com/address/0xd3195b706056349e8b371150220bf95cc6fb098b) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xe58e61ec19938c02055ef386873901bef08f23ff`](https://bscscan.com/address/0xe58e61ec19938c02055ef386873901bef08f23ff) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x61b571ad105240bca4dc3a757e9b62ef2390179f`](https://bscscan.com/address/0x61b571ad105240bca4dc3a757e9b62ef2390179f) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xfa1bf34fcacf54c5d11733dd487b3484dff07abc`](https://bscscan.com/address/0xfa1bf34fcacf54c5d11733dd487b3484dff07abc) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x197d5bad8e769ddba86ed4adcc2f467b82364874`](https://chiliscan.com/address/0x197d5bad8e769ddba86ed4adcc2f467b82364874) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x11aa6b13a7a96b38a067aed21e2a01a9e99604ca`](https://chiliscan.com/address/0x11aa6b13a7a96b38a067aed21e2a01a9e99604ca) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x8ccf1c388c3f679623effe54422ca09c2687220d`](https://chiliscan.com/address/0x8ccf1c388c3f679623effe54422ca09c2687220d) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x166D9de7685c3a78fecBF1fb2e20D32DB6c206aB`](https://chiliscan.com/address/0x166D9de7685c3a78fecBF1fb2e20D32DB6c206aB) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Core Dao ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xcd7606fddbf113f7d35081950a65abd6ad2f7d84`](https://scan.coredao.org/address/0xcd7606fddbf113f7d35081950a65abd6ad2f7d84) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x8716b9de62dbc08e5cb56b912dbfd6fd0c9d45a8`](https://scan.coredao.org/address/0x8716b9de62dbc08e5cb56b912dbfd6fd0c9d45a8) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0xc31c3f1735155451a36d88e0aaeaa3d7c1065a32`](https://scan.coredao.org/address/0xc31c3f1735155451a36d88e0aaeaa3d7c1065a32) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xa06b797ffa2652f7411e256d3e9d3fc44874db47`](https://scan.coredao.org/address/0xa06b797ffa2652f7411e256d3e9d3fc44874db47) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Denergy ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x63b92f7e2f69877184c955e63b9d8dff55e52e14`](https://explorer.denergychain.com/address/0x63b92f7e2f69877184c955e63b9d8dff55e52e14) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x28d116d7e917756310986c4207ea54183fcba06a`](https://explorer.denergychain.com/address/0x28d116d7e917756310986c4207ea54183fcba06a) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x5e73bb96493c10919204045fcdb639d35ad859f8`](https://explorer.denergychain.com/address/0x5e73bb96493c10919204045fcdb639d35ad859f8) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x6d64fc0bb0291c6a4f416ec1c379815c06967eaf`](https://explorer.denergychain.com/address/0x6d64fc0bb0291c6a4f416ec1c379815c06967eaf) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xcd307b7ceb5a73c6d3c247d9aafc61851a6bedaf`](https://gnosisscan.io/address/0xcd307b7ceb5a73c6d3c247d9aafc61851a6bedaf) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xfd95edeac8979492813de5d2a968cded164cd2ee`](https://gnosisscan.io/address/0xfd95edeac8979492813de5d2a968cded164cd2ee) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x70a4c689894d834b48f1aa85b8b847be7143b581`](https://gnosisscan.io/address/0x70a4c689894d834b48f1aa85b8b847be7143b581) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x96dd74883b920fd098893e4254d9fe119f35a662`](https://gnosisscan.io/address/0x96dd74883b920fd098893e4254d9fe119f35a662) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x17ccf6a73047eb6e477feb1d3bdac727425392a6`](https://hyperevmscan.io/address/0x17ccf6a73047eb6e477feb1d3bdac727425392a6) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xfbe39a52d26025d0fdde694b8cd16d0585022f8e`](https://hyperevmscan.io/address/0xfbe39a52d26025d0fdde694b8cd16d0585022f8e) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x936f86ba4ea97ebcb0659eac0885afc44b7baef1`](https://hyperevmscan.io/address/0x936f86ba4ea97ebcb0659eac0885afc44b7baef1) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xb229842fcf90dcb5d9832fd9401ee191e6ad355a`](https://hyperevmscan.io/address/0xb229842fcf90dcb5d9832fd9401ee191e6ad355a) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xb677dBC0d05b2d51C9A4c333d5d41e46ea3C9787`](https://phoenix.lightlink.io/address/0xb677dBC0d05b2d51C9A4c333d5d41e46ea3C9787) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xbE6A1337228AD28ccfAb97b2Ce52fCb7DEa48f5F`](https://phoenix.lightlink.io/address/0xbE6A1337228AD28ccfAb97b2Ce52fCb7DEa48f5F) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x52b40612E91A21CE6a55314520a9A4174Aa512D3`](https://phoenix.lightlink.io/address/0x52b40612E91A21CE6a55314520a9A4174Aa512D3) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x6437ECad55D68BB4B3b53a9A330D6cd7Fa8e687c`](https://phoenix.lightlink.io/address/0x6437ECad55D68BB4B3b53a9A330D6cd7Fa8e687c) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xbc378d190647154e8b193505a5e575e28c132fe6`](https://lineascan.build/address/0xbc378d190647154e8b193505a5e575e28c132fe6) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x224f6e6a1b71012428e93b666472b9052d7fda63`](https://lineascan.build/address/0x224f6e6a1b71012428e93b666472b9052d7fda63) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x5229beabd7d669e1142f989e78c09a7d716cb927`](https://lineascan.build/address/0x5229beabd7d669e1142f989e78c09a7d716cb927) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xa911c0f815d2d80fa6b4cdb81ebd252e308b80f8`](https://lineascan.build/address/0xa911c0f815d2d80fa6b4cdb81ebd252e308b80f8) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xc36ffed30c17f7b044975baee75ed41090df9d20`](https://modescan.io/address/0xc36ffed30c17f7b044975baee75ed41090df9d20) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xe7d5af3d04d55f1b1b7d71fcd8283f07c3553f61`](https://modescan.io/address/0xe7d5af3d04d55f1b1b7d71fcd8283f07c3553f61) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x90f20c6a71775cb4e5a4f59ad0086e28b42b8c39`](https://modescan.io/address/0x90f20c6a71775cb4e5a4f59ad0086e28b42b8c39) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x0787a601b523138513368fa725209773c69d41d5`](https://modescan.io/address/0x0787a601b523138513368fa725209773c69d41d5) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Monad ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xaB15e653cD3bBCe7B7412f81056a450BC0f2e7B9`](https://monadscan.com/address/0xaB15e653cD3bBCe7B7412f81056a450BC0f2e7B9) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x7DcAB43465c1EbDA92133c92262a6c55394dD69e`](https://monadscan.com/address/0x7DcAB43465c1EbDA92133c92262a6c55394dD69e) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0xfA2Bf3EDdEfE67631BfFA5C53b621A9C6BEbc9C3`](https://monadscan.com/address/0xfA2Bf3EDdEfE67631BfFA5C53b621A9C6BEbc9C3) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xCdCc46A7759dE01271E533BBC3b0F32899545a76`](https://monadscan.com/address/0xCdCc46A7759dE01271E533BBC3b0F32899545a76) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x39CBF38BDa1A629fcb17949cD7F8b2de2Bf6686b`](https://explorer.morphl2.io/address/0x39CBF38BDa1A629fcb17949cD7F8b2de2Bf6686b) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xbae3528d0f1963e0ca55f8816cc7174ea3da016f`](https://explorer.morphl2.io/address/0xbae3528d0f1963e0ca55f8816cc7174ea3da016f) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x36e6ac1d0bef5414855c62513be9723173526094`](https://explorer.morphl2.io/address/0x36e6ac1d0bef5414855c62513be9723173526094) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xf93b0dfa6770b6a989203395dd20560d68b09d3d`](https://explorer.morphl2.io/address/0xf93b0dfa6770b6a989203395dd20560d68b09d3d) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x2e0d590a0e48da5078c2a51d9638c6260a99915e`](https://optimistic.etherscan.io/address/0x2e0d590a0e48da5078c2a51d9638c6260a99915e) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x3f46f5d034af96248a59cd1319bf51b048938af0`](https://optimistic.etherscan.io/address/0x3f46f5d034af96248a59cd1319bf51b048938af0) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x564631122bb104721629a64c5b32cb6e3e32e75d`](https://optimistic.etherscan.io/address/0x564631122bb104721629a64c5b32cb6e3e32e75d) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xaadd56cf2e781476fe49a8b36aeda84097b8f957`](https://optimistic.etherscan.io/address/0xaadd56cf2e781476fe49a8b36aeda84097b8f957) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x77efc83efdfc4fa4a0d9e6a535c2efe00bdaff62`](https://polygonscan.com/address/0x77efc83efdfc4fa4a0d9e6a535c2efe00bdaff62) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x75cb79d603ba5ef927639aeb5b20690516e2cd08`](https://polygonscan.com/address/0x75cb79d603ba5ef927639aeb5b20690516e2cd08) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x1bb7e55cdb042d0ee2a074393eab66b8e04abd33`](https://polygonscan.com/address/0x1bb7e55cdb042d0ee2a074393eab66b8e04abd33) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x8b7e26c9a2dc8f24261ad7c103ba2cc524c8c21d`](https://polygonscan.com/address/0x8b7e26c9a2dc8f24261ad7c103ba2cc524c8c21d) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x29593be78eb8c0885a2ebcba4752574e48716eb4`](https://scrollscan.com/address/0x29593be78eb8c0885a2ebcba4752574e48716eb4) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xec5a923f0dc3cc0df5e2032e97c830ccff063447`](https://scrollscan.com/address/0xec5a923f0dc3cc0df5e2032e97c830ccff063447) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x9a666e9781c487fe628fff297cdd100517aaacfa`](https://scrollscan.com/address/0x9a666e9781c487fe628fff297cdd100517aaacfa) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x71c17ef6bd5fd2e0cc1734ec207fb0a2858e78c8`](https://scrollscan.com/address/0x71c17ef6bd5fd2e0cc1734ec207fb0a2858e78c8) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Sei Network ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xaea21609d62d58c8775d59ed584338b24bbc6eb2`](https://seiscan.io/address/0xaea21609d62d58c8775d59ed584338b24bbc6eb2) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x37720179ff6b3795607f113789cd78415beda80e`](https://seiscan.io/address/0x37720179ff6b3795607f113789cd78415beda80e) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x020bc6160fcaf7ebc75844d4d8f7a3b6095aac0f`](https://seiscan.io/address/0x020bc6160fcaf7ebc75844d4d8f7a3b6095aac0f) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x43da38c9a7fafe822465e023a75b85cf012b3f7a`](https://seiscan.io/address/0x43da38c9a7fafe822465e023a75b85cf012b3f7a) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xc310f89972a576c3ec510c9a3265dea01a2fecdb`](https://sonicscan.org/address/0xc310f89972a576c3ec510c9a3265dea01a2fecdb) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x829ad061e203361f5333787ee99e9f5e403fc627`](https://sonicscan.org/address/0x829ad061e203361f5333787ee99e9f5e403fc627) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x01e368a15bfb058968fe617c9e5dc403b45f75e0`](https://sonicscan.org/address/0x01e368a15bfb058968fe617c9e5dc403b45f75e0) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xd3c8f98e6f9cac5aebd5d653e2331c1e6fdc16a9`](https://sonicscan.org/address/0xd3c8f98e6f9cac5aebd5d653e2331c1e6fdc16a9) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x9f91de05cceb13b3fbe764bdcdd1659e447d3f63`](https://explorer.superseed.xyz/address/0x9f91de05cceb13b3fbe764bdcdd1659e447d3f63) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x1e83629989a3340f68d76dd770f271b06cb38e15`](https://explorer.superseed.xyz/address/0x1e83629989a3340f68d76dd770f271b06cb38e15) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x84328296e172146f2c58097d12f7049d0e5c28c1`](https://explorer.superseed.xyz/address/0x84328296e172146f2c58097d12f7049d0e5c28c1) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x38b33ab1c8697edfe68d177d50908bad9d66c2f7`](https://explorer.superseed.xyz/address/0x38b33ab1c8697edfe68d177d50908bad9d66c2f7) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x2f87acb56a7b1bf6ec76ac62c3880d87af025172`](https://uniscan.xyz/address/0x2f87acb56a7b1bf6ec76ac62c3880d87af025172) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x1e2cc73bed5d4225b46fdaccc4e3b7aa07fb1016`](https://uniscan.xyz/address/0x1e2cc73bed5d4225b46fdaccc4e3b7aa07fb1016) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0xf6456cd10d3b27120d637f391743de960d819abb`](https://uniscan.xyz/address/0xf6456cd10d3b27120d637f391743de960d819abb) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x994ed981a908f6c8c6dc86fb56fede1444f38759`](https://uniscan.xyz/address/0x994ed981a908f6c8c6dc86fb56fede1444f38759) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x0543a1aa6abf1b5b69bfd159ebb4bcaaa7b27aa7`](https://xdcscan.com/address/0x0543a1aa6abf1b5b69bfd159ebb4bcaaa7b27aa7) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x670b50259d979854c15494476938dec79c85ba29`](https://xdcscan.com/address/0x670b50259d979854c15494476938dec79c85ba29) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x075202c1224457d46d25cb481325a0218197682b`](https://xdcscan.com/address/0x075202c1224457d46d25cb481325a0218197682b) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x49e616b0123fd05d70355c472266c647a099936e`](https://xdcscan.com/address/0x49e616b0123fd05d70355c472266c647a099936e) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x27e0076b5be25d604e201bb602d7713dcf267a30`](https://explorer.zksync.io/address/0x27e0076b5be25d604e201bb602d7713dcf267a30) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x2c62a3a177101ce56b3de95b11d686973956e7f0`](https://explorer.zksync.io/address/0x2c62a3a177101ce56b3de95b11d686973956e7f0) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x38be7df6bd31cd0f74bb9089751620102e776976`](https://explorer.zksync.io/address/0x38be7df6bd31cd0f74bb9089751620102e776976) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xe2b3edb3b49bf65ec7d48322d1c24dee2d27c9b1`](https://explorer.zksync.io/address/0xe2b3edb3b49bf65ec7d48322d1c24dee2d27c9b1) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xea37da106ce1ad0264b20764824baca76bb88bd0`](https://sepolia.arbiscan.io/address/0xea37da106ce1ad0264b20764824baca76bb88bd0) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0xaaaf38c81fec5dc0b32c4b53f3aab31f37d76046`](https://sepolia.arbiscan.io/address/0xaaaf38c81fec5dc0b32c4b53f3aab31f37d76046) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x02ab5777c9a91b2d29e215ad2b7a49332755af8f`](https://sepolia.arbiscan.io/address/0x02ab5777c9a91b2d29e215ad2b7a49332755af8f) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x686f70dfc05a52f2842b4a7f7963b3fa0f8f9a40`](https://sepolia.arbiscan.io/address/0x686f70dfc05a52f2842b4a7f7963b3fa0f8f9a40) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0xb04a60866516e335471b801a65812c1eb02fcc75`](https://sepolia.basescan.org/address/0xb04a60866516e335471b801a65812c1eb02fcc75) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x1ea20273d24aa570074f390b9f9666e8c775afff`](https://sepolia.basescan.org/address/0x1ea20273d24aa570074f390b9f9666e8c775afff) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x04de506964b7ca8f4031bbd7f34376eb8c714764`](https://sepolia.basescan.org/address/0x04de506964b7ca8f4031bbd7f34376eb8c714764) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x9a4acab21921c0e874095aeec9d49ce15a452bfc`](https://sepolia.basescan.org/address/0x9a4acab21921c0e874095aeec9d49ce15a452bfc) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x15917930cc30093823790c9d1f83d550c0de33d3`](https://optimism-sepolia.blockscout.com/address/0x15917930cc30093823790c9d1f83d550c0de33d3) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x5796005a94e119ff282a2783dc46eaf543e498cd`](https://optimism-sepolia.blockscout.com/address/0x5796005a94e119ff282a2783dc46eaf543e498cd) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x4894387bec155e73fa0b7673c11bc7074949f2a1`](https://optimism-sepolia.blockscout.com/address/0x4894387bec155e73fa0b7673c11bc7074949f2a1) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0xd3d12e4281f56fc3f97b9373d7085e694a2ec438`](https://optimism-sepolia.blockscout.com/address/0xd3d12e4281f56fc3f97b9373d7085e694a2ec438) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFactoryMerkleInstant | [`0x3633462151339dea950cBED2fd4d132Bd942b64b`](https://sepolia.etherscan.io/address/0x3633462151339dea950cBED2fd4d132Bd942b64b) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLL | [`0x24e7707BdE221D711CbA180385cd419A43E87c5A`](https://sepolia.etherscan.io/address/0x24e7707BdE221D711CbA180385cd419A43E87c5A) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleLT | [`0x4b1764eD795948273D3D7360D885DDEB9CF48Fc6`](https://sepolia.etherscan.io/address/0x4b1764eD795948273D3D7360D885DDEB9CF48Fc6) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | | SablierFactoryMerkleVCA | [`0x960b2fdfaf112edc66da793db96b0dc732d91483`](https://sepolia.etherscan.io/address/0x960b2fdfaf112edc66da793db96b0dc732d91483) | [`airdrops-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/airdrops/v2.0) | --- ## Bob Deployments Source: https://docs.sablier.com/guides/bob/deployments # Bob Deployments This section contains the deployment addresses for the v1.0 release of [@sablier/bob](https://npmjs.com/package/@sablier/bob). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info Stay up to date with any new releases by [subscribing](https://x.com/Sablier/status/1821220784661995627) to the official Sablier repositories on GitHub. ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierBob | [`0xC8AB7E45E6DF99596b86870c26C25c721eB5C9af`](https://etherscan.io/address/0xC8AB7E45E6DF99596b86870c26C25c721eB5C9af) | [`bob-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/bob/v1.0) | | SablierEscrow | [`0xe1662e09e68b700A0C17F17BD08445EC1de0d206`](https://etherscan.io/address/0xe1662e09e68b700A0C17F17BD08445EC1de0d206) | [`bob-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/bob/v1.0) | | SablierLidoAdapter | [`0x40c564A59bB2f1544222D6848E3eEc1Cb68837E6`](https://etherscan.io/address/0x40c564A59bB2f1544222D6848E3eEc1Cb68837E6) | [`bob-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/bob/v1.0) | ## Testnets ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierBob | [`0x2007fE3B4CcBfD887345Cf7C455D5C3F4A19fA2F`](https://sepolia.etherscan.io/address/0x2007fE3B4CcBfD887345Cf7C455D5C3F4A19fA2F) | [`bob-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/bob/v1.0) | | SablierEscrow | [`0x98bB3Ed39028f3882e2C4B054F3DD4E7b1Fefd93`](https://sepolia.etherscan.io/address/0x98bB3Ed39028f3882e2C4B054F3DD4E7b1Fefd93) | [`bob-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/bob/v1.0) | | SablierLidoAdapter | [`0xbcB10B911CA7B74DdE7EeFDafe11E9EF4Fe32C59`](https://sepolia.etherscan.io/address/0xbcB10B911CA7B74DdE7EeFDafe11E9EF4Fe32C59) | [`bob-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/bob/v1.0) | --- ## Etherscan: Manual Operations Source: https://docs.sablier.com/guides/bob/etherscan # Etherscan: Manual Operations ## Introduction Like any other open protocol, Bob can be interacted with directly through a blockchain explorer like Etherscan. In this guide, we will show you how to enter a vault, redeem from a vault, and sync the oracle price by manually interacting with the SablierBob contract on Etherscan. ## Go to Contract Page Head over to the [Bob deployments](/guides/bob/deployments) list and click on the SablierBob contract address for your chain. On Ethereum Mainnet, this is [`0xC8AB7E45E6DF99596b86870c26C25c721eB5C9af`](https://etherscan.io/address/0xC8AB7E45E6DF99596b86870c26C25c721eB5C9af). Click on the "Contract" tab, then the "Write Contract" sub-tab. Connect your wallet by clicking "Connect to Web3". ![Etherscan Write Contract](/assets/images/01-write-contract-292766d3c96e699041fb6459e5c4d94e.webp) ## Depositing Tokens into a Vault ### Prerequisites Before entering a vault, you must approve SablierBob contract to spend your tokens. Go to the token's contract page on Etherscan (e.g., [WETH](https://etherscan.io/token/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2#writeContract)), find the `approve` method, and set: - **spender**: the SablierBob contract address - **amount**: the amount you want to deposit (with decimals, e.g., `10000000000000000000` for 10 WETH) ### Fill in Parameters Open the **`enter`** method on the SablierBob Write Contract page. #### Vault ID The ID of the vault you want to deposit into. #### Amount The amount of tokens to deposit, **decimals included**. For WETH (18 decimals), 10 WETH is `10000000000000000000`. ![Etherscan Enter](/assets/images/02-enter-5a3322e32fdc44febe2d458eec4f98e4.webp) Click "Write" and confirm the transaction. You will receive share tokens 1:1 with the deposited amount. ## Redeeming from a Vault Once a vault has [settled or expired](/concepts/bob/statuses), you can redeem your share tokens for the underlying tokens. Open the **`redeem`** method. ### Payable Amount - For vaults **with** an adapter (e.g., WETH/Lido): set to `0`. - For vaults **without** an adapter: set to the minimum fee in ETH. You can check this by calling `calculateMinFeeWei` on the Read Contract tab. ### Vault ID The ID of the vault to redeem from. ![Etherscan Redeem](/assets/images/03-redeem-d318bc88847a3592477cc99342eccfd9.webp) Click "Write" and confirm. Your share tokens will be burned and you will receive the underlying tokens (plus any yield, minus fees). ## Syncing Price from Oracle If you think that the last synced price is not accurate, you can sync it by calling `syncPriceFromOracle` function. Open the **`syncPriceFromOracle`** method. ### Vault ID The ID of the active vault whose price you want to sync. ![Etherscan Sync Price](/assets/images/04-sync-price-0f68907fae403d05e71bdb0458625f4e.webp) Click "Write" and confirm. The vault's `lastSyncedPrice` will be updated. If the new price meets or exceeds the target price, the vault transitions to `SETTLED`. --- ## Sablier Bob Source: https://docs.sablier.com/guides/bob/overview # Sablier Bob Welcome to the Sablier Bob documentation. This section contains guides and technical references for the Bob protocol. # Guides If you are new to Sablier, we recommend you start with the [Concepts](/concepts/what-is-sablier) section first. Then, read the [Bob](/concepts/bob/overview) concepts to understand how it works. # Reference For a deeper dive into the protocol specifications, read through the [technical reference](/reference/bob/diagrams). # Resources - [Source Code](https://github.com/sablier-labs/evm-monorepo/tree/main/bob) - [Foundry Book](https://book.getfoundry.sh/) --- ## Custom Deployments Source: https://docs.sablier.com/guides/custom-deployments # Custom Deployments :::warning [Maintenance Mode] Sablier Labs is no longer accepting requests for new chain deployments. The information below is provided for reference only. ::: ## Requirements The Sablier Interface currently supports only The Graph and Envio indexer services. - GraphQL indexer: either [The Graph](https://thegraph.com) or [Envio](https://envio.dev) - Blockchain explorer with instructions for verifying contracts, e.g., [Etherscan](https://etherscan.io) - Functional JSON-RPC endpoint, ideally listed on [ChainList](https://chainlist.org) - Bridge, and instructions for how to obtain gas tokens (e.g., ETH) and ERC-20 tokens to the target chain - Support for [Shanghai](https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options) EVM version or later - Support for Solidity v0.8.22 or later ### Contracts - Foundry's [Deterministic Deployer](https://github.com/Arachnid/deterministic-deployment-proxy) contract at [`0x4e59b44847b379578588920cA78FbF26c0B4956C`](https://etherscan.io/address/0x4e59b44847b379578588920cA78FbF26c0B4956C) - [Multicall3](https://github.com/mds1/multicall) contract at [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://etherscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11) ## Making a Deployment :::warning [NOTE] The rest of the guide applies ONLY IF you have been granted a [BUSL license](https://app.ens.domains/license-grants.sablier.eth?tab=records) to deploy the protocol. Otherwise, this section is not relevant to you. ::: ### Prerequisites - Check if the deployments are not already made: [Airdrops](/guides/airdrops/deployments), [Flow](/guides/flow/deployments), [Lockup](/guides/lockup/deployments) - Follow the [contributing guide](https://github.com/sablier-labs/evm-monorepo/blob/main/CONTRIBUTING.md). - RPC endpoint, e.g., a paid [Infura](https://www.infura.io/) or [Alchemy](https://www.alchemy.com/) account - Have enough gas tokens (e.g., ETH) in your deployer account - Have an Etherscan API key (for source code verification) ### Comptroller Deployment #### Step 1: Clone the [evm-monorepo](https://github.com/sablier-labs/evm-monorepo) and checkout to the `utils@v2.0` tag ```bash git clone -b utils@v2.0 git@github.com:sablier-labs/evm-monorepo.git cd evm-monorepo/utils bun install --frozen-lockfile ``` #### Step 2: Create an `.env` file ```bash touch .env ``` Add the following variables to `.env` file: ```text ETH_FROM="DEPLOYER ADDRESS" ETHERSCAN_API_KEY="EXPLORER API KEY" PRIVATE_KEY="PRIVATE KEY OF DEPLOYER ADDRESS" RPC_URL="RPC ENDPOINT URL" VERIFIER_URL="EXPLORER VERIFICATION URL" ``` Load the environment variables into your shell: ```bash source .env ``` #### Step 3: Run the following deployment command ```bash FOUNDRY_PROFILE=optimized \ forge script scripts/solidity/DeployDeterministicComptrollerProxy.s.sol:DeployDeterministicComptrollerProxy \ --broadcast \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ --sig "run()" \ --etherscan-api-key $ETHERSCAN_API_KEY \ --verifier "etherscan" \ --verify \ -vvv ``` The above command works for etherscan equivalent explorers. If the chain uses a custom explorer, modify the verifier arguments according to [forge script documentation](https://getfoundry.sh/forge/reference/script/#forge-script). You should also refer to the forge script documentation for different wallet options such as mnemonic or hardware device. #### Step 4: Update the EVM Utils repository Add the chain ID of your newly deployed chain to the `ChainId` library in the [evm-monorepo](https://github.com/sablier-labs/evm-monorepo/blob/main/utils/src/tests/ChainId.sol). Then, bump the EVM Utils version in each protocol's `package.json` file within the monorepo. ### Lockup Deployment #### Step 1: Clone the [evm-monorepo](https://github.com/sablier-labs/evm-monorepo) and checkout to the `lockup@v4.0` tag ```bash git clone -b lockup@v4.0 git@github.com:sablier-labs/evm-monorepo.git cd evm-monorepo/lockup bun install --frozen-lockfile ``` #### Step 2: Create an `.env` file ```bash touch .env ``` Add the following variables to `.env` file: ```text ETH_FROM="DEPLOYER ADDRESS" ETHERSCAN_API_KEY="EXPLORER API KEY" PRIVATE_KEY="PRIVATE KEY OF DEPLOYER ADDRESS" RPC_URL="RPC ENDPOINT URL" VERIFIER_URL="EXPLORER VERIFICATION URL" ``` Load the environment variables into your shell: ```bash source .env ``` #### Step 3: Run the following deployment command ```bash FOUNDRY_PROFILE=optimized \ forge script scripts/solidity/DeployDeterministicProtocol.s.sol:DeployDeterministicProtocol \ --broadcast \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ --sig "run()" \ --etherscan-api-key $ETHERSCAN_API_KEY \ --verifier "etherscan" \ --verify \ -vvv ``` The above command works for etherscan equivalent explorers. If the chain uses a custom explorer, modify the verifier arguments according to [forge script documentation](https://getfoundry.sh/forge/reference/script/#forge-script). You should also refer to the forge script documentation for different wallet options such as mnemonic or hardware device. ### Merkle Airdrops Deployment #### Step 1: Clone the [evm-monorepo](https://github.com/sablier-labs/evm-monorepo) and checkout to the `airdrops@v3.0` tag ```bash git clone -b airdrops@v3.0 git@github.com:sablier-labs/evm-monorepo.git cd evm-monorepo/airdrops bun install --frozen-lockfile ``` #### Step 2: Create an `.env` file ```bash touch .env ``` Add the following variables to `.env` file: ```text ETH_FROM="DEPLOYER ADDRESS" ETHERSCAN_API_KEY="EXPLORER API KEY" PRIVATE_KEY="PRIVATE KEY OF DEPLOYER ADDRESS" RPC_URL="RPC ENDPOINT URL" VERIFIER_URL="EXPLORER VERIFICATION URL" ``` Load the environment variables into shell: ```bash source .env ``` #### Step 3: Run the following command to deploy all merkle airdrop contracts ```bash FOUNDRY_PROFILE=optimized \ forge script scripts/solidity/DeployDeterministicFactories.s.sol:DeployDeterministicFactories \ --broadcast \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ --sig "run()" \ --etherscan-api-key $ETHERSCAN_API_KEY \ --verifier "etherscan" \ --verify \ -vvv ``` The above command works for etherscan equivalent explorers. If the chain uses a custom explorer, modify the verifier arguments according to [forge script documentation](https://getfoundry.sh/forge/reference/script/#forge-script). You should also refer to the forge script documentation for different wallet options such as mnemonic or hardware device. ### Flow Deployment #### Step 1: Clone the [evm-monorepo](https://github.com/sablier-labs/evm-monorepo) and checkout to the `flow@v3.0` tag ```bash git clone -b flow@v3.0 git@github.com:sablier-labs/evm-monorepo.git cd evm-monorepo/flow bun install --frozen-lockfile ``` #### Step 2: Create an `.env` file ```bash touch .env ``` Add the following variables to `.env` file: ```text ETH_FROM="DEPLOYER ADDRESS" ETHERSCAN_API_KEY="EXPLORER API KEY" PRIVATE_KEY="PRIVATE KEY OF DEPLOYER ADDRESS" RPC_URL="RPC ENDPOINT URL" VERIFIER_URL="EXPLORER VERIFICATION URL" ``` Load the environment variables into your shell: ```bash source .env ``` #### Step 3: Run the following deployment command ```bash FOUNDRY_PROFILE=optimized \ forge script scripts/solidity/DeployDeterministicProtocol.s.sol:DeployDeterministicProtocol \ --broadcast \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ --sig "run()" \ --etherscan-api-key $ETHERSCAN_API_KEY \ --verifier "etherscan" \ --verify \ -vvv ``` The above command works for etherscan equivalent explorers. If the chain uses a custom explorer, modify the verifier arguments according to [forge script documentation](https://getfoundry.sh/forge/reference/script/#forge-script). You should also refer to the forge script documentation for different wallet options such as mnemonic or hardware device. ### List your deployments After the contracts are deployed, you can submit a PR in the [SDK repo](https://github.com/sablier-labs/sdk) by including the following details: - Add the broadcast files (JSON) to [deployments](https://github.com/sablier-labs/sdk/tree/main/deployments) directory. - Add the deployment addresses to the following files: - [Comptroller Deployments](https://github.com/sablier-labs/sdk/blob/main/src/comptroller.ts) - [Airdrops Deployments](https://github.com/sablier-labs/sdk/blob/main/src/releases/airdrops/v2.0/deployments.ts) - [Flow Deployments](https://github.com/sablier-labs/sdk/blob/main/src/releases/flow/v2.0/deployments.ts) - [Lockup Deployments](https://github.com/sablier-labs/sdk/blob/main/src/releases/lockup/v3.0/deployments.ts) --- ## Flow Deployments Source: https://docs.sablier.com/guides/flow/deployments # Flow Deployments This section contains the deployment addresses for the v3.0 release of [@sablier/flow](https://npmjs.com/package/@sablier/flow). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains ## Versions Any updates or additional features will require a new deployment of the protocol, due to its immutable nature. Came here looking for the previous deployments? Click below to see other versions. | Version | Release Date | UI Aliases | | --- | --- | --- | | [v3.0](/guides/flow/deployments) (latest) | March 2026 | - `FL4` | | [v2.0](/guides/flow/previous-deployments/v2.0) | October 2025 | - `FL3` | | [v1.1](/guides/flow/previous-deployments/v1.1) | February 2025 | - `FL2` | | [v1.0](/guides/flow/previous-deployments/v1.0) | December 2024 | - `FL` | :::info Stay up to date with any new releases by [subscribing](https://x.com/Sablier/status/1821220784661995627) to the official Sablier repositories on Github. ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x844344cd871b28221d725ece9630e8bde4e3a181`](https://etherscan.io/address/0x844344cd871b28221d725ece9630e8bde4e3a181) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x24bE13897eE1F83367661B6bA616a72523fC55C9`](https://etherscan.io/address/0x24bE13897eE1F83367661B6bA616a72523fC55C9) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x2fac86e709bac0d970c0e103d3b9580d2df4be5d`](https://abscan.org/address/0x2fac86e709bac0d970c0e103d3b9580d2df4be5d) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x6CefdBc5Ba80937235F012c83d6aA83F1200d6cC`](https://abscan.org/address/0x6CefdBc5Ba80937235F012c83d6aA83F1200d6cC) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xa70b8555157500b11f41a37dd93f4b4e997d583d`](https://arbiscan.io/address/0xa70b8555157500b11f41a37dd93f4b4e997d583d) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x5F23eF12A7e861CB92c24B4314Af2A5F363CDD4F`](https://arbiscan.io/address/0x5F23eF12A7e861CB92c24B4314Af2A5F363CDD4F) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x980878b890e755c788bce5db7725bcc6df76bf5b`](https://snowscan.xyz/address/0x980878b890e755c788bce5db7725bcc6df76bf5b) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xb09b714B0feC83675E09fc997B7D532cF6620326`](https://snowscan.xyz/address/0xb09b714B0feC83675E09fc997B7D532cF6620326) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x0cbfe6ce6f05c47d6243bb3818837971c6ccb46b`](https://basescan.org/address/0x0cbfe6ce6f05c47d6243bb3818837971c6ccb46b) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x5b5e742305Be3A484EacCB124C83456463c24E6a`](https://basescan.org/address/0x5b5e742305Be3A484EacCB124C83456463c24E6a) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x1794f514d7c1d771055ffd2a880148f619107945`](https://berascan.com/address/0x1794f514d7c1d771055ffd2a880148f619107945) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x581250eE4311F7Dc1afCF965cF8024004B423e9E`](https://berascan.com/address/0x581250eE4311F7Dc1afCF965cF8024004B423e9E) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xa9b86b045caedb791af729f6c15435b978c34f7f`](https://bscscan.com/address/0xa9b86b045caedb791af729f6c15435b978c34f7f) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xAE557c04B46d47Ecac24edA63F22cabB4571Da61`](https://bscscan.com/address/0xAE557c04B46d47Ecac24edA63F22cabB4571Da61) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x4d3cecb8eeddd5e69c201017e884ae5e8338474f`](https://chiliscan.com/address/0x4d3cecb8eeddd5e69c201017e884ae5e8338474f) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xC7fd18CA19938d559dC45aDE362a850015CF0bd8`](https://chiliscan.com/address/0xC7fd18CA19938d559dC45aDE362a850015CF0bd8) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Denergy ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x0B5f82Fa564D2B7F97d6048308167aA8B710e20E`](https://explorer.denergychain.com/address/0x0B5f82Fa564D2B7F97d6048308167aA8B710e20E) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x8C4bCE3A96CA4E1275B11FDcC38d00D142af2C3f`](https://explorer.denergychain.com/address/0x8C4bCE3A96CA4E1275B11FDcC38d00D142af2C3f) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xb3a9a358794b101962a3741ef882b367e9e56c72`](https://gnosisscan.io/address/0xb3a9a358794b101962a3741ef882b367e9e56c72) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x5A47FC8732d399a2f3845c4FC91aB91bb97da31F`](https://gnosisscan.io/address/0x5A47FC8732d399a2f3845c4FC91aB91bb97da31F) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x91B9B0e3be6EeE0556f1cf5bCba2f2673AA28dFE`](https://hyperevmscan.io/address/0x91B9B0e3be6EeE0556f1cf5bCba2f2673AA28dFE) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x81Cc8C4B57B9A60a56330d087D6854A8E17Dfc7A`](https://hyperevmscan.io/address/0x81Cc8C4B57B9A60a56330d087D6854A8E17Dfc7A) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x95f0d947befaecafa8b1e89bbada723d81783d4b`](https://phoenix.lightlink.io/address/0x95f0d947befaecafa8b1e89bbada723d81783d4b) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xc58E948Cb0a010105467C92856bcd4842B759fb1`](https://phoenix.lightlink.io/address/0xc58E948Cb0a010105467C92856bcd4842B759fb1) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x7a92392b7c35610a861f82c42043e6705979369c`](https://lineascan.build/address/0x7a92392b7c35610a861f82c42043e6705979369c) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x294D7fceBa43C4507771707CeBBB7b6d81d0BFdE`](https://lineascan.build/address/0x294D7fceBa43C4507771707CeBBB7b6d81d0BFdE) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x5a51fd153874429f4cad36cc54560beffeead6df`](https://modescan.io/address/0x5a51fd153874429f4cad36cc54560beffeead6df) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xD9E2822a33606741BeDbA31614E68A745e430102`](https://modescan.io/address/0xD9E2822a33606741BeDbA31614E68A745e430102) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Monad ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x95004df5abe86a246664d8f5fb2683f24df768d1`](https://monadscan.com/address/0x95004df5abe86a246664d8f5fb2683f24df768d1) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xf51BB8bd1cfc7C890dB68c39dCCA67CAd7810Ce4`](https://monadscan.com/address/0xf51BB8bd1cfc7C890dB68c39dCCA67CAd7810Ce4) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x5ba4cc0a1014faf0967624f3f1c3d63b9ffeb287`](https://explorer.morphl2.io/address/0x5ba4cc0a1014faf0967624f3f1c3d63b9ffeb287) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x1dd4dcE2BB742908b4062E583d9c035973413A3F`](https://explorer.morphl2.io/address/0x1dd4dcE2BB742908b4062E583d9c035973413A3F) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xe8a69dabae3003df4cb0901389766c4b2d34c2eb`](https://optimistic.etherscan.io/address/0xe8a69dabae3003df4cb0901389766c4b2d34c2eb) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x7AD245b74bBC1B71Da1713D53238931F791b90A3`](https://optimistic.etherscan.io/address/0x7AD245b74bBC1B71Da1713D53238931F791b90A3) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x20080f7e2d58b5cfc4e6d997c841999e3416843c`](https://polygonscan.com/address/0x20080f7e2d58b5cfc4e6d997c841999e3416843c) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x87B836a9e26673feB3E409A0da2EAf99C79f26C3`](https://polygonscan.com/address/0x87B836a9e26673feB3E409A0da2EAf99C79f26C3) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xd3dec781af1f5ccb828f97d3e5deb86f6efc5e5a`](https://scrollscan.com/address/0xd3dec781af1f5ccb828f97d3e5deb86f6efc5e5a) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x797Fe78c41d9cbE81BBEA2f420101be5e47d2aFf`](https://scrollscan.com/address/0x797Fe78c41d9cbE81BBEA2f420101be5e47d2aFf) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x1598ed7ffb006a4e233268e7846faa9e17ac9c16`](https://sonicscan.org/address/0x1598ed7ffb006a4e233268e7846faa9e17ac9c16) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xAab30e5CB903f67F109aFc7102ac8ED803681EA5`](https://sonicscan.org/address/0xAab30e5CB903f67F109aFc7102ac8ED803681EA5) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xa80de83ea03335396161bb267e1250fb5cc99cdf`](https://explorer.superseed.xyz/address/0xa80de83ea03335396161bb267e1250fb5cc99cdf) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xd932fDA016eE9d9F70f745544b4F56715b1E723b`](https://explorer.superseed.xyz/address/0xd932fDA016eE9d9F70f745544b4F56715b1E723b) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x12a6a5f809d451d29e4c1a6bca31b88c914100ac`](https://uniscan.xyz/address/0x12a6a5f809d451d29e4c1a6bca31b88c914100ac) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x89824A7e48dcf6B7AE9DeE6E566f62A5aDF037F2`](https://uniscan.xyz/address/0x89824A7e48dcf6B7AE9DeE6E566f62A5aDF037F2) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x2a89ddeafebf51cb8517da2d00df2365bf3ef49e`](https://xdcscan.com/address/0x2a89ddeafebf51cb8517da2d00df2365bf3ef49e) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x9D3F0122b260D2218ecf681c416495882003deDd`](https://xdcscan.com/address/0x9D3F0122b260D2218ecf681c416495882003deDd) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xa1b75ac1e36504c93279c69c2583ff0c73eb036b`](https://explorer.zksync.io/address/0xa1b75ac1e36504c93279c69c2583ff0c73eb036b) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x423C1b454250992Ede8516D36DE456F609714B53`](https://explorer.zksync.io/address/0x423C1b454250992Ede8516D36DE456F609714B53) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x2c81c86a66c9459f461eb0c49963b9539eca87ef`](https://sepolia.arbiscan.io/address/0x2c81c86a66c9459f461eb0c49963b9539eca87ef) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x3E64A31C3974b6ae9f09a8fbc784519bF551e795`](https://sepolia.arbiscan.io/address/0x3E64A31C3974b6ae9f09a8fbc784519bF551e795) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xc1ba5a41936aaab0ff920446db556efe17fc1c5d`](https://sepolia.basescan.org/address/0xc1ba5a41936aaab0ff920446db556efe17fc1c5d) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xcb5591F6d0e0fFC03037ef7b006D1361C6D33D25`](https://sepolia.basescan.org/address/0xcb5591F6d0e0fFC03037ef7b006D1361C6D33D25) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### BattleChain Testnet ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x711900e5f55d427cd88e5E3FCAe54Ccf02De71F4`](https://explorer.testnet.battlechain.com/address/0x711900e5f55d427cd88e5E3FCAe54Ccf02De71F4) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x28eAB88ee8a951F78e1028557D0C3fD97af61A33`](https://explorer.testnet.battlechain.com/address/0x28eAB88ee8a951F78e1028557D0C3fD97af61A33) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xf1c1411b446bd630791de95b6a503871f7bbac5f`](https://optimism-sepolia.blockscout.com/address/0xf1c1411b446bd630791de95b6a503871f7bbac5f) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0x4739327acfb56E90177d44Cb0845e759276BCA88`](https://optimism-sepolia.blockscout.com/address/0x4739327acfb56E90177d44Cb0845e759276BCA88) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xbd9326f6366c95e39bd8ef825c1b2f2ee0dceaa1`](https://sepolia.etherscan.io/address/0xbd9326f6366c95e39bd8ef825c1b2f2ee0dceaa1) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | | FlowNFTDescriptor | [`0xc9dBf2D207D178875b698e5f7493ce2d8BA88994`](https://sepolia.etherscan.io/address/0xc9dBf2D207D178875b698e5f7493ce2d8BA88994) | [`flow-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v3.0) | --- ## Batching Functions Source: https://docs.sablier.com/guides/flow/examples/batchable # Batching Functions A neat feature of Sablier Flow is the ability to batch multiple function calls into a single transaction. This is made possible by the [`Batch`](/reference/flow/contracts/abstracts/abstract.Batch) contract, which is inherited by `SablierFlow`. With this, you can efficiently batch multiple function calls in a single transaction. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Import the relevant symbols: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; ``` Create a contract called `FlowBatchable`, and declare a constant `USDC` of type `IERC20` and a constant `FLOW` of type `ISablierFlow`: ```solidity // Mainnet addresses IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ISablierFlow public constant FLOW = ISablierFlow(0x844344Cd871B28221d725ecE9630E8bDE4E3a181); ``` 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, Flow addresses can be obtained from the [Flow Deployments](/guides/flow/deployments) page. ## Create multiple streams One of the most useful features achieved by `batch` is the ability to create multiple streams in a single transaction. Let's focus on it in this example. Define a function that creates multiple streams and returns their respective stream IDs: ```solidity function createMultiple() external returns (uint256[] memory streamIds) { address sender = msg.sender; address firstRecipient = address(0xCAFE); address secondRecipient = address(0xBEEF); UD21x18 firstRatePerSecond = ud21x18(0.0001e18); UD21x18 secondRatePerSecond = ud21x18(0.0002e18); bool transferable = true; // The call data declared as bytes bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall( FLOW.create, (sender, firstRecipient, firstRatePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[1] = abi.encodeCall( FLOW.create, (sender, secondRecipient, secondRatePerSecond, uint40(block.timestamp), USDC, transferable) ); // Prepare the `streamIds` array to return them uint256 nextStreamId = FLOW.nextStreamId(); streamIds = new uint256[](2); streamIds[0] = nextStreamId; streamIds[1] = nextStreamId + 1; // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch(calls); } /// @dev A function to create multiple streams and deposit into all the streams in a single transaction. function createMultipleAndDeposit() external returns (uint256[] memory streamIds) { address sender = msg.sender; address firstRecipient = address(0xCAFE); address secondRecipient = address(0xBEEF); UD21x18 ratePerSecond = ud21x18(0.0001e18); uint128 depositAmount = 1000e6; bool transferable = true; // Transfer the deposit amount of USDC tokens to this contract for both streams USDC.transferFrom(msg.sender, address(this), 2 * depositAmount); // Approve the Sablier contract to spend USDC. USDC.approve(address(FLOW), 2 * depositAmount); uint256 nextStreamId = FLOW.nextStreamId(); streamIds = new uint256[](2); streamIds[0] = nextStreamId; streamIds[1] = nextStreamId + 1; // We need to have 4 different function calls, 2 for creating streams and 2 for depositing bytes[] memory calls = new bytes[](4); calls[0] = abi.encodeCall( FLOW.create, (sender, firstRecipient, ratePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[1] = abi.encodeCall( FLOW.create, (sender, secondRecipient, ratePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[2] = abi.encodeCall(FLOW.deposit, (streamIds[0], depositAmount, sender, firstRecipient)); calls[3] = abi.encodeCall(FLOW.deposit, (streamIds[1], depositAmount, sender, secondRecipient)); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch(calls); } /// @dev A function to pause a stream and withdraw the maximum available funds. /// Note: The streamId's sender must be this contract, otherwise, the call will fail due to no authorization. function pauseAndWithdrawMax(uint256 streamId) external payable { // The call data declared as bytes. bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall(FLOW.pause, (streamId)); calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); // Calculate the fee. uint256 fee = FLOW.calculateMinFeeWei(streamId); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: fee }(calls); } /// @dev A function to void a stream and withdraw what is left. /// Note: The streamId's sender must be this contract, otherwise, the call will fail due to no authorization. function voidAndWithdrawMax(uint256 streamId) external payable { // The call data declared as bytes bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall(FLOW.void, (streamId)); calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); // Calculate the fee. uint256 fee = FLOW.calculateMinFeeWei(streamId); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: fee }(calls); } /// @dev A function to withdraw maximum available funds from multiple streams in a single transaction. function withdrawMaxMultiple(uint256[] calldata streamIds) external payable { uint256 count = streamIds.length; uint256 maxFeeRequired; // Iterate over the streamIds and prepare the call data for each stream. bytes[] memory calls = new bytes[](count); for (uint256 i = 0; i < count; ++i) { address recipient = FLOW.getRecipient(streamIds[i]); calls[i] = abi.encodeCall(FLOW.withdrawMax, (streamIds[i], recipient)); // Calculate the fee required to withdraw the amount. It is the maximum of the fees required to withdraw // each stream. uint256 feeForStreamId = FLOW.calculateMinFeeWei(streamIds[i]); if (feeForStreamId > maxFeeRequired) { maxFeeRequired = feeForStreamId; } } // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: maxFeeRequired }(calls); } } ``` ### Parameters We will create two streams with same stream parameters required by the `create` function. ```solidity address sender = msg.sender; address firstRecipient = address(0xCAFE); address secondRecipient = address(0xBEEF); UD21x18 firstRatePerSecond = ud21x18(0.0001e18); UD21x18 secondRatePerSecond = ud21x18(0.0002e18); bool transferable = true; ``` Construct an array of `bytes` of length 2 to be passed into the `batch` function: ```solidity // The call data declared as bytes bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall( FLOW.create, (sender, firstRecipient, firstRatePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[1] = abi.encodeCall( FLOW.create, (sender, secondRecipient, secondRatePerSecond, uint40(block.timestamp), USDC, transferable) ); ``` Since we are creating two streams, the function will return an array containing the two generated stream IDs: ```solidity // Prepare the `streamIds` array to return them uint256 nextStreamId = FLOW.nextStreamId(); streamIds = new uint256[](2); streamIds[0] = nextStreamId; streamIds[1] = nextStreamId + 1; ``` Execute the `batch`: ```solidity FLOW.batch(calls); } /// @dev A function to create multiple streams and deposit into all the streams in a single transaction. function createMultipleAndDeposit() external returns (uint256[] memory streamIds) { address sender = msg.sender; address firstRecipient = address(0xCAFE); address secondRecipient = address(0xBEEF); UD21x18 ratePerSecond = ud21x18(0.0001e18); uint128 depositAmount = 1000e6; bool transferable = true; // Transfer the deposit amount of USDC tokens to this contract for both streams USDC.transferFrom(msg.sender, address(this), 2 * depositAmount); // Approve the Sablier contract to spend USDC. USDC.approve(address(FLOW), 2 * depositAmount); uint256 nextStreamId = FLOW.nextStreamId(); streamIds = new uint256[](2); streamIds[0] = nextStreamId; streamIds[1] = nextStreamId + 1; // We need to have 4 different function calls, 2 for creating streams and 2 for depositing bytes[] memory calls = new bytes[](4); calls[0] = abi.encodeCall( FLOW.create, (sender, firstRecipient, ratePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[1] = abi.encodeCall( FLOW.create, (sender, secondRecipient, ratePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[2] = abi.encodeCall(FLOW.deposit, (streamIds[0], depositAmount, sender, firstRecipient)); calls[3] = abi.encodeCall(FLOW.deposit, (streamIds[1], depositAmount, sender, secondRecipient)); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch(calls); } /// @dev A function to pause a stream and withdraw the maximum available funds. /// Note: The streamId's sender must be this contract, otherwise, the call will fail due to no authorization. function pauseAndWithdrawMax(uint256 streamId) external payable { // The call data declared as bytes. bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall(FLOW.pause, (streamId)); calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); // Calculate the fee. uint256 fee = FLOW.calculateMinFeeWei(streamId); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: fee }(calls); } /// @dev A function to void a stream and withdraw what is left. /// Note: The streamId's sender must be this contract, otherwise, the call will fail due to no authorization. function voidAndWithdrawMax(uint256 streamId) external payable { // The call data declared as bytes bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall(FLOW.void, (streamId)); calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); // Calculate the fee. uint256 fee = FLOW.calculateMinFeeWei(streamId); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: fee }(calls); } /// @dev A function to withdraw maximum available funds from multiple streams in a single transaction. function withdrawMaxMultiple(uint256[] calldata streamIds) external payable { uint256 count = streamIds.length; uint256 maxFeeRequired; // Iterate over the streamIds and prepare the call data for each stream. bytes[] memory calls = new bytes[](count); for (uint256 i = 0; i < count; ++i) { address recipient = FLOW.getRecipient(streamIds[i]); calls[i] = abi.encodeCall(FLOW.withdrawMax, (streamIds[i], recipient)); // Calculate the fee required to withdraw the amount. It is the maximum of the fees required to withdraw // each stream. uint256 feeForStreamId = FLOW.calculateMinFeeWei(streamIds[i]); if (feeForStreamId > maxFeeRequired) { maxFeeRequired = feeForStreamId; } } // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: maxFeeRequired }(calls); } } ``` ## Homework Try to implement the following ideas using `batch` function: - Adjust Rate Per Second and Deposit - Pause and Withdraw Max - Void and Withdraw Max - Multiple Withdraw Max :::note If you include any `withdraw` function in your batch, keep in mind that it requires a fee. Make sure to send the correct amount in `msg.value`, as shown below. ::: Below, you will find the full code for it. ## Other ideas There are plenty of other possibilities as well! Feel free to experiment and come up with combinations that suit your system. 🚀 ## Full code Below you can see the complete `FlowBatchable` contract: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; /// @notice The `Batch` contract, inherited in SablierFlow, allows multiple function calls to be batched together. This /// enables any possible combination of functions to be executed within a single transaction. /// @dev For some functions to work, `msg.sender` must have approved this contract to spend USDC. contract FlowBatchable { // Mainnet addresses IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ISablierFlow public constant FLOW = ISablierFlow(0x844344Cd871B28221d725ecE9630E8bDE4E3a181); /// @dev A function to adjust the rate per second and deposit into a stream in a single transaction. /// Note: The streamId's sender must be this contract, otherwise, the call will fail due to no authorization. function adjustRatePerSecondAndDeposit(uint256 streamId) external { UD21x18 newRatePerSecond = ud21x18(0.0002e18); uint128 depositAmount = 1000e6; // Transfer to this contract the amount to deposit in the stream. USDC.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend USDC. USDC.approve(address(FLOW), depositAmount); // Fetch the stream recipient. address recipient = FLOW.getRecipient(streamId); // The call data declared as bytes. bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall(FLOW.adjustRatePerSecond, (streamId, newRatePerSecond)); calls[1] = abi.encodeCall(FLOW.deposit, (streamId, depositAmount, msg.sender, recipient)); FLOW.batch(calls); } /// @dev A function to create a stream and deposit in a single transaction. function createAndDeposit() external returns (uint256 streamId) { address sender = msg.sender; address recipient = address(0xCAFE); UD21x18 ratePerSecond = ud21x18(0.0001e18); uint128 depositAmount = 1000e6; bool transferable = true; // Transfer to this contract the amount to deposit in the stream. USDC.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend USDC. USDC.approve(address(FLOW), depositAmount); streamId = FLOW.nextStreamId(); // The call data declared as bytes bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall( FLOW.create, (sender, recipient, ratePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[1] = abi.encodeCall(FLOW.deposit, (streamId, depositAmount, sender, recipient)); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch(calls); } /// @dev A function to create multiple streams in a single transaction. function createMultiple() external returns (uint256[] memory streamIds) { address sender = msg.sender; address firstRecipient = address(0xCAFE); address secondRecipient = address(0xBEEF); UD21x18 firstRatePerSecond = ud21x18(0.0001e18); UD21x18 secondRatePerSecond = ud21x18(0.0002e18); bool transferable = true; // The call data declared as bytes bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall( FLOW.create, (sender, firstRecipient, firstRatePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[1] = abi.encodeCall( FLOW.create, (sender, secondRecipient, secondRatePerSecond, uint40(block.timestamp), USDC, transferable) ); // Prepare the `streamIds` array to return them uint256 nextStreamId = FLOW.nextStreamId(); streamIds = new uint256[](2); streamIds[0] = nextStreamId; streamIds[1] = nextStreamId + 1; // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch(calls); } /// @dev A function to create multiple streams and deposit into all the streams in a single transaction. function createMultipleAndDeposit() external returns (uint256[] memory streamIds) { address sender = msg.sender; address firstRecipient = address(0xCAFE); address secondRecipient = address(0xBEEF); UD21x18 ratePerSecond = ud21x18(0.0001e18); uint128 depositAmount = 1000e6; bool transferable = true; // Transfer the deposit amount of USDC tokens to this contract for both streams USDC.transferFrom(msg.sender, address(this), 2 * depositAmount); // Approve the Sablier contract to spend USDC. USDC.approve(address(FLOW), 2 * depositAmount); uint256 nextStreamId = FLOW.nextStreamId(); streamIds = new uint256[](2); streamIds[0] = nextStreamId; streamIds[1] = nextStreamId + 1; // We need to have 4 different function calls, 2 for creating streams and 2 for depositing bytes[] memory calls = new bytes[](4); calls[0] = abi.encodeCall( FLOW.create, (sender, firstRecipient, ratePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[1] = abi.encodeCall( FLOW.create, (sender, secondRecipient, ratePerSecond, uint40(block.timestamp), USDC, transferable) ); calls[2] = abi.encodeCall(FLOW.deposit, (streamIds[0], depositAmount, sender, firstRecipient)); calls[3] = abi.encodeCall(FLOW.deposit, (streamIds[1], depositAmount, sender, secondRecipient)); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch(calls); } /// @dev A function to pause a stream and withdraw the maximum available funds. /// Note: The streamId's sender must be this contract, otherwise, the call will fail due to no authorization. function pauseAndWithdrawMax(uint256 streamId) external payable { // The call data declared as bytes. bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall(FLOW.pause, (streamId)); calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); // Calculate the fee. uint256 fee = FLOW.calculateMinFeeWei(streamId); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: fee }(calls); } /// @dev A function to void a stream and withdraw what is left. /// Note: The streamId's sender must be this contract, otherwise, the call will fail due to no authorization. function voidAndWithdrawMax(uint256 streamId) external payable { // The call data declared as bytes bytes[] memory calls = new bytes[](2); calls[0] = abi.encodeCall(FLOW.void, (streamId)); calls[1] = abi.encodeCall(FLOW.withdrawMax, (streamId, address(0xCAFE))); // Calculate the fee. uint256 fee = FLOW.calculateMinFeeWei(streamId); // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: fee }(calls); } /// @dev A function to withdraw maximum available funds from multiple streams in a single transaction. function withdrawMaxMultiple(uint256[] calldata streamIds) external payable { uint256 count = streamIds.length; uint256 maxFeeRequired; // Iterate over the streamIds and prepare the call data for each stream. bytes[] memory calls = new bytes[](count); for (uint256 i = 0; i < count; ++i) { address recipient = FLOW.getRecipient(streamIds[i]); calls[i] = abi.encodeCall(FLOW.withdrawMax, (streamIds[i], recipient)); // Calculate the fee required to withdraw the amount. It is the maximum of the fees required to withdraw // each stream. uint256 feeForStreamId = FLOW.calculateMinFeeWei(streamIds[i]); if (feeForStreamId > maxFeeRequired) { maxFeeRequired = feeForStreamId; } } // Execute multiple calls in a single transaction using the prepared call data. FLOW.batch{ value: maxFeeRequired }(calls); } } ``` --- ## Create a Flow stream Source: https://docs.sablier.com/guides/flow/examples/create-stream # Create a Flow stream In this guide, we will show you how you can create a Flow stream using Solidity. It is important to note that a Flow stream has no end date, which means it will continue to accumulate debt even if no funds are deposited. This guide assumes that you have already gone through the [Calculate Rate per Second](/guides/flow/examples/flow-calculate-rps) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Import the relevant symbols from `@sablier/flow`, and the `FlowUtilities` library: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UD21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; import { FlowUtilities } from "./FlowUtilities.sol"; ``` Create a contract called `FlowStreamCreator`, and declare a constant `USDC` of type `IERC20` and a constant `FLOW` of type `ISablierFlow`: ```solidity // Mainnet addresses IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ISablierFlow public constant FLOW = ISablierFlow(0x844344Cd871B28221d725ecE9630E8bDE4E3a181); ``` 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, Flow addresses can be obtained from the [Flow Deployments](/guides/flow/deployments) page. We will declare two functions, based on the amount desired to stream over a period of time. ## Define a function Define a function to stream a salary of 1000 USDC per month, call it `createStream_1K_PerMonth` which returns the newly created stream ID: ```solidity function createStream_1K_PerMonth() external returns (uint256 streamId) { ``` ## Input parameters ### Rate Per Second Use the [`FlowUtilities`](/guides/flow/examples/flow-calculate-rps) library to calculate the rate per second for the desired amount: ```solidity UD21x18 ratePerSecond = FlowUtilities.ratePerSecondWithDuration({ token: address(USDC), amount: 1000e6, duration: 30 days }); ``` ### Sender The address streaming the tokens, with the ability to `pause` the stream: ```solidity sender: msg.sender, // The sender will be able to manage the stream ``` ### Recipient The address receiving the tokens: ```solidity recipient: address(0xCAFE), // The recipient of the streamed tokens ``` ### Start time The timestamp when the stream starts. ```solidity startTime: uint40(block.timestamp), // The stream starts now ``` ### Token The contract address of the ERC-20 token used for streaming. In this example, we will stream `USDC`: ```solidity token: USDC, // The token to be streamed ``` ### Transferable Boolean that indicates whether the stream will be transferable or not. ```solidity transferable: true // Whether the stream will be transferable or not ``` ### Invoke the create function With all the parameters, we can call the `create` function on the `FLOW` contract and assign the newly created stream to `streamId` variable: ```solidity streamId = FLOW.create({ sender: msg.sender, // The sender will be able to manage the stream recipient: address(0xCAFE), // The recipient of the streamed tokens ratePerSecond: ratePerSecond, // The rate per second equivalent to 1000 USDC per month startTime: uint40(block.timestamp), // The stream starts now token: USDC, // The token to be streamed transferable: true // Whether the stream will be transferable or not }); ``` ## Full code Below you can see the complete `FlowStreamCreator` contract: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UD21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; import { FlowUtilities } from "./FlowUtilities.sol"; contract FlowStreamCreator { // Mainnet addresses IERC20 public constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); ISablierFlow public constant FLOW = ISablierFlow(0x844344Cd871B28221d725ecE9630E8bDE4E3a181); // Create a stream that sends 1000 USDC per month. function createStream_1K_PerMonth() external returns (uint256 streamId) { UD21x18 ratePerSecond = FlowUtilities.ratePerSecondWithDuration({ token: address(USDC), amount: 1000e6, duration: 30 days }); streamId = FLOW.create({ sender: msg.sender, // The sender will be able to manage the stream recipient: address(0xCAFE), // The recipient of the streamed tokens ratePerSecond: ratePerSecond, // The rate per second equivalent to 1000 USDC per month startTime: uint40(block.timestamp), // The stream starts now token: USDC, // The token to be streamed transferable: true // Whether the stream will be transferable or not }); } // Create a stream that sends 1,000,000 USDC per year. function createStream_1M_PerYear() external returns (uint256 streamId) { UD21x18 ratePerSecond = FlowUtilities.ratePerSecondWithDuration({ token: address(USDC), amount: 1_000_000e6, duration: 365 days }); streamId = FLOW.create({ sender: msg.sender, // The sender will be able to manage the stream recipient: address(0xCAFE), // The recipient of the streamed tokens ratePerSecond: ratePerSecond, // The rate per second equivalent to 1,000,00 USDC per year startTime: uint40(block.timestamp), // The stream starts now token: USDC, // The token to be streamed transferable: true // Whether the stream will be transferable or not }); } } ``` --- ## Calculate Rate per Second Source: https://docs.sablier.com/guides/flow/examples/flow-calculate-rps # Calculate Rate per Second This guide explains how to calculate the rate per second when creating a Flow stream. It is the most important step in setting up a stream since the rate per second is a key parameter in the stream's configuration. We assume that you have already gone through the [Protocol Concepts](/concepts/streaming) and the [Flow Overview](/concepts/flow/overview) sections. :::caution The code in this guide is not production-ready, and is implemented in a simplistic manner for the purpose of learning. ::: The rate per second is the amount of tokens streamed in one second. It is represented as a fixed-point number with 18 decimals, specifically as a `UD21x18` type from the `PRBMath` library. The underlying native Solidity type associated with `UD21x18` is `uint128`. Depending on how you receive payments, you have to calculate the rate per second and scale its value to 18 decimals format as below: 1. Based on a duration, e.g., 3 months 2. Between two points in time, e.g., January 1, 2025 to April, 1 2025 The calculation method is the same in either case. ## Set up a library Declare the Solidity version used to compile the library: ```solidity pragma solidity >=0.8.22; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; /// @dev A utility library to calculate rate per second and streamed amount based on a given time frame. library FlowUtilities { /// @notice This function calculates the rate per second based on a given amount of tokens and a specified duration. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / duration`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param duration The duration in seconds user wishes to stream. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondWithDuration( address token, uint128 amount, uint40 duration ) internal view returns (UD21x18 ratePerSecond) { // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); // If the token has 18 decimals, we can simply divide the amount by the duration as it returns a 18 decimal // fixed-point number. if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the rate per second based on a given amount of tokens and a specified range. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / (end - start)`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param start The start timestamp. /// @param end The end timestamp. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondForTimestamps( address token, uint128 amount, uint40 start, uint40 end ) internal view returns (UD21x18 ratePerSecond) { // Calculate the duration. uint40 duration = end - start; // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } } ``` Import the relevant symbols: ```solidity import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; ``` Declare a library that can be used in other contracts: ```solidity library FlowUtilities { /// @notice This function calculates the rate per second based on a given amount of tokens and a specified duration. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / duration`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param duration The duration in seconds user wishes to stream. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondWithDuration( address token, uint128 amount, uint40 duration ) internal view returns (UD21x18 ratePerSecond) { // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); // If the token has 18 decimals, we can simply divide the amount by the duration as it returns a 18 decimal // fixed-point number. if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the rate per second based on a given amount of tokens and a specified range. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / (end - start)`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param start The start timestamp. /// @param end The end timestamp. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondForTimestamps( address token, uint128 amount, uint40 start, uint40 end ) internal view returns (UD21x18 ratePerSecond) { // Calculate the duration. uint40 duration = end - start; // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } } ``` ## Calculate the rate per second on a duration Define a function called `ratePerSecondWithDuration` that takes the following parameters and the returned value: ```solidity function ratePerSecondWithDuration( address token, uint128 amount, uint40 duration ) internal view returns (UD21x18 ratePerSecond) ``` First, retrieve the token's decimals. Note that not all ERC-20 tokens use the 18-decimal standard. ```solidity uint8 decimals = IERC20Metadata(token).decimals(); // If the token has 18 decimals, we can simply divide the amount by the duration as it returns a 18 decimal // fixed-point number. if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the rate per second based on a given amount of tokens and a specified range. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / (end - start)`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param start The start timestamp. /// @param end The end timestamp. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondForTimestamps( address token, uint128 amount, uint40 start, uint40 end ) internal view returns (UD21x18 ratePerSecond) { // Calculate the duration. uint40 duration = end - start; // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } } ``` If the token uses 18 decimals, simply divide the amount by the duration: ```solidity if (decimals == 18) { return ud21x18(amount / duration); } ``` If the token has less than 18 decimals, calculate the scale factor from the token's decimals: ```solidity uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the rate per second based on a given amount of tokens and a specified range. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / (end - start)`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param start The start timestamp. /// @param end The end timestamp. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondForTimestamps( address token, uint128 amount, uint40 start, uint40 end ) internal view returns (UD21x18 ratePerSecond) { // Calculate the duration. uint40 duration = end - start; // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } } ``` Then, multiply the amount by the scale factor and divide it by the duration: ```solidity ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the rate per second based on a given amount of tokens and a specified range. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / (end - start)`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param start The start timestamp. /// @param end The end timestamp. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondForTimestamps( address token, uint128 amount, uint40 start, uint40 end ) internal view returns (UD21x18 ratePerSecond) { // Calculate the duration. uint40 duration = end - start; // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } } ``` ## Calculate the rate per second on timestamps Here, there are two time parameters, a start and an end time, instead of a duration. Let's define the function: ```solidity function ratePerSecondForTimestamps( address token, uint128 amount, uint40 start, uint40 end ) internal view returns (UD21x18 ratePerSecond) ``` The first step is to calculate the duration between the two timestamps: ```solidity uint40 duration = end - start; // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } } ``` The remaining logic is identical to the duration-based calculation: ```solidity uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); ``` ## Additional utilities To calculate earnings for specific durations from an existing stream, you can use the following functions: ```solidity function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } ``` ## Full code Below you can see the complete `FlowUtilities` library: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { ud21x18, UD21x18 } from "@prb/math/src/UD21x18.sol"; /// @dev A utility library to calculate rate per second and streamed amount based on a given time frame. library FlowUtilities { /// @notice This function calculates the rate per second based on a given amount of tokens and a specified duration. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / duration`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param duration The duration in seconds user wishes to stream. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondWithDuration( address token, uint128 amount, uint40 duration ) internal view returns (UD21x18 ratePerSecond) { // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); // If the token has 18 decimals, we can simply divide the amount by the duration as it returns a 18 decimal // fixed-point number. if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the rate per second based on a given amount of tokens and a specified range. /// @dev The rate per second is a 18-decimal fixed-point number and it is calculated as `amount / (end - start)`. /// @param token The address of the token. /// @param amount The amount of tokens, denoted in token's decimals. /// @param start The start timestamp. /// @param end The end timestamp. /// @return ratePerSecond The rate per second as a 18-decimal fixed-point number. function ratePerSecondForTimestamps( address token, uint128 amount, uint40 start, uint40 end ) internal view returns (UD21x18 ratePerSecond) { // Calculate the duration. uint40 duration = end - start; // Get the decimals of the token. uint8 decimals = IERC20Metadata(token).decimals(); if (decimals == 18) { return ud21x18(amount / duration); } // Calculate the scale factor from the token's decimals. uint128 scaleFactor = uint128(10 ** (18 - decimals)); // Multiply the amount by the scale factor and divide by the duration. ratePerSecond = ud21x18((scaleFactor * amount) / duration); } /// @notice This function calculates the amount streamed over a week for a given rate per second. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerWeek The amount streamed over a week. function calculateAmountStreamedPerWeek(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerWeek) { amountPerWeek = ratePerSecond.unwrap() * 1 weeks; } /// @notice This function calculates the amount streamed over a month for a given rate per second. /// @dev For simplicity, we have assumed that there are 30 days in a month. /// @param ratePerSecond The rate per second as a 18-decimal fixed-point number. /// @return amountPerMonth The amount streamed over a month. function calculateAmountStreamedPerMonth(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerMonth) { amountPerMonth = ratePerSecond.unwrap() * 30 days; } /// @notice This function calculates the amount streamed over a year for a given rate per second. /// @dev For simplicity, we have assumed that there are 365 days in a year. /// @param ratePerSecond The rate per second as a fixed-point number. /// @return amountPerYear The amount streamed over a year. function calculateAmountStreamedPerYear(UD21x18 ratePerSecond) internal pure returns (uint128 amountPerYear) { amountPerYear = ratePerSecond.unwrap() * 365 days; } } ``` --- ## Configure Your Local Environment Source: https://docs.sablier.com/guides/flow/examples/local-environment # Configure Your Local Environment In this guide, we will go through the steps to set up a local development environment for building onchain integrations with Flow. We will use Foundry to install Flow as a dependency, and run a simple test. At the end, you’ll have a development environment set up that you can use to build the rest of the examples under "Guides", or start your own integration project. ## Pre-requisites You will need the following software on your machine: - [Git](https://git-scm.com/downloads) - [Foundry](https://github.com/foundry-rs/foundry) - [Node.js](https://nodejs.org/en/download) - [Bun](https://bun.sh) In addition, familiarity with [Ethereum](https://ethereum.org/) and [Solidity](https://soliditylang.org/) is requisite. ## Set up using integration template :::tip Make sure you are using the latest version of Foundry by running `foundryup`. ::: We put together a template repository that you can use to get started quickly. This repository features a basic project structure, pre-configured Flow imports, and a selection of sample contracts and tests. To install the template, simply execute the following commands: ```bash $ mkdir flow-integration-template $ cd flow-integration-template $ forge init --template sablier-labs/flow-integration-template $ bun install ``` Then, hop to the [Run a Fork Test](#run-a-fork-test) section to complete your set up and start developing. ## Set up using Foundry template Foundry is a popular development toolkit for Ethereum projects, which we have used to build the Flow Protocol. For the purposes of this guide, Foundry will provide us with the tooling needed to compile and test our contracts. Let's use this command to spin up a new Foundry project: ```bash $ forge init my-project $ cd my-project ``` Once the initialization completes, take a look around at what got set up: ```bash ├── foundry.toml ├── script ├── src └── test ``` The folder structure should be intuitive: - `src` is where you'll write Solidity contracts - `test` is where you'll write tests (also in Solidity) - `script` is where you'll write scripts to perform actions like deploying contracts (you guessed it, in Solidity) - `foundry.toml` is where you can configure your Foundry settings, which we will leave as is in this guide :::note You might notice that the CLI is `forge` rather than `foundry`. This is because Foundry is a toolkit, and `forge` is just one of the tools that comes with it. ::: ## Install via npm package Let's install the Flow Node.js packages using Bun: ```bash $ bun add @sablier/flow ``` Bun will download the Flow contracts, along with their dependencies, and put them in the `node_modules` directory. That's it! You should now have a functional development environment to start building onchain Flow integrations. Let's run a quick test to confirm everything is set up properly. ## Write your first contract Delete the `src/Counter.sol` and `test/Counter.t.sol` files generated by Forge, and create two new files: `src/StreamCreator.sol` and `test/StreamCreator.t.sol`. Paste the following code into `src/StreamCreator.sol`: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UD21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; /// @title FlowStreamCreator /// @dev This contract allows users to create Sablier flow streams. contract FlowStreamCreator { IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierFlow public constant FLOW = ISablierFlow(0x7a86d3e6894f9c5B5f25FFBDAaE658CFc7569623); /// @notice Creates a new Sablier flow stream without upfront deposit. function createFlowStream() external returns (uint256 streamId) { // Create the flow stream using the `create` function. streamId = FLOW.create({ sender: msg.sender, // The sender will be able to pause the stream or change rate per second recipient: address(0xCAFE), // The recipient of the streamed tokens ratePerSecond: UD21x18.wrap(1_157_407_407_407_407), // Equivalent to 100e18 DAI per day startTime: uint40(block.timestamp), // The stream starts now token: DAI, // The streaming token transferable: true // Whether the stream will be transferable or not }); } /// @notice Creates a new Sablier flow stream with some upfront deposit. /// @dev Before calling this function, the user must first approve this contract to spend the tokens from the user's /// address. function createFlowStreamAndDeposit(uint128 depositAmount) external returns (uint256 streamId) { // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Flow contract to spend DAI DAI.approve(address(FLOW), depositAmount); // Create the flow stream using the `createAndDeposit` function which would also deposit tokens into the stream. streamId = FLOW.createAndDeposit({ sender: msg.sender, // The sender will be able to pause the stream or change rate per second recipient: address(0xCAFE), // The recipient of the streamed tokens ratePerSecond: UD21x18.wrap(1_157_407_407_407_407), // Equivalent to 100e18 DAI per day startTime: uint40(block.timestamp), // The stream starts now token: DAI, // The streaming token transferable: true, // Whether the stream will be transferable or not amount: depositAmount // The amount to deposit into the stream }); } } ``` Let's use Forge to compile this contract: ```bash $ forge build ``` If the contract was compiled correctly, you should see this message: ```bash [⠢] Compiling... [⠰] Compiling 62 files with Solc 0.8.29 [⠒] Solc 0.8.29 finished in 798.47ms Compiler run successful! ``` :::info The minimum Solidity version supported by the Flow contracts is v0.8.22. ::: ## Run a fork test Foundry offers native support for running tests against a fork of Ethereum Mainnet, testnets and L2s, which is useful when building and testing integrations with onchain protocols like Sablier. In practice, this enables you to access all Sablier contracts deployed on Ethereum, and use them for testing your integration. As a prerequisite, you will need an RPC that supports forking. A good solution for this is [Alchemy](https://alchemy.com/), as it includes forking in its free tier plan. Once you have obtained your RPC, you can proceed to run the following test: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { Test } from "forge-std/src/Test.sol"; import { FlowStreamCreator } from "../src/FlowStreamCreator.sol"; contract FlowStreamCreatorTest is Test { // Test contracts FlowStreamCreator internal creator; address internal user; function setUp() public { // Fork Ethereum Mainnet at the latest block vm.createSelectFork({ urlOrAlias: "mainnet" }); // Deploy the stream creator contract creator = new FlowStreamCreator(); // Create a test user user = payable(makeAddr("User")); vm.deal({ account: user, newBalance: 1 ether }); // Mint some DAI tokens to the test user. deal({ token: address(creator.DAI()), to: user, give: 1337e18 }); // Make the test user the `msg.sender` in all following calls vm.startPrank({ msgSender: user }); } function test_CreateFlowStream() public { uint256 expectedStreamId = creator.FLOW().nextStreamId(); uint256 actualStreamId = creator.createFlowStream(); // Check that creating flow stream works by checking the stream ids assertEq(actualStreamId, expectedStreamId); // Check that stream is created with no initial balance assertEq(creator.FLOW().getBalance(actualStreamId), 0); } function test_CreateFlowStreamAndDeposit() public { // Approve the creator contract to pull DAI tokens from the test user creator.DAI().approve({ spender: address(creator), value: 1337e18 }); uint256 expectedStreamId = creator.FLOW().nextStreamId(); uint256 actualStreamId = creator.createFlowStreamAndDeposit({ depositAmount: 1337e18 }); // Check that creating flow stream works by checking the stream ids assertEq(actualStreamId, expectedStreamId); // Check that the stream is created with the deposit balance assertEq(creator.FLOW().getBalance(actualStreamId), 1337e18); } } ``` You can run the test using Forge: ```bash $ forge test ``` If the test passed, you should see a message like this: ```bash Ran 2 tests for test/FlowStreamCreator.t.sol:FlowStreamCreatorTest [PASS] test_CreateFlowStream() (gas: 246830) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 626.58ms (500.67µs CPU time) ``` ## Next steps Congratulations! Your environment is now configured, and you are prepared to start building. Explore the guides section to discover various features available for Flow integration. Remember to include all contracts (`.sol` files) in the `src` folder and their corresponding tests in the `test` folder. As far as Foundry is concerned, there is much more to uncover. If you want to learn more about it, check out the [Foundry Book](https://book.getfoundry.sh/), which contains numerous examples and tutorials. A deep understanding of Foundry will enable you to create more sophisticated integrations with Flow protocol. --- ## Managing a Stream Source: https://docs.sablier.com/guides/flow/examples/stream-management # Managing a Stream This section will guide you through the different functions of Flow and how to interact with them. Before diving in, please note the following: 1. We assume you are already familiar with [creating Flow streams](/guides/flow/examples/create-stream). 2. We also assume that the stream management contract is authorized to invoke each respective function. To learn more about access control in Flow, see the [Access Control](/reference/flow/access-control) guide. :::caution The code in this guide is not production-ready, and is implemented in a simplistic manner for the purpose of learning. ::: # Set up your contract Declare the Solidity version used to compile the contract: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Import the relevant symbols from `@sablier/flow` and `@prb/math`: ```solidity import { ud21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; ``` Declare a contract `FlowStreamManager` and add the Flow address as a constant: ```solidity // Mainnet address ISablierFlow public constant FLOW = ISablierFlow(0x844344Cd871B28221d725ecE9630E8bDE4E3a181); ``` 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, Flow addresses can be obtained from the [Flow Deployments](/guides/flow/deployments) page. ## Deposit Depositing into streams means adding tokens to the stream, which will then be distributed to the recipient based on the value of rate per second. :::info A deposit is also referred to as a top-up. ::: There are three deposit functions: 1. [`deposit`](/reference/flow/contracts/contract.SablierFlow#deposit): deposits an amount of tokens. 2. [`depositAndPause`](/reference/flow/contracts/contract.SablierFlow#depositandpause): deposits an amount of tokens and then pauses the stream. ```solidity function deposit(uint256 streamId) external { FLOW.deposit({ streamId: streamId, amount: 3.14159e18, sender: msg.sender, recipient: address(0xCAFE) }); } function depositAndPause(uint256 streamId) external { FLOW.depositAndPause(streamId, 3.14159e18); } ``` ## Withdraw The recipient of a stream can withdraw any amount, not exceeding the withdrawable amount. The recipient also has the option to withdraw the tokens to an alternate address of their choice. There are two withdrawal functions: 1. [`withdraw`](/reference/flow/contracts/contract.SablierFlow#withdraw): withdraws an amount of tokens not exceeding the withdrawable amount. 2. [`withdrawMax`](/reference/flow/contracts/contract.SablierFlow#withdrawmax): withdraws the entire withdrawable amount of tokens. :::note The `withdraw` functions requires a fee. Make sure to send the correct amount in `msg.value`, as shown below. ::: ```solidity function withdraw(uint256 streamId) external payable { uint256 fee = FLOW.calculateMinFeeWei(streamId); FLOW.withdraw{ value: fee }({ streamId: streamId, to: address(0xCAFE), amount: 2.71828e18 }); } function withdrawMax(uint256 streamId) external payable { uint256 fee = FLOW.calculateMinFeeWei(streamId); FLOW.withdrawMax{ value: fee }({ streamId: streamId, to: address(0xCAFE) }); } ``` ## Adjust Rate per Second Adjusting the rate per second means changing the amount of tokens that is streamed each second. ```solidity function adjustRatePerSecond(uint256 streamId) external { FLOW.adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ud21x18(0.0001e18) }); } ``` ## Pause Pausing a stream means setting the rate per second to zero, which means no more streaming. ```solidity function pause(uint256 streamId) external { FLOW.pause(streamId); } ``` ## Restart There are two restart functions: 1. [`restart`](/reference/flow/contracts/contract.SablierFlow#restart): restarts a stream. 2. [`restartAndDeposit`](/reference/flow/contracts/contract.SablierFlow#restartanddeposit): restarts a stream followed by depositing an amount of tokens into it. ```solidity function restart(uint256 streamId) external { FLOW.restart({ streamId: streamId, ratePerSecond: ud21x18(0.0001e18) }); } function restartAndDeposit(uint256 streamId) external { FLOW.restartAndDeposit({ streamId: streamId, ratePerSecond: ud21x18(0.0001e18), amount: 2.71828e18 }); } ``` ## Refund There are three refund functions: 1. [`refund`](/reference/flow/contracts/contract.SablierFlow#refund): refunds an amount of tokens not exceeding the refundable amount. 2. [`refundAndPause`](/reference/flow/contracts/contract.SablierFlow#refundandpause): refunds an amount of tokens, and then pauses the stream. 3. [`refundMax`](/reference/flow/contracts/contract.SablierFlow#refundmax): refunds the entire refundable amount of tokens. ```solidity function refund(uint256 streamId) external { FLOW.refund({ streamId: streamId, amount: 1.61803e18 }); } function refundAndPause(uint256 streamId) external { FLOW.refundAndPause({ streamId: streamId, amount: 1.61803e18 }); } function refundMax(uint256 streamId) external { FLOW.refundMax(streamId); } ``` ## Void Voiding a stream means permanently stopping it from streaming any tokens. This is slightly different from pausing a stream because it also sets the stream's uncovered debt to zero. ```solidity function void(uint256 streamId) external { FLOW.void(streamId); } ``` ## Full code Below you can see the complete `FlowStreamManager` contract: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { ud21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; contract FlowStreamManager { // Mainnet address ISablierFlow public constant FLOW = ISablierFlow(0x844344Cd871B28221d725ecE9630E8bDE4E3a181); function adjustRatePerSecond(uint256 streamId) external { FLOW.adjustRatePerSecond({ streamId: streamId, newRatePerSecond: ud21x18(0.0001e18) }); } function deposit(uint256 streamId) external { FLOW.deposit({ streamId: streamId, amount: 3.14159e18, sender: msg.sender, recipient: address(0xCAFE) }); } function depositAndPause(uint256 streamId) external { FLOW.depositAndPause(streamId, 3.14159e18); } function pause(uint256 streamId) external { FLOW.pause(streamId); } function refund(uint256 streamId) external { FLOW.refund({ streamId: streamId, amount: 1.61803e18 }); } function refundAndPause(uint256 streamId) external { FLOW.refundAndPause({ streamId: streamId, amount: 1.61803e18 }); } function refundMax(uint256 streamId) external { FLOW.refundMax(streamId); } function restart(uint256 streamId) external { FLOW.restart({ streamId: streamId, ratePerSecond: ud21x18(0.0001e18) }); } function restartAndDeposit(uint256 streamId) external { FLOW.restartAndDeposit({ streamId: streamId, ratePerSecond: ud21x18(0.0001e18), amount: 2.71828e18 }); } function void(uint256 streamId) external { FLOW.void(streamId); } function withdraw(uint256 streamId) external payable { uint256 fee = FLOW.calculateMinFeeWei(streamId); FLOW.withdraw{ value: fee }({ streamId: streamId, to: address(0xCAFE), amount: 2.71828e18 }); } function withdrawMax(uint256 streamId) external payable { uint256 fee = FLOW.calculateMinFeeWei(streamId); FLOW.withdrawMax{ value: fee }({ streamId: streamId, to: address(0xCAFE) }); } } ``` --- ## Gas Benchmarks Source: https://docs.sablier.com/guides/flow/gas-benchmarks # Gas Benchmarks The gas usage of the Flow protocol is not deterministic and varies by user. Calls to third-party contracts, such as ERC-20 tokens, may use an arbitrary amount of gas. The values in the table below are rough estimations on Ethereum mainnet - you shouldn't take them for granted. The gas usage may vary depending on the network. :::note The benchmarks were generated using the code in this [GitHub repository](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/benchmarks). ::: ## SablierFlow With USDC as the streaming token. | Function | Stream Solvency | Gas Usage | | :-------------------- | :-------------- | :-------- | | \`adjustRatePerSecond\` | N/A | 44,520 | | \`create\` | N/A | 127,027 | | \`deposit\` | N/A | 37,028 | | \`pause\` | N/A | 8312 | | \`refund\` | Solvent | 24,767 | | \`refundMax\` | Solvent | 25,802 | | \`restart\` | N/A | 7536 | | \`void\` | Solvent | 10,100 | | \`void\` | Insolvent | 37,601 | | \`withdraw\` | Insolvent | 69,289 | | \`withdraw\` | Solvent | 47,757 | | \`withdrawMax\` | Solvent | 61,693 | --- ## Sablier Flow Source: https://docs.sablier.com/guides/flow/overview # Sablier Flow Welcome to the Sablier Flow documentation. This section contains detailed guides and technical references for the Flow protocol. These documents offer insight into the operational nuances of the contracts, providing a valuable resource for building onchain integrations. # Guides If you are new to Sablier, we recommend you start with the [Concepts](/concepts/what-is-sablier) section first. If you want to setup your local environment, head over to [the guide](/guides/flow/examples/local-environment). # Reference For a deeper dive into the protocol specifications, read through the [technical reference](/reference/flow/diagrams). # Resources - [Source Code](https://github.com/sablier-labs/evm-monorepo/blob/main/flow) - [Integration Templates](https://github.com/sablier-labs/flow-integration-template) - [Examples](https://github.com/sablier-labs/evm-monorepo/tree/main/misc/examples/flow/) - [Foundry Book](https://book.getfoundry.sh/) --- ## Flow v1.0 Source: https://docs.sablier.com/guides/flow/previous-deployments/v1.0 # Flow v1.0 This section contains the deployment addresses for the v1.0 release of [@sablier/flow@1.0.0](https://npmjs.com/package/@sablier/flow/v/1.0.0). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Flow protocol. See the latest version [here](/guides/flow/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x2d9221a63e12aa796619cb381ec4a71b201281f5`](https://etherscan.io/address/0x2d9221a63e12aa796619cb381ec4a71b201281f5) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xb69b27073fa0366cddf432f5976c34c9baf7eae6`](https://etherscan.io/address/0xb69b27073fa0366cddf432f5976c34c9baf7eae6) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x001F1408515Ccd5C1A19A682455ed4eFa39DadD6`](https://abscan.org/address/0x001F1408515Ccd5C1A19A682455ed4eFa39DadD6) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x20C9A3E27322Fc2b21Ced430D1B2e12d90804db6`](https://abscan.org/address/0x20C9A3E27322Fc2b21Ced430D1B2e12d90804db6) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x18a12a7035aa56240bcd236bc019aa245dcc015a`](https://arbiscan.io/address/0x18a12a7035aa56240bcd236bc019aa245dcc015a) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x900ebdb9ecfb19f9463d68d1fd6e5fa7ab9c6897`](https://arbiscan.io/address/0x900ebdb9ecfb19f9463d68d1fd6e5fa7ab9c6897) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x8c172e42c06302e3cfe555dc4d6b71a756ee186b`](https://snowscan.xyz/address/0x8c172e42c06302e3cfe555dc4d6b71a756ee186b) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x82ea83ab59b106c125168492cd468c322bd0d195`](https://snowscan.xyz/address/0x82ea83ab59b106c125168492cd468c322bd0d195) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x1a9adc0e2114c8407cc31669baafeee031d15dd2`](https://basescan.org/address/0x1a9adc0e2114c8407cc31669baafeee031d15dd2) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x8e64f389a4697e004647162ec6ea0a7779d5d899`](https://basescan.org/address/0x8e64f389a4697e004647162ec6ea0a7779d5d899) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xfdac2799644141856e20e021ac06f231cafc731f`](https://blastscan.io/address/0xfdac2799644141856e20e021ac06f231cafc731f) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xb40624ce2af67227529f713bac46e2b7064b7b92`](https://blastscan.io/address/0xb40624ce2af67227529f713bac46e2b7064b7b92) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xfce01f79247cf450062545e7155d7bd568551d0e`](https://bscscan.com/address/0xfce01f79247cf450062545e7155d7bd568551d0e) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xbc6fdd3f59900b9fcd445f8df159e2e794f098ec`](https://bscscan.com/address/0xbc6fdd3f59900b9fcd445f8df159e2e794f098ec) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x9EfC8663cAB0e2d97ad17C9fbfc8392445517E94`](https://chiliscan.com/address/0x9EfC8663cAB0e2d97ad17C9fbfc8392445517E94) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x3D664B2Da905DDD0Db931982FD9a759ea950D6e1`](https://chiliscan.com/address/0x3D664B2Da905DDD0Db931982FD9a759ea950D6e1) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Core Dao ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x447c6ea25540611541ff98fc677ca865f4e92450`](https://scan.coredao.org/address/0x447c6ea25540611541ff98fc677ca865f4e92450) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xbfaa055ecfe503e1323dc9fc26b7d3aa3bf54364`](https://scan.coredao.org/address/0xbfaa055ecfe503e1323dc9fc26b7d3aa3bf54364) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x5515f774a4db42820802333ba575f68a6e85bd13`](https://gnosisscan.io/address/0x5515f774a4db42820802333ba575f68a6e85bd13) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xc07c1128c19c2bf303b68ae061eff5293927630e`](https://gnosisscan.io/address/0xc07c1128c19c2bf303b68ae061eff5293927630e) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x46fa0164c5af9382d330e5a245a2ca8a18398950`](https://phoenix.lightlink.io/address/0x46fa0164c5af9382d330e5a245a2ca8a18398950) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xa2a48b83b6c96e1536336df9ead024d557a97a23`](https://phoenix.lightlink.io/address/0xa2a48b83b6c96e1536336df9ead024d557a97a23) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x949bFa08f1632432A2656a9dB17CA34d54Da8296`](https://lineascan.build/address/0x949bFa08f1632432A2656a9dB17CA34d54Da8296) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xF430f0d2f798c42fDFAc35b5e32BD4f63Bf51130`](https://lineascan.build/address/0xF430f0d2f798c42fDFAc35b5e32BD4f63Bf51130) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x75970dde488431fc4961494569def3269f20d6b3`](https://modescan.io/address/0x75970dde488431fc4961494569def3269f20d6b3) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x46fa0164c5af9382d330e5a245a2ca8a18398950`](https://modescan.io/address/0x46fa0164c5af9382d330e5a245a2ca8a18398950) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xfe6972d0ae797fae343e5a58d0c7d8513937f092`](https://explorer.morphl2.io/address/0xfe6972d0ae797fae343e5a58d0c7d8513937f092) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xab281bbc2bc34a1f202ddff17ffd1c00edf73f3a`](https://explorer.morphl2.io/address/0xab281bbc2bc34a1f202ddff17ffd1c00edf73f3a) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x906356e4e6410ea0a97dbc5b071cf394ab0dcd69`](https://optimistic.etherscan.io/address/0x906356e4e6410ea0a97dbc5b071cf394ab0dcd69) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xe674fb603d6f72b88bf297c1ba69f57b588a8f6d`](https://optimistic.etherscan.io/address/0xe674fb603d6f72b88bf297c1ba69f57b588a8f6d) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xcf2d812d5aad4e6fec3b05850ff056b21159d496`](https://polygonscan.com/address/0xcf2d812d5aad4e6fec3b05850ff056b21159d496) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x011277c87158e52cfbd8a1dd4a29118d602dda3a`](https://polygonscan.com/address/0x011277c87158e52cfbd8a1dd4a29118d602dda3a) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x66826f53bffeaab71adc7fe1a77e86f8268848d8`](https://scrollscan.com/address/0x66826f53bffeaab71adc7fe1a77e86f8268848d8) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x57fd892b3dc20eadb83cd8fb0240a87960046daa`](https://scrollscan.com/address/0x57fd892b3dc20eadb83cd8fb0240a87960046daa) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x4f5f9b3fb57bba43aaf90e3f71d8f8f384e88e20`](https://explorer.superseed.xyz/address/0x4f5f9b3fb57bba43aaf90e3f71d8f8f384e88e20) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xac2c36347869d8d779f7872c6202de3efd6ef2bb`](https://explorer.superseed.xyz/address/0xac2c36347869d8d779f7872c6202de3efd6ef2bb) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Taiko ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x3d303e4c61285f87da9f61aaadc8c89b7d55dfa2`](https://taikoscan.io/address/0x3d303e4c61285f87da9f61aaadc8c89b7d55dfa2) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xe790b6178612eeba6faeec16a2e1354c872f8bde`](https://taikoscan.io/address/0xe790b6178612eeba6faeec16a2e1354c872f8bde) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Tangle ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2`](https://explorer.tangle.tools/address/0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x2De92156000269fa2fde7544F10f01E8cBC80fFa`](https://explorer.tangle.tools/address/0x2De92156000269fa2fde7544F10f01E8cBC80fFa) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x015899a075B7C181e357Cd0ed000683DBB4F1FcE`](https://explorer.zksync.io/address/0x015899a075B7C181e357Cd0ed000683DBB4F1FcE) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x01C40608f2822816cF25a0a911c1df330487ba62`](https://explorer.zksync.io/address/0x01C40608f2822816cF25a0a911c1df330487ba62) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x781b3b2527f2a0a1e6b429161f2717a8a28b9f46`](https://sepolia.arbiscan.io/address/0x781b3b2527f2a0a1e6b429161f2717a8a28b9f46) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x9a08e6ae67c28002ee2c7cff9badecd33ae2151c`](https://sepolia.arbiscan.io/address/0x9a08e6ae67c28002ee2c7cff9badecd33ae2151c) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xd5f78708d83ac2bc8734a8cdf2d112c1bb3b62a2`](https://sepolia.basescan.org/address/0xd5f78708d83ac2bc8734a8cdf2d112c1bb3b62a2) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x168ad0b246f604bc70bef87ecde585c3f1d49617`](https://sepolia.basescan.org/address/0x168ad0b246f604bc70bef87ecde585c3f1d49617) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Linea Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xb0255ed1ee5c01dfe865c1b21bbf56a80f9ae739`](https://sepolia.lineascan.build/address/0xb0255ed1ee5c01dfe865c1b21bbf56a80f9ae739) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xcd8871a22640c57ba36984fb57e9c794f5df7f40`](https://sepolia.lineascan.build/address/0xcd8871a22640c57ba36984fb57e9c794f5df7f40) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x417db0f2bd020fc4d6bccea6b2bb6be0c541862e`](https://optimism-sepolia.blockscout.com/address/0x417db0f2bd020fc4d6bccea6b2bb6be0c541862e) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x28401987a23ed9b8926b07f3b6855222a70c2128`](https://optimism-sepolia.blockscout.com/address/0x28401987a23ed9b8926b07f3b6855222a70c2128) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x5ae8c13f6ae094887322012425b34b0919097d8a`](https://sepolia.etherscan.io/address/0x5ae8c13f6ae094887322012425b34b0919097d8a) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xbc4da2fbdfe5c5eaa11bd0e282201e2abf40b1ee`](https://sepolia.etherscan.io/address/0xbc4da2fbdfe5c5eaa11bd0e282201e2abf40b1ee) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### Superseed Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x905756b52efeaf75f6b1bb1bb0fc35eea15ae260`](https://sepolia-explorer.superseed.xyz/address/0x905756b52efeaf75f6b1bb1bb0fc35eea15ae260) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0xc43fb9fe4477d8e8bf68b9fd3a0163a4cffcbb31`](https://sepolia-explorer.superseed.xyz/address/0xc43fb9fe4477d8e8bf68b9fd3a0163a4cffcbb31) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | ### ZKsync Sepolia Testnet ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x8e70296F8972eBE94d885B1Caf94Da4836976140`](https://sepolia.explorer.zksync.io/address/0x8e70296F8972eBE94d885B1Caf94Da4836976140) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | | FlowNFTDescriptor | [`0x900277DBB45a04eB79028b3A44c650Ac81Ca86c4`](https://sepolia.explorer.zksync.io/address/0x900277DBB45a04eB79028b3A44c650Ac81Ca86c4) | [`flow-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.0) | --- ## Flow v1.1 Source: https://docs.sablier.com/guides/flow/previous-deployments/v1.1 # Flow v1.1 This section contains the deployment addresses for the v1.1 release of [@sablier/flow@1.1.1](https://npmjs.com/package/@sablier/flow/v/1.1.1). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Flow protocol. See the latest version [here](/guides/flow/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x3DF2AAEdE81D2F6b261F79047517713B8E844E04`](https://etherscan.io/address/0x3DF2AAEdE81D2F6b261F79047517713B8E844E04) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x24bE13897eE1F83367661B6bA616a72523fC55C9`](https://etherscan.io/address/0x24bE13897eE1F83367661B6bA616a72523fC55C9) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x555B0766f494c641bb522086da4E728AC08c1420`](https://abscan.org/address/0x555B0766f494c641bb522086da4E728AC08c1420) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x6CefdBc5Ba80937235F012c83d6aA83F1200d6cC`](https://abscan.org/address/0x6CefdBc5Ba80937235F012c83d6aA83F1200d6cC) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x87CF87ec5de33DeB4a88787065373563Ba85Ee72`](https://arbiscan.io/address/0x87CF87ec5de33DeB4a88787065373563Ba85Ee72) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x5F23eF12A7e861CB92c24B4314Af2A5F363CDD4F`](https://arbiscan.io/address/0x5F23eF12A7e861CB92c24B4314Af2A5F363CDD4F) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xac7CB985d4022A5Ebd4a385374ac3d3B487b3C63`](https://snowscan.xyz/address/0xac7CB985d4022A5Ebd4a385374ac3d3B487b3C63) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xb09b714B0feC83675E09fc997B7D532cF6620326`](https://snowscan.xyz/address/0xb09b714B0feC83675E09fc997B7D532cF6620326) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x6FE93c7f6cd1DC394e71591E3c42715Be7180A6A`](https://basescan.org/address/0x6FE93c7f6cd1DC394e71591E3c42715Be7180A6A) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x5b5e742305Be3A484EacCB124C83456463c24E6a`](https://basescan.org/address/0x5b5e742305Be3A484EacCB124C83456463c24E6a) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xA031544946ED769377128fBD961c9d621c4b4179`](https://berascan.com/address/0xA031544946ED769377128fBD961c9d621c4b4179) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x581250eE4311F7Dc1afCF965cF8024004B423e9E`](https://berascan.com/address/0x581250eE4311F7Dc1afCF965cF8024004B423e9E) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x16b50eb5eAeF0366f1A4da594e2A8c8943A297e0`](https://blastscan.io/address/0x16b50eb5eAeF0366f1A4da594e2A8c8943A297e0) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x92f1dB592C771D9Ec7708abFEe79771AbC1b4fAd`](https://blastscan.io/address/0x92f1dB592C771D9Ec7708abFEe79771AbC1b4fAd) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x4C4610aF3f3861EC99b6F6F8066C03E4C3a0E023`](https://bscscan.com/address/0x4C4610aF3f3861EC99b6F6F8066C03E4C3a0E023) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xAE557c04B46d47Ecac24edA63F22cabB4571Da61`](https://bscscan.com/address/0xAE557c04B46d47Ecac24edA63F22cabB4571Da61) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x28eAB88ee8a951F78e1028557D0C3fD97af61A33`](https://chiliscan.com/address/0x28eAB88ee8a951F78e1028557D0C3fD97af61A33) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xC7fd18CA19938d559dC45aDE362a850015CF0bd8`](https://chiliscan.com/address/0xC7fd18CA19938d559dC45aDE362a850015CF0bd8) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Core Dao ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xa0aE7F1bE0DB024Beda05c80722413EDDe7231Bd`](https://scan.coredao.org/address/0xa0aE7F1bE0DB024Beda05c80722413EDDe7231Bd) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x7293F2D4A4e676EF67C085E92277AdF560AECb88`](https://scan.coredao.org/address/0x7293F2D4A4e676EF67C085E92277AdF560AECb88) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x34Bc0C2BF1F2DA51c65cd821bA4133aFCacdb8f5`](https://gnosisscan.io/address/0x34Bc0C2BF1F2DA51c65cd821bA4133aFCacdb8f5) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x5A47FC8732d399a2f3845c4FC91aB91bb97da31F`](https://gnosisscan.io/address/0x5A47FC8732d399a2f3845c4FC91aB91bb97da31F) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x556d859DFEB58Ed3fa092B6526b09da6b74113e2`](https://hyperevmscan.io/address/0x556d859DFEB58Ed3fa092B6526b09da6b74113e2) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x81Cc8C4B57B9A60a56330d087D6854A8E17Dfc7A`](https://hyperevmscan.io/address/0x81Cc8C4B57B9A60a56330d087D6854A8E17Dfc7A) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x89d964E0b508234bCfDc7a32aE0aA0356f422B70`](https://phoenix.lightlink.io/address/0x89d964E0b508234bCfDc7a32aE0aA0356f422B70) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xc58E948Cb0a010105467C92856bcd4842B759fb1`](https://phoenix.lightlink.io/address/0xc58E948Cb0a010105467C92856bcd4842B759fb1) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xEFc6e4C7DC5faA0CfBFEbB5e04eA7Cd47f64012f`](https://lineascan.build/address/0xEFc6e4C7DC5faA0CfBFEbB5e04eA7Cd47f64012f) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x294D7fceBa43C4507771707CeBBB7b6d81d0BFdE`](https://lineascan.build/address/0x294D7fceBa43C4507771707CeBBB7b6d81d0BFdE) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xc968E8eEFe19BD6De8868df40D9740Be127a172a`](https://modescan.io/address/0xc968E8eEFe19BD6De8868df40D9740Be127a172a) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xD9E2822a33606741BeDbA31614E68A745e430102`](https://modescan.io/address/0xD9E2822a33606741BeDbA31614E68A745e430102) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xf31c8E7D9a0Bd310a9d5Fb317ba67BB1f0101c6D`](https://explorer.morphl2.io/address/0xf31c8E7D9a0Bd310a9d5Fb317ba67BB1f0101c6D) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x1dd4dcE2BB742908b4062E583d9c035973413A3F`](https://explorer.morphl2.io/address/0x1dd4dcE2BB742908b4062E583d9c035973413A3F) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xC5612feA2D370127ac67048115bd6b1dF7b7F7C0`](https://optimistic.etherscan.io/address/0xC5612feA2D370127ac67048115bd6b1dF7b7F7C0) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x7AD245b74bBC1B71Da1713D53238931F791b90A3`](https://optimistic.etherscan.io/address/0x7AD245b74bBC1B71Da1713D53238931F791b90A3) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x3e5c4130Ea7cfbD364FA5f170289d697865cA94b`](https://polygonscan.com/address/0x3e5c4130Ea7cfbD364FA5f170289d697865cA94b) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x87B836a9e26673feB3E409A0da2EAf99C79f26C3`](https://polygonscan.com/address/0x87B836a9e26673feB3E409A0da2EAf99C79f26C3) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xC4F104cE12cb12484Ff67cF0C4Bd0561F0014ec2`](https://scrollscan.com/address/0xC4F104cE12cb12484Ff67cF0C4Bd0561F0014ec2) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x797Fe78c41d9cbE81BBEA2f420101be5e47d2aFf`](https://scrollscan.com/address/0x797Fe78c41d9cbE81BBEA2f420101be5e47d2aFf) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Sei Network ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xdEF70082ebda4944A55311624900E42A720b4Ec9`](https://seiscan.io/address/0xdEF70082ebda4944A55311624900E42A720b4Ec9) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xF3D18b06c87735a58DAb3baC45af058b3772fD54`](https://seiscan.io/address/0xF3D18b06c87735a58DAb3baC45af058b3772fD54) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x63815da47C97063cc24b28D0b6F59234f71D5c96`](https://sonicscan.org/address/0x63815da47C97063cc24b28D0b6F59234f71D5c96) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xAab30e5CB903f67F109aFc7102ac8ED803681EA5`](https://sonicscan.org/address/0xAab30e5CB903f67F109aFc7102ac8ED803681EA5) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Sophon ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x20C9A3E27322Fc2b21Ced430D1B2e12d90804db6`](https://sophscan.xyz/address/0x20C9A3E27322Fc2b21Ced430D1B2e12d90804db6) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x2F1eB117A87217E8bE9AA96795F69c9e380686Db`](https://sophscan.xyz/address/0x2F1eB117A87217E8bE9AA96795F69c9e380686Db) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x40E75bb2F2aA3507D3a332872829c71be19eF623`](https://explorer.superseed.xyz/address/0x40E75bb2F2aA3507D3a332872829c71be19eF623) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xd932fDA016eE9d9F70f745544b4F56715b1E723b`](https://explorer.superseed.xyz/address/0xd932fDA016eE9d9F70f745544b4F56715b1E723b) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Taiko ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x9d4bc7f013cCddAE1658dc28F981C2D424d7F0Dd`](https://taikoscan.io/address/0x9d4bc7f013cCddAE1658dc28F981C2D424d7F0Dd) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x80Bde7C505eFE9960b673567CB25Cd8af85552BE`](https://taikoscan.io/address/0x80Bde7C505eFE9960b673567CB25Cd8af85552BE) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Tangle ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xcb099EfC90e88690e287259410B9AE63e1658CC6`](https://explorer.tangle.tools/address/0xcb099EfC90e88690e287259410B9AE63e1658CC6) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xDf578C2c70A86945999c65961417057363530a1c`](https://explorer.tangle.tools/address/0xDf578C2c70A86945999c65961417057363530a1c) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x9797B40340be0bFc9EC0dBb8712627Bcdd17E771`](https://uniscan.xyz/address/0x9797B40340be0bFc9EC0dBb8712627Bcdd17E771) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x89824A7e48dcf6B7AE9DeE6E566f62A5aDF037F2`](https://uniscan.xyz/address/0x89824A7e48dcf6B7AE9DeE6E566f62A5aDF037F2) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xD6482334242862951dA3E730F818c3f6E3f45A30`](https://xdcscan.com/address/0xD6482334242862951dA3E730F818c3f6E3f45A30) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x9D3F0122b260D2218ecf681c416495882003deDd`](https://xdcscan.com/address/0x9D3F0122b260D2218ecf681c416495882003deDd) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xE3747379bF7282e0ab5389A63eA053a5256042df`](https://explorer.zksync.io/address/0xE3747379bF7282e0ab5389A63eA053a5256042df) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x423C1b454250992Ede8516D36DE456F609714B53`](https://explorer.zksync.io/address/0x423C1b454250992Ede8516D36DE456F609714B53) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xF9cbfFAe10010475A2800a5eFC11f4D4780cA48d`](https://sepolia.arbiscan.io/address/0xF9cbfFAe10010475A2800a5eFC11f4D4780cA48d) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x3E64A31C3974b6ae9f09a8fbc784519bF551e795`](https://sepolia.arbiscan.io/address/0x3E64A31C3974b6ae9f09a8fbc784519bF551e795) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xFB6B72a5988A7701a9090C56936269241693a9CC`](https://sepolia.basescan.org/address/0xFB6B72a5988A7701a9090C56936269241693a9CC) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xcb5591F6d0e0fFC03037ef7b006D1361C6D33D25`](https://sepolia.basescan.org/address/0xcb5591F6d0e0fFC03037ef7b006D1361C6D33D25) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Linea Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x3D0804610dE1b8DC19B1DDf90C26d5a51ab2B6b6`](https://sepolia.lineascan.build/address/0x3D0804610dE1b8DC19B1DDf90C26d5a51ab2B6b6) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xbd17DFd74078dB49f12101Ca929b5153E924e9C7`](https://sepolia.lineascan.build/address/0xbd17DFd74078dB49f12101Ca929b5153E924e9C7) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x77873085a88189c8B82B3a01BcbC294108D02805`](https://optimism-sepolia.blockscout.com/address/0x77873085a88189c8B82B3a01BcbC294108D02805) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0x4739327acfb56E90177d44Cb0845e759276BCA88`](https://optimism-sepolia.blockscout.com/address/0x4739327acfb56E90177d44Cb0845e759276BCA88) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x93FE8f86e881a23e5A2FEB4B160514Fd332576A6`](https://sepolia.etherscan.io/address/0x93FE8f86e881a23e5A2FEB4B160514Fd332576A6) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xc9dBf2D207D178875b698e5f7493ce2d8BA88994`](https://sepolia.etherscan.io/address/0xc9dBf2D207D178875b698e5f7493ce2d8BA88994) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### Superseed Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x905756b52efeaf75f6b1bb1bb0fc35eea15ae260`](https://sepolia-explorer.superseed.xyz/address/0x905756b52efeaf75f6b1bb1bb0fc35eea15ae260) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xc43fb9fe4477d8e8bf68b9fd3a0163a4cffcbb31`](https://sepolia-explorer.superseed.xyz/address/0xc43fb9fe4477d8e8bf68b9fd3a0163a4cffcbb31) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | ### ZKsync Sepolia Testnet ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xf499b35e2e932a05ecD6115Aa4DcCeb29aF55E3D`](https://sepolia.explorer.zksync.io/address/0xf499b35e2e932a05ecD6115Aa4DcCeb29aF55E3D) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | | FlowNFTDescriptor | [`0xb3eCE4451825f865479813d42f74a080D2CcC928`](https://sepolia.explorer.zksync.io/address/0xb3eCE4451825f865479813d42f74a080D2CcC928) | [`flow-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v1.1) | --- ## Flow v2.0 Source: https://docs.sablier.com/guides/flow/previous-deployments/v2.0 # Flow v2.0 This section contains the deployment addresses for the v2.0 release of [@sablier/flow@2.0.1](https://npmjs.com/package/@sablier/flow/v/2.0.1). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Flow protocol. See the latest version [here](/guides/flow/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x7a86d3e6894f9c5b5f25ffbdaae658cfc7569623`](https://etherscan.io/address/0x7a86d3e6894f9c5b5f25ffbdaae658cfc7569623) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x24bE13897eE1F83367661B6bA616a72523fC55C9`](https://etherscan.io/address/0x24bE13897eE1F83367661B6bA616a72523fC55C9) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xc415425e56cc6c42b87bacffb276db2292cc1e50`](https://abscan.org/address/0xc415425e56cc6c42b87bacffb276db2292cc1e50) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x6CefdBc5Ba80937235F012c83d6aA83F1200d6cC`](https://abscan.org/address/0x6CefdBc5Ba80937235F012c83d6aA83F1200d6cC) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xf0f6477422a346378458f73cf02f05a7492e0c25`](https://arbiscan.io/address/0xf0f6477422a346378458f73cf02f05a7492e0c25) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x5F23eF12A7e861CB92c24B4314Af2A5F363CDD4F`](https://arbiscan.io/address/0x5F23eF12A7e861CB92c24B4314Af2A5F363CDD4F) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x64dc318ba879eca8222e963d319728f211c600c7`](https://snowscan.xyz/address/0x64dc318ba879eca8222e963d319728f211c600c7) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xb09b714B0feC83675E09fc997B7D532cF6620326`](https://snowscan.xyz/address/0xb09b714B0feC83675E09fc997B7D532cF6620326) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x8551208f75375abfaee1fbe0a69e390a94000ec2`](https://basescan.org/address/0x8551208f75375abfaee1fbe0a69e390a94000ec2) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x5b5e742305Be3A484EacCB124C83456463c24E6a`](https://basescan.org/address/0x5b5e742305Be3A484EacCB124C83456463c24E6a) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xb89cc68b2ef376ca1b9645f109f7a490b180cf1b`](https://berascan.com/address/0xb89cc68b2ef376ca1b9645f109f7a490b180cf1b) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x581250eE4311F7Dc1afCF965cF8024004B423e9E`](https://berascan.com/address/0x581250eE4311F7Dc1afCF965cF8024004B423e9E) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x13ce2ca4602d5d1dd323014cd5a4e8414d310a06`](https://blastscan.io/address/0x13ce2ca4602d5d1dd323014cd5a4e8414d310a06) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x92f1dB592C771D9Ec7708abFEe79771AbC1b4fAd`](https://blastscan.io/address/0x92f1dB592C771D9Ec7708abFEe79771AbC1b4fAd) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x5505c2397B0BeBEEE64919F21Df84F83C008C51b`](https://bscscan.com/address/0x5505c2397B0BeBEEE64919F21Df84F83C008C51b) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xAE557c04B46d47Ecac24edA63F22cabB4571Da61`](https://bscscan.com/address/0xAE557c04B46d47Ecac24edA63F22cabB4571Da61) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x21797da50e180d24d6a68e8be6f8daca1c06f0ee`](https://chiliscan.com/address/0x21797da50e180d24d6a68e8be6f8daca1c06f0ee) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xC7fd18CA19938d559dC45aDE362a850015CF0bd8`](https://chiliscan.com/address/0xC7fd18CA19938d559dC45aDE362a850015CF0bd8) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Core Dao ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x4cb7fb49e4b646b472a5609804004722b3b94f93`](https://scan.coredao.org/address/0x4cb7fb49e4b646b472a5609804004722b3b94f93) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x7293F2D4A4e676EF67C085E92277AdF560AECb88`](https://scan.coredao.org/address/0x7293F2D4A4e676EF67C085E92277AdF560AECb88) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Denergy ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xB2Fc49d89B72cD8Aadd7f07D602CF005D5A017Ea`](https://explorer.denergychain.com/address/0xB2Fc49d89B72cD8Aadd7f07D602CF005D5A017Ea) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x8C4bCE3A96CA4E1275B11FDcC38d00D142af2C3f`](https://explorer.denergychain.com/address/0x8C4bCE3A96CA4E1275B11FDcC38d00D142af2C3f) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xcdd3eb5283e4a675f16ba83e9d8c28c871a550a2`](https://gnosisscan.io/address/0xcdd3eb5283e4a675f16ba83e9d8c28c871a550a2) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x5A47FC8732d399a2f3845c4FC91aB91bb97da31F`](https://gnosisscan.io/address/0x5A47FC8732d399a2f3845c4FC91aB91bb97da31F) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x70ce7795896c1e226C71360F9d77B743d8302182`](https://hyperevmscan.io/address/0x70ce7795896c1e226C71360F9d77B743d8302182) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x81Cc8C4B57B9A60a56330d087D6854A8E17Dfc7A`](https://hyperevmscan.io/address/0x81Cc8C4B57B9A60a56330d087D6854A8E17Dfc7A) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x5f742f6becc61e76ae67b0dc29d58f5c964e2c78`](https://phoenix.lightlink.io/address/0x5f742f6becc61e76ae67b0dc29d58f5c964e2c78) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xc58E948Cb0a010105467C92856bcd4842B759fb1`](https://phoenix.lightlink.io/address/0xc58E948Cb0a010105467C92856bcd4842B759fb1) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x977FDf70abeD6b60eECcee85322beA4575B0b6Ed`](https://lineascan.build/address/0x977FDf70abeD6b60eECcee85322beA4575B0b6Ed) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x294D7fceBa43C4507771707CeBBB7b6d81d0BFdE`](https://lineascan.build/address/0x294D7fceBa43C4507771707CeBBB7b6d81d0BFdE) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xbed2f163cc0aa3278261ef1c3fa51b98db270829`](https://modescan.io/address/0xbed2f163cc0aa3278261ef1c3fa51b98db270829) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xD9E2822a33606741BeDbA31614E68A745e430102`](https://modescan.io/address/0xD9E2822a33606741BeDbA31614E68A745e430102) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Monad ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x0340a829b6dC3aDF7710a5bAF1970914af4977f5`](https://monadscan.com/address/0x0340a829b6dC3aDF7710a5bAF1970914af4977f5) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xf51BB8bd1cfc7C890dB68c39dCCA67CAd7810Ce4`](https://monadscan.com/address/0xf51BB8bd1cfc7C890dB68c39dCCA67CAd7810Ce4) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xbf407836021c993dfa27cb8232415d15faea709a`](https://explorer.morphl2.io/address/0xbf407836021c993dfa27cb8232415d15faea709a) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x1dd4dcE2BB742908b4062E583d9c035973413A3F`](https://explorer.morphl2.io/address/0x1dd4dcE2BB742908b4062E583d9c035973413A3F) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xd18491649440d6338532f260761cee64e79d7bb2`](https://optimistic.etherscan.io/address/0xd18491649440d6338532f260761cee64e79d7bb2) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x7AD245b74bBC1B71Da1713D53238931F791b90A3`](https://optimistic.etherscan.io/address/0x7AD245b74bBC1B71Da1713D53238931F791b90A3) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x62b6d5a3ac0cc91ecebd019d1c70fe955d8c7426`](https://polygonscan.com/address/0x62b6d5a3ac0cc91ecebd019d1c70fe955d8c7426) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x87B836a9e26673feB3E409A0da2EAf99C79f26C3`](https://polygonscan.com/address/0x87B836a9e26673feB3E409A0da2EAf99C79f26C3) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xc3e92b9714ed01b51fdc29bb88b17af5cddd2c22`](https://scrollscan.com/address/0xc3e92b9714ed01b51fdc29bb88b17af5cddd2c22) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x797Fe78c41d9cbE81BBEA2f420101be5e47d2aFf`](https://scrollscan.com/address/0x797Fe78c41d9cbE81BBEA2f420101be5e47d2aFf) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Sei Network ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x9eaf5a3f23964148a1321078f9cce4c2325c603e`](https://seiscan.io/address/0x9eaf5a3f23964148a1321078f9cce4c2325c603e) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xF3D18b06c87735a58DAb3baC45af058b3772fD54`](https://seiscan.io/address/0xF3D18b06c87735a58DAb3baC45af058b3772fD54) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x3954146884425accb86a6476dad69ec3591838cd`](https://sonicscan.org/address/0x3954146884425accb86a6476dad69ec3591838cd) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xAab30e5CB903f67F109aFc7102ac8ED803681EA5`](https://sonicscan.org/address/0xAab30e5CB903f67F109aFc7102ac8ED803681EA5) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xe91bbae6c7d67b7c5055de1c9635c17af056211b`](https://explorer.superseed.xyz/address/0xe91bbae6c7d67b7c5055de1c9635c17af056211b) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xd932fDA016eE9d9F70f745544b4F56715b1E723b`](https://explorer.superseed.xyz/address/0xd932fDA016eE9d9F70f745544b4F56715b1E723b) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x170ecc032c96aa976fa702e94fbc9fa5bb64ee7c`](https://uniscan.xyz/address/0x170ecc032c96aa976fa702e94fbc9fa5bb64ee7c) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x89824A7e48dcf6B7AE9DeE6E566f62A5aDF037F2`](https://uniscan.xyz/address/0x89824A7e48dcf6B7AE9DeE6E566f62A5aDF037F2) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x3F00b8334EBE2A85875D1F8b50a43a12db67ACAD`](https://xdcscan.com/address/0x3F00b8334EBE2A85875D1F8b50a43a12db67ACAD) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x9D3F0122b260D2218ecf681c416495882003deDd`](https://xdcscan.com/address/0x9D3F0122b260D2218ecf681c416495882003deDd) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xa7f02e692973b6315eaca7fb4285ad2536a89cd0`](https://explorer.zksync.io/address/0xa7f02e692973b6315eaca7fb4285ad2536a89cd0) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x423C1b454250992Ede8516D36DE456F609714B53`](https://explorer.zksync.io/address/0x423C1b454250992Ede8516D36DE456F609714B53) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x73a474c9995b659bc4736486f25501e0a4a671ed`](https://sepolia.arbiscan.io/address/0x73a474c9995b659bc4736486f25501e0a4a671ed) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x3E64A31C3974b6ae9f09a8fbc784519bF551e795`](https://sepolia.arbiscan.io/address/0x3E64A31C3974b6ae9f09a8fbc784519bF551e795) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x19e99dcdbaf2fbf43c60cfd026d571860da29d43`](https://sepolia.basescan.org/address/0x19e99dcdbaf2fbf43c60cfd026d571860da29d43) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xcb5591F6d0e0fFC03037ef7b006D1361C6D33D25`](https://sepolia.basescan.org/address/0xcb5591F6d0e0fFC03037ef7b006D1361C6D33D25) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0x4cc7b50b0856c607edee0b6547221360e82e768c`](https://optimism-sepolia.blockscout.com/address/0x4cc7b50b0856c607edee0b6547221360e82e768c) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0x4739327acfb56E90177d44Cb0845e759276BCA88`](https://optimism-sepolia.blockscout.com/address/0x4739327acfb56E90177d44Cb0845e759276BCA88) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierFlow | [`0xde489096eC9C718358c52a8BBe4ffD74857356e9`](https://sepolia.etherscan.io/address/0xde489096eC9C718358c52a8BBe4ffD74857356e9) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | | FlowNFTDescriptor | [`0xc9dBf2D207D178875b698e5f7493ce2d8BA88994`](https://sepolia.etherscan.io/address/0xc9dBf2D207D178875b698e5f7493ce2d8BA88994) | [`flow-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/flow/v2.0) | --- ## Codebase Source: https://docs.sablier.com/guides/legacy/codebase # Codebase ## Repository The Sablier Legacy code is hosted on GitHub and the source code for each contract is verified on Etherscan. [ Github - sablier-labs/sablier: Sablier Legacy protocolGitHub ](https://github.com/sablier-labs/legacy) ## ABIs Depending on the web3 library you're working with, you may need to get hold of the Lockup ABIs (application binary interfaces). The ABI acts as an interface between two program modules, one of which is the smart contract and the other the Ethereum virtual machine code. There are two ways to obtain it: 1. Copy `Sablier.json` from [sablier-labs/legacy-abis](https://github.com/sablier-labs/legacy-abis). 2. Clone [sablier-labs/legacy](https://github.com/sablier-labs/legacy) and compile the contract yourself. Here's an example for how to do step 2 with yarn and truffle: ```bash $ git clone git@github.com/sablier-labs/legacy.git sablier-legacy $ cd ./sablier-legacy $ yarn bootstrap $ cd ./packages/protocol $ truffle compile The `Sablier.json` artifact should be generated in the relative `build/contracts` folder. ``` --- ## Legacy Deployment Addresses Source: https://docs.sablier.com/guides/legacy/deployments # Legacy Deployment Addresses This section contains deployment addresses for Sablier Legacy, an old release that has been superseded by [Sablier Lockup](/guides/lockup/overview). Legacy is still accessible through the [legacy user interfaces](https://legacy-sender.sablier.com). ## Legacy v1.1 | Contract | Chain | Address | | --- | --- | --- | | Sablier.sol | Ethereum Mainnet | [0xCD18eAa163733Da39c232722cBC4E8940b1D8888](https://etherscan.io/address/0xCD18eAa163733Da39c232722cBC4E8940b1D8888) | | Sablier.sol | Arbitrum One | [0xaDB944B478818d95659067E70D2e5Fc43Fa3eDe9](https://arbiscan.io/address/0xaDB944B478818d95659067E70D2e5Fc43Fa3eDe9) | | Sablier.sol | Avalanche | [0x73f503fad13203C87889c3D5c567550b2d41D7a4](https://snowtrace.io/address/0x73f503fad13203C87889c3D5c567550b2d41D7a4) | | Sablier.sol | BNB Smart Chain | [0x05BC7f5fb7F248d44d38703e5C921A8c16825161](https://bscscan.com/address/0x05BC7f5fb7F248d44d38703e5C921A8c16825161) | | Sablier.sol | Optimism | [0x6C5927c0679e6d857E87367bb635decbcB20F31c](https://optimistic.etherscan.io/address/0x6C5927c0679e6d857E87367bb635decbcB20F31c) | | Sablier.sol | Polygon Mainnet | [0xAC18EAB6592F5fF6F9aCf5E0DCE0Df8E49124C06](https://polygonscan.com/address/0xAC18EAB6592F5fF6F9aCf5E0DCE0Df8E49124C06) | | Sablier.sol | Ronin | [0xDe9dCc27aa1552d591Fc9B9c21881feE43BD8118](https://explorer.roninchain.com/address/ronin:de9dcc27aa1552d591fc9b9c21881fee43bd8118) | | Sablier.sol | Goerli | [0xFc7E3a3073F88B0f249151192812209117C2014b](https://goerli.etherscan.io/address/0xFc7E3a3073F88B0f249151192812209117C2014b) | ## Legacy v1.0 _This is an outdated deployment_. | Contract | Chain | Address | | --- | --- | --- | | Payroll.sol | Ethereum Mainnet | [0xbd6a40Bb904aEa5a49c59050B5395f7484A4203d](https://etherscan.io/address/0xbd6a40Bb904aEa5a49c59050B5395f7484A4203d) | | Sablier.sol | Ethereum Mainnet | [0xA4fc358455Febe425536fd1878bE67FfDBDEC59a](https://etherscan.io/address/0xA4fc358455Febe425536fd1878bE67FfDBDEC59a) | ## Unofficial deployments Unofficial deployments are made by external teams, and they are not supported in the official user interface. | Chain | Address | Deployer | | --- | --- | --- | | IoTeX | [0x93Efd750a7F589f9FE26408a91e15587a88c4E78](https://iotexscout.io/address/0x93Efd750a7F589f9FE26408a91e15587a88c4E78) | [IoTeX team](https://twitter.com/iotex_io) | ## Testnet Tokens If you want to use the Sablier interfaces on a testnet, you need to get some testnet DAI first. To do this, you have to go to the Etherscan page of the associated token, tap the "Write Contract" tab, connect your Ethereum wallet and call the `mint` method. Note that the testnet token has 18 decimals, so you may want to use a [unit converter](https://tools.deth.net/token-unit-conversion). | Chain | Network | Ethereum address | | --- | --- | --- | | TestnetDAI | Goerli | [0x97cb342Cf2F6EcF48c1285Fb8668f5a4237BF862](https://goerli.etherscan.io/address/0x97cb342Cf2F6EcF48c1285Fb8668f5a4237BF862) | --- ## Gas Costs Source: https://docs.sablier.com/guides/legacy/gas-costs # Gas Costs The gas usage of Sablier Legacy is not deterministic and varies by user. Calls to third-party contracts, such as [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens, may use an arbitrary amount of gas. The values in the table below are rough estimations - you shouldn't take them for granted: | Action | Typical Gas Cost | | --- | --- | | Create stream | ~240K | | Withdraw from stream | ~80K | | Cancel stream | ~90K | --- ## Legacy Source: https://docs.sablier.com/guides/legacy/overview # Legacy :::warning Legacy has been superseded by [Sablier Lockup](/guides/lockup/overview). ::: This is a technical account on how to integrate Legacy into your own application. If you have any questions along the way, please join the #dev channel in the [Sablier Discord server](https://discord.sablier.com); our team, and members of the community, look forward to helping you. What we will cover: - Smart contract architecture and ABI. - Networks and typical gas costs. - How to create, withdraw from and cancel streams. --- ## Streams Source: https://docs.sablier.com/guides/legacy/streams # Streams Every interaction with Sablier is in relation to a specific "token stream". This is how we refer to a real-time payment. A token stream has six properties: 1. Sender. 2. Recipient. 3. Fixed deposit amount. 4. ERC-20 token used as streaming currency. 5. Start time 6. Stop Time ## 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 earned approximately 1,000 DAI. --- ## Lockup Deployments Source: https://docs.sablier.com/guides/lockup/deployments # Lockup Deployments This section contains the deployment addresses for the v4.0 release of [@sablier/lockup](https://npmjs.com/package/@sablier/lockup). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info [important] The Lockup Periphery repo has been discontinued in favor of the new [Airdrops repo](/guides/airdrops/overview). ::: ## Versions Any updates or additional features will require a new deployment of the protocol, due to its immutable nature. Came here looking for the previous Lockup deployments? Click below to see other versions. | Version | Release Date | UI Aliases | | --- | --- | --- | | [v4.0](/guides/lockup/deployments) (latest) | March 2026 | - `LK3` (Lockup) | | [v3.0](/guides/lockup/previous-deployments/v3.0) | October 2025 | - `LK2` (Lockup) | | [v2.0](/guides/lockup/previous-deployments/v2.0) | February 2025 | - `LK` (Lockup): all models have been merged into a single contract | | [v1.2](/guides/lockup/previous-deployments/v1.2) | July 2024 | - `LD3` (Lockup Dynamic) - `LL3` (Lockup Linear) - `LT3` (Lockup Tranched) | | [v1.1](/guides/lockup/previous-deployments/v1.1) | December 2023 | - `LD2` (Lockup Dynamic) - `LL2` (Lockup Linear) | | [v1.0](/guides/lockup/previous-deployments/v1.0) | July 2023 | - `LD` (Lockup Dynamic) - `LL` (Lockup Linear) | Or maybe you're looking for Legacy? [Click here](/guides/legacy/deployments). :::info Stay up to date with any new releases by [subscribing](https://x.com/Sablier/status/1821220784661995627) to the official Sablier repositories on Github. ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB`](https://etherscan.io/address/0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875`](https://etherscan.io/address/0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://etherscan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://etherscan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xA9dC6878C979B5cc1d98a1803F0664ad725A1f56`](https://etherscan.io/address/0xA9dC6878C979B5cc1d98a1803F0664ad725A1f56) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x2a8887a7Cc494e35EEB615df34026DBfaE027a5C`](https://abscan.org/address/0x2a8887a7Cc494e35EEB615df34026DBfaE027a5C) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x5Ed8660672aFDd2bb991b4c9097dbB2e0a6BcB6C`](https://abscan.org/address/0x5Ed8660672aFDd2bb991b4c9097dbB2e0a6BcB6C) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://abscan.org/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://abscan.org/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x63Ff2E370788C163D5a1909B5FCb299DB327AEF9`](https://abscan.org/address/0x63Ff2E370788C163D5a1909B5FCb299DB327AEF9) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xD103611856F3c2BbAe61D9bF138078794fC09C33`](https://arbiscan.io/address/0xD103611856F3c2BbAe61D9bF138078794fC09C33) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x629AC39918E97ccAF5908e74dEE1bFea0489FE95`](https://arbiscan.io/address/0x629AC39918E97ccAF5908e74dEE1bFea0489FE95) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://arbiscan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://arbiscan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xd5c6a0Dd2E1822865c308850b8b3E2CcE762D061`](https://arbiscan.io/address/0xd5c6a0Dd2E1822865c308850b8b3E2CcE762D061) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xB891b41533776Ec20f7738c647a11506AA44b8A8`](https://snowscan.xyz/address/0xB891b41533776Ec20f7738c647a11506AA44b8A8) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x3813bc15b71aEB1E4ea301c4DE37b43B0cd6b3aC`](https://snowscan.xyz/address/0x3813bc15b71aEB1E4ea301c4DE37b43B0cd6b3aC) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://snowscan.xyz/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://snowscan.xyz/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x906A4BD5dD0EF13654eA29bFD6185d0d64A4b674`](https://snowscan.xyz/address/0x906A4BD5dD0EF13654eA29bFD6185d0d64A4b674) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xc19a09A66887017F603E5dF420ed3Cb9a5c07C0A`](https://basescan.org/address/0xc19a09A66887017F603E5dF420ed3Cb9a5c07C0A) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xE001bae5Ef0d86362Ca258bff994a97CAE1245F5`](https://basescan.org/address/0xE001bae5Ef0d86362Ca258bff994a97CAE1245F5) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://basescan.org/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://basescan.org/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x87e437030b7439150605a641483de98672E26317`](https://basescan.org/address/0x87e437030b7439150605a641483de98672E26317) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x2455c72a4aFE3b0e2B26b5EFD7F8EFFE6B828C90`](https://berascan.com/address/0x2455c72a4aFE3b0e2B26b5EFD7F8EFFE6B828C90) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xfC8153d02464374C96683F09FE2B24065A03A64C`](https://berascan.com/address/0xfC8153d02464374C96683F09FE2B24065A03A64C) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://berascan.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://berascan.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x3bbE0a21792564604B0fDc00019532Adeffa70eb`](https://berascan.com/address/0x3bbE0a21792564604B0fDc00019532Adeffa70eb) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x6cd06Aaf06506bC3fF382d83023354E2B80EeD22`](https://bscscan.com/address/0x6cd06Aaf06506bC3fF382d83023354E2B80EeD22) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xF90ca72fa2CC584Db0Ee2F34ac2f54DAfBe7C045`](https://bscscan.com/address/0xF90ca72fa2CC584Db0Ee2F34ac2f54DAfBe7C045) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://bscscan.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://bscscan.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x56831a5a932793E02251126831174Ab8Bf2f7695`](https://bscscan.com/address/0x56831a5a932793E02251126831174Ab8Bf2f7695) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x003b2D58A97315CE9fB3888Db6BCB9770e73f398`](https://chiliscan.com/address/0x003b2D58A97315CE9fB3888Db6BCB9770e73f398) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xE47A0D9081035a6aB8e4f5c29B6BA396A303cA16`](https://chiliscan.com/address/0xE47A0D9081035a6aB8e4f5c29B6BA396A303cA16) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://chiliscan.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://chiliscan.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x8A96f827082FB349B6e268baa0a7A5584c4Ccda6`](https://chiliscan.com/address/0x8A96f827082FB349B6e268baa0a7A5584c4Ccda6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Denergy ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xB9636F3dc2Fc1B5Ad2a7323210084DBEeD7B2377`](https://explorer.denergychain.com/address/0xB9636F3dc2Fc1B5Ad2a7323210084DBEeD7B2377) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x29F5527C7CdBe1548dE5634D295FD7DAC3AD327D`](https://explorer.denergychain.com/address/0x29F5527C7CdBe1548dE5634D295FD7DAC3AD327D) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0x2eFEa83cAbF1c3357Ba7661a32AaE5bc74A5aFD6`](https://explorer.denergychain.com/address/0x2eFEa83cAbF1c3357Ba7661a32AaE5bc74A5aFD6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x2e5ad3dE34C0f36B3f96a7F740Db2d9b4BAd0b33`](https://explorer.denergychain.com/address/0x2e5ad3dE34C0f36B3f96a7F740Db2d9b4BAd0b33) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x4749dB4834be9b473D586Ad4d98133dAfC678313`](https://explorer.denergychain.com/address/0x4749dB4834be9b473D586Ad4d98133dAfC678313) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xF24e804B0Eb4fC0eAD41dF0e392D25fb230Bbab4`](https://gnosisscan.io/address/0xF24e804B0Eb4fC0eAD41dF0e392D25fb230Bbab4) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x653Cbc0cC19dCb43F06a0d0909835a9d7dec33dF`](https://gnosisscan.io/address/0x653Cbc0cC19dCb43F06a0d0909835a9d7dec33dF) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://gnosisscan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://gnosisscan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x3140a6900AA2FF3186730741ad8255ee4e6d8Ff1`](https://gnosisscan.io/address/0x3140a6900AA2FF3186730741ad8255ee4e6d8Ff1) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x5369E34C92EACC1cceaFFe1be01F057C68ca1b19`](https://hyperevmscan.io/address/0x5369E34C92EACC1cceaFFe1be01F057C68ca1b19) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xCBFaA5f5d0DB294eeb726334b16f75e4d7AfB5fe`](https://hyperevmscan.io/address/0xCBFaA5f5d0DB294eeb726334b16f75e4d7AfB5fe) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://hyperevmscan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://hyperevmscan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x7263d77e9e872f82A15e5E1a9816440D23758708`](https://hyperevmscan.io/address/0x7263d77e9e872f82A15e5E1a9816440D23758708) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xa39376a844dB3aA3fAaF119321b761cfE296fe19`](https://phoenix.lightlink.io/address/0xa39376a844dB3aA3fAaF119321b761cfE296fe19) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x2621819f84Ef7a8424e7A8Ff573b7Fe595856D41`](https://phoenix.lightlink.io/address/0x2621819f84Ef7a8424e7A8Ff573b7Fe595856D41) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://phoenix.lightlink.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://phoenix.lightlink.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xCFB5F90370A7884DEc59C55533782B45FA24f4d1`](https://phoenix.lightlink.io/address/0xCFB5F90370A7884DEc59C55533782B45FA24f4d1) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xFb898e1626c9B32F89fFB0FedD145B89590d219e`](https://lineascan.build/address/0xFb898e1626c9B32F89fFB0FedD145B89590d219e) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xa0B12148210bD25F739bEA4824A35F071989F60a`](https://lineascan.build/address/0xa0B12148210bD25F739bEA4824A35F071989F60a) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://lineascan.build/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://lineascan.build/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x1514a869D29a8B22961e8F9eBa3DC64000b96BCe`](https://lineascan.build/address/0x1514a869D29a8B22961e8F9eBa3DC64000b96BCe) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x43916BAb157B56124C46dC09D45A9516489D84B7`](https://modescan.io/address/0x43916BAb157B56124C46dC09D45A9516489D84B7) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x0d3cd659d417c45ac6A79db594848fCd34583671`](https://modescan.io/address/0x0d3cd659d417c45ac6A79db594848fCd34583671) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://modescan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://modescan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x64e7879558b6dfE2f510bd4b9Ad196ef0371EAA8`](https://modescan.io/address/0x64e7879558b6dfE2f510bd4b9Ad196ef0371EAA8) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Monad ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x82723C1ffEc9D43dE5FA80b25Da8df99AfD470ba`](https://monadscan.com/address/0x82723C1ffEc9D43dE5FA80b25Da8df99AfD470ba) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xB02d463F531c3eB1a92B18B9d4756e9d03AB2562`](https://monadscan.com/address/0xB02d463F531c3eB1a92B18B9d4756e9d03AB2562) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://monadscan.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://monadscan.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x37BA02a35861F7254fAE733E3a7cADD96D9D32A2`](https://monadscan.com/address/0x37BA02a35861F7254fAE733E3a7cADD96D9D32A2) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xA74F2Cf047A67509f332DD9B2D6D51989e546548`](https://explorer.morphl2.io/address/0xA74F2Cf047A67509f332DD9B2D6D51989e546548) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x9F8A023c90ab65cAf8eE4D7e1248E883e2B74A36`](https://explorer.morphl2.io/address/0x9F8A023c90ab65cAf8eE4D7e1248E883e2B74A36) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://explorer.morphl2.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://explorer.morphl2.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x660314f09ac3B65E216B6De288aAdc2599AF14e2`](https://explorer.morphl2.io/address/0x660314f09ac3B65E216B6De288aAdc2599AF14e2) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x945ba0D0EeAa5766d4bae5455a9817D7ae150550`](https://optimistic.etherscan.io/address/0x945ba0D0EeAa5766d4bae5455a9817D7ae150550) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x9A8486b4D65134197A42076559e4A8CA9917f289`](https://optimistic.etherscan.io/address/0x9A8486b4D65134197A42076559e4A8CA9917f289) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://optimistic.etherscan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://optimistic.etherscan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x41dBa1AfBB6DF91b3330dc009842327A9858Cbae`](https://optimistic.etherscan.io/address/0x41dBa1AfBB6DF91b3330dc009842327A9858Cbae) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xCEb5253Db890347D45778FB0834fb3c0B57aFf93`](https://polygonscan.com/address/0xCEb5253Db890347D45778FB0834fb3c0B57aFf93) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x052c85cB2Db728d5A7709D41f648016469C09388`](https://polygonscan.com/address/0x052c85cB2Db728d5A7709D41f648016469C09388) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://polygonscan.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://polygonscan.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xf5e12d0bA25FCa0D738Ec57f149736B2e4C46980`](https://polygonscan.com/address/0xf5e12d0bA25FCa0D738Ec57f149736B2e4C46980) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x9435E262A4A312d30D6C41fE055f648e91Af411e`](https://scrollscan.com/address/0x9435E262A4A312d30D6C41fE055f648e91Af411e) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x92CB40d8De23A4C657d03F4a926Dd684FfB74063`](https://scrollscan.com/address/0x92CB40d8De23A4C657d03F4a926Dd684FfB74063) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://scrollscan.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://scrollscan.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x00Ff6443E902874924dd217c1435e3be04f57431`](https://scrollscan.com/address/0x00Ff6443E902874924dd217c1435e3be04f57431) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xa697988451F921185A8c824aD4867DC8933C4ECB`](https://sonicscan.org/address/0xa697988451F921185A8c824aD4867DC8933C4ECB) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x414e7871fDEbFCDd82095431CB3b5B1C9f8f4d25`](https://sonicscan.org/address/0x414e7871fDEbFCDd82095431CB3b5B1C9f8f4d25) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://sonicscan.org/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://sonicscan.org/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc`](https://sonicscan.org/address/0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x3AC18F736d0E1B9bd9259Cd6C8a43539C86C16fD`](https://explorer.superseed.xyz/address/0x3AC18F736d0E1B9bd9259Cd6C8a43539C86C16fD) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xD68f7514Abadad1CAF00B4606f43d03109ad1373`](https://explorer.superseed.xyz/address/0xD68f7514Abadad1CAF00B4606f43d03109ad1373) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://explorer.superseed.xyz/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://explorer.superseed.xyz/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xa4576b58Ec760A8282D081dc94F3dc716DFc61e9`](https://explorer.superseed.xyz/address/0xa4576b58Ec760A8282D081dc94F3dc716DFc61e9) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xE72830E2845B74aA3bA71fB6E833D7A677129793`](https://uniscan.xyz/address/0xE72830E2845B74aA3bA71fB6E833D7A677129793) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x838478f87d2A907DE53862c1B36b919b5e19FCEa`](https://uniscan.xyz/address/0x838478f87d2A907DE53862c1B36b919b5e19FCEa) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://uniscan.xyz/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://uniscan.xyz/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xa5F12D63E18a28C9BE27B6f3d91ce693320067ba`](https://uniscan.xyz/address/0xa5F12D63E18a28C9BE27B6f3d91ce693320067ba) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x16f5c4Ddc5b828F00E8f92267f3ABf60b700dB5c`](https://xdcscan.com/address/0x16f5c4Ddc5b828F00E8f92267f3ABf60b700dB5c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xFE9B690173DbFDf4Ad937A0555AFb32D50A26daD`](https://xdcscan.com/address/0xFE9B690173DbFDf4Ad937A0555AFb32D50A26daD) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://xdcscan.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://xdcscan.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x4c1311a9d88BFb7023148aB04F7321C2E91c29bf`](https://xdcscan.com/address/0x4c1311a9d88BFb7023148aB04F7321C2E91c29bf) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xc2FDF5DCDEaa1F7c83e569D03b22eA8636073F4A`](https://explorer.zksync.io/address/0xc2FDF5DCDEaa1F7c83e569D03b22eA8636073F4A) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x02Dec2d93E47e2d5Fda3969E45B16BAf42ebA607`](https://explorer.zksync.io/address/0x02Dec2d93E47e2d5Fda3969E45B16BAf42ebA607) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://explorer.zksync.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://explorer.zksync.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc`](https://explorer.zksync.io/address/0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xe4E4ecC586A79aAeAb423B2C6da8F7510de811c5`](https://sepolia.arbiscan.io/address/0xe4E4ecC586A79aAeAb423B2C6da8F7510de811c5) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x77DA2e9939D45031A6C003A227fB13F683b288F0`](https://sepolia.arbiscan.io/address/0x77DA2e9939D45031A6C003A227fB13F683b288F0) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://sepolia.arbiscan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://sepolia.arbiscan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8`](https://sepolia.arbiscan.io/address/0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xA65A1ecEf02C2B9BDc604951c6A6D02335D4532e`](https://sepolia.basescan.org/address/0xA65A1ecEf02C2B9BDc604951c6A6D02335D4532e) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x41813cE5F8CF780ba1429A24512f90AaAEFb8144`](https://sepolia.basescan.org/address/0x41813cE5F8CF780ba1429A24512f90AaAEFb8144) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://sepolia.basescan.org/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://sepolia.basescan.org/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xCA2593027BA24856c292Fdcb5F987E0c25e755a4`](https://sepolia.basescan.org/address/0xCA2593027BA24856c292Fdcb5F987E0c25e755a4) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### BattleChain Testnet ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xbf85cD17cA59b7A2b81d3D776cE1602a7C0aF9D9`](https://explorer.testnet.battlechain.com/address/0xbf85cD17cA59b7A2b81d3D776cE1602a7C0aF9D9) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0x85290381D6dfD4ff0d33265B32b050C1bDe9a56a`](https://explorer.testnet.battlechain.com/address/0x85290381D6dfD4ff0d33265B32b050C1bDe9a56a) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://explorer.testnet.battlechain.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://explorer.testnet.battlechain.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xf978034bb3CAB5fe88d23DB5Cb38D510485DaB90`](https://explorer.testnet.battlechain.com/address/0xf978034bb3CAB5fe88d23DB5Cb38D510485DaB90) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x9D68477ef056552D769752171f348B74E0050Fe8`](https://optimism-sepolia.blockscout.com/address/0x9D68477ef056552D769752171f348B74E0050Fe8) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xB5549e9348ce9E0D461e0f8b9F05173E052Ef312`](https://optimism-sepolia.blockscout.com/address/0xB5549e9348ce9E0D461e0f8b9F05173E052Ef312) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://optimism-sepolia.blockscout.com/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://optimism-sepolia.blockscout.com/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0xDf6163ddD3Ebcb552Cc1379a9c65AFe68683534e`](https://optimism-sepolia.blockscout.com/address/0xDf6163ddD3Ebcb552Cc1379a9c65AFe68683534e) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xe61cb9153356419bdaD0A8767c059f92d221a3C4`](https://sepolia.etherscan.io/address/0xe61cb9153356419bdaD0A8767c059f92d221a3C4) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | SablierBatchLockup | [`0xd4ddc49F9D03a48293b5c8d89Cc210Af49d03D72`](https://sepolia.etherscan.io/address/0xd4ddc49F9D03a48293b5c8d89Cc210Af49d03D72) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupHelpers | [`0xC86B56250D2758f30d09B3420D9ec5b646244C7c`](https://sepolia.etherscan.io/address/0xC86B56250D2758f30d09B3420D9ec5b646244C7c) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupMath | [`0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6`](https://sepolia.etherscan.io/address/0x6c873BcE27aA6Ca803EF7013F05d1802AB6995b6) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | | LockupNFTDescriptor | [`0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc`](https://sepolia.etherscan.io/address/0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc) | [`lockup-v4.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v4.0) | --- ## Etherscan: Manual Operations Source: https://docs.sablier.com/guides/lockup/etherscan # Etherscan: Manual Operations ## Introduction Just like any other open protocol, Lockup can be interacted with directly through a blockchain explorer like Etherscan. In this guide, we will show you how to create a stream and withdraw from a stream by manually interacting with the Lockup Core contracts on Etherscan. If you're interested in interacting with Legacy contracts, please refer to this [article](https://blog.sablier.com/operating-the-sablier-v1-protocol-manually/). ## Creating a Stream ### Prerequisites Before being able to create a stream using the Lockup Core contracts you need to have granted a sufficient token allowance. See the [Allowances](#prerequisite-erc20-allowances) section below for a guide on how to do that. ### Step 1: Go to contract page Head over to our [deployments](/guides/lockup/deployments) list to pick the contract address you want to interact with. In this tutorial, we will create a Lockup stream on Ethereum Sepolia. Once you find the right contract, click on the address to access its explorer's page. Click on the "Contract" tab, and then on the "Write Contract" sub-tab. ![Etherscan 01](/assets/images/01-9c40cb2f1951fc3abcdfcfbf4d1ce2a5.webp) ![Etherscan 02](/assets/images/02-4713fd45c3a8951bac9917f08462a49b.webp) You can now connect your wallet to the interface by clicking on "Connect to Web3". ![Etherscan 03](/assets/images/03-198c876c78c88c3edbb48b4725ee3437.webp) ### Step 2: Fill in parameters We will now proceed to create our first stream. Let's go with the following parameters: - a Lockup Linear stream - and a deposit of 100 DAI - starting Jan 1, 2026 - ending Jan 1, 2027 - with cliff until Jan 24, 2026 and cliff amount of 2 DAI - no token unlock at start time - cancelable - and transferrable As the start and end date are fixed, we'll be using the [`createwithtimestampsLL`](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear#createwithtimestampsll) method. Please note that using [`createwithdurationsLL`](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear#createwithdurationsll) is also possible if you specify durations instead of the timestamps. Open the **"createwithtimestampsLL"** method, and start filling in the stream details: :::note All functions are marked as `payable`, however, its entirely optional to attach any value (in native token) to the transaction. For this example, we will not be attaching any value and therefore `payableAmount(ether)` will be 0. ::: ![Etherscan 04](/assets/images/04-a7dcb2a87f73e876fa832cbfb9c1efd8.webp) ```json { "sender": "0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F", "recipient": "0xf26994E6Af0b95cCa8DFA22A0BC25E1f38a54C42", "depositAmount": 100000000000000000000, "token": "0x776b6fC2eD15D6Bb5Fc32e0c89DE68683118c62A", "cancelable": true, "transferable": true, "timestamps": [1767261600, 1798797600], "shape": "", "unlockAmounts": ["0", "2000000000000000000"], "granularity": 1, "cliffTime": 1769261600 } ``` If the Etherscan UI does not break down the inputs into separate fields (like in the screenshot above), you will have to provide it like this: ```json [ "0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F", "0xf26994E6Af0b95cCa8DFA22A0BC25E1f38a54C42", 100000000000000000000, "0x776b6fC2eD15D6Bb5Fc32e0c89DE68683118c62A", false, true, [1737936000, 1769472000], "", ["0", "2000000000000000000"], 1, 1738195200 ] ``` :::tip In case of a tuple, ensure to follow the best practices: 1. Use square brackets 2. Wrap addresses in double quotes 3. Wrap large numbers in quotes Follow [this guide](https://info.etherscan.com/understanding-the-required-input-formats-for-read-write-contract-tab/) from Etherscan to learn how to correctly format input data for Write Contract tab. ::: As an example, in the screenshot below, we are providing input parameters for [`createWithTimestampsLL`](/reference/lockup/contracts/contract.SablierBatchLockup#createwithtimestampsll) function in [`SablierBatchLockup`](https://sepolia.etherscan.io/address/0x44Fd5d5854833975E5Fc80666a10cF3376C088E0#writeContract) contract. As you can see, since `batch` requires a tuple and does not break it down into separate fields, we had to use the above method. ![Etherscan 08](/assets/images/08-06813f7b67e28fb91ad633d3703e1b59.webp) ```json [ [ "0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F", "0xf26994E6Af0b95cCa8DFA22A0BC25E1f38a54C42", "100000000000000000000", true, true, [1767261600, 1798797600], 1769261600, ["0", "2000000000000000000"], 1, "" ] ] ``` #### Sender If the stream is cancelable, the sender is the wallet that will have the ability to cancel and renounce the stream. But if the stream is non-cancelable, the sender cannot cancel the stream. Most users will set their own wallet address as the sender. #### Recipient The address you want to stream tokens to. The owner of this address is the stream recipient and will receive tokens on [withdraw](#withdrawing-from-a-stream). #### Deposit Amount This is the deposit amount of tokens available to be streamed, **DECIMALS INCLUDED**. If the token has 18 decimals, for example, you will need to add eighteen zeros after the amount. Let's say you want to stream 20,000 DAI like in this example, you will need to fill in `20000000000000000000000`. #### Token The token is the contract address of the ERC-20 token being streamed. You can obtain this from the [Sablier Interface](#step-1-go-to-token-page) or from [Etherscan explorer](https://sepolia.etherscan.io/token/0x776b6fc2ed15d6bb5fc32e0c89de68683118c62a). Please double check the token address is correct before continuing. #### Cancelable This field indicates whether or not you want the stream to be cancelable. This can be set to either `true` or `false`. If set to true, the stream will be cancelable. You can make a cancelable stream non-cancelable after the stream has been created, but if it's a non-cancelable stream, it cannot become cancelable post-creation. #### Transferable The `transferable` field indicates whether the NFT owner is allowed to transfer the NFT or not. This can be set to either `true` or `false`. This flag cannot be changed later. #### Timestamps The `timestamps` field contains the start time, and the end time of the stream, in this order. The values should be UNIX timestamps (represented in **seconds**). You can find a Unix timestamp converter [here](https://www.unixtimestamp.com/). #### Shape The shape field can be used to specify the shape of the stream that you want the User Interface to display. This is an optional field and can be left empty. #### Unlock Amounts The `unlockAmounts` field contains the amount of tokens that will be unlocked at the start time and at the cliff time. For this example, we do not want to unlock any amount at the start time, however, we want to unlock 2 DAI at the cliff time. #### Cliff Time If you prefer to not have a cliff, you can simply set the cliff time to 0. If, however, you want to have a cliff, fill in the timestamp for the cliff there. #### Granularity The `granularity` field defines the smallest step in time (in seconds) between two consecutive token unlocks. For example, a granularity of `1` means tokens unlock every second (standard linear behavior), while `604800` means tokens unlock weekly in discrete steps. A value of `0` is a sentinel value that is equivalent to 1 second. For most standard linear streams, set this to `1`. :::caution Inside tuples/arrays (the `[ ... ]` structures in the example) make sure that you: - Use square brackets - Wrap addresses in double quotes, i.e. `" "` ::: Once the data is filled, and after you double-checked, click on the "Write" button and confirm the transaction in your wallet. That's all! You are done. You can now head over to the [Sablier Interface](https://app.sablier.com), connect your wallet, and your stream should appear like this: ![Etherscan 05](/assets/images/05-41b5cf45dada1a773d8e0eb2e362c48b.webp) #### How about [`createWithDurationsLL`](/reference/lockup/contracts/abstracts/abstract.SablierLockupLinear#createwithdurationsll)? For the durations version, we'll replace the `timestamps` and `cliffTime` parameters with a single `durations` parameter to represent the total duration of the stream (in seconds) and the duration of the cliff (in seconds). ```json { "granularity": 1, "durations": [0, 864000] // no cliff and a total duration of 10 days } ``` | Total Duration | Cliff Duration | \[Cliff, Total\] | | --- | --- | --- | | 10 days | no cliff | `[0, 864000]` | | 10 days | 1 day | `[86400, 864000]` | ## Withdrawing from a Stream ### Prerequisites To withdraw from a stream using Etherscan, you will need to obtain the stream's ID. To obtain this without the Sablier Interface, find the transaction which created the stream on Etherscan. Here's an [example](https://sepolia.etherscan.io/tx/0x1346d7bcb82b70f20e35ed2d404b0b65593344cf54b4b402af170434799e40cf) of what it should look like. Once found, you will see the stream ID between the two brackets. Note that stream ID and "Token ID" are the same thing. :::info Anyone can withdraw on your behalf if they pay the gas fee. When a third party withdraws, the recipient is the only allowed withdrawal address. However, if you withdraw yourself, you can choose to withdraw to any other address. You can read more about this advanced feature [here](/reference/lockup/access-control#overview). ::: ![Etherscan 06](/assets/images/06-cbc232a18f5f2c26af6fe26e03b2e8c4.webp) ### Step 1: Go to contract page Head over to our [deployments](/guides/lockup/deployments) list and select the contract address you want to interact with. Once you find the right contract, click on the address to access its explorer's page. Click on the "Contract" tab, and then on the "Write Contract" sub-tab. ![Etherscan 01](/assets/images/01-9c40cb2f1951fc3abcdfcfbf4d1ce2a5.webp) ![Etherscan 02](/assets/images/02-4713fd45c3a8951bac9917f08462a49b.webp) You can now connect your wallet to the interface by clicking on "Connect to Web3". ![Etherscan 03](/assets/images/03-198c876c78c88c3edbb48b4725ee3437.webp) ### Step 2: Fill in parameters Head over to the **`withdraw`** method, and fill in the data. ![Etherscan 07](/assets/images/07-71a4eaa32841bc09c260466edf4128c1.webp) ```json { "streamId": 1, "to": "0xb1bEF51ebCA01EB12001a639bDBbFF6eEcA12B9F", "amount": 100000000000000000000 } ``` #### Stream ID The `streamId` is the value you have previously located in the transaction in which the stream was created. #### To The `to` field is there for the stream recipient's address. This will most likely be your own wallet, but you can also choose to withdraw these funds to another wallet (e.g. a separate cold wallet) if you are the stream's recipient. If you are not the stream recipient, it MUST be the address of the recipient. #### Amount This represents the amount of tokens that you want to withdraw, **DECIMALS INCLUDED**. For example, if the token you are looking to withdraw has 18 decimals, you will need to add eighteen zeros after the amount. Let's say you want to withdraw 100 DAI like in this example, you will need to put in `100000000000000000000`. Oh, and make sure that the amount has already been streamed, you cannot withdraw funds that haven't yet been streamed over to you. Once ready, click on the "Write" button, and confirm the transaction in your wallet. You are done! --- Apart from the main flows, you may be required to do some other actions, usually listed as prerequisites. ## Prerequisite: ERC20 Allowances Before interacting directly with the Lockup [contracts](/guides/lockup/deployments) to [create a stream](#creating-a-stream) you will need to manually grant proper ERC20 allowances. ### Step 1: Go to token page Pick a token you want to stream, e.g. [DAI](https://sepolia.etherscan.io/token/0x68194a729C2450ad26072b3D33ADaCbcef39D574). Using its address, visit the token page on Etherscan (in this example, we're using Ethereum Sepolia): `https://sepolia.etherscan.io/token/` :::info To get the address of an asset in the [Sablier Interface](/apps/features/overview), you can click on its name in the token list dialog or find an existing stream with that token and click on the icon inside the stream circle. ![Etherscan 09](/assets/images/09-e0763326ba0f69fd2daa16f13c3e9968.webp) ![Etherscan 10](/assets/images/10-d2f81cb0ef08d5a1aa7b1eb0437f3322.webp) ::: ### Step 2: Go to `approve` Next, look for the "Contract" tab and the "Write Contract" sub-tab. You'll see a list of methods that can be called for that token. Pick the `approve` method (e.g. [DAI's approve](https://sepolia.etherscan.io/token/0x68194a729C2450ad26072b3D33ADaCbcef39D574#writeContract#F1)). Most ERC-20 approve methods will contain two fields: 1. The spender 2. The amount :::tip Some tokens like [USDC](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48?a=#writeProxyContract) or [AAVE](https://etherscan.io/token/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9?a=#writeProxyContract) are upgradeable and use a proxy pattern. For these, you have to use the "Write as Proxy" tab. ::: ### Step 3: Send transaction For the purpose of creating a **LockupLinear** stream with Lockup, the spender will be the [SablierLockup](/guides/lockup/deployments) contract. As for the amount, you'll have to pad it with the right number of decimals. For DAI, that's 18 decimals, so a value of `100` will turn into `100 * 1e18` (100 followed by 18 zeroes). For USDC, that's 6 decimals, so a value of `100` will turn into `100 * 1e6` (100 followed by 6 zeroes). The same logic applies to the [deposit amounts](#deposit-amount) when creating the stream. ```json { "spender": "0x6b0307b4338f2963A62106028E3B074C2c0510DA", "amount": 100000000000000000000 } ``` Before clicking on the "Write" button to submit your allowance update, make sure to connect your wallet to the interface by clicking on "Connect to Web3". --- ## Create a Batch of Dynamic Streams Source: https://docs.sablier.com/guides/lockup/examples/batch-create-streams/batch-lockup-dynamic # Create a Batch of Dynamic Streams In this guide, we will show you how you can use Solidity to batch create dynamic streams via the [Batch Lockup](/reference/lockup/contracts/contract.SablierBatchLockup) contract. This guide assumes that you have already gone through the [Protocol Concepts](/concepts/streaming) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Now, import the relevant symbols from `@sablier/lockup`: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud2x18 } from "@prb/math/src/UD2x18.sol"; import { ISablierBatchLockup } from "@sablier/lockup/src/interfaces/ISablierBatchLockup.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { BatchLockup } from "@sablier/lockup/src/types/BatchLockup.sol"; import { LockupDynamic } from "@sablier/lockup/src/types/LockupDynamic.sol"; ``` Create a contract called `BatchLDStreamCreator`, and declare a constant `DAI` of type `IERC20`, a constant `LOCKUP` of type `ISablierLockup`, and a constant `BATCH_LOCKUP` of type `ISablierBatchLockup`: ```solidity // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ISablierBatchLockup public constant BATCH_LOCKUP = ISablierBatchLockup(0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875); ``` 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](/guides/lockup/deployments) page. ## Batch create functions There are two batch create functions for the Dynamic streams: - [`createWithDurationsLD`](/reference/lockup/contracts/contract.SablierBatchLockup#createwithdurationsld) - [`createWithTimestampsLD`](/reference/lockup/contracts/contract.SablierBatchLockup#createwithtimestampsld) 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: ```solidity BatchLockup.CreateWithTimestampsLD memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit 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); // The start time of the stream // 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) })); ``` ## Batch size Next, declare a batch size, which is needed to calculate the transfer amount: ```solidity BatchLockup.CreateWithTimestampsLD memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.startTime = uint40(block.timestamp); // The start time of the stream // 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) })); ``` ## 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 tokens that the creator contract will be in possession of after they are transferred from the calling address (you): ```solidity BatchLockup.CreateWithTimestampsLD[] memory batch = new BatchLockup.CreateWithTimestampsLD[](batchSize); batch[0] = stream0; batch[1] = stream1; ``` For more guidance on how to approve and transfer ERC-20 tokens, see [this article](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) on the Ethereum website. ## Stream Parameters Given that we declared a `batchSize` of two, we need to define two [BatchLockup.CreateWithTimestampsLD](/reference/lockup/contracts/types/library.BatchLockup#createwithtimestampsld) structs: ```solidity streamIds = BATCH_LOCKUP.createWithTimestampsLD(LOCKUP, DAI, batch); } } ``` To add some variety, we will change the parameters of the second stream: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud2x18 } from "@prb/math/src/UD2x18.sol"; import { ISablierBatchLockup } from "@sablier/lockup/src/interfaces/ISablierBatchLockup.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { BatchLockup } from "@sablier/lockup/src/types/BatchLockup.sol"; import { LockupDynamic } from "@sablier/lockup/src/types/LockupDynamic.sol"; contract BatchLDStreamCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ISablierBatchLockup public constant BATCH_LOCKUP = ISablierBatchLockup(0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875); /// @dev For this function to work, the sender must have approved this dummy contract to spend DAI. function batchCreateStreams(uint128 perStreamAmount) public returns (uint256[] memory streamIds) { // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); // Declare the first stream in the batch BatchLockup.CreateWithTimestampsLD memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit 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); // The start time of the stream // 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) })); // Declare the second stream in the batch BatchLockup.CreateWithTimestampsLD memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.startTime = uint40(block.timestamp); // The start time of the stream // 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) })); // Fill the batch array BatchLockup.CreateWithTimestampsLD[] memory batch = new BatchLockup.CreateWithTimestampsLD[](batchSize); batch[0] = stream0; batch[1] = stream1; streamIds = BATCH_LOCKUP.createWithTimestampsLD(LOCKUP, DAI, batch); } } ``` Once both structs are declared, the batch array has to be filled: ```solidity function batchCreateStreams(uint128 perStreamAmount) public returns (uint256[] memory streamIds) { // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); // Declare the first stream in the batch BatchLockup.CreateWithDurationsLL memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit amount of each stream stream0.cancelable = true; // Whether the stream will be cancelable or not stream0.transferable = false; // Whether the recipient can transfer the NFT or not stream0.durations = LockupLinear.Durations({ cliff: 4 weeks, // Tokens will start streaming continuously after 4 weeks total: 52 weeks // Setting a total duration of ~1 year }); stream0.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the cliff time }); // Declare the second stream in the batch BatchLockup.CreateWithDurationsLL memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.durations = LockupLinear.Durations({ cliff: 1 weeks, // Tokens will start streaming continuously after 4 weeks total: 26 weeks // Setting a total duration of ~6 months }); stream1.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the start time }); // Fill the batch param BatchLockup.CreateWithDurationsLL[] memory batch = new BatchLockup.CreateWithDurationsLL[](batchSize); batch[0] = stream0; batch[1] = stream1; streamIds = BATCH_LOCKUP.createWithDurationsLL(LOCKUP, DAI, batch); } } ``` ## 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: ```solidity // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; ``` ## Full code Below you can see the full code. You can also access the code on GitHub through [this link](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/lockup/BatchLDStreamCreator.sol). ```solidity // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); ``` --- ## Create a Batch of Linear Streams Source: https://docs.sablier.com/guides/lockup/examples/batch-create-streams/batch-lockup-linear # Create a Batch of Linear Streams In this guide, we will show you how you can use Solidity to batch create linear streams via the [Batch Lockup](/reference/lockup/contracts/contract.SablierBatchLockup) contract. This guide assumes that you have already gone through the [Protocol Concepts](/concepts/streaming) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Now, import the relevant symbols from `@sablier/lockup`: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { ISablierBatchLockup } from "@sablier/lockup/src/interfaces/ISablierBatchLockup.sol"; import { BatchLockup } from "@sablier/lockup/src/types/BatchLockup.sol"; import { LockupLinear } from "@sablier/lockup/src/types/LockupLinear.sol"; ``` Create a contract called `BatchLLStreamCreator`, and declare a constant `DAI` of type `IERC20`, a constant `LOCKUP` of type `ISablierLockup`, and a constant `BATCH_LOCKUP` of type `ISablierBatchLockup`: ```solidity // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ISablierBatchLockup public constant BATCH_LOCKUP = ISablierBatchLockup(0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875); ``` 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](/guides/lockup/deployments) page. ## Batch create functions There are two batch create functions for the Linear streams: - [`createWithDurationsLL`](/reference/lockup/contracts/contract.SablierBatchLockup#createwithdurationsll) - [`createWithTimestampsLL`](/reference/lockup/contracts/contract.SablierBatchLockup#createwithtimestampsll) Which one you choose depends upon your use case. In this guide, we will use `createWithDurationsLL`. ## Function definition Define a function called `batchCreateStreams` that takes a parameter `perStreamAmount` and returns an array of ids for the created streams: ```solidity BatchLockup.CreateWithDurationsLL memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit amount of each stream stream0.cancelable = true; // Whether the stream will be cancelable or not stream0.transferable = false; // Whether the recipient can transfer the NFT or not stream0.durations = LockupLinear.Durations({ cliff: 4 weeks, // Tokens will start streaming continuously after 4 weeks total: 52 weeks // Setting a total duration of ~1 year }); stream0.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the cliff time }); ``` ## Batch size Next, declare a batch size, which is needed to calculate the transfer amount: ```solidity BatchLockup.CreateWithDurationsLL memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.durations = LockupLinear.Durations({ cliff: 1 weeks, // Tokens will start streaming continuously after 4 weeks total: 26 weeks // Setting a total duration of ~6 months }); stream1.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the start time }); ``` ## 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 tokens that the creator contract will be in possession of after they are transferred from the calling address (you): ```solidity BatchLockup.CreateWithDurationsLL[] memory batch = new BatchLockup.CreateWithDurationsLL[](batchSize); batch[0] = stream0; batch[1] = stream1; ``` For more guidance on how to approve and transfer ERC-20 tokens, see [this article](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) on the Ethereum website. ## Stream Parameters Given that we declared a `batchSize` of two, we need to define two [BatchLockup.CreateWithDurationsLL](/reference/lockup/contracts/types/library.BatchLockup#createwithdurationsll) structs: ```solidity streamIds = BATCH_LOCKUP.createWithDurationsLL(LOCKUP, DAI, batch); } } ``` To add some variety, we will change the parameters of the second stream: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { ISablierBatchLockup } from "@sablier/lockup/src/interfaces/ISablierBatchLockup.sol"; import { BatchLockup } from "@sablier/lockup/src/types/BatchLockup.sol"; import { LockupLinear } from "@sablier/lockup/src/types/LockupLinear.sol"; contract BatchLLStreamCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ISablierBatchLockup public constant BATCH_LOCKUP = ISablierBatchLockup(0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875); /// @dev For this function to work, the sender must have approved this dummy contract to spend DAI. function batchCreateStreams(uint128 perStreamAmount) public returns (uint256[] memory streamIds) { // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); // Declare the first stream in the batch BatchLockup.CreateWithDurationsLL memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit amount of each stream stream0.cancelable = true; // Whether the stream will be cancelable or not stream0.transferable = false; // Whether the recipient can transfer the NFT or not stream0.durations = LockupLinear.Durations({ cliff: 4 weeks, // Tokens will start streaming continuously after 4 weeks total: 52 weeks // Setting a total duration of ~1 year }); stream0.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the cliff time }); // Declare the second stream in the batch BatchLockup.CreateWithDurationsLL memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.durations = LockupLinear.Durations({ cliff: 1 weeks, // Tokens will start streaming continuously after 4 weeks total: 26 weeks // Setting a total duration of ~6 months }); stream1.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the start time }); // Fill the batch param BatchLockup.CreateWithDurationsLL[] memory batch = new BatchLockup.CreateWithDurationsLL[](batchSize); batch[0] = stream0; batch[1] = stream1; streamIds = BATCH_LOCKUP.createWithDurationsLL(LOCKUP, DAI, batch); } } ``` Once both structs are declared, the batch array has to be filled: ```solidity function batchCreateStreams(uint128 perStreamAmount) public returns (uint256[] memory streamIds) { // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); // Declare the first stream in the batch BatchLockup.CreateWithDurationsLL memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit amount of each stream stream0.cancelable = true; // Whether the stream will be cancelable or not stream0.transferable = false; // Whether the recipient can transfer the NFT or not stream0.durations = LockupLinear.Durations({ cliff: 4 weeks, // Tokens will start streaming continuously after 4 weeks total: 52 weeks // Setting a total duration of ~1 year }); stream0.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the cliff time }); // Declare the second stream in the batch BatchLockup.CreateWithDurationsLL memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.durations = LockupLinear.Durations({ cliff: 1 weeks, // Tokens will start streaming continuously after 4 weeks total: 26 weeks // Setting a total duration of ~6 months }); stream1.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the start time }); // Fill the batch param BatchLockup.CreateWithDurationsLL[] memory batch = new BatchLockup.CreateWithDurationsLL[](batchSize); batch[0] = stream0; batch[1] = stream1; streamIds = BATCH_LOCKUP.createWithDurationsLL(LOCKUP, DAI, batch); } } ``` ## Invoke the batch create function With all parameters set, we can now call the `createWithDurationsLL` function, and assign the ids of the newly created streams to the array: ```solidity // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; ``` ## Full code Below you can see the full code. You can also access the code on GitHub through [this link](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/lockup/BatchLLStreamCreator.sol). ```solidity // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); ``` --- ## Create a Batch of Tranched Streams Source: https://docs.sablier.com/guides/lockup/examples/batch-create-streams/batch-lockup-tranched # Create a Batch of Tranched Streams In this guide, we will show you how you can use Solidity to batch create tranched streams via the [Batch Lockup](/reference/lockup/contracts/contract.SablierBatchLockup) contract. This guide assumes that you have already gone through the [Protocol Concepts](/concepts/streaming) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Now, import the relevant symbols from `@sablier/lockup`: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierBatchLockup } from "@sablier/lockup/src/interfaces/ISablierBatchLockup.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { BatchLockup } from "@sablier/lockup/src/types/BatchLockup.sol"; import { LockupTranched } from "@sablier/lockup/src/types/LockupTranched.sol"; ``` Create a contract called `BatchLTStreamCreator`, and declare a constant `DAI` of type `IERC20`, a constant `LOCKUP` of type `ISablierLockup`, and a constant `BATCH_LOCKUP` of type `ISablierBatchLockup`: ```solidity // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ISablierBatchLockup public constant BATCH_LOCKUP = ISablierBatchLockup(0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875); ``` 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](/guides/lockup/deployments) page. ## Batch create functions There are two batch create functions for the Tranched streams: - [`createWithDurationsLT`](/reference/lockup/contracts/contract.SablierBatchLockup#createwithdurationslt) - [`createWithTimestampsLT`](/reference/lockup/contracts/contract.SablierBatchLockup#createwithtimestampslt) Which one you choose depends upon your use case. In this guide, we will use `createWithTimestampsLT`. ## Function definition Define a function called `batchCreateStreams` that takes a parameter `perStreamAmount` and returns an array of ids for the created streams: ```solidity BatchLockup.CreateWithTimestampsLT memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit amount of each stream 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 tranches stream0.tranches = new LockupTranched.Tranche[](2); stream0.tranches[0] = LockupTranched.Tranche({ amount: uint128(perStreamAmount / 2), timestamp: uint40(block.timestamp + 1 weeks) }); stream0.tranches[1] = LockupTranched.Tranche({ amount: uint128(perStreamAmount - stream0.tranches[0].amount), timestamp: uint40(block.timestamp + 24 weeks) }); ``` ## Batch size Next, declare a batch size, which is needed to calculate the transfer amount: ```solidity BatchLockup.CreateWithTimestampsLT memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.startTime = uint40(block.timestamp); // Set the start time to block timestamp // Declare some dummy tranches stream1.tranches = new LockupTranched.Tranche[](2); stream1.tranches[0] = LockupTranched.Tranche({ amount: uint128(perStreamAmount / 4), timestamp: uint40(block.timestamp + 4 weeks) }); stream1.tranches[1] = LockupTranched.Tranche({ amount: uint128(perStreamAmount - stream1.tranches[0].amount), timestamp: uint40(block.timestamp + 24 weeks) }); ``` ## 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 tokens that the creator contract will be in possession of after they are transferred from the calling address (you): ```solidity BatchLockup.CreateWithTimestampsLT[] memory batch = new BatchLockup.CreateWithTimestampsLT[](batchSize); batch[0] = stream0; batch[1] = stream1; ``` For more guidance on how to approve and transfer ERC-20 tokens, see [this article](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) on the Ethereum website. ## Stream Parameters Given that we declared a `batchSize` of two, we need to define two [BatchLockup.CreateWithTimestampsLT](/reference/lockup/contracts/types/library.BatchLockup#createwithtimestampslt) structs: ```solidity streamIds = BATCH_LOCKUP.createWithTimestampsLT(LOCKUP, DAI, batch); } } ``` To add some variety, we will change the parameters of the second stream: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierBatchLockup } from "@sablier/lockup/src/interfaces/ISablierBatchLockup.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { BatchLockup } from "@sablier/lockup/src/types/BatchLockup.sol"; import { LockupTranched } from "@sablier/lockup/src/types/LockupTranched.sol"; contract BatchLTStreamCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // See https://docs.sablier.com/guides/lockup/deployments for all deployments ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ISablierBatchLockup public constant BATCH_LOCKUP = ISablierBatchLockup(0x4f3be262D1358A82b468CF81bfc5A9cC32Cf9875); /// @dev For this function to work, the sender must have approved this dummy contract to spend DAI. function batchCreateStreams(uint128 perStreamAmount) public returns (uint256[] memory streamIds) { // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); // Declare the first stream in the batch BatchLockup.CreateWithTimestampsLT memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit amount of each stream 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 tranches stream0.tranches = new LockupTranched.Tranche[](2); stream0.tranches[0] = LockupTranched.Tranche({ amount: uint128(perStreamAmount / 2), timestamp: uint40(block.timestamp + 1 weeks) }); stream0.tranches[1] = LockupTranched.Tranche({ amount: uint128(perStreamAmount - stream0.tranches[0].amount), timestamp: uint40(block.timestamp + 24 weeks) }); // Declare the second stream in the batch BatchLockup.CreateWithTimestampsLT memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.startTime = uint40(block.timestamp); // Set the start time to block timestamp // Declare some dummy tranches stream1.tranches = new LockupTranched.Tranche[](2); stream1.tranches[0] = LockupTranched.Tranche({ amount: uint128(perStreamAmount / 4), timestamp: uint40(block.timestamp + 4 weeks) }); stream1.tranches[1] = LockupTranched.Tranche({ amount: uint128(perStreamAmount - stream1.tranches[0].amount), timestamp: uint40(block.timestamp + 24 weeks) }); // Fill the batch array BatchLockup.CreateWithTimestampsLT[] memory batch = new BatchLockup.CreateWithTimestampsLT[](batchSize); batch[0] = stream0; batch[1] = stream1; streamIds = BATCH_LOCKUP.createWithTimestampsLT(LOCKUP, DAI, batch); } } ``` Once both structs are declared, the batch array has to be filled: ```solidity function batchCreateStreams(uint128 perStreamAmount) public returns (uint256[] memory streamIds) { // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); // Declare the first stream in the batch BatchLockup.CreateWithDurationsLL memory stream0; stream0.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream0.recipient = address(0xCAFE); // The recipient of the streamed tokens stream0.depositAmount = perStreamAmount; // The deposit amount of each stream stream0.cancelable = true; // Whether the stream will be cancelable or not stream0.transferable = false; // Whether the recipient can transfer the NFT or not stream0.durations = LockupLinear.Durations({ cliff: 4 weeks, // Tokens will start streaming continuously after 4 weeks total: 52 weeks // Setting a total duration of ~1 year }); stream0.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the cliff time }); // Declare the second stream in the batch BatchLockup.CreateWithDurationsLL memory stream1; stream1.sender = address(0xABCD); // The sender to stream the tokens, he will be able to cancel the stream stream1.recipient = address(0xBEEF); // The recipient of the streamed tokens stream1.depositAmount = perStreamAmount; // The deposit amount of each stream stream1.cancelable = false; // Whether the stream will be cancelable or not stream1.transferable = false; // Whether the recipient can transfer the NFT or not stream1.durations = LockupLinear.Durations({ cliff: 1 weeks, // Tokens will start streaming continuously after 4 weeks total: 26 weeks // Setting a total duration of ~6 months }); stream1.unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, // Whether the stream will unlock a certain amount of tokens at the start time cliff: 0 // Whether the stream will unlock a certain amount of tokens at the start time }); // Fill the batch param BatchLockup.CreateWithDurationsLL[] memory batch = new BatchLockup.CreateWithDurationsLL[](batchSize); batch[0] = stream0; batch[1] = stream1; streamIds = BATCH_LOCKUP.createWithDurationsLL(LOCKUP, DAI, batch); } } ``` ## Invoke the batch create function With all parameters set, we can now call the `createWithTimestampsLT` function, and assign the ids of the newly created streams to the array: ```solidity // Create a batch of two streams uint256 batchSize = 2; // Calculate the combined amount of DAI tokens to transfer to this contract uint256 transferAmount = perStreamAmount * batchSize; ``` ## Full code Below you can see the full code. You can also access the code on GitHub through [this link](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/lockup/BatchLTStreamCreator.sol). ```solidity // 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({ spender: address(BATCH_LOCKUP), value: transferAmount }); ``` --- ## Create a Lockup Dynamic Stream Source: https://docs.sablier.com/guides/lockup/examples/create-stream/lockup-dynamic # Create a Lockup Dynamic Stream Dynamic streams are streams with a custom streaming function. In this guide, we will show you how to create a Lockup Dynamic stream using Solidity. This guide assumes that you have already gone through the [Protocol Concepts](/concepts/streaming) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Import the relevant symbols from `@sablier/lockup`: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud2x18 } from "@prb/math/src/UD2x18.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Lockup } from "@sablier/lockup/src/types/Lockup.sol"; import { LockupDynamic } from "@sablier/lockup/src/types/LockupDynamic.sol"; ``` Create a contract called `LockupDynamicStreamCreator`, and declare a constant `DAI` of type `IERC20` and a constant `LOCKUP` of type `ISablierLockup`: ```solidity // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ``` 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](/guides/lockup/deployments) page. There are two create functions in the Lockup contract that can be used to create Dynamic streams: - `createWithDurationsLD`: takes duration and calculates the segment timestamps based on the provided durations. - `createWithTimestampsLD`: takes UNIX timestamps for segments. Which one you choose depends upon your use case. In this guide, we will use `createWithTimestampsLD`. ## Function definition Define a function called `createStream` which takes two parameters, `amount0` and `amount1`, and which returns the id of the created stream: ```solidity function createStream(uint128 amount0, uint128 amount1) public returns (uint256 streamId) { // Sum the segment amounts uint128 depositAmount = amount0 + amount1; // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithTimestamps memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not params.timestamps.start = uint40(block.timestamp + 100 seconds); params.timestamps.end = uint40(block.timestamp + 52 weeks); // Declare some dummy segments LockupDynamic.Segment[] memory segments = new LockupDynamic.Segment[](2); segments[0] = LockupDynamic.Segment({ amount: amount0, exponent: ud2x18(1e18), timestamp: uint40(block.timestamp + 4 weeks) }); segments[1] = (LockupDynamic.Segment({ amount: amount1, exponent: ud2x18(3.14e18), timestamp: uint40(block.timestamp + 52 weeks) })); // Create the LockupDynamic stream streamId = LOCKUP.createWithTimestampsLD({ params: params, segments: segments }); } } ``` Next, sum up the `amount0` and `amount1` parameters to get the deposit amount of the stream, which will be needed in many of the steps below: ```solidity uint128 depositAmount = amount0 + amount1; // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithTimestamps memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not params.timestamps.start = uint40(block.timestamp + 100 seconds); params.timestamps.end = uint40(block.timestamp + 52 weeks); // Declare some dummy segments LockupDynamic.Segment[] memory segments = new LockupDynamic.Segment[](2); segments[0] = LockupDynamic.Segment({ amount: amount0, exponent: ud2x18(1e18), timestamp: uint40(block.timestamp + 4 weeks) }); segments[1] = (LockupDynamic.Segment({ amount: amount1, exponent: ud2x18(3.14e18), timestamp: uint40(block.timestamp + 52 weeks) })); // Create the LockupDynamic stream streamId = LOCKUP.createWithTimestampsLD({ params: params, segments: segments }); } } ``` ## 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 approve the Lockup contract to pull the tokens that the creator contract will be in possession of after they are transferred from the calling address (you): ```solidity Lockup.CreateWithTimestamps memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not params.timestamps.start = uint40(block.timestamp + 100 seconds); params.timestamps.end = uint40(block.timestamp + 52 weeks); // Declare some dummy segments LockupDynamic.Segment[] memory segments = new LockupDynamic.Segment[](2); segments[0] = LockupDynamic.Segment({ amount: amount0, exponent: ud2x18(1e18), timestamp: uint40(block.timestamp + 4 weeks) }); segments[1] = (LockupDynamic.Segment({ amount: amount1, exponent: ud2x18(3.14e18), timestamp: uint40(block.timestamp + 52 weeks) })); // Create the LockupDynamic stream streamId = LOCKUP.createWithTimestampsLD({ params: params, segments: segments }); } } ``` For more guidance on how to approve and transfer ERC-20 tokens, see [this article](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) on the Ethereum website. ## Parameters The struct associated with [`createWithTimestampsLD`](/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic#createwithtimestampsld) are [`Lockup.CreateWithTimestamps`](/reference/lockup/contracts/types/library.Lockup#createwithtimestamps) (a shared struct across all the lockup streams) and [`LockupDynamic.Segment`](/reference/lockup/contracts/types/library.LockupDynamic#segment). Let's review each parameter in detail. ### Create With Timestamps ```solidity params.timestamps.start = uint40(block.timestamp + 100 seconds); params.timestamps.end = uint40(block.timestamp + 52 weeks); ``` #### Sender The address streaming the tokens, with the ability to cancel the stream: ```solidity LockupDynamic.Segment[] memory segments = new LockupDynamic.Segment[](2); segments[0] = LockupDynamic.Segment({ amount: amount0, exponent: ud2x18(1e18), timestamp: uint40(block.timestamp + 4 weeks) }); segments[1] = (LockupDynamic.Segment({ amount: amount1, exponent: ud2x18(3.14e18), timestamp: uint40(block.timestamp + 52 weeks) })); ``` #### Recipient The address receiving the tokens: ```solidity streamId = LOCKUP.createWithTimestampsLD({ params: params, segments: segments }); } } ``` #### Deposit amount The deposit amount of ERC-20 tokens to be paid, denoted in units of the token's decimals. ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud2x18 } from "@prb/math/src/UD2x18.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Lockup } from "@sablier/lockup/src/types/Lockup.sol"; import { LockupDynamic } from "@sablier/lockup/src/types/LockupDynamic.sol"; /// @notice Example of how to create a Lockup Dynamic stream. /// @dev This code is referenced in the docs: /// https://docs.sablier.com/guides/lockup/examples/create-stream/lockup-dynamic contract LockupDynamicStreamCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); /// @dev For this function to work, the sender must have approved this dummy contract to spend DAI. function createStream(uint128 amount0, uint128 amount1) public returns (uint256 streamId) { // Sum the segment amounts uint128 depositAmount = amount0 + amount1; // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithTimestamps memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not params.timestamps.start = uint40(block.timestamp + 100 seconds); params.timestamps.end = uint40(block.timestamp + 52 weeks); // Declare some dummy segments LockupDynamic.Segment[] memory segments = new LockupDynamic.Segment[](2); segments[0] = LockupDynamic.Segment({ amount: amount0, exponent: ud2x18(1e18), timestamp: uint40(block.timestamp + 4 weeks) }); segments[1] = (LockupDynamic.Segment({ amount: amount1, exponent: ud2x18(3.14e18), timestamp: uint40(block.timestamp + 52 weeks) })); // Create the LockupDynamic stream streamId = LOCKUP.createWithTimestampsLD({ params: params, segments: segments }); } } ``` #### Token The contract address of the ERC-20 token used for streaming. In this example, we will stream DAI: ```solidity params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Cancelable Boolean that indicates whether the stream will be cancelable or not. ```solidity params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Transferable Boolean that indicates whether the stream will be transferable or not. ```solidity params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ### Start Time and End Time The start and end timestamps for the stream. Note that the end timestamps much match the timestamp of the last segment. ```solidity params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ### Segments Segments are what the protocol uses to compose the custom distribution curve of a Dynamic stream. For a full exposition of segments, see the [Segments](/concepts/lockup/segments) guide. The term "segment" refers to the splitting of the stream into separate partitions, with each segment characterized by a specific amount, exponent, and timestamp. These segments are supplied to the function in the form of an array containing [`LockupDynamic.Segment`](/reference/lockup/contracts/types/library.LockupDynamic#segment) structs. Let's define two dummy segments: ```solidity params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` In this example, the first segment (`amount0`) will stream much faster than the second segment (`amount1`), because the exponents are different. As a rule of thumb: the higher the exponent, the slower the stream. :::note The segment timestamp must be in ascending order. ::: :::info The `ud2x18` function wraps a basic integer to the `UD2x18` value type, which is part of the [PRBMath](https://github.com/PaulRBerg/prb-math) library. ::: ## Invoke the create function With all parameters set, we can now call the `createWithTimestampsLD` function, and assign the id of the newly created stream to a variable: ```solidity params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ## Full code Below you can see the full code. You can also access the code on GitHub through [this link](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/lockup/LockupDynamicStreamCreator.sol). ```solidity // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); ``` --- ## Create a Lockup Linear Stream Source: https://docs.sablier.com/guides/lockup/examples/create-stream/lockup-linear # Create a Lockup Linear Stream Linear streams are streams with a linear streaming function. In this guide, we will show you how to create a Lockup Linear stream using Solidity. This guide assumes that you have already gone through the [Protocol Concepts](/concepts/streaming) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Import the relevant symbols from `@sablier/lockup`: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Lockup } from "@sablier/lockup/src/types/Lockup.sol"; import { LockupLinear } from "@sablier/lockup/src/types/LockupLinear.sol"; ``` Create a contract called `LockupLinearStreamCreator`, and declare a constant `DAI` of type `IERC20` and a constant `LOCKUP` of type `ISablierLockup`: ```solidity // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ``` 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](/guides/lockup/deployments) page. There are two create functions in the Lockup contract that can be used to create Linear streams: - `createWithDurationsLL`: takes duration and calculates the start and end timestamps based on the provided durations. - `createWithTimestampsLL`: takes start and end timestamps. Which one you choose depends upon your use case. In this guide, we will use `createWithDurationsLL`. ## Function definition Define a function called `createStream` which takes a single parameter `depositAmount`, and which returns the id of the created stream: ```solidity function createStream(uint128 depositAmount) public returns (uint256 streamId) { // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ## 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 approve the Lockup contract to pull the tokens that the creator contract will be in possession of after they are transferred from the calling address (you): ```solidity Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` For more guidance on how to approve and transfer ERC-20 tokens, see [this article](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) on the Ethereum website. ## Parameters The parameters for [`createWithDurationsLL`](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear#createwithdurationsll) include [`Lockup.CreateWithDurations`](/reference/lockup/contracts/types/library.Lockup#createwithdurations) (a shared struct across all the lockup streams), [`LockupLinear.UnlockAmounts`](/reference/lockup/contracts/types/library.LockupLinear#unlockamounts), a `uint40 granularity`, and [`LockupLinear.Durations`](/reference/lockup/contracts/types/library.LockupLinear#durations). Let's review each parameter in detail. ### Create With Durations ```solidity LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Sender The address streaming the tokens, with the ability to cancel the stream: ```solidity granularity: 1 seconds, durations: durations }); } } ``` #### Recipient The address receiving the tokens: ```solidity LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); ``` #### Deposit amount The deposit amount of ERC-20 tokens to be paid, denoted in units of the token's decimals. ```solidity streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); ``` #### Token The contract address of the ERC-20 token used for streaming. In this example, we will stream DAI: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Lockup } from "@sablier/lockup/src/types/Lockup.sol"; import { LockupLinear } from "@sablier/lockup/src/types/LockupLinear.sol"; /// @notice Example of how to create a Lockup Linear stream. /// @dev This code is referenced in the docs: /// https://docs.sablier.com/guides/lockup/examples/create-stream/lockup-linear contract LockupLinearStreamCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); /// @dev For this function to work, the sender must have approved this dummy contract to spend DAI. function createStream(uint128 depositAmount) public returns (uint256 streamId) { // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Cancelable Boolean that indicates whether the stream will be cancelable or not. ```solidity params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Transferable Boolean that indicates whether the stream will be transferable or not. ```solidity params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ### Unlock Amounts Struct containing details on unlock amounts at start time and at cliff time. ```solidity params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ### Granularity The smallest step in time (in seconds) between two consecutive token unlocks. A value of zero is a sentinel for 1 second. For standard linear streaming behavior, use `1 seconds`. For periodic unlock patterns (e.g., weekly), use the period duration (e.g., `1 weeks`). ```solidity params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ### Durations Struct containing (i) cliff duration and (ii) total stream duration, both denoted in seconds. ```solidity params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ## Invoke the create function With all parameters set, we can now call the `createWithDurationsLL` function, and assign the id of the newly created stream to a variable: ```solidity params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ## Full code Below you can see the full code. You can also access the code on GitHub through [this link](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/lockup/LockupLinearStreamCreator.sol). ```solidity // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); ``` --- ## Create a Lockup Tranched Stream Source: https://docs.sablier.com/guides/lockup/examples/create-stream/lockup-tranched # Create a Lockup Tranched Stream Lockup Tranched are streams with discrete unlocks. In this guide, we will show you how to create a Lockup Tranched stream using Solidity. This guide assumes that you have already gone through the [Protocol Concepts](/concepts/streaming) section. :::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: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Import the relevant symbols from `@sablier/lockup`: ```solidity import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Lockup } from "@sablier/lockup/src/types/Lockup.sol"; import { LockupTranched } from "@sablier/lockup/src/types/LockupTranched.sol"; ``` Create a contract called `LockupTranchedStreamCreator`, and declare a constant `DAI` of type `IERC20` and a constant `LOCKUP` of type `ISablierLockup`: ```solidity // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); ``` 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](/guides/lockup/deployments) page. There are two create functions in the Lockup contract that can be used to create Tranched streams: - `createWithDurationsLT`: takes duration and calculates the tranche timestamps based on the provided durations. - `createWithTimestampsLT`: takes UNIX timestamps for tranches. Which one you choose depends upon your use case. In this guide, we will use `createWithDurationsLT`. ## Function definition Define a function called `createStream` which takes two parameters, `amount0` and `amount1`, and which returns the id of the created stream: ```solidity function createStream(uint128 amount0, uint128 amount1) public returns (uint256 streamId) { // Sum the tranche amounts uint128 depositAmount = amount0 + amount1; // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not // Declare some dummy tranches LockupTranched.TrancheWithDuration[] memory tranches = new LockupTranched.TrancheWithDuration[](2); tranches[0] = LockupTranched.TrancheWithDuration({ amount: amount0, duration: uint40(4 weeks) }); tranches[1] = (LockupTranched.TrancheWithDuration({ amount: amount1, duration: uint40(6 weeks) })); // Create the LockupTranched stream streamId = LOCKUP.createWithDurationsLT({ params: params, tranchesWithDuration: tranches }); } } ``` Next, sum up the `amount0` and `amount1` parameters to get the deposit amount of the stream, which will be needed in many of the steps below: ```solidity uint128 depositAmount = amount0 + amount1; // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not // Declare some dummy tranches LockupTranched.TrancheWithDuration[] memory tranches = new LockupTranched.TrancheWithDuration[](2); tranches[0] = LockupTranched.TrancheWithDuration({ amount: amount0, duration: uint40(4 weeks) }); tranches[1] = (LockupTranched.TrancheWithDuration({ amount: amount1, duration: uint40(6 weeks) })); // Create the LockupTranched stream streamId = LOCKUP.createWithDurationsLT({ params: params, tranchesWithDuration: tranches }); } } ``` ## 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 approve the Lockup contract to pull the tokens that the creator contract will be in possession of after they are transferred from the calling address (you): ```solidity Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not // Declare some dummy tranches LockupTranched.TrancheWithDuration[] memory tranches = new LockupTranched.TrancheWithDuration[](2); tranches[0] = LockupTranched.TrancheWithDuration({ amount: amount0, duration: uint40(4 weeks) }); tranches[1] = (LockupTranched.TrancheWithDuration({ amount: amount1, duration: uint40(6 weeks) })); // Create the LockupTranched stream streamId = LOCKUP.createWithDurationsLT({ params: params, tranchesWithDuration: tranches }); } } ``` For more guidance on how to approve and transfer ERC-20 tokens, see [this article](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) on the Ethereum website. ## Parameters The struct associated with [`createWithDurationsLT`](/reference/lockup/contracts/interfaces/interface.ISablierLockupTranched#createwithdurationslt) are [`Lockup.CreateWithDurations`](/reference/lockup/contracts/types/library.Lockup#createwithdurations) (a shared struct across all the lockup streams) and [`LockupTranched.TrancheWithDuration`](/reference/lockup/contracts/types/library.LockupTranched#tranchewithduration). Let's review each parameter in detail. ### Create With Durations ```solidity LockupTranched.TrancheWithDuration[] memory tranches = new LockupTranched.TrancheWithDuration[](2); tranches[0] = LockupTranched.TrancheWithDuration({ amount: amount0, duration: uint40(4 weeks) }); tranches[1] = (LockupTranched.TrancheWithDuration({ amount: amount1, duration: uint40(6 weeks) })); ``` #### Sender The address streaming the tokens, with the ability to cancel the stream: ```solidity streamId = LOCKUP.createWithDurationsLT({ params: params, tranchesWithDuration: tranches }); } } ``` #### Recipient The address receiving the tokens: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Lockup } from "@sablier/lockup/src/types/Lockup.sol"; import { LockupTranched } from "@sablier/lockup/src/types/LockupTranched.sol"; /// @notice Example of how to create a Lockup Tranched stream. /// @dev This code is referenced in the docs: /// https://docs.sablier.com/guides/lockup/examples/create-stream/lockup-tranched contract LockupTranchedStreamCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); /// @dev For this function to work, the sender must have approved this dummy contract to spend DAI. function createStream(uint128 amount0, uint128 amount1) public returns (uint256 streamId) { // Sum the tranche amounts uint128 depositAmount = amount0 + amount1; // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); // Declare the params struct Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not // Declare some dummy tranches LockupTranched.TrancheWithDuration[] memory tranches = new LockupTranched.TrancheWithDuration[](2); tranches[0] = LockupTranched.TrancheWithDuration({ amount: amount0, duration: uint40(4 weeks) }); tranches[1] = (LockupTranched.TrancheWithDuration({ amount: amount1, duration: uint40(6 weeks) })); // Create the LockupTranched stream streamId = LOCKUP.createWithDurationsLT({ params: params, tranchesWithDuration: tranches }); } } ``` #### Deposit amount The deposit amount of ERC-20 tokens to be paid, denoted in units of the token's decimals. ```solidity params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Token The contract address of the ERC-20 token used for streaming. In this example, we will stream DAI: ```solidity params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Cancelable Boolean that indicates whether the stream will be cancelable or not. ```solidity params.depositAmount = depositAmount; // The deposit amount into the stream params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` #### Transferable Boolean that indicates whether the stream will be transferable or not. ```solidity params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ### Tranches With Duration Tranches are what the protocol uses to compose the discrete unlocks. For a full exposition of tranches, see the [Tranches](/concepts/lockup/tranches) guide. Each tranche is characterized by a specific amount and timestamp. Because we are using `createWithDurationsLT` in this example, these tranches are supplied to the function in the form of an array containing [`LockupTranched.TrancheWithDuration`](/reference/lockup/contracts/types/library.LockupTranched#tranchewithduration) structs. Let's define two dummy tranches: ```solidity params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` 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 deposit amount will be unlocked in 10 weeks. ## Invoke the create function With all parameters set, we can now call the `createWithDurationsLT` function, and assign the ID of the newly created stream to a variable: ```solidity params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 52 weeks // Setting a total duration of ~1 year }); // Create the LockupLinear stream using a function that sets the start time to `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, granularity: 1 seconds, durations: durations }); } } ``` ## Full code Below you can see the full code. You can also access the code on GitHub through [this link](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/examples/lockup/LockupTranchedStreamCreator.sol). ```solidity // Transfer the provided amount of DAI tokens to this contract DAI.transferFrom(msg.sender, address(this), depositAmount); // Approve the Sablier contract to spend DAI DAI.approve(address(LOCKUP), depositAmount); ``` --- ## Implement Hooks Source: https://docs.sablier.com/guides/lockup/examples/hooks # Implement Hooks Hooks provide an interface for recipient contracts to react upon cancellations and withdrawals. In order to allow your contract to be able to hook into Lockup, you must implement this interface and it must have been allowlisted by the Lockup contract's admin. :::info [`allowToHook`](/reference/lockup/contracts/interfaces/interface.ISablierLockup#allowtohook) is an irreversible operation, i.e., once a contract has been added to the allowlist, it can never be removed. This is to ensure stronger immutability and decentralization guarantees. Once a recipient contract is allowlisted, integrators should NOT have to trust us to keep their contract on the allowlist. ::: In this guide, we will explain how to implement [hooks](/concepts/lockup/hooks) in your smart contract to allow interacting with Lockup streams. ### Overview ### Requirements The recipient contract should implement the `{IERC165-supportsInterface}` method, which MUST return `true` when called with `0xf8ee98d3`, which is the interface ID for `ISablierLockupRecipient`. ```solidity function supportsInterface(bytes4 interfaceId) public pure override(IERC165) returns (bool) { return interfaceId == 0xf8ee98d3; } ``` ### Hook Functions These are the hooks that can be implemented by a recipient contract: | Hook | Arguments | Return value | Description | | --- | --- | --- | --- | | `onSablierLockupCancel` | `(streamId, sender, senderAmount, recipientAmount)` | function selector | Called when the stream is canceled by the sender. | | `onSablierLockupWithdraw` | `(streamId, caller, to, amount)` | function selector | Called when an amount is withdrawn from the stream. | The complete interface for `ISablierLockupRecipient` can be found [here](/reference/lockup/contracts/interfaces/interface.ISablierLockupRecipient). ### Sample Implementations #### Recipient ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.22; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { ISablierLockupRecipient } from "@sablier/lockup/src/interfaces/ISablierLockupRecipient.sol"; contract RecipientHooks is ISablierLockupRecipient { error CallerNotSablierContract(address caller, address sablierLockup); /// @dev The address of the lockup contract. It could be either LockupLinear, LockupDynamic or LockupTranched /// depending on which type of streams are supported in this hook. address public immutable SABLIER_LOCKUP; mapping(address account => uint256 amount) internal _balances; /// @dev Constructor will set the address of the lockup contract. constructor(address sablierLockup_) { SABLIER_LOCKUP = sablierLockup_; } // {IERC165-supportsInterface} implementation as required by `ISablierLockupRecipient` interface. function supportsInterface(bytes4 interfaceId) public pure override(IERC165) returns (bool) { return interfaceId == 0xf8ee98d3; } // This will be called by Sablier contract when a stream is canceled by the sender. function onSablierLockupCancel( uint256 streamId, address sender, uint128 senderAmount, uint128 recipientAmount ) external view returns (bytes4 selector) { // Check: the caller is the lockup contract. if (msg.sender != SABLIER_LOCKUP) { revert CallerNotSablierContract(msg.sender, SABLIER_LOCKUP); } // Unstake the user's NFT. _unstake({ nftId: streamId }); // Update data. _updateData(streamId, sender, senderAmount, recipientAmount); return ISablierLockupRecipient.onSablierLockupCancel.selector; } // This will be called by Sablier contract when withdraw is called on a stream. function onSablierLockupWithdraw( uint256 streamId, address caller, address to, uint128 amount ) external view returns (bytes4 selector) { // Check: the caller is the lockup contract. if (msg.sender != SABLIER_LOCKUP) { revert CallerNotSablierContract(msg.sender, SABLIER_LOCKUP); } // Transfer the withdrawn amount to the original user. _transfer(to, amount); // Update data. _updateData(streamId, caller, amount, 0); return ISablierLockupRecipient.onSablierLockupWithdraw.selector; } function _unstake(uint256 nftId) internal pure { } function _updateData( uint256 streamId, address sender, uint128 senderAmount, uint128 recipientAmount ) internal pure { } function _transfer(address to, uint128 amount) internal pure { } } ``` --- ## Configure Your Local Environment Source: https://docs.sablier.com/guides/lockup/examples/local-environment # Configure Your Local Environment In this guide, we will go through the steps to set up a local development environment for building onchain integrations with Lockup. We will use Foundry to install Lockup as a dependency, and run a simple test. At the end, you’ll have a development environment set up that you can use to build the rest of the examples under "Guides", or start your own integration project. ## Pre-requisites You will need the following software on your machine: - [Git](https://git-scm.com/downloads) - [Foundry](https://github.com/foundry-rs/foundry) - [Node.js](https://nodejs.org/en/download) - [Bun](https://bun.sh) In addition, familiarity with [Ethereum](https://ethereum.org/) and [Solidity](https://soliditylang.org/) is requisite. ## Set up using integration template :::tip Make sure you are using the latest version of Foundry by running `foundryup`. ::: We put together a template repository that you can use to get started quickly. This repository features a basic project structure, pre-configured Lockup imports, and a selection of sample contracts and tests. To install the template, simply execute the following commands: ```bash $ mkdir lockup-integration-template $ cd lockup-integration-template $ forge init --template sablier-labs/lockup-integration-template $ bun install ``` Then, hop to the [Run a Fork Test](#run-a-fork-test) section to complete your set up and start developing. ## Set up using Foundry template Foundry is a popular development toolkit for Ethereum projects, which we have used to build the Lockup Protocol. For the purposes of this guide, Foundry will provide us with the tooling needed to compile and test our contracts. Let's use this command to spin up a new Foundry project: ```bash $ forge init my-project $ cd my-project ``` Once the initialization completes, take a look around at what got set up: ```bash ├── foundry.toml ├── script ├── src └── test ``` The folder structure should be intuitive: - `src` is where you'll write Solidity contracts - `test` is where you'll write tests (also in Solidity) - `script` is where you'll write scripts to perform actions like deploying contracts (you guessed it, in Solidity) - `foundry.toml` is where you can configure your Foundry settings, which we will leave as is in this guide :::note You might notice that the CLI is `forge` rather than `foundry`. This is because Foundry is a toolkit, and `forge` is just one of the tools that comes with it. ::: ## Install via npm package Let's install the Lockup Node.js packages using Bun: ```bash $ bun add @sablier/lockup ``` Bun will download the Lockup contracts, along with their dependencies, and put them in the `node_modules` directory. That's it! You should now have a functional development environment to start building onchain Lockup integrations. Let's run a quick test to confirm everything is set up properly. ## Write your first contract Delete the `src/Counter.sol` and `test/Counter.t.sol` files generated by Forge, and create two new files: `src/StreamCreator.sol` and `test/StreamCreator.t.sol`. Paste the following code into `src/StreamCreator.sol`: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Lockup } from "@sablier/lockup/src/types/Lockup.sol"; import { LockupLinear } from "@sablier/lockup/src/types/LockupLinear.sol"; /// @title LockupStreamCreator /// @dev This contract allows users to create Sablier lockup streams using the Lockup contract. contract LockupStreamCreator { // Mainnet addresses IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); ISablierLockup public constant LOCKUP = ISablierLockup(0x93b37Bd5B6b278373217333Ac30D7E74c85fBDCB); /// @dev Before calling this function, the user must first approve this contract to spend the tokens from the user's /// address. function createLinearStream() public returns (uint256 streamId) { // Declare the total amount as 100 DAI uint128 totalAmount = 100e18; // 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), totalAmount); // Declare the params struct Lockup.CreateWithDurations memory params; // Declare the function parameters params.sender = msg.sender; // The sender will be able to cancel the stream params.recipient = address(0xCAFE); // The recipient of the streamed tokens params.depositAmount = totalAmount; // Total amount is the amount inclusive of all fees params.token = DAI; // The streaming token params.cancelable = true; // Whether the stream will be cancelable or not params.transferable = true; // Whether the stream will be transferable or not LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, // Setting a cliff of 0 total: 100 days // Setting a total duration of 100 days }); // Create the Lockup stream with Linear shape, no cliff and start time as `block.timestamp` streamId = LOCKUP.createWithDurationsLL({ params: params, unlockAmounts: unlockAmounts, durations: durations, granularity: 1 seconds }); } } ``` Let's use Forge to compile this contract: ```bash $ forge build ``` If the contract was compiled correctly, you should see this message: ```bash [⠢] Compiling... [⠰] Compiling 62 files with Solc 0.8.29 [⠒] Solc 0.8.29 finished in 798.47ms Compiler run successful! ``` :::info The minimum Solidity version supported by the Lockup contracts is v0.8.22. ::: ## Run a fork test Foundry offers native support for running tests against a fork of Ethereum Mainnet, testnets and L2s, which is useful when building and testing integrations with onchain protocols like Sablier. In practice, this enables you to access all Sablier contracts deployed on Ethereum, and use them for testing your integration. As a prerequisite, you will need an RPC that supports forking. A good solution for this is [Alchemy](https://alchemy.com/), as it includes forking in its free tier plan. Once you have obtained your RPC, you can proceed to run the following test: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { Test } from "forge-std/src/Test.sol"; import { LockupStreamCreator } from "../src/LockupStreamCreator.sol"; contract LockupStreamCreatorTest is Test { // Test contracts LockupStreamCreator internal creator; address internal user; function setUp() public { // Fork Ethereum Mainnet at the latest block vm.createSelectFork("mainnet"); // Deploy the stream creator contract creator = new LockupStreamCreator(); // Create a test user user = payable(makeAddr("User")); vm.deal({ account: user, newBalance: 1 ether }); // Mint some DAI tokens to the test user, which will be pulled by the creator contract. Make sure its more than // `params.totalAmount`. deal({ token: address(creator.DAI()), to: user, give: 1337e18 }); // Make the test user the `msg.sender` in all following calls vm.startPrank({ msgSender: user }); // Approve the creator contract to pull DAI tokens from the test user creator.DAI().approve({ spender: address(creator), value: 1337e18 }); } function test_CreateLinearStream() public { uint256 expectedStreamId = creator.LOCKUP().nextStreamId(); uint256 actualStreamId = creator.createLinearStream(); // Check that creating linear stream works by checking the stream id assertEq(actualStreamId, expectedStreamId); } } ``` You can run the test using Forge: ```bash $ forge test ``` If the test passed, you should see a message like this: ```bash Ran 2 tests for test/LockupStreamCreator.t.sol:LockupStreamCreatorTest [PASS] test_CreateLockupStream() (gas: 246830) Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 626.58ms (500.67µs CPU time) ``` ## Next steps Congratulations! Your environment is now configured, and you are prepared to start building. Explore the guides section to discover various features available for Lockup integration. Remember to include all contracts (`.sol` files) in the `src` folder and their corresponding tests in the `test` folder. As far as Foundry is concerned, there is much more to uncover. If you want to learn more about it, check out the [Foundry Book](https://book.getfoundry.sh/), which contains numerous examples and tutorials. A deep understanding of Foundry will enable you to create more sophisticated integrations with Lockup protocol. --- ## Cancel Streams Source: https://docs.sablier.com/guides/lockup/examples/stream-management/cancel # Cancel Streams :::note This section assumes that you have already gone through the [setup](/guides/lockup/examples/stream-management/setup) part. ::: :::tip See the [Access Control](/reference/lockup/access-control) guide for an overview of who is allowed to cancel streams. ::: Canceling streams involves stopping the flow of tokens before the stream's end time and refunding the remaining funds to the sender. However, the portion that has already been streamed is NOT automatically transferred - the recipient will need to withdraw it. There are two functions that can be used to cancel streams: 1. [`cancel`](/reference/lockup/contracts/contract.SablierLockup#cancel): cancels a single stream 2. [`cancelMultiple`](/reference/lockup/contracts/contract.SablierLockup#cancelmultiple): cancels multiple streams at once To call any of these functions, you need to have created a cancelable stream. If you don't have one yet, go back to the [previous guide](/guides/lockup/examples/create-stream/lockup-linear) and create a stream. Then, you can use the `cancel` function like this: ```solidity function cancel(uint256 streamId) external { sablier.cancel(streamId); } ``` In addition to the `cancel` function, there is the `cancelMultiple` function, which allows you to cancel several streams at once: ```solidity function cancelMultiple(uint256[] calldata streamIds) external { sablier.cancelMultiple(streamIds); } ``` --- ## Renounce Streams Source: https://docs.sablier.com/guides/lockup/examples/stream-management/renounce # Renounce Streams :::note This section assumes that you have already gone through the [setup](/guides/lockup/examples/stream-management/setup) part. ::: Renouncing a stream means that the sender of the stream will no longer be able to cancel it. This is useful if the sender wants to give up control of the stream. To renounce a stream, you can use [`renounce`](/reference/lockup/contracts/contract.SablierLockup#renounce). Before invoking this function, ensure that you have an active, cancelable stream with the sender set to the `StreamManagement` contract. Once the stream is created, you can use the `renounce` function like this: ```solidity function renounce(uint256 streamId) external { sablier.renounce(streamId); } ``` --- ## Set Up Your Contract Source: https://docs.sablier.com/guides/lockup/examples/stream-management/setup # Set Up Your Contract The "Stream Management" series will guide you through how to withdraw, cancel, renounce, and transfer ownership of streams. Before diving in, please note the following: 1. We assume you are already familiar with [creating streams](/guides/lockup/examples/create-stream/lockup-linear). 2. We also assume that the stream management contract is authorized to invoke each respective function. To learn more about access control in Lockup, see the [Access Control](/reference/lockup/access-control) guide. With that said, let's begin. First, declare the Solidity version used to compile the contract: ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; ``` Import the relevant symbols from `@sablier/core`: ```solidity import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; /// @notice Examples of how to manage Sablier streams after they have been created. /// @dev This code is referenced in the docs: https://docs.sablier.com/guides/lockup/examples/stream-management/setup contract StreamManagement { ISablierLockup public immutable sablier; constructor(ISablierLockup sablier_) { sablier = sablier_; } /*////////////////////////////////////////////////////////////////////////// 02-WITHDRAW //////////////////////////////////////////////////////////////////////////*/ // This function can be called by the sender, recipient, or an approved NFT operator function withdraw(uint256 streamId) external payable { uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdraw{ value: fee }({ streamId: streamId, to: address(0xCAFE), amount: 1337e18 }); } // This function can be called by the sender, recipient, or an approved NFT operator function withdrawMax(uint256 streamId) external payable { uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdrawMax{ value: fee }({ streamId: streamId, to: address(0xCAFE) }); } // This function can be called by either the recipient or an approved NFT operator function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external payable { uint256 maxFeeRequired; // The fee required to call withdraw multiple is the maximum of the fees required to withdraw each stream. for (uint256 i = 0; i < streamIds.length; i++) { uint256 feeForStreamId = sablier.calculateMinFeeWei(streamIds[i]); if (feeForStreamId > maxFeeRequired) { maxFeeRequired = feeForStreamId; } } sablier.withdrawMultiple{ value: maxFeeRequired }({ streamIds: streamIds, amounts: amounts }); } /*////////////////////////////////////////////////////////////////////////// 03-CANCEL //////////////////////////////////////////////////////////////////////////*/ // This function can be called only by the sender function cancel(uint256 streamId) external { sablier.cancel(streamId); } // This function can be called only by the sender function cancelMultiple(uint256[] calldata streamIds) external { sablier.cancelMultiple(streamIds); } /*////////////////////////////////////////////////////////////////////////// 04-RENOUNCE //////////////////////////////////////////////////////////////////////////*/ // This function can be called only by the sender function renounce(uint256 streamId) external { sablier.renounce(streamId); } /*////////////////////////////////////////////////////////////////////////// 05-TRANSFER //////////////////////////////////////////////////////////////////////////*/ // This function can be called by either the recipient or an approved NFT operator function safeTransferFrom(uint256 streamId) external { sablier.safeTransferFrom({ from: address(this), to: address(0xCAFE), tokenId: streamId }); } // This function can be called by either the recipient or an approved NFT operator function transferFrom(uint256 streamId) external { sablier.transferFrom({ from: address(this), to: address(0xCAFE), tokenId: streamId }); } // This function can be called only by the recipient function withdrawMaxAndTransfer(uint256 streamId) external payable { // Calculate the minimum fee to withdraw the amount. uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdrawMaxAndTransfer{ value: fee }({ streamId: streamId, newRecipient: address(0xCAFE) }); } } ``` Create a contract called `StreamManagement` and declare an immutable variable `sablier` of type `ISablierLockup`: ```solidity ISablierLockup public immutable sablier; constructor(ISablierLockup sablier_) { sablier = sablier_; } /*////////////////////////////////////////////////////////////////////////// 02-WITHDRAW //////////////////////////////////////////////////////////////////////////*/ // This function can be called by the sender, recipient, or an approved NFT operator function withdraw(uint256 streamId) external payable { uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdraw{ value: fee }({ streamId: streamId, to: address(0xCAFE), amount: 1337e18 }); } // This function can be called by the sender, recipient, or an approved NFT operator function withdrawMax(uint256 streamId) external payable { uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdrawMax{ value: fee }({ streamId: streamId, to: address(0xCAFE) }); } // This function can be called by either the recipient or an approved NFT operator function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external payable { uint256 maxFeeRequired; // The fee required to call withdraw multiple is the maximum of the fees required to withdraw each stream. for (uint256 i = 0; i < streamIds.length; i++) { uint256 feeForStreamId = sablier.calculateMinFeeWei(streamIds[i]); if (feeForStreamId > maxFeeRequired) { maxFeeRequired = feeForStreamId; } } sablier.withdrawMultiple{ value: maxFeeRequired }({ streamIds: streamIds, amounts: amounts }); } /*////////////////////////////////////////////////////////////////////////// 03-CANCEL //////////////////////////////////////////////////////////////////////////*/ // This function can be called only by the sender function cancel(uint256 streamId) external { sablier.cancel(streamId); } // This function can be called only by the sender function cancelMultiple(uint256[] calldata streamIds) external { sablier.cancelMultiple(streamIds); } /*////////////////////////////////////////////////////////////////////////// 04-RENOUNCE //////////////////////////////////////////////////////////////////////////*/ // This function can be called only by the sender function renounce(uint256 streamId) external { sablier.renounce(streamId); } /*////////////////////////////////////////////////////////////////////////// 05-TRANSFER //////////////////////////////////////////////////////////////////////////*/ // This function can be called by either the recipient or an approved NFT operator function safeTransferFrom(uint256 streamId) external { sablier.safeTransferFrom({ from: address(this), to: address(0xCAFE), tokenId: streamId }); } // This function can be called by either the recipient or an approved NFT operator function transferFrom(uint256 streamId) external { sablier.transferFrom({ from: address(this), to: address(0xCAFE), tokenId: streamId }); } // This function can be called only by the recipient function withdrawMaxAndTransfer(uint256 streamId) external payable { // Calculate the minimum fee to withdraw the amount. uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdrawMaxAndTransfer{ value: fee }({ streamId: streamId, newRecipient: address(0xCAFE) }); } } ``` Just like in the create stream guides, the next step requires you to head over to the [Deployment Addresses](/guides/lockup/deployments) page and copy the address of the Lockup contract. Then, you can deploy the stream management contract: ```solidity constructor(ISablierLockup sablier_) { sablier = sablier_; } ``` You're all set! You can now move on to the next page, which will teach you how to withdraw from a stream. --- ## Transfer Ownership Source: https://docs.sablier.com/guides/lockup/examples/stream-management/transfer # Transfer Ownership :::note This section assumes that you have already gone through the [setup](/guides/lockup/examples/stream-management/setup) part. ::: :::tip See the [Access Control](/reference/lockup/access-control) guide for an overview of who is allowed to transfer ownership. ::: You may remember from the [NFT](/concepts/nft) guide that every Lockup stream is wrapped in an [ERC-721](https://eips.ethereum.org/EIPS/eip-721) non-fungible token (NFT). One of the key benefits of this design is that the recipient of the stream has the ability to transfer the NFT to a different address, effectively redirecting the streaming of tokens to that new address. To transfer ownership of a stream, it is recommended to invoke the [`withdrawMaxAndTransfer`](/reference/lockup/contracts/contract.SablierLockup#withdrawmaxandtransfer) function, which withdraws all the unclaimed funds to the current recipient prior to transferring ownership to the new recipient: ```solidity function withdrawMaxAndTransfer(uint256 streamId) external payable { // Calculate the minimum fee to withdraw the amount. uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdrawMaxAndTransfer{ value: fee }({ streamId: streamId, newRecipient: address(0xCAFE) }); } ``` The withdrawal will be skipped if there are no unclaimed funds. If you want to transfer ownership without withdrawing the funds, you can use the `IERC721.transferFrom` function: ```solidity function transferFrom(uint256 streamId) external { sablier.transferFrom({ from: address(this), to: address(0xCAFE), tokenId: streamId }); } ``` :::caution Be careful with `transferFrom`. All remaining funds, including the already streamed portion, will enter into the possession of the new recipient. Consider using `withdrawMaxAndTransfer` instead. ::: Finally, note that in the examples above, the new recipient is hard-coded for demonstration purposes. However, in a production environment, the new recipient would definitely be an adjustable parameter provided by the user. --- ## Withdraw from Streams Source: https://docs.sablier.com/guides/lockup/examples/stream-management/withdraw # Withdraw from Streams :::note This section assumes that you have already gone through the [setup](/guides/lockup/examples/stream-management/setup) part. ::: :::tip See the [Access Control](/reference/lockup/access-control) guide for an overview of who is allowed to withdraw from streams. ::: Withdrawing from streams means claiming the tokens that have become due to the recipient, who has the option to direct the withdrawal to an alternative address of their choice. There are four withdrawal functions: 1. [`withdraw`](/reference/lockup/contracts/contract.SablierLockup#withdraw): withdraws a specific amount of tokens. 2. [`withdrawMax`](/reference/lockup/contracts/contract.SablierLockup#withdrawmax): withdraws the maximum withdrawable amount of tokens. 3. [`withdrawMaxAndTransfer`](/reference/lockup/contracts/contract.SablierLockup#withdrawmaxandtransfer): withdraws the maximum withdrawable amount and transfers the NFT. 4. [`withdrawMultiple`](/reference/lockup/contracts/contract.SablierLockup#withdrawmultiple): withdraws specific amounts of tokens from multiple streams at once. To call any of these functions, you need to have created a stream. If you don't have one yet, go back to the [previous guide](/guides/lockup/examples/create-stream/lockup-linear) and create a stream with a brief duration, assigning the `StreamManagement` contract as the recipient. Then, you can use the `withdraw` function like this: :::note The `withdraw` functions requires a fee. Make sure to send the correct amount in `msg.value`, as shown below. ::: ```solidity function withdraw(uint256 streamId) external payable { uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdraw{ value: fee }({ streamId: streamId, to: address(0xCAFE), amount: 1337e18 }); } ``` In this example, the withdrawal address and withdrawal amount are hard-coded for demonstration purposes. However, in a production environment, these values would likely be adjustable parameters determined by the user. Alternatively, you can use [`withdrawableAmountOf`](/reference/lockup/contracts/contract.SablierLockup#withdrawableamountof) function to determine how much amount of tokens is available to withdraw. In addition to the `withdraw` function, there is the `withdrawMax` function, which you can use to withdraw the maximum withdrawable amount of tokens at the time of invocation: ```solidity function withdrawMax(uint256 streamId) external payable { uint256 fee = sablier.calculateMinFeeWei(streamId); sablier.withdrawMax{ value: fee }({ streamId: streamId, to: address(0xCAFE) }); } ``` What `withdrawMax` does is call the [`withdrawableAmountOf`](/reference/lockup/contracts/contract.SablierLockup#withdrawableamountof) function and pass its value to `withdraw`. Similar to `withdrawMax`, you can use `withdrawMaxAndTransfer` to withdraw the maximum withdrawable tokens and at the same time, transfer the NFT to another address. Lastly, there is the `withdrawMultiple` function, with which you can use to withdraw from multiple streams at once: ```solidity function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external payable { uint256 maxFeeRequired; // The fee required to call withdraw multiple is the maximum of the fees required to withdraw each stream. for (uint256 i = 0; i < streamIds.length; i++) { uint256 feeForStreamId = sablier.calculateMinFeeWei(streamIds[i]); if (feeForStreamId > maxFeeRequired) { maxFeeRequired = feeForStreamId; } } sablier.withdrawMultiple{ value: maxFeeRequired }({ streamIds: streamIds, amounts: amounts }); } ``` --- ## Pull Vesting Data Source: https://docs.sablier.com/guides/lockup/examples/vesting-data # Pull Vesting Data This guide explains how you can pull vesting data from Sablier Lockup streams. This data can be useful for a variety of use cases, including but not limited to staking contracts, blockchain explorers, and data dashboards: 1. **Staking**: Staking of Sablier streams require access to the amount of tokens that are held by the stream. You do not want to distribute rewards for tokens that have been withdrawn by users. 2. **Explorers (e.g., Etherscan, CoinGecko)**: One major feature of blockchain explorers is to show accurate circulating supplies. When tokens are vested through Sablier, you may want to exclude the amount of unvested tokens from the circulating supply. This is helpful to accurately calculate the market cap, which depends upon the amount of liquid tokens. 3. **Data Dashboards (e.g., Tokenomist, Nansen, Dune)**: Investors and traders use data dashboards to make informed trading decisions. When Sablier is used, you may want to show the amount of liquid (or vested) tokens and the amount of illiquid (or unvested) tokens. This is helpful to understand the token distribution and the team's commitment to the long-term success of the project. :::note Note that 'streamed amount' is synonymous with 'vested amount'. ::: ## Frontend Sandbox The examples in this guide are written in Solidity, but you may want to interact with the Sablier Lockup contract from your frontend application. A good starting point for this is the [Sablier Sandbox](https://github.com/sablier-labs/sandbox). For a comprehensive list of all the functions available in Sablier Lockup, visit the [References](/reference/overview) section of this website. ## Actions ### Cancel on first withdraw To automatically cancel streams as soon as the user withdraws their tokens, you can use one of two methods: onchain or offchain. The onchain method is to track the withdrawn amount and check if its value is greater than 0: ```solidity if (lockup.getWithdrawnAmount(streamId) > 0) { lockup.cancel(streamId); } ``` Offchain, you can monitor the [`WithdrawFromLockupStream`](/reference/lockup/contracts/interfaces/interface.ISablierLockup) events. As soon as a withdrawal event is detected, you can send a transaction to cancel the stream. ## Calculating Amounts ### Amount in stream This is the amount of tokens held by the stream. It is the sum of locked tokens and vested tokens that have not been withdrawn. This value is particularly useful for applications like staking. The following formula can be used for both cancelable and non-cancelable streams: ```solidity uint256 amountInStream = sablierLockup.getDepositedAmount(streamId) - sablierLockup.getWithdrawnAmount(streamId) - sablierLockup.getRefundedAmount(streamId); ``` For a non-cancelable stream, a more efficient way to calculate the amount in stream is: ```solidity uint256 amountInStream = sablierLockup.getDepositedAmount(streamId) - sablierLockup.getWithdrawnAmount(streamId); ``` ### Locked amount This is the amount of tokens that are locked in the stream and are effectively illiquid. This is particularly relevant when calculating the circulating supply of a token. ```solidity uint256 lockedAmount = lockup.getDepositedAmount(streamId) - lockup.streamedAmountOf(streamId) - sablierLockup.getRefundedAmount(streamId); ``` For a non-cancelable stream, a more efficient way to calculate locked amount is: ```solidity uint256 lockedAmount = lockup.getDepositedAmount(streamId) - lockup.streamedAmountOf(streamId); ``` While calculating the circulating supply, you may want to subtract the locked amount from your calculations. ### Unlocked amount As opposed to the locked amount, the unlocked amount refers to tokens that are no longer locked and are effectively liquid. ```solidity uint256 unlockedAmount = lockup.streamedAmountOf(streamId) + sablierLockup.getRefundedAmount(streamId); ``` For a non-cancelable stream, a more efficient way to calculate unlocked amount is: ```solidity uint256 unlockedAmount = lockup.streamedAmountOf(streamId); ``` ## Vested amount not withdrawn If you are building an application that requires access to amount of tokens that have been vested but not yet withdrawn, you can use the following formula: ```solidity uint256 vestedAmount = lockup.streamedAmountOf(streamId) - lockup.getWithdrawnAmount(streamId); ``` This may be useful for use cases in which you want to reward 'diamond hands', i.e., users who have not withdrawn their share of airdrops. ## Unlock Dates This section is useful if you are building a data dashboard and want to index the dates when tokens will be unlocked in Sablier. To obtain the time at which a stream will be fully unlocked, you can use the following function: ```solidity uint256 unlockTime = lockup.getEndTime(streamId); ``` Obtaining the earlier unlock times depends on the type of stream. Let's go through each stream type: ### Linear streams For Linear streams, make requests to `lockup.getCliffTime(streamId)` and `lockup.getEndTime(streamId)` to read cliff time and end time respectively. ### Dynamic streams For Dynamic streams, you may be particularly interested in the unlock amount and time of the current segment. ```solidity LockupDynamic.Segment[] memory segments = lockup.getSegments(streamId); // Loop over the segments to find the next unlock time. for (uint i; i < segments.length; ++i) { if (segments[i].timestamp > block.timestamp) { nextUnlockAmount = segments[i].amount; nextUnlockTime = segments[i].timestamp; break; } } ``` ### Tranched streams For Tranched streams, you may be particularly interested in the unlock amount and time of the current tranche. ```solidity LockupTranched.Tranche[] memory tranches = lockup.getTranches(streamId); // Loop over the tranches to find the next unlock time. for (uint i; i < tranches.length; ++i) { if (tranches[i].timestamp > block.timestamp) { nextUnlockAmount = tranches[i].amount; nextUnlockTime = tranches[i].timestamp; break; } } ``` We hope you have found this guide helpful. --- ## Gas Benchmarks Source: https://docs.sablier.com/guides/lockup/gas-benchmarks # Gas Benchmarks The gas usage of the Lockup protocol is not deterministic and varies by user. Calls to third-party contracts, such as ERC-20 tokens, may use an arbitrary amount of gas. The values in the table below are rough estimations on Ethereum mainnet - you shouldn't take them for granted. The gas usage may vary depending on the network. :::note The benchmarks were generated using the code in this [GitHub repository](https://github.com/sablier-labs/evm-monorepo/blob/main/misc/benchmarks). ::: ## BatchLockup With WETH as the streaming token. | Lockup Model | Function | Batch Size | Segments/Tranches | Gas Usage | | :----------- | :----------------------- | :--------- | :---------------- | :--------- | | Linear | \`createWithDurationsLL\` | 5 | N/A | 1,050,118 | | Linear | \`createWithTimestampsLL\` | 5 | N/A | 998,961 | | Dynamic | \`createWithDurationsLD\` | 5 | 24 | 4,183,356 | | Dynamic | \`createWithTimestampsLD\` | 5 | 24 | 3,938,420 | | Tranched | \`createWithDurationsLT\` | 5 | 24 | 4,046,168 | | Tranched | \`createWithTimestampsLT\` | 5 | 24 | 3,853,698 | | Linear | \`createWithDurationsLL\` | 10 | N/A | 1,952,882 | | Linear | \`createWithTimestampsLL\` | 10 | N/A | 1,946,687 | | Dynamic | \`createWithDurationsLD\` | 10 | 24 | 8,321,775 | | Dynamic | \`createWithTimestampsLD\` | 10 | 24 | 7,826,938 | | Tranched | \`createWithDurationsLT\` | 10 | 24 | 8,039,187 | | Tranched | \`createWithTimestampsLT\` | 10 | 24 | 7,657,724 | | Linear | \`createWithDurationsLL\` | 20 | N/A | 3,856,454 | | Linear | \`createWithTimestampsLL\` | 20 | N/A | 3,844,693 | | Dynamic | \`createWithDurationsLD\` | 20 | 24 | 16,614,741 | | Dynamic | \`createWithTimestampsLD\` | 20 | 24 | 15,607,548 | | Tranched | \`createWithDurationsLT\` | 20 | 24 | 16,022,800 | | Tranched | \`createWithTimestampsLT\` | 20 | 24 | 15,269,123 | | Linear | \`createWithDurationsLL\` | 50 | N/A | 9,576,314 | | Linear | \`createWithTimestampsLL\` | 50 | N/A | 9,550,888 | | Dynamic | \`createWithDurationsLD\` | 50 | 12 | 24,460,057 | | Dynamic | \`createWithTimestampsLD\` | 50 | 12 | 23,154,421 | | Tranched | \`createWithDurationsLT\` | 50 | 12 | 23,662,837 | | Tranched | \`createWithTimestampsLT\` | 50 | 12 | 22,742,309 | ## LockupLinear streams With WETH as the streaming token. | Function | Configuration | Gas Usage | | :----------------------- | :----------------------------------------- | :-------- | | \`burn\` | N/A | 8510 | | \`cancel\` | N/A | 39,195 | | \`renounce\` | N/A | 4532 | | \`createWithDurationsLL\` | no cliff | 145,964 | | \`createWithDurationsLL\` | with cliff | 185,937 | | \`createWithTimestampsLL\` | no cliff | 145,249 | | \`createWithTimestampsLL\` | with cliff | 184,994 | | \`withdraw\` | vesting ongoing && called by recipient | 32,194 | | \`withdraw\` | vesting completed && called by recipient | 32,394 | | \`withdraw\` | vesting ongoing && called by third-party | 32,433 | | \`withdraw\` | vesting completed && called by third-party | 32,633 | ## LockupDynamic streams With WETH as the streaming token. | Function | Segments | Configuration | Gas Usage | | :----------------------- | :------- | :----------------------------------------- | :-------- | | \`burn\` | 2 | N/A | 8510 | | \`cancel\` | 2 | N/A | 50,777 | | \`renounce\` | 2 | N/A | 4532 | | \`createWithDurationsLD\` | 2 | N/A | 197,298 | | \`createWithTimestampsLD\` | 2 | N/A | 191,161 | | \`withdraw\` | 2 | vesting ongoing && called by recipient | 43,779 | | \`withdraw\` | 2 | vesting completed && called by recipient | 32,628 | | \`withdraw\` | 2 | vesting ongoing && called by third-party | 44,018 | | \`withdraw\` | 2 | vesting completed && called by third-party | 32,867 | | \`createWithDurationsLD\` | 10 | N/A | 421,226 | | \`createWithTimestampsLD\` | 10 | N/A | 399,192 | | \`withdraw\` | 10 | vesting ongoing && called by recipient | 48,903 | | \`withdraw\` | 10 | vesting completed && called by recipient | 34,816 | | \`withdraw\` | 10 | vesting ongoing && called by third-party | 49,142 | | \`withdraw\` | 10 | vesting completed && called by third-party | 35,055 | | \`createWithDurationsLD\` | 100 | N/A | 2,947,148 | | \`createWithTimestampsLD\` | 100 | N/A | 2,742,934 | | \`withdraw\` | 100 | vesting ongoing && called by recipient | 106,703 | | \`withdraw\` | 100 | vesting completed && called by recipient | 59,586 | | \`withdraw\` | 100 | vesting ongoing && called by third-party | 106,942 | | \`withdraw\` | 100 | vesting completed && called by third-party | 59,825 | ## LockupTranched streams With WETH as the streaming token. | Function | Tranches | Configuration | Gas Usage | | :----------------------- | :------- | :----------------------------------------- | :-------- | | \`burn\` | 2 | N/A | 8510 | | \`cancel\` | 2 | N/A | 39,459 | | \`renounce\` | 2 | N/A | 4532 | | \`createWithDurationsLT\` | 2 | N/A | 194,663 | | \`createWithTimestampsLT\` | 2 | N/A | 190,054 | | \`withdraw\` | 2 | vesting ongoing && called by recipient | 32,458 | | \`withdraw\` | 2 | vesting completed && called by recipient | 32,810 | | \`withdraw\` | 2 | vesting ongoing && called by third-party | 32,697 | | \`withdraw\` | 2 | vesting completed && called by third-party | 33,049 | | \`createWithDurationsLT\` | 10 | N/A | 410,233 | | \`createWithTimestampsLT\` | 10 | N/A | 392,857 | | \`withdraw\` | 10 | vesting ongoing && called by recipient | 37,460 | | \`withdraw\` | 10 | vesting completed && called by recipient | 34,636 | | \`withdraw\` | 10 | vesting ongoing && called by third-party | 37,699 | | \`withdraw\` | 10 | vesting completed && called by third-party | 34,875 | | \`createWithDurationsLT\` | 100 | N/A | 2,838,746 | | \`createWithTimestampsLT\` | 100 | N/A | 2,676,076 | | \`withdraw\` | 100 | vesting ongoing && called by recipient | 93,804 | | \`withdraw\` | 100 | vesting completed && called by recipient | 55,250 | | \`withdraw\` | 100 | vesting ongoing && called by third-party | 94,043 | | \`withdraw\` | 100 | vesting completed && called by third-party | 55,489 | --- ## Sablier Lockup Source: https://docs.sablier.com/guides/lockup/overview # Sablier Lockup Welcome to the Sablier Lockup documentation. This section contains detailed guides and technical references for the Lockup protocol, a suite of smart contracts running autonomously in the Ethereum ecosystem. These documents offer insight into the operational nuances of the contracts, providing a valuable resource for building onchain integrations. # Guides If you are new to Sablier, we recommend you start with the [Concepts](/concepts/what-is-sablier) section first. You can then setup your [local environment](/guides/lockup/examples/local-environment) and create your [first stream](/guides/lockup/examples/create-stream/lockup-linear). # Reference For a deeper dive into the protocol specifications, read through the [technical reference](/reference/lockup/diagrams). # Versioning The product uses a unified versioning system across releases and NPM packages. Prior to Lockup v1.2, we used a different versioning scheme (V2.0, V2.1, V2.2), while the NPM package used a semantic versioning scheme (e.g., v1.0.2, v1.1.2). Since Lockup v1.2, the versioning has been unified into a single system for greater consistency across protocol releases and NPM packages. # Resources - [Source Code](https://github.com/sablier-labs/evm-monorepo/blob/main/lockup) - [Integration Templates](https://github.com/sablier-labs/lockup-integration-template) - [Examples](https://github.com/sablier-labs/evm-monorepo/tree/main/misc/examples/lockup/) - [Foundry Book](https://book.getfoundry.sh/) --- ## Lockup v1.0 Source: https://docs.sablier.com/guides/lockup/previous-deployments/v1.0 # Lockup v1.0 This section contains the deployment addresses for the v1.0 release of [@sablier/v2-core@1.0.2](https://npmjs.com/package/@sablier/v2-core/v/1.0.2) and [@sablier/v2-periphery@1.0.3](https://npmjs.com/package/@sablier/v2-periphery/v/1.0.3). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Lockup protocol. See the latest version [here](/guides/lockup/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0xC3Be6BffAeab7B297c03383B4254aa3Af2b9a5BA`](https://etherscan.io/address/0xC3Be6BffAeab7B297c03383B4254aa3Af2b9a5BA) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0x39EFdC3dbB57B2388CcC4bb40aC4CB1226Bc9E44`](https://etherscan.io/address/0x39EFdC3dbB57B2388CcC4bb40aC4CB1226Bc9E44) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0xB10daee1FCF62243aE27776D7a92D39dC8740f95`](https://etherscan.io/address/0xB10daee1FCF62243aE27776D7a92D39dC8740f95) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0x98F2196fECc01C240d1429B624d007Ca268EEA29`](https://etherscan.io/address/0x98F2196fECc01C240d1429B624d007Ca268EEA29) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0x0Be20a8242B0781B6fd4d453e90DCC1CcF7DBcc6`](https://etherscan.io/address/0x0Be20a8242B0781B6fd4d453e90DCC1CcF7DBcc6) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0x9bdebF4F9adEB99387f46e4020FBf3dDa885D2b8`](https://etherscan.io/address/0x9bdebF4F9adEB99387f46e4020FBf3dDa885D2b8) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x297b43aE44660cA7826ef92D8353324C018573Ef`](https://etherscan.io/address/0x297b43aE44660cA7826ef92D8353324C018573Ef) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0x638a7aC8315767cEAfc57a6f5e3559454347C3f6`](https://etherscan.io/address/0x638a7aC8315767cEAfc57a6f5e3559454347C3f6) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x17Ec73692F0aDf7E7C554822FBEAACB4BE781762`](https://arbiscan.io/address/0x17Ec73692F0aDf7E7C554822FBEAACB4BE781762) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0xA9EfBEf1A35fF80041F567391bdc9813b2D50197`](https://arbiscan.io/address/0xA9EfBEf1A35fF80041F567391bdc9813b2D50197) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0x197D655F3be03903fD25e7828c3534504bfe525e`](https://arbiscan.io/address/0x197D655F3be03903fD25e7828c3534504bfe525e) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0xc245d6C9608769CeF91C3858e4d2a74802B9f1bB`](https://arbiscan.io/address/0xc245d6C9608769CeF91C3858e4d2a74802B9f1bB) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0xDFa4512d07AbD4eb8Be570Cd79e2e6Fe21ff15C9`](https://arbiscan.io/address/0xDFa4512d07AbD4eb8Be570Cd79e2e6Fe21ff15C9) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0x9aB73CA73c89AF0bdc69642aCeb23CC6A55A514C`](https://arbiscan.io/address/0x9aB73CA73c89AF0bdc69642aCeb23CC6A55A514C) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0xB7185AcAF42C4966fFA3c81486d9ED9633aa4c13`](https://arbiscan.io/address/0xB7185AcAF42C4966fFA3c81486d9ED9633aa4c13) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0x90cc23dc3e12e80f27c05b8137b5f0d2b1edfa20`](https://arbiscan.io/address/0x90cc23dc3e12e80f27c05b8137b5f0d2b1edfa20) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x66F5431B0765D984f82A4fc4551b2c9ccF7eAC9C`](https://snowscan.xyz/address/0x66F5431B0765D984f82A4fc4551b2c9ccF7eAC9C) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0x665d1C8337F1035cfBe13DD94bB669110b975f5F`](https://snowscan.xyz/address/0x665d1C8337F1035cfBe13DD94bB669110b975f5F) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0x610346E9088AFA70D6B03e96A800B3267E75cA19`](https://snowscan.xyz/address/0x610346E9088AFA70D6B03e96A800B3267E75cA19) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0xFd050AFA2e04aA0596947DaD3Ec5690162aDc77F`](https://snowscan.xyz/address/0xFd050AFA2e04aA0596947DaD3Ec5690162aDc77F) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0x7b1ef644ce9a625537e9e0c3d7fef3be667e6159`](https://snowscan.xyz/address/0x7b1ef644ce9a625537e9e0c3d7fef3be667e6159) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0x17167A7e2763121e263B4331B700a1BF9113b387`](https://snowscan.xyz/address/0x17167A7e2763121e263B4331B700a1BF9113b387) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x48B4889cf5d6f8360050f9d7606505F1433120BC`](https://snowscan.xyz/address/0x48B4889cf5d6f8360050f9d7606505F1433120BC) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0x817fE1364A9d57d1fB951945B53942234163Ef10`](https://snowscan.xyz/address/0x817fE1364A9d57d1fB951945B53942234163Ef10) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x7Faaedd40B1385C118cA7432952D9DC6b5CbC49e`](https://basescan.org/address/0x7Faaedd40B1385C118cA7432952D9DC6b5CbC49e) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0x645B00960Dc352e699F89a81Fc845C0C645231cf`](https://basescan.org/address/0x645B00960Dc352e699F89a81Fc845C0C645231cf) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0x6b9a46C8377f21517E65fa3899b3A9Fab19D17f5`](https://basescan.org/address/0x6b9a46C8377f21517E65fa3899b3A9Fab19D17f5) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0xEFc2896c29F70bc23e82892Df827d4e2259028Fd`](https://basescan.org/address/0xEFc2896c29F70bc23e82892Df827d4e2259028Fd) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0x1C5Ac71dd48c7ff291743e5E6e3689ba92F73cC6`](https://basescan.org/address/0x1C5Ac71dd48c7ff291743e5E6e3689ba92F73cC6) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0x50E8B9dC7F28e5cA9253759455C1077e497c4232`](https://basescan.org/address/0x50E8B9dC7F28e5cA9253759455C1077e497c4232) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x0648C80b969501c7778b6ff3ba47aBb78fEeDF39`](https://basescan.org/address/0x0648C80b969501c7778b6ff3ba47aBb78fEeDF39) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0xf19576Ab425753816eCbF98aca8132A0f693aEc5`](https://basescan.org/address/0xf19576Ab425753816eCbF98aca8132A0f693aEc5) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x33511f69A784Fd958E6713aCaC7c9dCF1A5578E8`](https://bscscan.com/address/0x33511f69A784Fd958E6713aCaC7c9dCF1A5578E8) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0xF2f3feF2454DcA59ECA929D2D8cD2a8669Cc6214`](https://bscscan.com/address/0xF2f3feF2454DcA59ECA929D2D8cD2a8669Cc6214) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0x3FE4333f62A75c2a85C8211c6AeFd1b9Bfde6e51`](https://bscscan.com/address/0x3FE4333f62A75c2a85C8211c6AeFd1b9Bfde6e51) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0x3daD1bF57edCFF979Fb68a802AC54c5AAfB78F4c`](https://bscscan.com/address/0x3daD1bF57edCFF979Fb68a802AC54c5AAfB78F4c) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0xeDe48EB173A869c0b27Cb98CC56d00BC391e5887`](https://bscscan.com/address/0xeDe48EB173A869c0b27Cb98CC56d00BC391e5887) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0xC43b2d8CedB71df30F45dFd9a21eC1E50A813bD6`](https://bscscan.com/address/0xC43b2d8CedB71df30F45dFd9a21eC1E50A813bD6) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x135e78B8E17B1d189Af75FcfCC018ab2E6c7b879`](https://bscscan.com/address/0x135e78B8E17B1d189Af75FcfCC018ab2E6c7b879) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0xc9bf2A6bD467A813908d836c1506efE61E465761`](https://bscscan.com/address/0xc9bf2A6bD467A813908d836c1506efE61E465761) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x73962c44c0fB4cC5e4545FB91732a5c5e87F55C2`](https://gnosisscan.io/address/0x73962c44c0fB4cC5e4545FB91732a5c5e87F55C2) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0xeb148E4ec13aaA65328c0BA089a278138E9E53F9`](https://gnosisscan.io/address/0xeb148E4ec13aaA65328c0BA089a278138E9E53F9) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0x685E92c9cA2bB23f1B596d0a7D749c0603e88585`](https://gnosisscan.io/address/0x685E92c9cA2bB23f1B596d0a7D749c0603e88585) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0x8CE9Cd651e03325Cf6D4Ce9cfa74BE79CDf6d530`](https://gnosisscan.io/address/0x8CE9Cd651e03325Cf6D4Ce9cfa74BE79CDf6d530) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0xF4A6F47Da7c6b26b6Dd774671aABA48fb4bFE309`](https://gnosisscan.io/address/0xF4A6F47Da7c6b26b6Dd774671aABA48fb4bFE309) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0xc84f0e95815A576171A19EB9E0fA55a217Ab1536`](https://gnosisscan.io/address/0xc84f0e95815A576171A19EB9E0fA55a217Ab1536) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x5B144C3B9C8cfd48297Aeb59B90a024Ef3fCcE92`](https://gnosisscan.io/address/0x5B144C3B9C8cfd48297Aeb59B90a024Ef3fCcE92) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0x89AfE038714e547C29Fa881029DD4B5CFB008454`](https://gnosisscan.io/address/0x89AfE038714e547C29Fa881029DD4B5CFB008454) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x1EECb6e6EaE6a1eD1CCB4323F3a146A7C5443A10`](https://optimistic.etherscan.io/address/0x1EECb6e6EaE6a1eD1CCB4323F3a146A7C5443A10) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0x6f68516c21E248cdDfaf4898e66b2b0Adee0e0d6`](https://optimistic.etherscan.io/address/0x6f68516c21E248cdDfaf4898e66b2b0Adee0e0d6) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0xB923aBdCA17Aed90EB5EC5E407bd37164f632bFD`](https://optimistic.etherscan.io/address/0xB923aBdCA17Aed90EB5EC5E407bd37164f632bFD) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0xe0138C596939CC0D2382046795bC163ad5755e0E`](https://optimistic.etherscan.io/address/0xe0138C596939CC0D2382046795bC163ad5755e0E) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0x9A09eC6f991386718854aDDCEe68647776Befd5b`](https://optimistic.etherscan.io/address/0x9A09eC6f991386718854aDDCEe68647776Befd5b) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0x77C8516B1F327890C956bb38F93Ac2d6B24795Ea`](https://optimistic.etherscan.io/address/0x77C8516B1F327890C956bb38F93Ac2d6B24795Ea) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x194ed7D6005C8ba4084A948406545DF299ad37cD`](https://optimistic.etherscan.io/address/0x194ed7D6005C8ba4084A948406545DF299ad37cD) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0x8a6974c162fdc7Cb67996F7dB8bAAFb9a99566e0`](https://optimistic.etherscan.io/address/0x8a6974c162fdc7Cb67996F7dB8bAAFb9a99566e0) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x9761692EDf10F5F2A69f0150e2fd50dcecf05F2E`](https://polygonscan.com/address/0x9761692EDf10F5F2A69f0150e2fd50dcecf05F2E) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0x7313AdDb53f96a4f710D3b91645c62B434190725`](https://polygonscan.com/address/0x7313AdDb53f96a4f710D3b91645c62B434190725) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0x67422C3E36A908D5C3237e9cFfEB40bDE7060f6E`](https://polygonscan.com/address/0x67422C3E36A908D5C3237e9cFfEB40bDE7060f6E) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0xA820946EaAceB2a85aF123f706f23192c28bC6B9`](https://polygonscan.com/address/0xA820946EaAceB2a85aF123f706f23192c28bC6B9) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0xA2f5B2e798e7ADd59d85d9b76645E6AC13fC4e1f`](https://polygonscan.com/address/0xA2f5B2e798e7ADd59d85d9b76645E6AC13fC4e1f) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0xBe4cad0e99865CC62787Ecf029aD9DD4815d3d2e`](https://polygonscan.com/address/0xBe4cad0e99865CC62787Ecf029aD9DD4815d3d2e) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x576743075fc5F771bbC1376c3267A6185Af9D62B`](https://polygonscan.com/address/0x576743075fc5F771bbC1376c3267A6185Af9D62B) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0xccA6dd77bA2cfcccEdA01A82CB309e2A17901682`](https://polygonscan.com/address/0xccA6dd77bA2cfcccEdA01A82CB309e2A17901682) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x859708495E3B3c61Bbe19e6E3E1F41dE3A5C5C5b`](https://scrollscan.com/address/0x859708495E3B3c61Bbe19e6E3E1F41dE3A5C5C5b) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0xde6a30D851eFD0Fc2a9C922F294801Cfd5FCB3A1`](https://scrollscan.com/address/0xde6a30D851eFD0Fc2a9C922F294801Cfd5FCB3A1) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0x80640ca758615ee83801EC43452feEA09a202D33`](https://scrollscan.com/address/0x80640ca758615ee83801EC43452feEA09a202D33) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0xC1fa624733203F2B7185c3724039C4D5E5234fE4`](https://scrollscan.com/address/0xC1fa624733203F2B7185c3724039C4D5E5234fE4) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0x94A18AC6e4B7d97E31f1587f6a666Dc5503086c3`](https://scrollscan.com/address/0x94A18AC6e4B7d97E31f1587f6a666Dc5503086c3) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0xED1591BD6038032a74D786A452A23536b3201490`](https://scrollscan.com/address/0xED1591BD6038032a74D786A452A23536b3201490) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x91154fc80933D25793E6B4D7CE19fb51dE6794B7`](https://scrollscan.com/address/0x91154fc80933D25793E6B4D7CE19fb51dE6794B7) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0x71CeA9c4d15fed2E58785cE0C05165CE34313A74`](https://scrollscan.com/address/0x71CeA9c4d15fed2E58785cE0C05165CE34313A74) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0xA6A0cfA3442053fbB516D55205A749Ef2D33aed9`](https://sepolia.arbiscan.io/address/0xA6A0cfA3442053fbB516D55205A749Ef2D33aed9) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0x7938c18a59FaD2bA11426AcfBe8d74F0F598a4D2`](https://sepolia.arbiscan.io/address/0x7938c18a59FaD2bA11426AcfBe8d74F0F598a4D2) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0xa3e36b51B7A456812c92253780f4B15bad56e34c`](https://sepolia.arbiscan.io/address/0xa3e36b51B7A456812c92253780f4B15bad56e34c) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0xEe93BFf599C17C6fF8e31F2De6c3e40bd5e51312`](https://sepolia.arbiscan.io/address/0xEe93BFf599C17C6fF8e31F2De6c3e40bd5e51312) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0x2C8fA48361C7D48Dc21b27a3D549402Cf8AE16B0`](https://sepolia.arbiscan.io/address/0x2C8fA48361C7D48Dc21b27a3D549402Cf8AE16B0) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0x7D310803c3824636bAff74e4f80e81ece167c440`](https://sepolia.arbiscan.io/address/0x7D310803c3824636bAff74e4f80e81ece167c440) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x396A3a169918A4C0B339ECf86C583f46D696254E`](https://sepolia.arbiscan.io/address/0x396A3a169918A4C0B339ECf86C583f46D696254E) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x2006d43E65e66C5FF20254836E63947FA8bAaD68`](https://sepolia.etherscan.io/address/0x2006d43E65e66C5FF20254836E63947FA8bAaD68) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupDynamic | [`0x421e1E7a53FF360f70A2D02037Ee394FA474e035`](https://sepolia.etherscan.io/address/0x421e1E7a53FF360f70A2D02037Ee394FA474e035) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2LockupLinear | [`0xd4300c5bc0b9e27c73ebabdc747ba990b1b570db`](https://sepolia.etherscan.io/address/0xd4300c5bc0b9e27c73ebabdc747ba990b1b570db) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2NFTDescriptor | [`0x3cb51943EbcEA05B23C35c50491B3d296FF675db`](https://sepolia.etherscan.io/address/0x3cb51943EbcEA05B23C35c50491B3d296FF675db) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2Archive | [`0x83495d8DF6221f566232e1353a6e7231A86C61fF`](https://sepolia.etherscan.io/address/0x83495d8DF6221f566232e1353a6e7231A86C61fF) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyPlugin | [`0xa333c8233CfD04740E64AB4fd5447995E357561B`](https://sepolia.etherscan.io/address/0xa333c8233CfD04740E64AB4fd5447995E357561B) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTarget | [`0x5091900B7cF803a7407FCE6333A6bAE4aA779Fd4`](https://sepolia.etherscan.io/address/0x5091900B7cF803a7407FCE6333A6bAE4aA779Fd4) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | | SablierV2ProxyTargetApprove | [`0x105E7728C5706Ad41d194EbDc7873B047352F3d2`](https://sepolia.etherscan.io/address/0x105E7728C5706Ad41d194EbDc7873B047352F3d2) | [`lockup-v1.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.0) | --- ## Lockup v1.1 Source: https://docs.sablier.com/guides/lockup/previous-deployments/v1.1 # Lockup v1.1 This section contains the deployment addresses for the v1.1 release of [@sablier/v2-core@1.1.2](https://npmjs.com/package/@sablier/v2-core/v/1.1.2) and [@sablier/v2-periphery@1.1.1](https://npmjs.com/package/@sablier/v2-periphery/v/1.1.1). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Lockup protocol. See the latest version [here](/guides/lockup/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0xC3Be6BffAeab7B297c03383B4254aa3Af2b9a5BA`](https://etherscan.io/address/0xC3Be6BffAeab7B297c03383B4254aa3Af2b9a5BA) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0x7CC7e125d83A581ff438608490Cc0f7bDff79127`](https://etherscan.io/address/0x7CC7e125d83A581ff438608490Cc0f7bDff79127) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xAFb979d9afAd1aD27C5eFf4E27226E3AB9e5dCC9`](https://etherscan.io/address/0xAFb979d9afAd1aD27C5eFf4E27226E3AB9e5dCC9) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0x23eD5DA55AF4286c0dE55fAcb414dEE2e317F4CB`](https://etherscan.io/address/0x23eD5DA55AF4286c0dE55fAcb414dEE2e317F4CB) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0xEa07DdBBeA804E7fe66b958329F8Fa5cDA95Bd55`](https://etherscan.io/address/0xEa07DdBBeA804E7fe66b958329F8Fa5cDA95Bd55) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x1A272b596b10f02931480BC7a3617db4a8d154E3`](https://etherscan.io/address/0x1A272b596b10f02931480BC7a3617db4a8d154E3) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x17Ec73692F0aDf7E7C554822FBEAACB4BE781762`](https://arbiscan.io/address/0x17Ec73692F0aDf7E7C554822FBEAACB4BE781762) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xf390cE6f54e4dc7C5A5f7f8689062b7591F7111d`](https://arbiscan.io/address/0xf390cE6f54e4dc7C5A5f7f8689062b7591F7111d) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xFDD9d122B451F549f48c4942c6fa6646D849e8C1`](https://arbiscan.io/address/0xFDD9d122B451F549f48c4942c6fa6646D849e8C1) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0x2fb103fC853b2F5022a840091ab1cDf5172E7cfa`](https://arbiscan.io/address/0x2fb103fC853b2F5022a840091ab1cDf5172E7cfa) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0xAFd1434296e29a0711E24014656158055F00784c`](https://arbiscan.io/address/0xAFd1434296e29a0711E24014656158055F00784c) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x237400eF5a41886a75B0e036228221Df075b3B80`](https://arbiscan.io/address/0x237400eF5a41886a75B0e036228221Df075b3B80) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x66F5431B0765D984f82A4fc4551b2c9ccF7eAC9C`](https://snowscan.xyz/address/0x66F5431B0765D984f82A4fc4551b2c9ccF7eAC9C) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0x0310Da0D8fF141166eD47548f00c96464880781F`](https://snowscan.xyz/address/0x0310Da0D8fF141166eD47548f00c96464880781F) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xB24B65E015620455bB41deAAd4e1902f1Be9805f`](https://snowscan.xyz/address/0xB24B65E015620455bB41deAAd4e1902f1Be9805f) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xaBEdCf46c5D1d8eD8B9a487144189887695835DC`](https://snowscan.xyz/address/0xaBEdCf46c5D1d8eD8B9a487144189887695835DC) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x68f156E5fa8C23D65B33aBEbbA50e0CA3626F741`](https://snowscan.xyz/address/0x68f156E5fa8C23D65B33aBEbbA50e0CA3626F741) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x4849e797d7Aab20FCC8f807EfafDffF98A83412E`](https://snowscan.xyz/address/0x4849e797d7Aab20FCC8f807EfafDffF98A83412E) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x7Faaedd40B1385C118cA7432952D9DC6b5CbC49e`](https://basescan.org/address/0x7Faaedd40B1385C118cA7432952D9DC6b5CbC49e) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0x461E13056a3a3265CEF4c593F01b2e960755dE91`](https://basescan.org/address/0x461E13056a3a3265CEF4c593F01b2e960755dE91) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xFCF737582d167c7D20A336532eb8BCcA8CF8e350`](https://basescan.org/address/0xFCF737582d167c7D20A336532eb8BCcA8CF8e350) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0x67e0a126b695DBA35128860cd61926B90C420Ceb`](https://basescan.org/address/0x67e0a126b695DBA35128860cd61926B90C420Ceb) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x94E596EEd73b4e3171c067f05A87AB0268cA993c`](https://basescan.org/address/0x94E596EEd73b4e3171c067f05A87AB0268cA993c) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x5545c8E7c3E1F74aDc98e518F2E8D23A002C4412`](https://basescan.org/address/0x5545c8E7c3E1F74aDc98e518F2E8D23A002C4412) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x2De92156000269fa2fde7544F10f01E8cBC80fFa`](https://blastscan.io/address/0x2De92156000269fa2fde7544F10f01E8cBC80fFa) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xDf578C2c70A86945999c65961417057363530a1c`](https://blastscan.io/address/0xDf578C2c70A86945999c65961417057363530a1c) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xcb099EfC90e88690e287259410B9AE63e1658CC6`](https://blastscan.io/address/0xcb099EfC90e88690e287259410B9AE63e1658CC6) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2`](https://blastscan.io/address/0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x0eDA15D606733f6CDe9DB67263E546bfcDDe9264`](https://blastscan.io/address/0x0eDA15D606733f6CDe9DB67263E546bfcDDe9264) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a`](https://blastscan.io/address/0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x33511f69A784Fd958E6713aCaC7c9dCF1A5578E8`](https://bscscan.com/address/0x33511f69A784Fd958E6713aCaC7c9dCF1A5578E8) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xf900c5E3aA95B59Cc976e6bc9c0998618729a5fa`](https://bscscan.com/address/0xf900c5E3aA95B59Cc976e6bc9c0998618729a5fa) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x14c35E126d75234a90c9fb185BF8ad3eDB6A90D2`](https://bscscan.com/address/0x14c35E126d75234a90c9fb185BF8ad3eDB6A90D2) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xEcAfcF09c23057210cB6470eB5D0FD8Bafd1755F`](https://bscscan.com/address/0xEcAfcF09c23057210cB6470eB5D0FD8Bafd1755F) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x2E30a2ae6565Db78C06C28dE937F668597c80a1c`](https://bscscan.com/address/0x2E30a2ae6565Db78C06C28dE937F668597c80a1c) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x434D73465aAc4125d204A6637eB6C579d8D69f48`](https://bscscan.com/address/0x434D73465aAc4125d204A6637eB6C579d8D69f48) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x73962c44c0fB4cC5e4545FB91732a5c5e87F55C2`](https://gnosisscan.io/address/0x73962c44c0fB4cC5e4545FB91732a5c5e87F55C2) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0x1DF83C7682080B0f0c26a20C6C9CB8623e0Df24E`](https://gnosisscan.io/address/0x1DF83C7682080B0f0c26a20C6C9CB8623e0Df24E) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xce49854a647a1723e8Fb7CC3D190CAB29A44aB48`](https://gnosisscan.io/address/0xce49854a647a1723e8Fb7CC3D190CAB29A44aB48) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0x01dbFE22205d8B109959e2Be02d0095379309eed`](https://gnosisscan.io/address/0x01dbFE22205d8B109959e2Be02d0095379309eed) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0xBd9DDbC55B85FF6Dc0b76E9EFdCd2547Ab482501`](https://gnosisscan.io/address/0xBd9DDbC55B85FF6Dc0b76E9EFdCd2547Ab482501) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x777F66477FF83aBabADf39a3F22A8CC3AEE43765`](https://gnosisscan.io/address/0x777F66477FF83aBabADf39a3F22A8CC3AEE43765) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0xb568f9Bc0dcE39B9B64e843bC19DA102B5E3E939`](https://phoenix.lightlink.io/address/0xb568f9Bc0dcE39B9B64e843bC19DA102B5E3E939) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0x49d753422ff05daa291A9efa383E4f57daEAd889`](https://phoenix.lightlink.io/address/0x49d753422ff05daa291A9efa383E4f57daEAd889) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x17c4f98c40e69a6A0D5c42B11E3733f076A99E20`](https://phoenix.lightlink.io/address/0x17c4f98c40e69a6A0D5c42B11E3733f076A99E20) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xda55fB3E53b7d205e37B6bdCe990b789255e4302`](https://phoenix.lightlink.io/address/0xda55fB3E53b7d205e37B6bdCe990b789255e4302) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x3eb9F8f80354a157315Fce64990C554434690c2f`](https://phoenix.lightlink.io/address/0x3eb9F8f80354a157315Fce64990C554434690c2f) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0xdB07a1749D5Ca49909C7C4159652Fbd527c735B8`](https://phoenix.lightlink.io/address/0xdB07a1749D5Ca49909C7C4159652Fbd527c735B8) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x1EECb6e6EaE6a1eD1CCB4323F3a146A7C5443A10`](https://optimistic.etherscan.io/address/0x1EECb6e6EaE6a1eD1CCB4323F3a146A7C5443A10) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xd6920c1094eABC4b71f3dC411A1566f64f4c206e`](https://optimistic.etherscan.io/address/0xd6920c1094eABC4b71f3dC411A1566f64f4c206e) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x4b45090152a5731b5bc71b5baF71E60e05B33867`](https://optimistic.etherscan.io/address/0x4b45090152a5731b5bc71b5baF71E60e05B33867) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xF5050c04425E639C647F5ED632218b16ce96694d`](https://optimistic.etherscan.io/address/0xF5050c04425E639C647F5ED632218b16ce96694d) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x8145429538dDBdDc4099B2bAfd24DD8958fa03b8`](https://optimistic.etherscan.io/address/0x8145429538dDBdDc4099B2bAfd24DD8958fa03b8) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x044EC80FbeC40f0eE7E7b3856828170971796C19`](https://optimistic.etherscan.io/address/0x044EC80FbeC40f0eE7E7b3856828170971796C19) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x9761692EDf10F5F2A69f0150e2fd50dcecf05F2E`](https://polygonscan.com/address/0x9761692EDf10F5F2A69f0150e2fd50dcecf05F2E) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xB194c7278C627D52E440316b74C5F24FC70c1565`](https://polygonscan.com/address/0xB194c7278C627D52E440316b74C5F24FC70c1565) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x5f0e1dea4A635976ef51eC2a2ED41490d1eBa003`](https://polygonscan.com/address/0x5f0e1dea4A635976ef51eC2a2ED41490d1eBa003) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0x8683da9DF8c5c3528e8251a5764EC7DAc7264795`](https://polygonscan.com/address/0x8683da9DF8c5c3528e8251a5764EC7DAc7264795) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x5865C73789C4496665eDE1CAF018dc52ac248598`](https://polygonscan.com/address/0x5865C73789C4496665eDE1CAF018dc52ac248598) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0xF4906225e783fb8977410BDBFb960caBed6C2EF4`](https://polygonscan.com/address/0xF4906225e783fb8977410BDBFb960caBed6C2EF4) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x859708495E3B3c61Bbe19e6E3E1F41dE3A5C5C5b`](https://scrollscan.com/address/0x859708495E3B3c61Bbe19e6E3E1F41dE3A5C5C5b) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xAaff2D11f9e7Cd2A9cDC674931fAC0358a165995`](https://scrollscan.com/address/0xAaff2D11f9e7Cd2A9cDC674931fAC0358a165995) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x57e14AB4DAd920548899d86B54AD47Ea27F00987`](https://scrollscan.com/address/0x57e14AB4DAd920548899d86B54AD47Ea27F00987) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xB71440B85172332E8B768e85EdBfdb34CB457c1c`](https://scrollscan.com/address/0xB71440B85172332E8B768e85EdBfdb34CB457c1c) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0xD18faa233E02d41EDFFdb64f20281dE0592FA3b5`](https://scrollscan.com/address/0xD18faa233E02d41EDFFdb64f20281dE0592FA3b5) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0xb3ade5463000E6c0D376e7d7570f372eBf98BDAf`](https://scrollscan.com/address/0xb3ade5463000E6c0D376e7d7570f372eBf98BDAf) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0xD05bdb4cF6Be7D647c5FEcC7952660bdD82cE44C`](https://explorer.zksync.io/address/0xD05bdb4cF6Be7D647c5FEcC7952660bdD82cE44C) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xE6c7324BEA8474209103e407779Eec600c07cF3F`](https://explorer.zksync.io/address/0xE6c7324BEA8474209103e407779Eec600c07cF3F) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x2FcA69fa0a318EFDf4c15eE8F13A873347a8A8D4`](https://explorer.zksync.io/address/0x2FcA69fa0a318EFDf4c15eE8F13A873347a8A8D4) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xf12d2B8ff4Fc0495Db9c6d16b6a03bff9a10657A`](https://explorer.zksync.io/address/0xf12d2B8ff4Fc0495Db9c6d16b6a03bff9a10657A) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x37A20Fb12DD6e0ADA47B327C0466A231dDc4504A`](https://explorer.zksync.io/address/0x37A20Fb12DD6e0ADA47B327C0466A231dDc4504A) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x46DE683D20c3575A0381fFd66C10Ab6836390140`](https://explorer.zksync.io/address/0x46DE683D20c3575A0381fFd66C10Ab6836390140) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0xA6A0cfA3442053fbB516D55205A749Ef2D33aed9`](https://sepolia.arbiscan.io/address/0xA6A0cfA3442053fbB516D55205A749Ef2D33aed9) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0x8c8102b92B1f31cC304A085D490796f4DfdF7aF3`](https://sepolia.arbiscan.io/address/0x8c8102b92B1f31cC304A085D490796f4DfdF7aF3) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x483bdd560dE53DC20f72dC66ACdB622C5075de34`](https://sepolia.arbiscan.io/address/0x483bdd560dE53DC20f72dC66ACdB622C5075de34) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0x593050f0360518C3A4F11c32Eb936146e1096FD1`](https://sepolia.arbiscan.io/address/0x593050f0360518C3A4F11c32Eb936146e1096FD1) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x72D921E579aB7FC5D19CD398B6be24d626Ccb6e7`](https://sepolia.arbiscan.io/address/0x72D921E579aB7FC5D19CD398B6be24d626Ccb6e7) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0xcc87b1A4de285832f226BD585bd54a2184D32105`](https://sepolia.arbiscan.io/address/0xcc87b1A4de285832f226BD585bd54a2184D32105) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x90b1C663314cFb55c8FF6f9a50a8D57a2D83a664`](https://sepolia.basescan.org/address/0x90b1C663314cFb55c8FF6f9a50a8D57a2D83a664) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xF46d5fA9bFC964E8d06846c8739AEc69BC06344d`](https://sepolia.basescan.org/address/0xF46d5fA9bFC964E8d06846c8739AEc69BC06344d) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xbd7AAA2984c0a887E93c66baae222749883763d3`](https://sepolia.basescan.org/address/0xbd7AAA2984c0a887E93c66baae222749883763d3) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xb2b4b1E69B16411AEBD30c8EA5aB395E13069160`](https://sepolia.basescan.org/address/0xb2b4b1E69B16411AEBD30c8EA5aB395E13069160) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0xbD636B8EF09760aC91f6Df3c6AC5531250420200`](https://sepolia.basescan.org/address/0xbD636B8EF09760aC91f6Df3c6AC5531250420200) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0xf632521bbAb0dBC2bEf169865e6c8e285AFe0a42`](https://sepolia.basescan.org/address/0xf632521bbAb0dBC2bEf169865e6c8e285AFe0a42) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x6587166c4F4E0b6203549463EbAB4dBeFA63fd8f`](https://optimism-sepolia.blockscout.com/address/0x6587166c4F4E0b6203549463EbAB4dBeFA63fd8f) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xf9e4095C1dfC058B34135C5c48cae66a8D2b3Aa5`](https://optimism-sepolia.blockscout.com/address/0xf9e4095C1dfC058B34135C5c48cae66a8D2b3Aa5) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xe59D28bEF2D37E99b93E734ed1dDcFc4B9C1bf73`](https://optimism-sepolia.blockscout.com/address/0xe59D28bEF2D37E99b93E734ed1dDcFc4B9C1bf73) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0x3590f54c5d3d83BA68c17cF5C28DB89C5d1DfA10`](https://optimism-sepolia.blockscout.com/address/0x3590f54c5d3d83BA68c17cF5C28DB89C5d1DfA10) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x65D3A5b99372ef59E741EE768443dF884aB56E0b`](https://optimism-sepolia.blockscout.com/address/0x65D3A5b99372ef59E741EE768443dF884aB56E0b) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0x9b6cC73522f22Ad3f2F8187e892A51b95f1A0E8a`](https://optimism-sepolia.blockscout.com/address/0x9b6cC73522f22Ad3f2F8187e892A51b95f1A0E8a) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0x2006d43E65e66C5FF20254836E63947FA8bAaD68`](https://sepolia.etherscan.io/address/0x2006d43E65e66C5FF20254836E63947FA8bAaD68) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xc9940AD8F43aAD8e8f33A4D5dbBf0a8F7FF4429A`](https://sepolia.etherscan.io/address/0xc9940AD8F43aAD8e8f33A4D5dbBf0a8F7FF4429A) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0x7a43F8a888fa15e68C103E18b0439Eb1e98E4301`](https://sepolia.etherscan.io/address/0x7a43F8a888fa15e68C103E18b0439Eb1e98E4301) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xE8fFEbA8963CD9302ffD39c704dc2c027128D36F`](https://sepolia.etherscan.io/address/0xE8fFEbA8963CD9302ffD39c704dc2c027128D36F) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0xd2569DC4A58dfE85d807Dffb976dbC0a3bf0B0Fb`](https://sepolia.etherscan.io/address/0xd2569DC4A58dfE85d807Dffb976dbC0a3bf0B0Fb) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0xBacC1d151A78eeD71D504f701c25E8739DC0262D`](https://sepolia.etherscan.io/address/0xBacC1d151A78eeD71D504f701c25E8739DC0262D) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | ### ZKsync Sepolia Testnet ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2Comptroller | [`0xEB4570723ae207a0473D73B3c2B255b0D5Ec9f01`](https://sepolia.explorer.zksync.io/address/0xEB4570723ae207a0473D73B3c2B255b0D5Ec9f01) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupDynamic | [`0xe101C69A6f9c071Ab79aEE0be56928565962F56d`](https://sepolia.explorer.zksync.io/address/0xe101C69A6f9c071Ab79aEE0be56928565962F56d) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2LockupLinear | [`0xdFC6F5D327dcF5DB579eC1b47fb260F93e042409`](https://sepolia.explorer.zksync.io/address/0xdFC6F5D327dcF5DB579eC1b47fb260F93e042409) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2NFTDescriptor | [`0xABF4a24519c9A3c68a354FD6d5D4429De0A0D36C`](https://sepolia.explorer.zksync.io/address/0xABF4a24519c9A3c68a354FD6d5D4429De0A0D36C) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2Batch | [`0x5F812F1332A2294149b9e1cBd216a5eED12cEbDD`](https://sepolia.explorer.zksync.io/address/0x5F812F1332A2294149b9e1cBd216a5eED12cEbDD) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | | SablierV2MerkleStreamerFactory | [`0xd9a834135c816FFd133a411a36219aAFD190fF97`](https://sepolia.explorer.zksync.io/address/0xd9a834135c816FFd133a411a36219aAFD190fF97) | [`lockup-v1.1`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.1) | --- ## Lockup v1.2 Source: https://docs.sablier.com/guides/lockup/previous-deployments/v1.2 # Lockup v1.2 This section contains the deployment addresses for the v1.2 release of [@sablier/v2-core@1.2.0](https://npmjs.com/package/@sablier/v2-core/v/1.2.0) and [@sablier/v2-periphery@1.2.0](https://npmjs.com/package/@sablier/v2-periphery/v/1.2.0). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Lockup protocol. See the latest version [here](/guides/lockup/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x9DeaBf7815b42Bf4E9a03EEc35a486fF74ee7459`](https://etherscan.io/address/0x9DeaBf7815b42Bf4E9a03EEc35a486fF74ee7459) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x3962f6585946823440d274aD7C719B02b49DE51E`](https://etherscan.io/address/0x3962f6585946823440d274aD7C719B02b49DE51E) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xf86B359035208e4529686A1825F2D5BeE38c28A8`](https://etherscan.io/address/0xf86B359035208e4529686A1825F2D5BeE38c28A8) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xAE32Ca14d85311A506Bb852D49bbfB315466bA26`](https://etherscan.io/address/0xAE32Ca14d85311A506Bb852D49bbfB315466bA26) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xB5Ec9706C3Be9d22326D208f491E5DEef7C8d9f0`](https://etherscan.io/address/0xB5Ec9706C3Be9d22326D208f491E5DEef7C8d9f0) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xF35aB407CF28012Ba57CAF5ee2f6d6E4420253bc`](https://etherscan.io/address/0xF35aB407CF28012Ba57CAF5ee2f6d6E4420253bc) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xc69c06c030E825EDE13F1486078Aa9a2E2AAffaf`](https://abscan.org/address/0xc69c06c030E825EDE13F1486078Aa9a2E2AAffaf) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x7282d83E49363f373102d195F66649eBD6C57B9B`](https://abscan.org/address/0x7282d83E49363f373102d195F66649eBD6C57B9B) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x28fCAE6bda2546C93183EeC8638691B2EB184003`](https://abscan.org/address/0x28fCAE6bda2546C93183EeC8638691B2EB184003) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xAc2E42b520364940c90Ce164412Ca9BA212d014B`](https://abscan.org/address/0xAc2E42b520364940c90Ce164412Ca9BA212d014B) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x2F1eB117A87217E8bE9AA96795F69c9e380686Db`](https://abscan.org/address/0x2F1eB117A87217E8bE9AA96795F69c9e380686Db) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xe2C0C3e0ff10Df4485a2dcbbdd1D002a40446164`](https://abscan.org/address/0xe2C0C3e0ff10Df4485a2dcbbdd1D002a40446164) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x53F5eEB133B99C6e59108F35bCC7a116da50c5ce`](https://arbiscan.io/address/0x53F5eEB133B99C6e59108F35bCC7a116da50c5ce) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x05a323a4C936fed6D02134c5f0877215CD186b51`](https://arbiscan.io/address/0x05a323a4C936fed6D02134c5f0877215CD186b51) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x0dA2c7Aa93E7CD43e6b8D043Aab5b85CfDDf3818`](https://arbiscan.io/address/0x0dA2c7Aa93E7CD43e6b8D043Aab5b85CfDDf3818) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xacA12cdC4DcD7063c82E69A358549ba082463608`](https://arbiscan.io/address/0xacA12cdC4DcD7063c82E69A358549ba082463608) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x785Edf1e617824A78EFE76295E040B1AE06002bf`](https://arbiscan.io/address/0x785Edf1e617824A78EFE76295E040B1AE06002bf) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xc9A5a0Bc2D8E217BDbdFE7486E9E72c5c3308F01`](https://arbiscan.io/address/0xc9A5a0Bc2D8E217BDbdFE7486E9E72c5c3308F01) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xE3826241E5EeBB3F5fEde33F9f677047674D3FBF`](https://snowscan.xyz/address/0xE3826241E5EeBB3F5fEde33F9f677047674D3FBF) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xc0bF14AfB95CA4C049BDc19E06a3531D8065F6Fd`](https://snowscan.xyz/address/0xc0bF14AfB95CA4C049BDc19E06a3531D8065F6Fd) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xfA536049652BFb5f57ba8DCFbec1B2b2Dd9803D3`](https://snowscan.xyz/address/0xfA536049652BFb5f57ba8DCFbec1B2b2Dd9803D3) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xcF24fb2a09227d955F8e9A12f36A26cf1ac079c6`](https://snowscan.xyz/address/0xcF24fb2a09227d955F8e9A12f36A26cf1ac079c6) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xaBCdF4dcDBa57a04889784A670b862540758f9E7`](https://snowscan.xyz/address/0xaBCdF4dcDBa57a04889784A670b862540758f9E7) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x0430ed39EA2789AcdF27b89268117EBABc8176D1`](https://snowscan.xyz/address/0x0430ed39EA2789AcdF27b89268117EBABc8176D1) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xF9E9eD67DD2Fab3b3ca024A2d66Fcf0764d36742`](https://basescan.org/address/0xF9E9eD67DD2Fab3b3ca024A2d66Fcf0764d36742) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x4CB16D4153123A74Bc724d161050959754f378D8`](https://basescan.org/address/0x4CB16D4153123A74Bc724d161050959754f378D8) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xf4937657Ed8B3f3cB379Eed47b8818eE947BEb1e`](https://basescan.org/address/0xf4937657Ed8B3f3cB379Eed47b8818eE947BEb1e) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x0fF9d05E6331A43A906fE1440E0C9D0742F475A3`](https://basescan.org/address/0x0fF9d05E6331A43A906fE1440E0C9D0742F475A3) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xc1c548F980669615772dadcBfEBC29937c29481A`](https://basescan.org/address/0xc1c548F980669615772dadcBfEBC29937c29481A) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x58A51E5382318EeA6065BB7721eecdF4331c0B90`](https://basescan.org/address/0x58A51E5382318EeA6065BB7721eecdF4331c0B90) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xA705DE617673e2Fe63a4Ea0E58c26897601D32A5`](https://blastscan.io/address/0xA705DE617673e2Fe63a4Ea0E58c26897601D32A5) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x9b1468d29b4A5869f00c92517c57f8656E928B93`](https://blastscan.io/address/0x9b1468d29b4A5869f00c92517c57f8656E928B93) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x91FB72e5297e2728c10FDe73BdE74A4888A68570`](https://blastscan.io/address/0x91FB72e5297e2728c10FDe73BdE74A4888A68570) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x5f111b49f8f8bdb4A6001701E0D330fF52D6B370`](https://blastscan.io/address/0x5f111b49f8f8bdb4A6001701E0D330fF52D6B370) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xdc988d7AD6F186ea4a236f3E61A45a7851edF84E`](https://blastscan.io/address/0xdc988d7AD6F186ea4a236f3E61A45a7851edF84E) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x3aBCDDa756d069Cf3c7a17410602343966EAFf27`](https://blastscan.io/address/0x3aBCDDa756d069Cf3c7a17410602343966EAFf27) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xeB6d84c585bf8AEA34F05a096D6fAA3b8477D146`](https://bscscan.com/address/0xeB6d84c585bf8AEA34F05a096D6fAA3b8477D146) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x88ad3B5c62A46Df953A5d428d33D70408F53C408`](https://bscscan.com/address/0x88ad3B5c62A46Df953A5d428d33D70408F53C408) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xAb5f007b33EDDA56962A0fC428B15D544EA46591`](https://bscscan.com/address/0xAb5f007b33EDDA56962A0fC428B15D544EA46591) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x27641f29b012d0d523EB5943011148c42c98e7F1`](https://bscscan.com/address/0x27641f29b012d0d523EB5943011148c42c98e7F1) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x70998557980CB6E8E63c46810081262B6c343051`](https://bscscan.com/address/0x70998557980CB6E8E63c46810081262B6c343051) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x96Aa12809CAC29Bba4944fEca1dFDC8e1704e6c1`](https://bscscan.com/address/0x96Aa12809CAC29Bba4944fEca1dFDC8e1704e6c1) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2`](https://chiliscan.com/address/0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xDf578C2c70A86945999c65961417057363530a1c`](https://chiliscan.com/address/0xDf578C2c70A86945999c65961417057363530a1c) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xcb099EfC90e88690e287259410B9AE63e1658CC6`](https://chiliscan.com/address/0xcb099EfC90e88690e287259410B9AE63e1658CC6) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x2De92156000269fa2fde7544F10f01E8cBC80fFa`](https://chiliscan.com/address/0x2De92156000269fa2fde7544F10f01E8cBC80fFa) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x0eDA15D606733f6CDe9DB67263E546bfcDDe9264`](https://chiliscan.com/address/0x0eDA15D606733f6CDe9DB67263E546bfcDDe9264) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a`](https://chiliscan.com/address/0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Core Dao ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xf0a7F2cCE911c298B5CB8106Db19EF1D00230710`](https://scan.coredao.org/address/0xf0a7F2cCE911c298B5CB8106Db19EF1D00230710) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x98Fe0d8b2c2c05d9C6a9e635f59474Aaa0000120`](https://scan.coredao.org/address/0x98Fe0d8b2c2c05d9C6a9e635f59474Aaa0000120) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x9C99EF88399bC1c1188399B39E7Cc667D78210ea`](https://scan.coredao.org/address/0x9C99EF88399bC1c1188399B39E7Cc667D78210ea) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x64C734B2F1704822D8E69CAF230aE8d2eC18AA3e`](https://scan.coredao.org/address/0x64C734B2F1704822D8E69CAF230aE8d2eC18AA3e) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xdE21BBFf718723E9069d8528d6Bb26c2971D58a7`](https://scan.coredao.org/address/0xdE21BBFf718723E9069d8528d6Bb26c2971D58a7) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x074CC814a8114126c505F5eecFC82A400B39cA03`](https://scan.coredao.org/address/0x074CC814a8114126c505F5eecFC82A400B39cA03) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x555eb55cbc477Aebbe5652D25d0fEA04052d3971`](https://gnosisscan.io/address/0x555eb55cbc477Aebbe5652D25d0fEA04052d3971) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xf1cAeB104AB29271463259335357D57772C90758`](https://gnosisscan.io/address/0xf1cAeB104AB29271463259335357D57772C90758) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x59A4B7255A5D01247837600e7828A6F77f664b34`](https://gnosisscan.io/address/0x59A4B7255A5D01247837600e7828A6F77f664b34) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xA0B5C851E3E9fED83f387f4D8847DA398Da4A8E2`](https://gnosisscan.io/address/0xA0B5C851E3E9fED83f387f4D8847DA398Da4A8E2) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x0F324E5CB01ac98b2883c8ac4231aCA7EfD3e750`](https://gnosisscan.io/address/0x0F324E5CB01ac98b2883c8ac4231aCA7EfD3e750) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x5f12318fc6cCa518A950e2Ee16063a6317C2a9Ef`](https://gnosisscan.io/address/0x5f12318fc6cCa518A950e2Ee16063a6317C2a9Ef) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xAa05E418Fb7851C211351C65435F1b17cbFa88Bf`](https://phoenix.lightlink.io/address/0xAa05E418Fb7851C211351C65435F1b17cbFa88Bf) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x6329591464FA6721c8E1c1271e4c6C41531Aea6b`](https://phoenix.lightlink.io/address/0x6329591464FA6721c8E1c1271e4c6C41531Aea6b) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x83403c6426E6D044bF3B84EC1C007Db211AaA140`](https://phoenix.lightlink.io/address/0x83403c6426E6D044bF3B84EC1C007Db211AaA140) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x5881ef3c0D3eB21b1b40E13b4a69c50754bc77C7`](https://phoenix.lightlink.io/address/0x5881ef3c0D3eB21b1b40E13b4a69c50754bc77C7) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x5C847244649BD74aB41f09C893aF792AD87D32aA`](https://phoenix.lightlink.io/address/0x5C847244649BD74aB41f09C893aF792AD87D32aA) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x278AC15622846806BD46FBDbdB8dB8d09614173A`](https://phoenix.lightlink.io/address/0x278AC15622846806BD46FBDbdB8dB8d09614173A) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xF2E46B249cFe09c2b3A2022dc81E0bB4bE3336F1`](https://lineascan.build/address/0xF2E46B249cFe09c2b3A2022dc81E0bB4bE3336F1) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xB5d39049510F47EE7f74c528105D225E42747d63`](https://lineascan.build/address/0xB5d39049510F47EE7f74c528105D225E42747d63) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xC46ce4B77cBc46D17A2EceB2Cc8e2EE23D96529F`](https://lineascan.build/address/0xC46ce4B77cBc46D17A2EceB2Cc8e2EE23D96529F) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x2E72F7523cFeaed6B841aCe20060E0b203c312F5`](https://lineascan.build/address/0x2E72F7523cFeaed6B841aCe20060E0b203c312F5) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x4259557F6665eCF5907c9019a30f3Cb009c20Ae7`](https://lineascan.build/address/0x4259557F6665eCF5907c9019a30f3Cb009c20Ae7) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x35E9C3445A039B258Eb7112A5Eea259a825E8AC0`](https://lineascan.build/address/0x35E9C3445A039B258Eb7112A5Eea259a825E8AC0) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x704552099f5aD679294D337638B9a57Fd4726F52`](https://modescan.io/address/0x704552099f5aD679294D337638B9a57Fd4726F52) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xD8C65Bd7CB6924EF895b2eDcA03407c652f5a2C5`](https://modescan.io/address/0xD8C65Bd7CB6924EF895b2eDcA03407c652f5a2C5) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xBbfA51A10bE68714fa33281646B986dae9f52021`](https://modescan.io/address/0xBbfA51A10bE68714fa33281646B986dae9f52021) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xA1976d4bd6572B68A677037B496D806ACC2cBdB3`](https://modescan.io/address/0xA1976d4bd6572B68A677037B496D806ACC2cBdB3) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x641A10A2c9e0CeB94F406e1EF68b1E1da002662d`](https://modescan.io/address/0x641A10A2c9e0CeB94F406e1EF68b1E1da002662d) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x0Fd01Dd30F96A15dE6AfAd5627d45Ef94752460a`](https://modescan.io/address/0x0Fd01Dd30F96A15dE6AfAd5627d45Ef94752460a) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x946654AB30Dd6eD10236C89f2C8B2719df653691`](https://explorer.morphl2.io/address/0x946654AB30Dd6eD10236C89f2C8B2719df653691) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xAC19F4181E58efb7094e0cb4e1BB18c79F6AAdf4`](https://explorer.morphl2.io/address/0xAC19F4181E58efb7094e0cb4e1BB18c79F6AAdf4) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x63B92F7E2f69877184C955E63B9D8Dff55e52e14`](https://explorer.morphl2.io/address/0x63B92F7E2f69877184C955E63B9D8Dff55e52e14) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xe785101Cb228693cc3EFdCd5d637fEf6A6Ff7259`](https://explorer.morphl2.io/address/0xe785101Cb228693cc3EFdCd5d637fEf6A6Ff7259) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x28D116d7e917756310986C4207eA54183fcba06A`](https://explorer.morphl2.io/address/0x28D116d7e917756310986C4207eA54183fcba06A) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x5e73bb96493C10919204045fCdb639D35ad859f8`](https://explorer.morphl2.io/address/0x5e73bb96493C10919204045fCdb639D35ad859f8) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x4994325F8D4B4A36Bd643128BEb3EC3e582192C0`](https://optimistic.etherscan.io/address/0x4994325F8D4B4A36Bd643128BEb3EC3e582192C0) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x5C22471A86E9558ed9d22235dD5E0429207ccf4B`](https://optimistic.etherscan.io/address/0x5C22471A86E9558ed9d22235dD5E0429207ccf4B) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x90952912a50079bef00D5F49c975058d6573aCdC`](https://optimistic.etherscan.io/address/0x90952912a50079bef00D5F49c975058d6573aCdC) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x1a4837b8c668b8F7BE22Ba156419b7b823Cfd05c`](https://optimistic.etherscan.io/address/0x1a4837b8c668b8F7BE22Ba156419b7b823Cfd05c) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x6cd7bB0f63aFCc9F6CeDd1Bf1E3Bd4ED078CD019`](https://optimistic.etherscan.io/address/0x6cd7bB0f63aFCc9F6CeDd1Bf1E3Bd4ED078CD019) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xe041629D99730b3EE4d6518097C45b4E3591992b`](https://optimistic.etherscan.io/address/0xe041629D99730b3EE4d6518097C45b4E3591992b) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x8D4dDc187a73017a5d7Cef733841f55115B13762`](https://polygonscan.com/address/0x8D4dDc187a73017a5d7Cef733841f55115B13762) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x8D87c5eddb5644D1a714F85930Ca940166e465f0`](https://polygonscan.com/address/0x8D87c5eddb5644D1a714F85930Ca940166e465f0) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xBF67f0A1E847564D0eFAD475782236D3Fa7e9Ec2`](https://polygonscan.com/address/0xBF67f0A1E847564D0eFAD475782236D3Fa7e9Ec2) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xf28BF9390fb57BB68386430550818D312699ED15`](https://polygonscan.com/address/0xf28BF9390fb57BB68386430550818D312699ED15) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xD29EC4B9203f2d1C9Cd4Ba8c68FCFE4ECd85f6f5`](https://polygonscan.com/address/0xD29EC4B9203f2d1C9Cd4Ba8c68FCFE4ECd85f6f5) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xC28872e0c1f3633EeD467907123727ac0155029D`](https://polygonscan.com/address/0xC28872e0c1f3633EeD467907123727ac0155029D) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xAc199bFea92aa4D4C3d8A49fd463EAD99C7a6A8f`](https://scrollscan.com/address/0xAc199bFea92aa4D4C3d8A49fd463EAD99C7a6A8f) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xBc5DC6D77612E636DA32af0d85Ca3179a57330fd`](https://scrollscan.com/address/0xBc5DC6D77612E636DA32af0d85Ca3179a57330fd) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xb0f78dDc01D829d8b567821Eb193De8082b57D9D`](https://scrollscan.com/address/0xb0f78dDc01D829d8b567821Eb193De8082b57D9D) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xA1A281BbcaED8f0A9Dcd0fe67cbC53e0993C24cb`](https://scrollscan.com/address/0xA1A281BbcaED8f0A9Dcd0fe67cbC53e0993C24cb) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x4B8BF9cD3274517609e7Fe905740fa151C9aa711`](https://scrollscan.com/address/0x4B8BF9cD3274517609e7Fe905740fa151C9aa711) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x344afe8ad5dBA3d55870dc398e0F53B635B2ed0d`](https://scrollscan.com/address/0x344afe8ad5dBA3d55870dc398e0F53B635B2ed0d) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x1fA500262b352d821B4e1c933A20f2242B45383d`](https://explorer.superseed.xyz/address/0x1fA500262b352d821B4e1c933A20f2242B45383d) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x251FC799344151026d19b959B8f3667416d56B88`](https://explorer.superseed.xyz/address/0x251FC799344151026d19b959B8f3667416d56B88) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x91211E1760280d3f7dF2182ce4D1Fd6A1735C202`](https://explorer.superseed.xyz/address/0x91211E1760280d3f7dF2182ce4D1Fd6A1735C202) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x0a6C2E6B61cf05800F9aA91494480440843d6c3c`](https://explorer.superseed.xyz/address/0x0a6C2E6B61cf05800F9aA91494480440843d6c3c) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xc4DE6f667435d5Ce0150e08BcEc9722C9017e90b`](https://explorer.superseed.xyz/address/0xc4DE6f667435d5Ce0150e08BcEc9722C9017e90b) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xF60bEADEfbeb98C927E13C4165BCa7D85Ba32cB2`](https://explorer.superseed.xyz/address/0xF60bEADEfbeb98C927E13C4165BCa7D85Ba32cB2) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Taiko ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x238C830FA8E4ED0f0A4bc9C986BF338aEC9e38D1`](https://taikoscan.io/address/0x238C830FA8E4ED0f0A4bc9C986BF338aEC9e38D1) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x5Ec0a2e88dAd09ad940Be2639c9aDb24D186989E`](https://taikoscan.io/address/0x5Ec0a2e88dAd09ad940Be2639c9aDb24D186989E) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x6a619d35972578E8458E33B7d1e07b155A51585E`](https://taikoscan.io/address/0x6a619d35972578E8458E33B7d1e07b155A51585E) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xBFD6048C80665792d949692CE77307e55dbb8986`](https://taikoscan.io/address/0xBFD6048C80665792d949692CE77307e55dbb8986) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x65E2C9990d4CAc5E54E65c1BD625CdcC9FDd1292`](https://taikoscan.io/address/0x65E2C9990d4CAc5E54E65c1BD625CdcC9FDd1292) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xd7df0b795756b60ab51a37e26f1edb7ef9e78828`](https://taikoscan.io/address/0xd7df0b795756b60ab51a37e26f1edb7ef9e78828) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Tangle ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x946654AB30Dd6eD10236C89f2C8B2719df653691`](https://explorer.tangle.tools/address/0x946654AB30Dd6eD10236C89f2C8B2719df653691) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xAC19F4181E58efb7094e0cb4e1BB18c79F6AAdf4`](https://explorer.tangle.tools/address/0xAC19F4181E58efb7094e0cb4e1BB18c79F6AAdf4) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x63B92F7E2f69877184C955E63B9D8Dff55e52e14`](https://explorer.tangle.tools/address/0x63B92F7E2f69877184C955E63B9D8Dff55e52e14) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0xe785101Cb228693cc3EFdCd5d637fEf6A6Ff7259`](https://explorer.tangle.tools/address/0xe785101Cb228693cc3EFdCd5d637fEf6A6Ff7259) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x28D116d7e917756310986C4207eA54183fcba06A`](https://explorer.tangle.tools/address/0x28D116d7e917756310986C4207eA54183fcba06A) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x5e73bb96493C10919204045fCdb639D35ad859f8`](https://explorer.tangle.tools/address/0x5e73bb96493C10919204045fCdb639D35ad859f8) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xf03f4Bf48b108360bAf1597Fb8053Ebe0F5245dA`](https://explorer.zksync.io/address/0xf03f4Bf48b108360bAf1597Fb8053Ebe0F5245dA) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x8cB69b514E97a904743922e1adf3D1627deeeE8D`](https://explorer.zksync.io/address/0x8cB69b514E97a904743922e1adf3D1627deeeE8D) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x1fB145A47Eb9b8bf565273e137356376197b3559`](https://explorer.zksync.io/address/0x1fB145A47Eb9b8bf565273e137356376197b3559) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x99BA0D464942e7166dEBb8BAaAF1192F8d4117eb`](https://explorer.zksync.io/address/0x99BA0D464942e7166dEBb8BAaAF1192F8d4117eb) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xAE1A55205A0499d6BBb0Cf0f1210641957e9cb7e`](https://explorer.zksync.io/address/0xAE1A55205A0499d6BBb0Cf0f1210641957e9cb7e) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x8a84fCF962163A7E98Bf0daFD918973c846fa5C8`](https://explorer.zksync.io/address/0x8a84fCF962163A7E98Bf0daFD918973c846fa5C8) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x8127E8081C22807c8a786Af1e1b174939577144A`](https://sepolia.arbiscan.io/address/0x8127E8081C22807c8a786Af1e1b174939577144A) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x9D1C257d9bc09E6E6B8E7e7c2496C12000f55457`](https://sepolia.arbiscan.io/address/0x9D1C257d9bc09E6E6B8E7e7c2496C12000f55457) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xaff2efFCF38Ea4A92E0cC5D7c48456C53358fE1a`](https://sepolia.arbiscan.io/address/0xaff2efFCF38Ea4A92E0cC5D7c48456C53358fE1a) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x46AEd4FE32aE1306d8073FE54A4E844e10a3ca16`](https://sepolia.arbiscan.io/address/0x46AEd4FE32aE1306d8073FE54A4E844e10a3ca16) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xC1FD380b3B0fF989C259D0b45B97F9663B638aA4`](https://sepolia.arbiscan.io/address/0xC1FD380b3B0fF989C259D0b45B97F9663B638aA4) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0xa11561F9e418f2C431B411E1CA22FD3F85D4c831`](https://sepolia.arbiscan.io/address/0xa11561F9e418f2C431B411E1CA22FD3F85D4c831) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x6DCB73E5F7e8e70bE20b3B9CF50E3be4625A91C3`](https://sepolia.basescan.org/address/0x6DCB73E5F7e8e70bE20b3B9CF50E3be4625A91C3) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xFE7fc0Bbde84C239C0aB89111D617dC7cc58049f`](https://sepolia.basescan.org/address/0xFE7fc0Bbde84C239C0aB89111D617dC7cc58049f) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xb8c724df3eC8f2Bf8fA808dF2cB5dbab22f3E68c`](https://sepolia.basescan.org/address/0xb8c724df3eC8f2Bf8fA808dF2cB5dbab22f3E68c) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x474dFf3Cdd6489523947bf08D538F56d07Ca699e`](https://sepolia.basescan.org/address/0x474dFf3Cdd6489523947bf08D538F56d07Ca699e) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x23d0B7691F4Ca0E5477132a7C7F54fdCEd1814B9`](https://sepolia.basescan.org/address/0x23d0B7691F4Ca0E5477132a7C7F54fdCEd1814B9) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x899a05feb160fe912f621733A1d0b39C1446B3eB`](https://sepolia.basescan.org/address/0x899a05feb160fe912f621733A1d0b39C1446B3eB) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Linea Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x95D29708be647BDD8dA0bdF82B84eB5f42d45918`](https://sepolia.lineascan.build/address/0x95D29708be647BDD8dA0bdF82B84eB5f42d45918) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x435F33C21B9Ea8BF207785616Bb28C46eDeD7366`](https://sepolia.lineascan.build/address/0x435F33C21B9Ea8BF207785616Bb28C46eDeD7366) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x5A52E9F4dFcdBcd68E50386D484378718167aB60`](https://sepolia.lineascan.build/address/0x5A52E9F4dFcdBcd68E50386D484378718167aB60) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x237f114a9cF62b87383684529d889DdfEd917f0c`](https://sepolia.lineascan.build/address/0x237f114a9cF62b87383684529d889DdfEd917f0c) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8`](https://sepolia.lineascan.build/address/0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x83Dd52FCA44E069020b58155b761A590F12B59d3`](https://sepolia.lineascan.build/address/0x83Dd52FCA44E069020b58155b761A590F12B59d3) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x89EC3830040dec63E9dF0C904d649fda4d49DF16`](https://optimism-sepolia.blockscout.com/address/0x89EC3830040dec63E9dF0C904d649fda4d49DF16) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x0a881bbd71a21710D56Ff1931EC8189d94019D60`](https://optimism-sepolia.blockscout.com/address/0x0a881bbd71a21710D56Ff1931EC8189d94019D60) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xb971A93608413C54F407eE86C7c15b295E0004bB`](https://optimism-sepolia.blockscout.com/address/0xb971A93608413C54F407eE86C7c15b295E0004bB) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x48F8C05C721E27FA82aD6c8ddB1a88eF43864A9A`](https://optimism-sepolia.blockscout.com/address/0x48F8C05C721E27FA82aD6c8ddB1a88eF43864A9A) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0xd9dD971D4800100aED0BfF3535aB116D4Be5c420`](https://optimism-sepolia.blockscout.com/address/0xd9dD971D4800100aED0BfF3535aB116D4Be5c420) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x6CBe6e298A9354306e6ee65f63FF85CFA7062a39`](https://optimism-sepolia.blockscout.com/address/0x6CBe6e298A9354306e6ee65f63FF85CFA7062a39) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0x73BB6dD3f5828d60F8b3dBc8798EB10fbA2c5636`](https://sepolia.etherscan.io/address/0x73BB6dD3f5828d60F8b3dBc8798EB10fbA2c5636) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x3E435560fd0a03ddF70694b35b673C25c65aBB6C`](https://sepolia.etherscan.io/address/0x3E435560fd0a03ddF70694b35b673C25c65aBB6C) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0x3a1beA13A8C24c0EA2b8fAE91E4b2762A59D7aF5`](https://sepolia.etherscan.io/address/0x3a1beA13A8C24c0EA2b8fAE91E4b2762A59D7aF5) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x56F2f7f4d15d1A9FF9d3782b6F6bB8f6fd690D33`](https://sepolia.etherscan.io/address/0x56F2f7f4d15d1A9FF9d3782b6F6bB8f6fd690D33) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x04A9c14b7a000640419aD5515Db4eF4172C00E31`](https://sepolia.etherscan.io/address/0x04A9c14b7a000640419aD5515Db4eF4172C00E31) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x56E9180A8d2C35c99F2F8a1A5Ab8aBe79E876E8c`](https://sepolia.etherscan.io/address/0x56E9180A8d2C35c99F2F8a1A5Ab8aBe79E876E8c) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### Superseed Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2`](https://sepolia-explorer.superseed.xyz/address/0xCff4a803b0Bf55dD1BE38Fb96088478F3D2eeCF2) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0xDf578C2c70A86945999c65961417057363530a1c`](https://sepolia-explorer.superseed.xyz/address/0xDf578C2c70A86945999c65961417057363530a1c) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xcb099EfC90e88690e287259410B9AE63e1658CC6`](https://sepolia-explorer.superseed.xyz/address/0xcb099EfC90e88690e287259410B9AE63e1658CC6) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x2De92156000269fa2fde7544F10f01E8cBC80fFa`](https://sepolia-explorer.superseed.xyz/address/0x2De92156000269fa2fde7544F10f01E8cBC80fFa) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x0eDA15D606733f6CDe9DB67263E546bfcDDe9264`](https://sepolia-explorer.superseed.xyz/address/0x0eDA15D606733f6CDe9DB67263E546bfcDDe9264) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a`](https://sepolia-explorer.superseed.xyz/address/0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | ### ZKsync Sepolia Testnet ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierV2LockupDynamic | [`0xc4311a5913953162111bF75530f7BB14ec24e014`](https://sepolia.explorer.zksync.io/address/0xc4311a5913953162111bF75530f7BB14ec24e014) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupLinear | [`0x43864C567b89FA5fEE8010f92d4473Bf19169BBA`](https://sepolia.explorer.zksync.io/address/0x43864C567b89FA5fEE8010f92d4473Bf19169BBA) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2LockupTranched | [`0xF6e869b73E20b812dcf0E850AA8822F74f67f670`](https://sepolia.explorer.zksync.io/address/0xF6e869b73E20b812dcf0E850AA8822F74f67f670) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2NFTDescriptor | [`0x477DDC91a7e13CBaC01c06737abF96d50ECa7961`](https://sepolia.explorer.zksync.io/address/0x477DDC91a7e13CBaC01c06737abF96d50ECa7961) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2BatchLockup | [`0x1D68417ff71855Eb0237Ff03a8FfF02Ef67e4AFb`](https://sepolia.explorer.zksync.io/address/0x1D68417ff71855Eb0237Ff03a8FfF02Ef67e4AFb) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | | SablierV2MerkleLockupFactory | [`0x2CEf8C06dDF7a1440Ad2561c53821e43adDbfA31`](https://sepolia.explorer.zksync.io/address/0x2CEf8C06dDF7a1440Ad2561c53821e43adDbfA31) | [`lockup-v1.2`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v1.2) | --- ## Lockup v2.0 Source: https://docs.sablier.com/guides/lockup/previous-deployments/v2.0 # Lockup v2.0 This section contains the deployment addresses for the v2.0 release of [@sablier/lockup@2.0.1](https://npmjs.com/package/@sablier/lockup/v/2.0.1). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Lockup protocol. See the latest version [here](/guides/lockup/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x7C01AA3783577E15fD7e272443D44B92d5b21056`](https://etherscan.io/address/0x7C01AA3783577E15fD7e272443D44B92d5b21056) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x3F6E8a8Cffe377c4649aCeB01e6F20c60fAA356c`](https://etherscan.io/address/0x3F6E8a8Cffe377c4649aCeB01e6F20c60fAA356c) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://etherscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xA9dC6878C979B5cc1d98a1803F0664ad725A1f56`](https://etherscan.io/address/0xA9dC6878C979B5cc1d98a1803F0664ad725A1f56) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://etherscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x14Eb4AB47B2ec2a71763eaBa202a252E176FAE88`](https://abscan.org/address/0x14Eb4AB47B2ec2a71763eaBa202a252E176FAE88) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x3409308357BB704f79f70d748da502F363Dc2f1D`](https://abscan.org/address/0x3409308357BB704f79f70d748da502F363Dc2f1D) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0x07c194dFE7DCe9Ae7Ffe4bF32683cf1F8CDD4aEa`](https://abscan.org/address/0x07c194dFE7DCe9Ae7Ffe4bF32683cf1F8CDD4aEa) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x63Ff2E370788C163D5a1909B5FCb299DB327AEF9`](https://abscan.org/address/0x63Ff2E370788C163D5a1909B5FCb299DB327AEF9) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0xbB2e2884AE40003BB55fd3A85A9f8f7f72Aa441F`](https://abscan.org/address/0xbB2e2884AE40003BB55fd3A85A9f8f7f72Aa441F) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x467D5Bf8Cfa1a5f99328fBdCb9C751c78934b725`](https://arbiscan.io/address/0x467D5Bf8Cfa1a5f99328fBdCb9C751c78934b725) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xB11Ead48F572155C5F8dB6201701e91A936896f7`](https://arbiscan.io/address/0xB11Ead48F572155C5F8dB6201701e91A936896f7) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://arbiscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xd5c6a0Dd2E1822865c308850b8b3E2CcE762D061`](https://arbiscan.io/address/0xd5c6a0Dd2E1822865c308850b8b3E2CcE762D061) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://arbiscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x3C81BBBe72EF8eF3fb1D19B0bd6310Ad0dd27E82`](https://snowscan.xyz/address/0x3C81BBBe72EF8eF3fb1D19B0bd6310Ad0dd27E82) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xABDE228d84D86D78029C31A37Ae2435C8f923c8b`](https://snowscan.xyz/address/0xABDE228d84D86D78029C31A37Ae2435C8f923c8b) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://snowscan.xyz/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x906A4BD5dD0EF13654eA29bFD6185d0d64A4b674`](https://snowscan.xyz/address/0x906A4BD5dD0EF13654eA29bFD6185d0d64A4b674) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://snowscan.xyz/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xb5D78DD3276325f5FAF3106Cc4Acc56E28e0Fe3B`](https://basescan.org/address/0xb5D78DD3276325f5FAF3106Cc4Acc56E28e0Fe3B) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xC26CdAFd6ec3c91AD9aEeB237Ee1f37205ED26a4`](https://basescan.org/address/0xC26CdAFd6ec3c91AD9aEeB237Ee1f37205ED26a4) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://basescan.org/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x87e437030b7439150605a641483de98672E26317`](https://basescan.org/address/0x87e437030b7439150605a641483de98672E26317) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://basescan.org/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xC19A2542156b5d7960e0eF46E9787E7d336cF428`](https://berascan.com/address/0xC19A2542156b5d7960e0eF46E9787E7d336cF428) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x75838C66Dfa2296bB9758f75fC7ad219718C8a88`](https://berascan.com/address/0x75838C66Dfa2296bB9758f75fC7ad219718C8a88) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://berascan.com/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x3bbE0a21792564604B0fDc00019532Adeffa70eb`](https://berascan.com/address/0x3bbE0a21792564604B0fDc00019532Adeffa70eb) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://berascan.com/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xDbB6e9653d7e41766712Db22eB08ED3F21009fdd`](https://blastscan.io/address/0xDbB6e9653d7e41766712Db22eB08ED3F21009fdd) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x193c2af965FEAca8D893c974712e5b6BD3cBc5ec`](https://blastscan.io/address/0x193c2af965FEAca8D893c974712e5b6BD3cBc5ec) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://blastscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x959c412d5919b1Ec5D07bee3443ea68c91d57dd7`](https://blastscan.io/address/0x959c412d5919b1Ec5D07bee3443ea68c91d57dd7) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://blastscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x6E0baD2c077d699841F1929b45bfb93FAfBEd395`](https://bscscan.com/address/0x6E0baD2c077d699841F1929b45bfb93FAfBEd395) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xcf990fA3267F0945bBf7cf40A0c03F9dFE6a1804`](https://bscscan.com/address/0xcf990fA3267F0945bBf7cf40A0c03F9dFE6a1804) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://bscscan.com/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x56831a5a932793E02251126831174Ab8Bf2f7695`](https://bscscan.com/address/0x56831a5a932793E02251126831174Ab8Bf2f7695) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://bscscan.com/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x711900e5f55d427cd88e5E3FCAe54Ccf02De71F4`](https://chiliscan.com/address/0x711900e5f55d427cd88e5E3FCAe54Ccf02De71F4) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x179536f3289fb50076968b339C7EF0Dc0B38E3AF`](https://chiliscan.com/address/0x179536f3289fb50076968b339C7EF0Dc0B38E3AF) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0x6FcAB41e3b62d05aB4fC729586CB06Af2a2662D0`](https://chiliscan.com/address/0x6FcAB41e3b62d05aB4fC729586CB06Af2a2662D0) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x8A96f827082FB349B6e268baa0a7A5584c4Ccda6`](https://chiliscan.com/address/0x8A96f827082FB349B6e268baa0a7A5584c4Ccda6) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x84f092cf4D7D36C2d4987F672df81a39200a7146`](https://chiliscan.com/address/0x84f092cf4D7D36C2d4987F672df81a39200a7146) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Core Dao ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x4fff53bfe86a0bd59a81c89d8ba84c67cf947764`](https://scan.coredao.org/address/0x4fff53bfe86a0bd59a81c89d8ba84c67cf947764) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x96dadeeab25413de04a1b8e40c4de41bd9d7fd29`](https://scan.coredao.org/address/0x96dadeeab25413de04a1b8e40c4de41bd9d7fd29) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://scan.coredao.org/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xac0cf0f2a96ed7ec3cfa4d0be621c67adc9dd903`](https://scan.coredao.org/address/0xac0cf0f2a96ed7ec3cfa4d0be621c67adc9dd903) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://scan.coredao.org/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x007aF5dC7b1CaA66Cf7Ebcc01E2e6ba4D55D3e92`](https://gnosisscan.io/address/0x007aF5dC7b1CaA66Cf7Ebcc01E2e6ba4D55D3e92) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xe89EE0b2B31A296C5cCb631C3670F94bDD64a0D2`](https://gnosisscan.io/address/0xe89EE0b2B31A296C5cCb631C3670F94bDD64a0D2) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://gnosisscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x3140a6900AA2FF3186730741ad8255ee4e6d8Ff1`](https://gnosisscan.io/address/0x3140a6900AA2FF3186730741ad8255ee4e6d8Ff1) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://gnosisscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x856167EE3e09Ba562d69A542Ab6A939903ad738e`](https://hyperevmscan.io/address/0x856167EE3e09Ba562d69A542Ab6A939903ad738e) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x5444eA1B8B636A8FdF7cE814077E664c28dE30Ec`](https://hyperevmscan.io/address/0x5444eA1B8B636A8FdF7cE814077E664c28dE30Ec) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xe2f66eEe2E227c40074668ba53021423ED7D4ea1`](https://hyperevmscan.io/address/0xe2f66eEe2E227c40074668ba53021423ED7D4ea1) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x7263d77e9e872f82A15e5E1a9816440D23758708`](https://hyperevmscan.io/address/0x7263d77e9e872f82A15e5E1a9816440D23758708) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0xf91e1a8fA643F8062C12Ca2865f1eb2258d6422F`](https://hyperevmscan.io/address/0xf91e1a8fA643F8062C12Ca2865f1eb2258d6422F) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x6c65aAf03186d1DA60127D3d7792cF36eD99D909`](https://phoenix.lightlink.io/address/0x6c65aAf03186d1DA60127D3d7792cF36eD99D909) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xe8fa70D0172BB36c952E3e20e2f3550Ca4557761`](https://phoenix.lightlink.io/address/0xe8fa70D0172BB36c952E3e20e2f3550Ca4557761) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://phoenix.lightlink.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xCFB5F90370A7884DEc59C55533782B45FA24f4d1`](https://phoenix.lightlink.io/address/0xCFB5F90370A7884DEc59C55533782B45FA24f4d1) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://phoenix.lightlink.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x6964252561e8762dD10267176EaC5078b6291e51`](https://lineascan.build/address/0x6964252561e8762dD10267176EaC5078b6291e51) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xEdf0A4b30defD14449604d1b97e2c39128c136CA`](https://lineascan.build/address/0xEdf0A4b30defD14449604d1b97e2c39128c136CA) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0x058aD99662FE7ecB8c3109920C99439a302b6573`](https://lineascan.build/address/0x058aD99662FE7ecB8c3109920C99439a302b6573) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x1514a869D29a8B22961e8F9eBa3DC64000b96BCe`](https://lineascan.build/address/0x1514a869D29a8B22961e8F9eBa3DC64000b96BCe) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0xdEe57959770667d97A90C94fE70C055496B7a791`](https://lineascan.build/address/0xdEe57959770667d97A90C94fE70C055496B7a791) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x3aEbaDFC423fD08BE4715986F68D5E9A597ec974`](https://modescan.io/address/0x3aEbaDFC423fD08BE4715986F68D5E9A597ec974) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x34dBab20FD097F63DDbf3092D83B1005D2573082`](https://modescan.io/address/0x34dBab20FD097F63DDbf3092D83B1005D2573082) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://modescan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x64e7879558b6dfE2f510bd4b9Ad196ef0371EAA8`](https://modescan.io/address/0x64e7879558b6dfE2f510bd4b9Ad196ef0371EAA8) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://modescan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xF3cd08105b6745965149eF02b8aBdCEa0Ae51241`](https://explorer.morphl2.io/address/0xF3cd08105b6745965149eF02b8aBdCEa0Ae51241) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x081BBbd4861BaBACE3E7eDC8a45741129DfC02fE`](https://explorer.morphl2.io/address/0x081BBbd4861BaBACE3E7eDC8a45741129DfC02fE) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://explorer.morphl2.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x660314f09ac3B65E216B6De288aAdc2599AF14e2`](https://explorer.morphl2.io/address/0x660314f09ac3B65E216B6De288aAdc2599AF14e2) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://explorer.morphl2.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x822e9c4852E978104d82F0f785bFA663c2b700c1`](https://optimistic.etherscan.io/address/0x822e9c4852E978104d82F0f785bFA663c2b700c1) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x0c4Cd6087DbFa3F74661BAbbFaa35273baC1c4b1`](https://optimistic.etherscan.io/address/0x0c4Cd6087DbFa3F74661BAbbFaa35273baC1c4b1) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://optimistic.etherscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x41dBa1AfBB6DF91b3330dc009842327A9858Cbae`](https://optimistic.etherscan.io/address/0x41dBa1AfBB6DF91b3330dc009842327A9858Cbae) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://optimistic.etherscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xE0BFe071Da104e571298f8b6e0fcE44C512C1Ff4`](https://polygonscan.com/address/0xE0BFe071Da104e571298f8b6e0fcE44C512C1Ff4) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x1aDd9385F2C5c8e446bbB77c7A36839aB7743AF4`](https://polygonscan.com/address/0x1aDd9385F2C5c8e446bbB77c7A36839aB7743AF4) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://polygonscan.com/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xf5e12d0bA25FCa0D738Ec57f149736B2e4C46980`](https://polygonscan.com/address/0xf5e12d0bA25FCa0D738Ec57f149736B2e4C46980) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://polygonscan.com/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xcB0B1f1D116eD62135848d8C90EB61afDA936Da8`](https://scrollscan.com/address/0xcB0B1f1D116eD62135848d8C90EB61afDA936Da8) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x8234Ad3CC4D29a4619C36a15286dac73078672a8`](https://scrollscan.com/address/0x8234Ad3CC4D29a4619C36a15286dac73078672a8) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://scrollscan.com/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x00Ff6443E902874924dd217c1435e3be04f57431`](https://scrollscan.com/address/0x00Ff6443E902874924dd217c1435e3be04f57431) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://scrollscan.com/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Sei Network ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x736A6E895790e089aEC2Bf76B2D7f368ce6Efff5`](https://seiscan.io/address/0x736A6E895790e089aEC2Bf76B2D7f368ce6Efff5) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x34686937bef23c6441248Cc5A63A79a3a707e7E4`](https://seiscan.io/address/0x34686937bef23c6441248Cc5A63A79a3a707e7E4) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://seiscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xeaFB40669fe3523b073904De76410b46e79a56D7`](https://seiscan.io/address/0xeaFB40669fe3523b073904De76410b46e79a56D7) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://seiscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xc37462eE6500D2C36c9131b921fAADBD6cb7B60b`](https://sonicscan.org/address/0xc37462eE6500D2C36c9131b921fAADBD6cb7B60b) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xc91b20dC29E19120BF3f54277947327AfD50fBa2`](https://sonicscan.org/address/0xc91b20dC29E19120BF3f54277947327AfD50fBa2) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://sonicscan.org/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xAE8FE47765f88e0A2D7E8fbaf33583DBf0373f01`](https://sonicscan.org/address/0xAE8FE47765f88e0A2D7E8fbaf33583DBf0373f01) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://sonicscan.org/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Sophon ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x28fCAE6bda2546C93183EeC8638691B2EB184003`](https://sophscan.xyz/address/0x28fCAE6bda2546C93183EeC8638691B2EB184003) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x7282d83E49363f373102d195F66649eBD6C57B9B`](https://sophscan.xyz/address/0x7282d83E49363f373102d195F66649eBD6C57B9B) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0x424B27529B49EF4Bfa0727aFcFE4387Ac2932944`](https://sophscan.xyz/address/0x424B27529B49EF4Bfa0727aFcFE4387Ac2932944) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xAc2E42b520364940c90Ce164412Ca9BA212d014B`](https://sophscan.xyz/address/0xAc2E42b520364940c90Ce164412Ca9BA212d014B) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x9971914DA16787F6cCfb27bEfB4404e33C8b869D`](https://sophscan.xyz/address/0x9971914DA16787F6cCfb27bEfB4404e33C8b869D) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xF46D1f8C85f215A515F6D738ab3E3bA081f6C083`](https://explorer.superseed.xyz/address/0xF46D1f8C85f215A515F6D738ab3E3bA081f6C083) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x89e9F2473836d9ab7D28Df6F180E30992b8CB5d6`](https://explorer.superseed.xyz/address/0x89e9F2473836d9ab7D28Df6F180E30992b8CB5d6) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://explorer.superseed.xyz/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xa4576b58Ec760A8282D081dc94F3dc716DFc61e9`](https://explorer.superseed.xyz/address/0xa4576b58Ec760A8282D081dc94F3dc716DFc61e9) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://explorer.superseed.xyz/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Taiko ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x628E88cDF558c0F4796c8CeB5068a023a7159aA7`](https://taikoscan.io/address/0x628E88cDF558c0F4796c8CeB5068a023a7159aA7) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xcBbA08768C4a9D9131dE0467Ae136b8450dC13B2`](https://taikoscan.io/address/0xcBbA08768C4a9D9131dE0467Ae136b8450dC13B2) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://taikoscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x79F1fD8bB2D455f64010063Fc79E27561980FE10`](https://taikoscan.io/address/0x79F1fD8bB2D455f64010063Fc79E27561980FE10) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://taikoscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Tangle ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x1cAe76b71913598d7664d16641CCB6037d8Ed61a`](https://explorer.tangle.tools/address/0x1cAe76b71913598d7664d16641CCB6037d8Ed61a) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xF5AC60870E1CCc4Bfce23cfbb7a796A0d8dBAf47`](https://explorer.tangle.tools/address/0xF5AC60870E1CCc4Bfce23cfbb7a796A0d8dBAf47) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0x3D664B2Da905DDD0Db931982FD9a759ea950D6e1`](https://explorer.tangle.tools/address/0x3D664B2Da905DDD0Db931982FD9a759ea950D6e1) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a`](https://explorer.tangle.tools/address/0x92FC05e49c27884d554D98a5C01Ff0894a9DC29a) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x9EfC8663cAB0e2d97ad17C9fbfc8392445517E94`](https://explorer.tangle.tools/address/0x9EfC8663cAB0e2d97ad17C9fbfc8392445517E94) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x26C341C4D79bA8F6BFB450a49E9165c936316B14`](https://uniscan.xyz/address/0x26C341C4D79bA8F6BFB450a49E9165c936316B14) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xaf875A2bDb74bA8872292FC371131eb45a9b570C`](https://uniscan.xyz/address/0xaf875A2bDb74bA8872292FC371131eb45a9b570C) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076e4fb5cfe8be1c26e61222dc51828db8c1dc`](https://uniscan.xyz/address/0xf8076e4fb5cfe8be1c26e61222dc51828db8c1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xa5F12D63E18a28C9BE27B6f3d91ce693320067ba`](https://uniscan.xyz/address/0xa5F12D63E18a28C9BE27B6f3d91ce693320067ba) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522ca06ce080800ab59ba4c091e63f6f54c5e6d`](https://uniscan.xyz/address/0x5522ca06ce080800ab59ba4c091e63f6f54c5e6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x489e0DC5E62A751A2b209f1cC03E189fd6257176`](https://xdcscan.com/address/0x489e0DC5E62A751A2b209f1cC03E189fd6257176) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x6d36227Dd84e2A3d898B192Bc82a005c3cc2320C`](https://xdcscan.com/address/0x6d36227Dd84e2A3d898B192Bc82a005c3cc2320C) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076e4fb5cfe8be1c26e61222dc51828db8c1dc`](https://xdcscan.com/address/0xf8076e4fb5cfe8be1c26e61222dc51828db8c1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x4c1311a9d88BFb7023148aB04F7321C2E91c29bf`](https://xdcscan.com/address/0x4c1311a9d88BFb7023148aB04F7321C2E91c29bf) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522ca06ce080800ab59ba4c091e63f6f54c5e6d`](https://xdcscan.com/address/0x5522ca06ce080800ab59ba4c091e63f6f54c5e6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x7BCcB3595Aa81Dbe8A69DD8C46f5C2A3cf76594A`](https://explorer.zksync.io/address/0x7BCcB3595Aa81Dbe8A69DD8C46f5C2A3cf76594A) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xCC926359DBE6b6311D63f8155fcC3B57F3fAAE80`](https://explorer.zksync.io/address/0xCC926359DBE6b6311D63f8155fcC3B57F3fAAE80) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0x37De3Fc44a07A40411AD0Cea4310990C9F88c1C1`](https://explorer.zksync.io/address/0x37De3Fc44a07A40411AD0Cea4310990C9F88c1C1) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xCB2d53c58496C2aA114bce4ED5C7fe768ce86542`](https://explorer.zksync.io/address/0xCB2d53c58496C2aA114bce4ED5C7fe768ce86542) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0xEE4a32E026aC2FD6BF71d9D7eB00803576aD314d`](https://explorer.zksync.io/address/0xEE4a32E026aC2FD6BF71d9D7eB00803576aD314d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x83Dd52FCA44E069020b58155b761A590F12B59d3`](https://sepolia.arbiscan.io/address/0x83Dd52FCA44E069020b58155b761A590F12B59d3) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xbf85cD17cA59b7A2b81d3D776cE1602a7C0aF9D9`](https://sepolia.arbiscan.io/address/0xbf85cD17cA59b7A2b81d3D776cE1602a7C0aF9D9) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://sepolia.arbiscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8`](https://sepolia.arbiscan.io/address/0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://sepolia.arbiscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xa4777CA525d43a7aF55D45b11b430606d7416f8d`](https://sepolia.basescan.org/address/0xa4777CA525d43a7aF55D45b11b430606d7416f8d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xEdc716E9672f672456d22b02532395c1e62B8C16`](https://sepolia.basescan.org/address/0xEdc716E9672f672456d22b02532395c1e62B8C16) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://sepolia.basescan.org/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xCA2593027BA24856c292Fdcb5F987E0c25e755a4`](https://sepolia.basescan.org/address/0xCA2593027BA24856c292Fdcb5F987E0c25e755a4) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://sepolia.basescan.org/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Linea Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xbb4A14868A4BEc78b7354582b8C818ba520d7A4E`](https://sepolia.lineascan.build/address/0xbb4A14868A4BEc78b7354582b8C818ba520d7A4E) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0x9A987181BF05b7C154118A3216d522fa2407a8Be`](https://sepolia.lineascan.build/address/0x9A987181BF05b7C154118A3216d522fa2407a8Be) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0x058aD99662FE7ecB8c3109920C99439a302b6573`](https://sepolia.lineascan.build/address/0x058aD99662FE7ecB8c3109920C99439a302b6573) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xCE94BE25320A51Ac868d0C133c251aE10682DabD`](https://sepolia.lineascan.build/address/0xCE94BE25320A51Ac868d0C133c251aE10682DabD) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0xdEe57959770667d97A90C94fE70C055496B7a791`](https://sepolia.lineascan.build/address/0xdEe57959770667d97A90C94fE70C055496B7a791) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x1f898895eAB949FfD34c29Cf859C035DC4525DF4`](https://optimism-sepolia.blockscout.com/address/0x1f898895eAB949FfD34c29Cf859C035DC4525DF4) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xF7BA8a7dc96d1939b789b91865bdb05596EBB558`](https://optimism-sepolia.blockscout.com/address/0xF7BA8a7dc96d1939b789b91865bdb05596EBB558) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://optimism-sepolia.blockscout.com/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xDf6163ddD3Ebcb552Cc1379a9c65AFe68683534e`](https://optimism-sepolia.blockscout.com/address/0xDf6163ddD3Ebcb552Cc1379a9c65AFe68683534e) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://optimism-sepolia.blockscout.com/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xd116c275541cdBe7594A202bD6AE4DBca4578462`](https://sepolia.etherscan.io/address/0xd116c275541cdBe7594A202bD6AE4DBca4578462) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xB655ecD83D27f7c683A9605783bd2866a4dCEB04`](https://sepolia.etherscan.io/address/0xB655ecD83D27f7c683A9605783bd2866a4dCEB04) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://sepolia.etherscan.io/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0x08D3C81626d9Cb19760835e8730Ec0D3F1899976`](https://sepolia.etherscan.io/address/0x08D3C81626d9Cb19760835e8730Ec0D3F1899976) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://sepolia.etherscan.io/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | ### Superseed Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x4E83EC1Ea3B885C1a3698dA7DC42F32575688ABE`](https://sepolia-explorer.superseed.xyz/address/0x4E83EC1Ea3B885C1a3698dA7DC42F32575688ABE) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | SablierBatchLockup | [`0xB2C6C57ee10B88E8344f34ffeCe39B0C6573c23D`](https://sepolia-explorer.superseed.xyz/address/0xB2C6C57ee10B88E8344f34ffeCe39B0C6573c23D) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | Helpers | [`0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc`](https://sepolia-explorer.superseed.xyz/address/0xf8076E4Fb5cfE8be1C26E61222DC51828Db8C1dc) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | LockupNFTDescriptor | [`0xc5D8E4317CE4a2E323192A5d856C90372bDE1558`](https://sepolia-explorer.superseed.xyz/address/0xc5D8E4317CE4a2E323192A5d856C90372bDE1558) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | | VestingMath | [`0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d`](https://sepolia-explorer.superseed.xyz/address/0x5522CA06Ce080800AB59BA4C091e63f6f54C5E6d) | [`lockup-v2.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v2.0) | --- ## Lockup v3.0 Source: https://docs.sablier.com/guides/lockup/previous-deployments/v3.0 # Lockup v3.0 This section contains the deployment addresses for the v3.0 release of [@sablier/lockup@3.0.1](https://npmjs.com/package/@sablier/lockup/v/3.0.1). A few noteworthy details about the deployments: - The addresses are final - All contracts are non-upgradeable - The source code is verified on Etherscan across all chains :::info This is an outdated version of the Lockup protocol. See the latest version [here](/guides/lockup/deployments). ::: ## Mainnets ### Ethereum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xcF8ce57fa442ba50aCbC57147a62aD03873FfA73`](https://etherscan.io/address/0xcF8ce57fa442ba50aCbC57147a62aD03873FfA73) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x0636d83b184d65c242c43de6aad10535bfb9d45a`](https://etherscan.io/address/0x0636d83b184d65c242c43de6aad10535bfb9d45a) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://etherscan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://etherscan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xA9dC6878C979B5cc1d98a1803F0664ad725A1f56`](https://etherscan.io/address/0xA9dC6878C979B5cc1d98a1803F0664ad725A1f56) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Abstract ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x293d8d192C0C93225FF6bBE7415a56B57379bbA3`](https://abscan.org/address/0x293d8d192C0C93225FF6bBE7415a56B57379bbA3) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x47a27E76b32A9aA168297A32c5F081740600A16F`](https://abscan.org/address/0x47a27E76b32A9aA168297A32c5F081740600A16F) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://abscan.org/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://abscan.org/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x63Ff2E370788C163D5a1909B5FCb299DB327AEF9`](https://abscan.org/address/0x63Ff2E370788C163D5a1909B5FCb299DB327AEF9) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Arbitrum ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xF12AbfB041b5064b839Ca56638cDB62fEA712Db5`](https://arbiscan.io/address/0xF12AbfB041b5064b839Ca56638cDB62fEA712Db5) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xf094baa1b754f54d8f282bc79a74bd76aff29d25`](https://arbiscan.io/address/0xf094baa1b754f54d8f282bc79a74bd76aff29d25) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://arbiscan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://arbiscan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xd5c6a0Dd2E1822865c308850b8b3E2CcE762D061`](https://arbiscan.io/address/0xd5c6a0Dd2E1822865c308850b8b3E2CcE762D061) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Avalanche ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x7e146250Ed5CCCC6Ada924D456947556902acaFD`](https://snowscan.xyz/address/0x7e146250Ed5CCCC6Ada924D456947556902acaFD) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x7125669bFbCA422bE806d62B6b21E42ED0D78494`](https://snowscan.xyz/address/0x7125669bFbCA422bE806d62B6b21E42ED0D78494) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://snowscan.xyz/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://snowscan.xyz/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x906A4BD5dD0EF13654eA29bFD6185d0d64A4b674`](https://snowscan.xyz/address/0x906A4BD5dD0EF13654eA29bFD6185d0d64A4b674) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Base ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xe261b366f231b12fcb58d6bbd71e57faee82431d`](https://basescan.org/address/0xe261b366f231b12fcb58d6bbd71e57faee82431d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x8882549b29dfed283738918d90b5f6e2ab0baeb6`](https://basescan.org/address/0x8882549b29dfed283738918d90b5f6e2ab0baeb6) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://basescan.org/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://basescan.org/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x87e437030b7439150605a641483de98672E26317`](https://basescan.org/address/0x87e437030b7439150605a641483de98672E26317) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Berachain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xC37B51a3c3Be55f0B34Fbd8Bd1F30cFF6d251408`](https://berascan.com/address/0xC37B51a3c3Be55f0B34Fbd8Bd1F30cFF6d251408) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x35860B173573CbDB7a14dE5F9fBB7489c57a5727`](https://berascan.com/address/0x35860B173573CbDB7a14dE5F9fBB7489c57a5727) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://berascan.com/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://berascan.com/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x3bbE0a21792564604B0fDc00019532Adeffa70eb`](https://berascan.com/address/0x3bbE0a21792564604B0fDc00019532Adeffa70eb) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Blast ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xcD16d89cc79Ab0b52717A46b8A3F73E61014c7dc`](https://blastscan.io/address/0xcD16d89cc79Ab0b52717A46b8A3F73E61014c7dc) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xd2187e309a93895fcd1c43fb5eae903b583d7e38`](https://blastscan.io/address/0xd2187e309a93895fcd1c43fb5eae903b583d7e38) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://blastscan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://blastscan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x959c412d5919b1Ec5D07bee3443ea68c91d57dd7`](https://blastscan.io/address/0x959c412d5919b1Ec5D07bee3443ea68c91d57dd7) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### BNB Chain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x06bd1Ec1d80acc45ba332f79B08d2d9e24240C74`](https://bscscan.com/address/0x06bd1Ec1d80acc45ba332f79B08d2d9e24240C74) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xFEd01907959CD5d470F438daad232a99cAffe67f`](https://bscscan.com/address/0xFEd01907959CD5d470F438daad232a99cAffe67f) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://bscscan.com/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://bscscan.com/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x56831a5a932793E02251126831174Ab8Bf2f7695`](https://bscscan.com/address/0x56831a5a932793E02251126831174Ab8Bf2f7695) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Chiliz ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x957a54aC691893B20c705e0b2EecbDDF5220d019`](https://chiliscan.com/address/0x957a54aC691893B20c705e0b2EecbDDF5220d019) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x202628bCF205BC57a2D181D6B4eF1f7E5538EC35`](https://chiliscan.com/address/0x202628bCF205BC57a2D181D6B4eF1f7E5538EC35) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://chiliscan.com/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://chiliscan.com/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x8A96f827082FB349B6e268baa0a7A5584c4Ccda6`](https://chiliscan.com/address/0x8A96f827082FB349B6e268baa0a7A5584c4Ccda6) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Core Dao ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x01Fed2aB51A830a3AF3AE1AB817dF1bA4F152bB0`](https://scan.coredao.org/address/0x01Fed2aB51A830a3AF3AE1AB817dF1bA4F152bB0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x80054b6797C5F657CaCaEC05E4890acfC4Ef79dB`](https://scan.coredao.org/address/0x80054b6797C5F657CaCaEC05E4890acfC4Ef79dB) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://scan.coredao.org/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://scan.coredao.org/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xac0cf0f2a96ed7ec3cfa4d0be621c67adc9dd903`](https://scan.coredao.org/address/0xac0cf0f2a96ed7ec3cfa4d0be621c67adc9dd903) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Denergy ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x9f5d28C8ed7F09e65519C1f6f394e523524cA38F`](https://explorer.denergychain.com/address/0x9f5d28C8ed7F09e65519C1f6f394e523524cA38F) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x6Fe81F4Bf1aF1b829f0E701647808f3Aa4b0BdE7`](https://explorer.denergychain.com/address/0x6Fe81F4Bf1aF1b829f0E701647808f3Aa4b0BdE7) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xd595D34ed96b253E7c7a934a7624F330a8411953`](https://explorer.denergychain.com/address/0xd595D34ed96b253E7c7a934a7624F330a8411953) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0xB8aB2E66d5E4931e675CBE499d8c01B778694fd3`](https://explorer.denergychain.com/address/0xB8aB2E66d5E4931e675CBE499d8c01B778694fd3) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x4749dB4834be9b473D586Ad4d98133dAfC678313`](https://explorer.denergychain.com/address/0x4749dB4834be9b473D586Ad4d98133dAfC678313) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Gnosis ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x87f87Eb0b59421D1b2Df7301037e923932176681`](https://gnosisscan.io/address/0x87f87Eb0b59421D1b2Df7301037e923932176681) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xb778B396dD6f3a770C4B4AE7b0983345b231C16C`](https://gnosisscan.io/address/0xb778B396dD6f3a770C4B4AE7b0983345b231C16C) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://gnosisscan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://gnosisscan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x3140a6900AA2FF3186730741ad8255ee4e6d8Ff1`](https://gnosisscan.io/address/0x3140a6900AA2FF3186730741ad8255ee4e6d8Ff1) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### HyperEVM ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x50ff828e66612A4D1F7141936F2B4078C7356329`](https://hyperevmscan.io/address/0x50ff828e66612A4D1F7141936F2B4078C7356329) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xF66b45FA5Be1F633Cd521d480A1832645DE0C710`](https://hyperevmscan.io/address/0xF66b45FA5Be1F633Cd521d480A1832645DE0C710) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://hyperevmscan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://hyperevmscan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x7263d77e9e872f82A15e5E1a9816440D23758708`](https://hyperevmscan.io/address/0x7263d77e9e872f82A15e5E1a9816440D23758708) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Lightlink ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xA4f1f4a5C55b5d9372CBB29112b14e1912A23d9D`](https://phoenix.lightlink.io/address/0xA4f1f4a5C55b5d9372CBB29112b14e1912A23d9D) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xc1d996e119f82D3Cb540687463804545A13065db`](https://phoenix.lightlink.io/address/0xc1d996e119f82D3Cb540687463804545A13065db) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://phoenix.lightlink.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://phoenix.lightlink.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xCFB5F90370A7884DEc59C55533782B45FA24f4d1`](https://phoenix.lightlink.io/address/0xCFB5F90370A7884DEc59C55533782B45FA24f4d1) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Linea Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xc853DB30a908dC1b655bbd4A8B9d5DB8588C13c8`](https://lineascan.build/address/0xc853DB30a908dC1b655bbd4A8B9d5DB8588C13c8) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x145Ca6411bD62172fbE8728893E8e8E75a2b066c`](https://lineascan.build/address/0x145Ca6411bD62172fbE8728893E8e8E75a2b066c) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0x1b06a7aed1f6e5d8c0f4b410687b37c867fa8987`](https://lineascan.build/address/0x1b06a7aed1f6e5d8c0f4b410687b37c867fa8987) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x8e6f2e21df39ffb23fbd5a51a5903952334435e3`](https://lineascan.build/address/0x8e6f2e21df39ffb23fbd5a51a5903952334435e3) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x1514a869D29a8B22961e8F9eBa3DC64000b96BCe`](https://lineascan.build/address/0x1514a869D29a8B22961e8F9eBa3DC64000b96BCe) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Mode ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x9513CE572D4f4AAc1Dd493bcd50866235D1c698d`](https://modescan.io/address/0x9513CE572D4f4AAc1Dd493bcd50866235D1c698d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x9ba4cE334706F822cbCaa4F6fA650e0054D5fa99`](https://modescan.io/address/0x9ba4cE334706F822cbCaa4F6fA650e0054D5fa99) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://modescan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://modescan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x64e7879558b6dfE2f510bd4b9Ad196ef0371EAA8`](https://modescan.io/address/0x64e7879558b6dfE2f510bd4b9Ad196ef0371EAA8) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Monad ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x003F5393F4836f710d492AD98D89F5BFCCF1C962`](https://monadscan.com/address/0x003F5393F4836f710d492AD98D89F5BFCCF1C962) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x4FCACf614E456728CaEa87f475bd78EC3550E20B`](https://monadscan.com/address/0x4FCACf614E456728CaEa87f475bd78EC3550E20B) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0A1aC47260B95D334763473B868117EF7343aA0`](https://monadscan.com/address/0xa0A1aC47260B95D334763473B868117EF7343aA0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feB172238638897B13b69C65feB508a0a96b35D`](https://monadscan.com/address/0x1feB172238638897B13b69C65feB508a0a96b35D) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x619E7f9832522EDeBd883482Cd3d84653A050725`](https://monadscan.com/address/0x619E7f9832522EDeBd883482Cd3d84653A050725) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Morph ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xE646D9A037c6B62e4d417592A10f57e77f007a27`](https://explorer.morphl2.io/address/0xE646D9A037c6B62e4d417592A10f57e77f007a27) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xf81d757E270790c03E3cd2dbFBc62d38ad2e17f9`](https://explorer.morphl2.io/address/0xf81d757E270790c03E3cd2dbFBc62d38ad2e17f9) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://explorer.morphl2.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://explorer.morphl2.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x660314f09ac3B65E216B6De288aAdc2599AF14e2`](https://explorer.morphl2.io/address/0x660314f09ac3B65E216B6De288aAdc2599AF14e2) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### OP Mainnet ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xe2620fB20fC9De61CD207d921691F4eE9d0fffd0`](https://optimistic.etherscan.io/address/0xe2620fB20fC9De61CD207d921691F4eE9d0fffd0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xf3aBc38b5e0f372716F9bc00fC9994cbd5A8e6FC`](https://optimistic.etherscan.io/address/0xf3aBc38b5e0f372716F9bc00fC9994cbd5A8e6FC) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://optimistic.etherscan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://optimistic.etherscan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x41dBa1AfBB6DF91b3330dc009842327A9858Cbae`](https://optimistic.etherscan.io/address/0x41dBa1AfBB6DF91b3330dc009842327A9858Cbae) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Polygon ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x1E901b0E05A78C011D6D4cfFdBdb28a42A1c32EF`](https://polygonscan.com/address/0x1E901b0E05A78C011D6D4cfFdBdb28a42A1c32EF) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x3395Db92edb3a992E4F0eC1dA203C92D5075b845`](https://polygonscan.com/address/0x3395Db92edb3a992E4F0eC1dA203C92D5075b845) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://polygonscan.com/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://polygonscan.com/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xf5e12d0bA25FCa0D738Ec57f149736B2e4C46980`](https://polygonscan.com/address/0xf5e12d0bA25FCa0D738Ec57f149736B2e4C46980) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Scroll ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xcb60a39942CD5D1c2a1C8aBBEd99C43A73dF3f8d`](https://scrollscan.com/address/0xcb60a39942CD5D1c2a1C8aBBEd99C43A73dF3f8d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xa57C667E78BA165e8f09899fdE4e8C974C2dD000`](https://scrollscan.com/address/0xa57C667E78BA165e8f09899fdE4e8C974C2dD000) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://scrollscan.com/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://scrollscan.com/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x00Ff6443E902874924dd217c1435e3be04f57431`](https://scrollscan.com/address/0x00Ff6443E902874924dd217c1435e3be04f57431) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Sei Network ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x1d96e9d05f6910d22876177299261290537cfBBc`](https://seiscan.io/address/0x1d96e9d05f6910d22876177299261290537cfBBc) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xB33f16e55Ac8Ff6CCdfe466C0D9A25c706952EF8`](https://seiscan.io/address/0xB33f16e55Ac8Ff6CCdfe466C0D9A25c706952EF8) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://seiscan.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://seiscan.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xeaFB40669fe3523b073904De76410b46e79a56D7`](https://seiscan.io/address/0xeaFB40669fe3523b073904De76410b46e79a56D7) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Sonic ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x763Cfb7DF1D1BFe50e35E295688b3Df789D2feBB`](https://sonicscan.org/address/0x763Cfb7DF1D1BFe50e35E295688b3Df789D2feBB) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x84A865542640B24301F1C8A8C60Eb098a7e1df9b`](https://sonicscan.org/address/0x84A865542640B24301F1C8A8C60Eb098a7e1df9b) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://sonicscan.org/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://sonicscan.org/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc`](https://sonicscan.org/address/0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Superseed ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x2F1c6AD6306Bd0200D55b59AD54d4b44067D00E6`](https://explorer.superseed.xyz/address/0x2F1c6AD6306Bd0200D55b59AD54d4b44067D00E6) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x48c45c1DF54bDA1F1819AD07B6B4f8B2dcF85BAA`](https://explorer.superseed.xyz/address/0x48c45c1DF54bDA1F1819AD07B6B4f8B2dcF85BAA) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://explorer.superseed.xyz/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://explorer.superseed.xyz/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xa4576b58Ec760A8282D081dc94F3dc716DFc61e9`](https://explorer.superseed.xyz/address/0xa4576b58Ec760A8282D081dc94F3dc716DFc61e9) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Unichain ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xfFb540fC132dCefb0Fdef96ef63FE2f2F1BD7CFd`](https://uniscan.xyz/address/0xfFb540fC132dCefb0Fdef96ef63FE2f2F1BD7CFd) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xA8B4355D37C3660aE0C8fD14c39D44Ad5EA60b3e`](https://uniscan.xyz/address/0xA8B4355D37C3660aE0C8fD14c39D44Ad5EA60b3e) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://uniscan.xyz/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://uniscan.xyz/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xa5F12D63E18a28C9BE27B6f3d91ce693320067ba`](https://uniscan.xyz/address/0xa5F12D63E18a28C9BE27B6f3d91ce693320067ba) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### XDC ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x2266901B1EcF499b4c91B6cBeA8e06700cFbde1e`](https://xdcscan.com/address/0x2266901B1EcF499b4c91B6cBeA8e06700cFbde1e) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xCf2491021E56E04e5ceF582c928d64b185843124`](https://xdcscan.com/address/0xCf2491021E56E04e5ceF582c928d64b185843124) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://xdcscan.com/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://xdcscan.com/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x4c1311a9d88BFb7023148aB04F7321C2E91c29bf`](https://xdcscan.com/address/0x4c1311a9d88BFb7023148aB04F7321C2E91c29bf) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### ZKsync Era ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xC07E338Ce1aEd183A8b3c55f980548f5E463b5c5`](https://explorer.zksync.io/address/0xC07E338Ce1aEd183A8b3c55f980548f5E463b5c5) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x60dD755dB423EB27983B7000b81e434249670608`](https://explorer.zksync.io/address/0x60dD755dB423EB27983B7000b81e434249670608) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0a1ac47260b95d334763473b868117ef7343aa0`](https://explorer.zksync.io/address/0xa0a1ac47260b95d334763473b868117ef7343aa0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feb172238638897b13b69c65feb508a0a96b35d`](https://explorer.zksync.io/address/0x1feb172238638897b13b69c65feb508a0a96b35d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc`](https://explorer.zksync.io/address/0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ## Testnets ### Arbitrum Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x5bd5a50100d0cbc93837a1d10c816614008554fe`](https://sepolia.arbiscan.io/address/0x5bd5a50100d0cbc93837a1d10c816614008554fe) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x1e963e9214679757ecbf2fa98499f1e61c44697c`](https://sepolia.arbiscan.io/address/0x1e963e9214679757ecbf2fa98499f1e61c44697c) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0A1aC47260B95D334763473B868117EF7343aA0`](https://sepolia.arbiscan.io/address/0xa0A1aC47260B95D334763473B868117EF7343aA0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feB172238638897B13b69C65feB508a0a96b35D`](https://sepolia.arbiscan.io/address/0x1feB172238638897B13b69C65feB508a0a96b35D) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8`](https://sepolia.arbiscan.io/address/0x8224eb5D7d76B2D7Df43b868D875E79B11500eA8) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Base Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x5C51EA827Bfa65f7c9AF699e19Ec9fB12A2D40E2`](https://sepolia.basescan.org/address/0x5C51EA827Bfa65f7c9AF699e19Ec9fB12A2D40E2) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0xB88458246a0657a54cc7b32C3f3a19969cdD571A`](https://sepolia.basescan.org/address/0xB88458246a0657a54cc7b32C3f3a19969cdD571A) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0A1aC47260B95D334763473B868117EF7343aA0`](https://sepolia.basescan.org/address/0xa0A1aC47260B95D334763473B868117EF7343aA0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feB172238638897B13b69C65feB508a0a96b35D`](https://sepolia.basescan.org/address/0x1feB172238638897B13b69C65feB508a0a96b35D) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xCA2593027BA24856c292Fdcb5F987E0c25e755a4`](https://sepolia.basescan.org/address/0xCA2593027BA24856c292Fdcb5F987E0c25e755a4) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### OP Sepolia ❌ Not supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0xc9a70D7190bE5dafDce4B09c4e78bB7C4A71255D`](https://optimism-sepolia.blockscout.com/address/0xc9a70D7190bE5dafDce4B09c4e78bB7C4A71255D) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x279593Bb164c79Ae8220A48F616434203B171E6d`](https://optimism-sepolia.blockscout.com/address/0x279593Bb164c79Ae8220A48F616434203B171E6d) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0A1aC47260B95D334763473B868117EF7343aA0`](https://optimism-sepolia.blockscout.com/address/0xa0A1aC47260B95D334763473B868117EF7343aA0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feB172238638897B13b69C65feB508a0a96b35D`](https://optimism-sepolia.blockscout.com/address/0x1feB172238638897B13b69C65feB508a0a96b35D) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0xDf6163ddD3Ebcb552Cc1379a9c65AFe68683534e`](https://optimism-sepolia.blockscout.com/address/0xDf6163ddD3Ebcb552Cc1379a9c65AFe68683534e) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | ### Sepolia ✅ Supported in Sablier UI | Contract | Address | Deployment | | --- | --- | --- | | SablierLockup | [`0x6b0307b4338f2963A62106028E3B074C2c0510DA`](https://sepolia.etherscan.io/address/0x6b0307b4338f2963A62106028E3B074C2c0510DA) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | SablierBatchLockup | [`0x44Fd5d5854833975E5Fc80666a10cF3376C088E0`](https://sepolia.etherscan.io/address/0x44Fd5d5854833975E5Fc80666a10cF3376C088E0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | Helpers | [`0xa0A1aC47260B95D334763473B868117EF7343aA0`](https://sepolia.etherscan.io/address/0xa0A1aC47260B95D334763473B868117EF7343aA0) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupMath | [`0x1feB172238638897B13b69C65feB508a0a96b35D`](https://sepolia.etherscan.io/address/0x1feB172238638897B13b69C65feB508a0a96b35D) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | | LockupNFTDescriptor | [`0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc`](https://sepolia.etherscan.io/address/0x955dC7A2170782344FA9Ac11De0C0C42C05De2Fc) | [`lockup-v3.0`](https://github.com/sablier-labs/sdk/blob/main/deployments/lockup/v3.0) | --- ## Snapshot Voting Strategies Source: https://docs.sablier.com/guides/snapshot-voting # Snapshot Voting Strategies To enable off-chain governance, we created a collection of Snapshot Strategies that calculate the voting power of tokens stored in Lockup streams. ## Lockup If you started using Sablier in July 2023 or later, you should be using the Lockup strategies. - [Snapshot playground](https://snapshot.org/#/playground/sablier-v2) - test the strategies - [Snapshot code repository](https://github.com/snapshot-labs/score-api/tree/master/src/strategies/strategies/sablier-v2) - see the implementation The following strategies will read the various amounts that can be found in Lockup streams. The voting power will be calculated based on some sub-strategies called `policies`. | Snapshot Playground | | --- | | ![Snapshot Playground](/assets/images/playground-9d15bbb5282d50c752107ca8fe4deedd.webp) | ### Example Setup ```json { "address": "0x97cb342cf2f6ecf48c1285fb8668f5a4237bf862", "symbol": "DAI", "decimals": 18, "policy": "withdrawable-recipient" // recommended, } ``` #### Other parameters Aside from this example setup, we use Snapshot's base parameters `Network`, `Snapshot` (block), and `Addresses`. Based on the chosen strategy, the values filled in the `Addresses` field will represent a list (>= 1) of senders or recipients. ### Policies #### Primary policies | Policy | Methodology | | --- | --- | | withdrawable-recipient | Tokens that are available for the stream's recipient to withdraw. | | reserved-recipient | Tokens available for withdraw aggregated with unstreamed tokens. | #### Secondary policies These policies are designed to address specific edge cases. We strongly recommend using the primary policies. | Policy | Methodology | | --- | --- | | deposited-recipient | Tokens that have been deposited in streams the recipient owned at snapshot time. | | deposited-sender | Tokens that have been deposited in streams started by the sender before the snapshot. | | streamed-recipient | Tokens that have been streamed to the recipient until the snapshot. | | unstreamed-recipient | Tokens that have not yet been streamed to the recipient at the time of snapshot. | ### Example ```text Lockup Stream #000001 --- Deposited: TKN 1000 for 30 days Withdrawn: TKN 450 before snapshot Snapshot: Day 15 (midway) with a streamed amount of TKN 500 +------------------------+----------+ | POLICY | POWER | +------------------------+----------+ | erc20-balance-of | TKN 450 | +------------------------+----------+ | withdrawable-recipient | TKN 50 | +------------------------+----------+ | reserved-recipient | TKN 550 | +------------------------+----------+ | deposited-recipient | TKN 1000 | +------------------------+----------+ | deposited-sender | TKN 1000 | +------------------------+----------+ | streamed-recipient | TKN 500 | +------------------------+----------+ | unstreamed-recipient | TKN 500 | +------------------------+----------+ ``` ### Recommendation For the best results, we recommend using the primary policies. 1. The best option is to combine the `withdrawable-recipient` policy with `erc20-balance-of`. Doing so will aggregate tokens streamed but not withdrawn yet, as well as tokens in the user's wallet. 2. The second best option is to combine `reserved-recipient` with `erc20-balance-of`. They will aggregate (i) tokens streamed but not withdrawn yet, (ii) unstreamed funds (which will become available in the future), and (iii) the tokens in the user's wallet. ### Details and Caveats #### `withdrawable-recipient` ⭐️ The withdrawable amount counts tokens that have been streamed but not withdrawn yet by the recipient. This is provided using the [`withdrawableAmountOf`](/reference/lockup/contracts/contract.SablierLockup#withdrawableamountof) contract method. Voting power: realized (present). #### `reserved-recipient` ⭐️ The reserved amount combines tokens that have been streamed but not withdrawn yet (similar to `withdrawable-recipient`) with tokens that haven't been streamed (which will become available in the future). Can be computed as `reserved = withdrawable + unstreamed === deposited - withdrawn`. Canceled streams will only count the final withdrawable amount, if any. Voting power: realized (present) + expected (future). --- #### `deposited-recipient` Aggregates historical deposits up to the snapshot time, counting only the streams owned by the recipient. :::caution Caveat - Streaming, canceling and streaming again will cause tokens to be counted multiple times. ::: #### `deposited-sender` Aggregates historical deposits up to the snapshot time, counting only the streams started by the sender. :::caution Caveats - Streaming, canceling and streaming again will cause tokens to be counted multiple times. - Takes into account streams created through [PRBProxy](/reference/lockup/contracts/contract.SablierBatchLockup) instances configured through the official [Sablier Interface](https://app.sablier.com/). ::: #### `streamed-recipient` Aggregates historical amounts that have already been streamed to the recipient. Crucially, withdrawn tokens are included. It relies on the `streamedAmountOf` method in the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup#streamedamountof) contract. :::caution Caveats - Using this policy alongside `erc20-balance-of` may double count tokens. In the example above, `TKN 500` was streamed, but the recipient withdrew `TKN 450`, so the total voting power would be `TKN 950`. - If funds are recycled (streamed, withdrawn and streamed again) the voting power may be increased artificially. ::: #### `unstreamed-recipient` The opposite of `streamed-recipient`, counting amounts that have not been streamed yet (locked, but will become available in the future). Subtracts the `streamed` amount from the initial `deposit`. For canceled streams, the unstreamed amount will be `0`. ## Legacy If you started using Sablier before July 2023, you should be using the Legacy strategies. - [Snapshot playground](https://snapshot.org/#/playground/sablier-v1-deposit) - test the strategies - [Snapshot code repository](https://github.com/snapshot-labs/score-api/tree/master/src/strategies/strategies/sablier-v1-deposit) - dive into the implementation The Legacy strategy regards the stream recipient as the voter. It returns the voting power for any voter as the **sum of all deposits** made by a sender towards the recipient (the **voter**) for a specific ERC-20 token. :::caution Caveats - Similar to the Sablier Lockup [`streamed-recipient`](#streamed-recipient) strategy, the voting power can be increased artificially. ::: ### Example Setup ```json { "sender": "0xC9F2D9adfa6C24ce0D5a999F2BA3c6b06E36F75E", "token": "0x7f8F6E42C169B294A384F5667c303fd8Eedb3CF3" } ``` --- ## Access Control Source: https://docs.sablier.com/reference/airdrops/access-control # Access Control With the exception of the [Comptroller functions](/concepts/governance#merkle-factory), all functions in Merkle campaign can only be triggered either by the campaign creator or the airdrop recipients. The Comptroller has no control over any funds in the campaign contract. This article will provide a comprehensive overview of the actions that can be performed on a campaign contract. :::note Every campaign has a creator and a recipient. A "public" caller is any address outside of creator and recipient. Anyone can call `claim` function on a campaign but the tokens will be transferred to the recipient. ::: ## Overview The table below offers a quick overview of the access control for each action that can be performed on a campaign. | Action | Creator | Recipient | Public | Compatibility | | --- | :---: | :---: | :---: | --- | | Claim | ✅ | ✅ | ✅ | Airstreams, Instant | | ClaimTo | ❌ | ✅ | ❌ | Airstreams, Instant, Variable Claim | | ClaimViaSig | ✅ | ✅ | ✅ | Airstreams, Instant, Variable Claim | | Clawback | ✅ | ❌ | ❌ | Airstreams, Instant, Variable Claim | ## Claim Claiming an airdrop using `claim` function requires four values: 1. Address of the eligible user 2. Amount that the user is eligible for 3. Claim index in the bitmap 4. Merkle proof Anybody can `claim` function with the correct set of values. The `claim` then automatically transfers amount of tokens to the eligible user. If the campaign requires token vesting, then the `claim` function will create a Sablier stream on behalf of the eligible user. :::important `claim` function is unavailable in a Variable Claim Airdrop Campaign. You can use either [`claimTo`](#claimto) or [`claimViaSig`](#claimviasig) to claim from it. ::: ## ClaimTo Claiming an airdrop using `claimTo` function requires four values: 1. Address to which the tokens should be sent on behalf of the eligible user 2. Amount that the user is eligible for 3. Claim index in the bitmap 4. Merkle proof Only eligible users can call `claimTo` function with the correct set of values. The `claimTo` then automatically transfers amount of tokens to the provided address (which could be different from the user address). If the campaign requires token vesting, then the `claimTo` function will create a Sablier stream on behalf of the eligible user. ## ClaimViaSig Claiming an airdrop using `claimViaSig` function requires seven values: 1. Address to which the tokens should be sent on behalf of the eligible user 2. Amount that the user is eligible for 3. Claim index in the bitmap 4. Merkle proof 5. Address of the eligible user 6. EIP-712 or EIP-1271 signature from the eligible user 7. A UNIX timestamp from which above signature should be valid Anybody can call `claimViaSig` function as long as the EIP-721 (or EIP-1271) signature is valid and signed by the eligible user. The `claimViaSig` then automatically transfers amount of tokens to the provided address (which could be different from the user address). If the campaign requires token vesting, then the `claimViaSig` function will create a Sablier stream on behalf of the eligible user. ## Clawback Only the campaign creator can clawback funds within grace period. ```mermaid sequenceDiagram actor Anyone Anyone ->> Campaign: claim(..) Create actor Recipient Campaign -->> Recipient: Transfer tokens/Create Stream ``` ```mermaid sequenceDiagram actor Recipient Recipient ->> Campaign: claimTo(..) Create actor Provided Address Campaign -->> Provided Address: Transfer tokens/Create Stream ``` ```mermaid sequenceDiagram actor Anybody Anybody ->> Campaign: claimViaSig(..) Create actor Provided Address Campaign -->> Provided Address: Transfer tokens/Create Stream ``` ```mermaid sequenceDiagram actor Campaign Creator Campaign Creator ->> Campaign: clawback() Campaign -->> Campaign Creator: Transfer unclaimed tokens ``` --- ## Adminable Source: https://docs.sablier.com/reference/airdrops/contracts/abstracts/abstract.Adminable # Adminable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/Adminable.sol) **Inherits:** [IAdminable](/reference/airdrops/contracts/interfaces/interface.IAdminable) **Title:** Adminable See the documentation in [IAdminable](/reference/airdrops/contracts/interfaces/interface.IAdminable). ## State Variables ### admin The address of the admin account or contract. ```solidity address public override admin ``` ## Functions ### onlyAdmin Reverts if called by any account other than the admin. ```solidity modifier onlyAdmin() ; ``` ### constructor Emits a {TransferAdmin} event. ```solidity constructor(address initialAdmin) ; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialAdmin` | `address` | The address of the initial admin. | ### transferAdmin Transfers the contract admin to a new address. Notes: - Does not revert if the admin is the same. - This function can potentially leave the contract without an admin, thereby removing any functionality that is only available to the admin. Requirements: - `msg.sender` must be the contract admin. ```solidity function transferAdmin(address newAdmin) public virtual override onlyAdmin; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newAdmin` | `address` | The address of the new admin. | ### \_transferAdmin An internal function to transfer the admin. ```solidity function _transferAdmin(address oldAdmin, address newAdmin) internal; ``` ### \_onlyAdmin A private function is used instead of inlining this logic in a modifier because Solidity copies modifiers into every function that uses them. ```solidity function _onlyAdmin() private view; ``` --- ## Comptrollerable Source: https://docs.sablier.com/reference/airdrops/contracts/abstracts/abstract.Comptrollerable # Comptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/Comptrollerable.sol) **Inherits:** [IComptrollerable](/reference/airdrops/contracts/interfaces/interface.IComptrollerable) **Title:** Comptrollerable See the documentation in [IComptrollerable](/reference/airdrops/contracts/interfaces/interface.IComptrollerable). ## State Variables ### comptroller Retrieves the address of the comptroller contract. ```solidity ISablierComptroller public override comptroller ``` ## Functions ### onlyComptroller Reverts if called by any account other than the comptroller. ```solidity modifier onlyComptroller() ; ``` ### constructor ```solidity constructor(address initialComptroller) ; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### setComptroller Sets the comptroller to a new address. Emits a {SetComptroller} event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a {TransferFeesToComptroller} event. ```solidity function transferFeesToComptroller() external override; ``` ### \_checkComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _checkComptroller() private view; ``` ### \_setComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _setComptroller( ISablierComptroller previousComptroller, ISablierComptroller newComptroller, bytes4 minimalInterfaceId ) private; ``` --- ## SablierFactoryMerkleBase Source: https://docs.sablier.com/reference/airdrops/contracts/abstracts/abstract.SablierFactoryMerkleBase # SablierFactoryMerkleBase [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierFactoryMerkleBase.sol) **Inherits:** [Comptrollerable](/reference/airdrops/contracts/abstracts/abstract.Comptrollerable), [ISablierFactoryMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase) **Title:** SablierFactoryMerkleBase See the documentation in [ISablierFactoryMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase). ## State Variables ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity address public override nativeToken ``` ## Functions ### constructor ```solidity constructor(address initialComptroller) [Comptrollerable](/docs/reference/03-airdrops/contracts/abstracts/abstract.Comptrollerable.md)(initialComptroller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for {nativeToken}. Emits a {SetNativeToken} event. Requirements: - `msg.sender` must be the comptroller. - `newNativeToken` must not be zero address. - The native token must not be already set. ```solidity function setNativeToken(address newNativeToken) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ### \_forbidNativeToken Checks that the provided token is not the native token. Reverts if the provided token is the native token. ```solidity function _forbidNativeToken(address token) internal view; ``` --- ## SablierMerkleBase Source: https://docs.sablier.com/reference/airdrops/contracts/abstracts/abstract.SablierMerkleBase # SablierMerkleBase [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierMerkleBase.sol) **Inherits:** [ISablierMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase), [Adminable](/reference/airdrops/contracts/abstracts/abstract.Adminable) **Title:** SablierMerkleBase See the documentation in [ISablierMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase). ## Constants ### CAMPAIGN\_START\_TIME The timestamp at which campaign starts and claim begins. This is an immutable state variable. ```solidity uint40 public immutable override CAMPAIGN_START_TIME ``` ### CLAIM\_TYPE Retrieves the claim type supported by the campaign. This is an immutable state variable. ```solidity ClaimType public immutable override CLAIM_TYPE ``` ### COMPTROLLER Retrieves the address of the comptroller contract. ```solidity address public immutable override COMPTROLLER ``` ### EXPIRATION The cut-off point for the campaign, as a Unix timestamp. A value of zero means there is no expiration. This is an immutable state variable. ```solidity uint40 public immutable override EXPIRATION ``` ### IS\_SABLIER\_MERKLE Returns `true` indicating that this campaign contract is deployed using the Sablier Factory. This is a constant state variable. ```solidity bool public constant override IS_SABLIER_MERKLE = true ``` ### MERKLE\_ROOT The root of the Merkle tree used to validate the proofs of inclusion. This is an immutable state variable. ```solidity bytes32 public immutable override MERKLE_ROOT ``` ### TOKEN The ERC-20 token to distribute. This is an immutable state variable. ```solidity IERC20 public immutable override TOKEN ``` ## State Variables ### campaignName Retrieves the name of the campaign. ```solidity string public override campaignName ``` ### firstClaimTime Retrieves the timestamp when the first claim is made, and zero if no claim was made yet. ```solidity uint40 public override firstClaimTime ``` ### ipfsCID The content identifier for indexing the campaign on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. ```solidity string public override ipfsCID ``` ### minFeeUSD Retrieves the min USD fee required to claim the airdrop, denominated in 8 decimals. The denomination is based on Chainlink's 8-decimal format for USD prices, where 1e8 is $1. ```solidity uint256 public override minFeeUSD ``` ### \_claimedBitMap Packed booleans that record the history of claims. ```solidity BitMaps.BitMap internal _claimedBitMap ``` ## Functions ### notZeroAddress Modifier to check that `to` is not zero address. ```solidity modifier notZeroAddress(address to) ; ``` ### revertIfNot Modifier to revert if `claimType` value does not match the campaign's claim type. ```solidity modifier revertIfNot(ClaimType claimType) ; ``` ### constructor Constructs the contract by initializing the immutable state variables. ```solidity constructor(MerkleBase.ConstructorParams memory baseParams) [Adminable](/docs/reference/03-airdrops/contracts/abstracts/abstract.Adminable.md)(baseParams.initialAdmin); ``` ### calculateMinFeeWei Calculates the minimum fee in wei required to claim the airdrop. ```solidity function calculateMinFeeWei() external view override returns (uint256); ``` ### hasClaimed Returns a flag indicating whether a claim has been made for a given index. Uses a bitmap to save gas. ```solidity function hasClaimed(uint256 index) public view override returns (bool); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient to check. | ### hasExpired Returns a flag indicating whether the campaign has expired. ```solidity function hasExpired() public view override returns (bool); ``` ### clawback Claws back the unclaimed tokens. Emits a {Clawback} event. Requirements: - `msg.sender` must be the admin. - No claim must be made, OR The current timestamp must not exceed 7 days after the first claim, OR The campaign must be expired. ```solidity function clawback(address to, uint128 amount) external override onlyAdmin; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `to` | `address` | The address to receive the tokens. | | `amount` | `uint128` | The amount of tokens to claw back. | ### lowerMinFeeUSD Lowers the min USD fee. Emits a {LowerMinFeeUSD} event. Requirements: - `msg.sender` must be the comptroller. - The new fee must be less than the current {minFeeUSD}. ```solidity function lowerMinFeeUSD(uint256 newMinFeeUSD) external override; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newMinFeeUSD` | `uint256` | The new min USD fee to set, denominated in 8 decimals. | ### sponsor Sponsors the claim fees for eligible recipients. Emits a {Sponsor} event. Notes: - This function only makes the payment. The claim fees are updated only after the payment has been verified off-chain. - Refer to the Sablier website in order to sponsor with the correct token, otherwise the sponsorship may be ignored. Requirements: - `biller` must not be the zero address. - `amount` must be greater than zero. - `token` must not be the zero address and must be a valid ERC20 token. - `msg.sender` must have approved the contract to spend the tokens. ```solidity function sponsor(IERC20 token, uint128 amount, address biller) external override notZeroAddress(biller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The ERC-20 token to transfer. | | `amount` | `uint128` | The amount of tokens to transfer. | | `biller` | `address` | The address to receive the tokens. | ### \_hasGracePeriodPassed Returns a flag indicating whether the grace period has passed. The grace period is 7 days after the first claim. ```solidity function _hasGracePeriodPassed() private view returns (bool); ``` ### \_preProcessClaim See the documentation for the user-facing functions that call this internal function. ```solidity function _preProcessClaim( uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof ) internal; ``` --- ## SablierMerkleLockup Source: https://docs.sablier.com/reference/airdrops/contracts/abstracts/abstract.SablierMerkleLockup # SablierMerkleLockup [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierMerkleLockup.sol) **Inherits:** [ISablierMerkleLockup](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLockup), [SablierMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleBase) **Title:** SablierMerkleLockup See the documentation in [ISablierMerkleLockup](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLockup). ## Constants ### SABLIER\_LOCKUP The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. ```solidity ISablierLockup public immutable override SABLIER_LOCKUP ``` ### STREAM\_CANCELABLE A flag indicating whether the streams can be canceled. This is an immutable state variable. ```solidity bool public immutable override STREAM_CANCELABLE ``` ### STREAM\_TRANSFERABLE A flag indicating whether the stream NFTs are transferable. This is an immutable state variable. ```solidity bool public immutable override STREAM_TRANSFERABLE ``` ## State Variables ### streamShape Retrieves the shape of the Lockup stream created upon claiming. ```solidity string public override streamShape ``` ### \_claimedStreams A mapping between recipient addresses and Lockup streams created through the claim function. ```solidity mapping(address recipient => uint256[] streamIds) internal _claimedStreams ``` ## Functions ### constructor Constructs the contract by initializing the immutable state vars, and max approving the Lockup contract. ```solidity constructor(MerkleLockup.ConstructorParams memory lockupParams) ; ``` ### claimedStreams Retrieves the stream IDs associated with the airdrops claimed by the provided recipient. In practice, most campaigns will only have one stream per recipient. ```solidity function claimedStreams(address recipient) external view override returns (uint256[] memory); ``` --- ## SablierMerkleSignature Source: https://docs.sablier.com/reference/airdrops/contracts/abstracts/abstract.SablierMerkleSignature # SablierMerkleSignature [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierMerkleSignature.sol) **Inherits:** [ISablierMerkleSignature](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature), [SablierMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleBase) **Title:** SablierMerkleSignature See the documentation in [ISablierMerkleSignature](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature). ## Constants ### \_CACHED\_CHAIN\_ID Cache the chain ID in order to invalidate the cached domain separator if the chain ID changes in case of a chain split. ```solidity uint256 private immutable _CACHED_CHAIN_ID ``` ### \_CACHED\_DOMAIN\_SEPARATOR The domain separator, as required by EIP-712 and EIP-1271, used for signing claim to prevent replay attacks across different campaigns. ```solidity bytes32 private immutable _CACHED_DOMAIN_SEPARATOR ``` ## State Variables ### \_attestor A private variable to store the attestor address if set after the contract is deployed. If zero, the attestor is queried from the comptroller. ```solidity address private _attestor ``` ## Functions ### constructor Constructs the contract by initializing the immutable state variables. ```solidity constructor() ; ``` ### attestor Retrieves the attestor address used for creating attestation signatures. ```solidity function attestor() external view override returns (address); ``` ### domainSeparator The domain separator, as required by EIP-712 and EIP-1271, used for signing claims to prevent replay attacks across different campaigns. ```solidity function domainSeparator() external view override returns (bytes32); ``` ### setAttestor Sets the attestor address used for verifying attestation signatures. Emits a {SetAttestor} event. Requirements: - `msg.sender` must be either the comptroller or the campaign admin. ```solidity function setAttestor(address newAttestor) external override; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newAttestor` | `address` | The new attestor address. If zero, the attestor from the comptroller will be used. | ### \_verifyAttestationSignature Verifies that the attestation signature created for the recipient is signed by the attestor. It supports both EIP-712 and EIP-1271 signatures. ```solidity function _verifyAttestationSignature(address recipient, uint40 expireAt, bytes calldata signature) internal view; ``` ### \_verifyClaimSignature Verifies that the claim signature is signed by the recipient. It supports both EIP-712 and EIP-1271 signatures. ```solidity function _verifyClaimSignature( uint256 index, address recipient, address to, uint128 amount, uint40 validFrom, bytes calldata signature ) internal view; ``` ### \_domainSeparator Returns the domain separator for the current chain. ```solidity function _domainSeparator() private view returns (bytes32); ``` ### \_getAttestor Returns the attestor address. ```solidity function _getAttestor() private view returns (address); ``` ### \_verifySignature Verifies that the EIP-712 or EIP-1271 signature is signed by the expected signer. ```solidity function _verifySignature(address signer, bytes32 structHash, bytes calldata signature) private view; ``` --- ## SablierFactoryMerkleExecute Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierFactoryMerkleExecute # SablierFactoryMerkleExecute [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierFactoryMerkleExecute.sol) **Inherits:** [ISablierFactoryMerkleExecute](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleExecute), [SablierFactoryMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierFactoryMerkleBase) **Title:** SablierFactoryMerkleExecute See the documentation in [ISablierFactoryMerkleExecute](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleExecute). ## Functions ### constructor ```solidity constructor(address initialComptroller) SablierFactoryMerkleBase(initialComptroller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### computeMerkleExecute Computes the deterministic address where [SablierMerkleExecute](/reference/airdrops/contracts/contract.SablierMerkleExecute) campaign will be deployed. Reverts if the requirements from {createMerkleExecute} are not met. ```solidity function computeMerkleExecute( address campaignCreator, MerkleExecute.ConstructorParams calldata campaignParams ) external view override returns (address merkleExecute); ``` ### createMerkleExecute Creates a new MerkleExecute campaign for claim-and-execute distribution of tokens. Emits a {CreateMerkleExecute} event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. - The create function does not validate if the `campaignParams.selector` is a function implemented by the target contract. In that case, the `claimAndExecute` function will revert. - If the target contract does not implement the `campaignParams.selector` but has `fallback`, the `claimAndExecute` call may silently succeed. If fallback does not transfer claim tokens, the claim tokens will be left in the campaign contract. These tokens can be clawbacked by the campaign creator. Requirements: - `campaignParams.token` must not be the forbidden native token. - `campaignParams.target` must be a contract. ```solidity function createMerkleExecute( MerkleExecute.ConstructorParams calldata campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external override returns (ISablierMerkleExecute merkleExecute); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleExecute.ConstructorParams` | Struct encapsulating the [SablierMerkleExecute](/reference/airdrops/contracts/contract.SablierMerkleExecute) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleExecute` | `ISablierMerkleExecute` | The address of the newly created MerkleExecute campaign. | ### \_checkDeploymentParams Validates the deployment parameters. ```solidity function _checkDeploymentParams(address token, address target) private view; ``` --- ## SablierFactoryMerkleInstant Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierFactoryMerkleInstant # SablierFactoryMerkleInstant [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierFactoryMerkleInstant.sol) **Inherits:** [ISablierFactoryMerkleInstant](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleInstant), [SablierFactoryMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierFactoryMerkleBase) **Title:** SablierFactoryMerkleInstant See the documentation in [ISablierFactoryMerkleInstant](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleInstant). ## Functions ### constructor ```solidity constructor(address initialComptroller) SablierFactoryMerkleBase(initialComptroller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### computeMerkleInstant Computes the deterministic address where [SablierMerkleInstant](/reference/airdrops/contracts/contract.SablierMerkleInstant) campaign will be deployed. Reverts if the requirements from {createMerkleInstant} are not met. ```solidity function computeMerkleInstant( address campaignCreator, MerkleInstant.ConstructorParams calldata campaignParams ) external view override returns (address merkleInstant); ``` ### createMerkleInstant Creates a new MerkleInstant campaign for instant distribution of tokens. Emits a {CreateMerkleInstant} event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. Requirements: - `campaignParams.token` must not be the forbidden native token. ```solidity function createMerkleInstant( MerkleInstant.ConstructorParams calldata campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external override returns (ISablierMerkleInstant merkleInstant); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleInstant.ConstructorParams` | Struct encapsulating the [SablierMerkleInstant](/reference/airdrops/contracts/contract.SablierMerkleInstant) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleInstant` | `ISablierMerkleInstant` | The address of the newly created MerkleInstant contract. | --- ## SablierFactoryMerkleLL Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierFactoryMerkleLL # SablierFactoryMerkleLL [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierFactoryMerkleLL.sol) **Inherits:** [ISablierFactoryMerkleLL](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLL), [SablierFactoryMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierFactoryMerkleBase) **Title:** SablierFactoryMerkleLL See the documentation in [ISablierFactoryMerkleLL](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLL). ## Functions ### constructor ```solidity constructor(address initialComptroller) SablierFactoryMerkleBase(initialComptroller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### computeMerkleLL Computes the deterministic address where [SablierMerkleLL](/reference/airdrops/contracts/contract.SablierMerkleLL) campaign will be deployed. Reverts if the requirements from {createMerkleLL} are not met. ```solidity function computeMerkleLL( address campaignCreator, MerkleLL.ConstructorParams calldata campaignParams ) external view override returns (address merkleLL); ``` ### createMerkleLL Creates a new Merkle Lockup campaign with a Lockup Linear distribution. Emits a {CreateMerkleLL} event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. - A value of zero for `campaignParams.granularity` would store the granularity as 1 second. Requirements: - `campaignParams.token` must not be the forbidden native token. ```solidity function createMerkleLL( MerkleLL.ConstructorParams memory campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external override returns (ISablierMerkleLL merkleLL); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleLL.ConstructorParams` | Struct encapsulating the [SablierMerkleLL](/reference/airdrops/contracts/contract.SablierMerkleLL) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleLL` | `ISablierMerkleLL` | The address of the newly created Merkle Lockup contract. | --- ## SablierFactoryMerkleLT Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierFactoryMerkleLT # SablierFactoryMerkleLT [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierFactoryMerkleLT.sol) **Inherits:** [ISablierFactoryMerkleLT](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLT), [SablierFactoryMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierFactoryMerkleBase) **Title:** SablierFactoryMerkleLT See the documentation in [ISablierFactoryMerkleLT](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLT). ## Functions ### constructor ```solidity constructor(address initialComptroller) SablierFactoryMerkleBase(initialComptroller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### computeMerkleLT Computes the deterministic address where [SablierMerkleLT](/reference/airdrops/contracts/contract.SablierMerkleLT) campaign will be deployed. Reverts if the requirements from {createMerkleLT} are not met. ```solidity function computeMerkleLT( address campaignCreator, MerkleLT.ConstructorParams calldata campaignParams ) external view override returns (address merkleLT); ``` ### isPercentagesSum100 Verifies if the sum of percentages in `tranches` equals 100%, i.e., 1e18. This is a helper function for the frontend. It is not used anywhere in the contracts. ```solidity function isPercentagesSum100(MerkleLT.TrancheWithPercentage[] calldata tranches) external pure override returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `tranches` | `MerkleLT.TrancheWithPercentage[]` | The tranches with their respective unlock percentages. | **Returns** | Name | Type | Description | | --- | --- | --- | | `result` | `bool` | True if the sum of percentages equals 100%, otherwise false. | ### createMerkleLT Creates a new Merkle Lockup campaign with a Lockup Tranched distribution. Emits a {CreateMerkleLT} event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. Requirements: - `campaignParams.token` must not be the forbidden native token. - The sum of percentages of the tranches must equal 100%. ```solidity function createMerkleLT( MerkleLT.ConstructorParams calldata campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external override returns (ISablierMerkleLT merkleLT); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleLT.ConstructorParams` | Struct encapsulating the [SablierMerkleLT](/reference/airdrops/contracts/contract.SablierMerkleLT) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleLT` | `ISablierMerkleLT` | The address of the newly created Merkle Lockup contract. | ### \_calculateTrancheTotals Calculate the total duration and total percentage of the tranches. ```solidity function _calculateTrancheTotals(MerkleLT.TrancheWithPercentage[] memory tranches) private pure returns (uint256 totalDuration, uint64 totalPercentage); ``` ### \_checkDeploymentParams Validate the token and the total percentage. ```solidity function _checkDeploymentParams(address token, uint64 totalPercentage) private view; ``` --- ## SablierFactoryMerkleVCA Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierFactoryMerkleVCA # SablierFactoryMerkleVCA [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierFactoryMerkleVCA.sol) **Inherits:** [ISablierFactoryMerkleVCA](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleVCA), [SablierFactoryMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierFactoryMerkleBase) **Title:** SablierFactoryMerkleVCA See the documentation in [ISablierFactoryMerkleVCA](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleVCA). ## Functions ### constructor ```solidity constructor(address initialComptroller) SablierFactoryMerkleBase(initialComptroller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### computeMerkleVCA Computes the deterministic address where [SablierMerkleVCA](/reference/airdrops/contracts/contract.SablierMerkleVCA) campaign will be deployed. Reverts if the requirements from {createMerkleVCA} are not met. ```solidity function computeMerkleVCA( address campaignCreator, MerkleVCA.ConstructorParams calldata campaignParams ) external view override returns (address merkleVCA); ``` ### createMerkleVCA Creates a new MerkleVCA campaign for variable distribution of tokens. Emits a {CreateMerkleVCA} event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - Users interested into funding the campaign before its deployment must meet the below requirements, otherwise the campaign deployment will revert. Requirements: - `campaignParams.token` must not be the forbidden native token. - `campaignParams.aggregateAmount` must be greater than 0. - Both `campaignParams.vestingStartTime` and `campaignParams.vestingEndTime` must be greater than 0. - `campaignParams.vestingEndTime` must be greater than `campaignParams.vestingStartTime`. - `campaignParams.expiration` must be greater than 0. - `campaignParams.expiration` must be at least 1 week beyond the end time to ensure loyal recipients have enough time to claim. - `campaignParams.unlockPercentage` must not be greater than 1e18, equivalent to 100%. ```solidity function createMerkleVCA( MerkleVCA.ConstructorParams calldata campaignParams, uint256 recipientCount ) external returns (ISablierMerkleVCA merkleVCA); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleVCA.ConstructorParams` | Struct encapsulating the [SablierMerkleVCA](/reference/airdrops/contracts/contract.SablierMerkleVCA) parameters. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleVCA` | `ISablierMerkleVCA` | The address of the newly created MerkleVCA campaign. | ### \_checkDeploymentParams Validate the deployment parameters. ```solidity function _checkDeploymentParams( uint256 aggregateAmount, uint40 expiration, address token, UD60x18 unlockPercentage, uint40 vestingEndTime, uint40 vestingStartTime ) private view; ``` --- ## SablierMerkleExecute Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierMerkleExecute # SablierMerkleExecute [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierMerkleExecute.sol) **Inherits:** [ISablierMerkleExecute](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleExecute), ReentrancyGuard, [SablierMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleBase) **Title:** SablierMerkleExecute See the documentation in [ISablierMerkleExecute](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleExecute). ## Constants ### SELECTOR The function selector to call on the target contract. This is an immutable state variable. ```solidity bytes4 public immutable override SELECTOR ``` ### TARGET The address of the target contract to call with the claim amount. This is an immutable state variable. ```solidity address public immutable override TARGET ``` ## Functions ### constructor Constructs the contract by initializing the immutable state variables. ```solidity constructor( MerkleExecute.ConstructorParams memory campaignParams, address campaignCreator, address comptroller ) SablierMerkleBase(MerkleBase.ConstructorParams({ campaignCreator: campaignCreator, campaignName: campaignParams.campaignName, campaignStartTime: campaignParams.campaignStartTime, claimType: ClaimType.EXECUTE, comptroller: comptroller, expiration: campaignParams.expiration, initialAdmin: campaignParams.initialAdmin, ipfsCID: campaignParams.ipfsCID, merkleRoot: campaignParams.merkleRoot, token: campaignParams.token })); ``` ### claimAndExecute Claim airdrop and execute the call to the target contract. It emits a {ClaimExecute} event. Notes: - The function approves the exact claim amount to the {TARGET}, executes the call, then revokes the approval. - The function does not forward native tokens to the target contract, so calls to targets that require a native token payment will revert. Requirements: - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - `msg.sender` must be the airdrop recipient. - The external call to the target contract must succeed. - The target contract must transfer the entire claim amount from the campaign. ```solidity function claimAndExecute( uint256 index, uint128 amount, bytes32[] calldata merkleProof, bytes calldata selectorArguments ) external payable override nonReentrant; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of `msg.sender` in the Merkle tree. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `selectorArguments` | `bytes` | The function ABI-encoded arguments for {SELECTOR}. | --- ## SablierMerkleInstant Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierMerkleInstant # SablierMerkleInstant [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierMerkleInstant.sol) **Inherits:** [ISablierMerkleInstant](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant), [SablierMerkleSignature](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleSignature) **Title:** SablierMerkleInstant See the documentation in [ISablierMerkleInstant](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant). ## Functions ### constructor Constructs the contract by initializing the immutable state variables. ```solidity constructor( MerkleInstant.ConstructorParams memory campaignParams, address campaignCreator, address comptroller ) SablierMerkleBase(MerkleBase.ConstructorParams({ campaignCreator: campaignCreator, campaignName: campaignParams.campaignName, campaignStartTime: campaignParams.campaignStartTime, claimType: campaignParams.claimType, comptroller: comptroller, expiration: campaignParams.expiration, initialAdmin: campaignParams.initialAdmin, ipfsCID: campaignParams.ipfsCID, merkleRoot: campaignParams.merkleRoot, token: campaignParams.token })); ``` ### claim Claim airdrop on behalf of eligible recipient and transfer it to the recipient address. It emits a {ClaimInstant} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. ```solidity function claim( uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof ) external payable override revertIfNot(ClaimType.DEFAULT); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimTo Claim airdrop and transfer the tokens to the `to` address. It emits a {ClaimInstant} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. - Refer to the requirements in {claim}. ```solidity function claimTo( uint256 index, address to, uint128 amount, bytes32[] calldata merkleProof ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). It emits a {ClaimInstant} event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the {claimViaSig} function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in {claim}. ```solidity function claimViaAttestation( uint256 index, address to, uint128 amount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable override revertIfNot(ClaimType.ATTEST) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature, and transfer the tokens to the `to` address. It emits a {ClaimInstant} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in {claim}. Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 amount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of the recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ### \_postProcessClaim Post-processes the claim execution by handling the tokens transfer and emitting an event. ```solidity function _postProcessClaim(uint256 index, address recipient, address to, uint128 amount, bool viaSig) private; ``` --- ## SablierMerkleLL Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierMerkleLL # SablierMerkleLL [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierMerkleLL.sol) **Inherits:** [ISablierMerkleLL](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL), [SablierMerkleSignature](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleSignature), [SablierMerkleLockup](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleLockup) **Title:** SablierMerkleLL See the documentation in [ISablierMerkleLL](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL). ## Constants ### VESTING\_CLIFF\_DURATION Retrieves the cliff duration of the vesting stream, in seconds. ```solidity uint40 public immutable override VESTING_CLIFF_DURATION ``` ### VESTING\_CLIFF\_UNLOCK\_PERCENTAGE Retrieves the percentage of the claim amount due to be unlocked at the vesting cliff time, as a fixed-point number where 1e18 is 100%. ```solidity UD60x18 public immutable override VESTING_CLIFF_UNLOCK_PERCENTAGE ``` ### VESTING\_GRANULARITY Retrieves the smallest step in time between two consecutive token unlocks. Zero is a sentinel value for 1 second. ```solidity uint40 public immutable override VESTING_GRANULARITY ``` ### VESTING\_START\_TIME Retrieves the start time of the vesting stream, as a Unix timestamp. Zero is a sentinel value for `block.timestamp`. ```solidity uint40 public immutable override VESTING_START_TIME ``` ### VESTING\_START\_UNLOCK\_PERCENTAGE Retrieves the percentage of the claim amount due to be unlocked at the vesting start time, as a fixed-point number where 1e18 is 100%. ```solidity UD60x18 public immutable override VESTING_START_UNLOCK_PERCENTAGE ``` ### VESTING\_TOTAL\_DURATION Retrieves the total duration of the vesting stream, in seconds. ```solidity uint40 public immutable override VESTING_TOTAL_DURATION ``` ## Functions ### constructor Constructs the contract by initializing the immutable state variables, and max approving the Lockup contract. ```solidity constructor( MerkleLL.ConstructorParams memory campaignParams, address campaignCreator, address comptroller ) SablierMerkleBase(MerkleBase.ConstructorParams({ campaignCreator: campaignCreator, campaignName: campaignParams.campaignName, campaignStartTime: campaignParams.campaignStartTime, claimType: campaignParams.claimType, comptroller: comptroller, expiration: campaignParams.expiration, initialAdmin: campaignParams.initialAdmin, ipfsCID: campaignParams.ipfsCID, merkleRoot: campaignParams.merkleRoot, token: campaignParams.token })) SablierMerkleLockup(MerkleLockup.ConstructorParams({ cancelable: campaignParams.cancelable, lockup: campaignParams.lockup, shape: campaignParams.shape, transferable: campaignParams.transferable })); ``` ### claim Claim airdrop on behalf of eligible recipient. If the vesting end time is in the future, it creates a Lockup Linear stream, otherwise it transfers the tokens directly to the recipient address. It emits either {ClaimLLWithTransfer} or {ClaimLLWithVesting} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - All requirements from {ISablierLockupLinear.createWithTimestampsLL} must be met. ```solidity function claim( uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof ) external payable override revertIfNot(ClaimType.DEFAULT); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimTo Claim airdrop. If the vesting end time is in the future, it creates a Lockup Linear stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either {ClaimLLWithTransfer} or {ClaimLLWithVesting} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. - Refer to the requirements in {claim}. ```solidity function claimTo( uint256 index, address to, uint128 amount, bytes32[] calldata merkleProof ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). If the vesting end time is in the future, it creates a Lockup Linear stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either {ClaimLLWithTransfer} or {ClaimLLWithVesting} event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the {claimViaSig} function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in {claim}. ```solidity function claimViaAttestation( uint256 index, address to, uint128 amount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable override revertIfNot(ClaimType.ATTEST) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature. If the vesting end time is in the future, it creates a Lockup Linear stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either {ClaimLLWithTransfer} or {ClaimLLWithVesting} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in {claim}. Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 amount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of the recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ### \_postProcessClaim Post-processes the claim execution by creating the stream or transferring the tokens directly and emitting an event. ```solidity function _postProcessClaim(uint256 index, address recipient, address to, uint128 amount, bool viaSig) private; ``` --- ## SablierMerkleLT Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierMerkleLT # SablierMerkleLT [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierMerkleLT.sol) **Inherits:** [ISablierMerkleLT](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT), [SablierMerkleLockup](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleLockup), [SablierMerkleSignature](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleSignature) **Title:** SablierMerkleLT See the documentation in [ISablierMerkleLT](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT). ## Constants ### VESTING\_START\_TIME Retrieves the start time of the vesting stream, as a Unix timestamp. Zero is a sentinel value for `block.timestamp`. ```solidity uint40 public immutable override VESTING_START_TIME ``` ## State Variables ### \_tranchesWithPercentages The tranches with their respective unlock percentages and durations. ```solidity MerkleLT.TrancheWithPercentage[] private _tranchesWithPercentages ``` ## Functions ### constructor Constructs the contract by initializing the immutable state variables, and max approving the Lockup contract. ```solidity constructor( MerkleLT.ConstructorParams memory campaignParams, address campaignCreator, address comptroller ) SablierMerkleBase(MerkleBase.ConstructorParams({ campaignCreator: campaignCreator, campaignName: campaignParams.campaignName, campaignStartTime: campaignParams.campaignStartTime, claimType: campaignParams.claimType, comptroller: comptroller, expiration: campaignParams.expiration, initialAdmin: campaignParams.initialAdmin, ipfsCID: campaignParams.ipfsCID, merkleRoot: campaignParams.merkleRoot, token: campaignParams.token })) SablierMerkleLockup(MerkleLockup.ConstructorParams({ cancelable: campaignParams.cancelable, lockup: campaignParams.lockup, shape: campaignParams.shape, transferable: campaignParams.transferable })); ``` ### tranchesWithPercentages Retrieves the tranches with their respective unlock percentages and durations. ```solidity function tranchesWithPercentages() external view override returns (MerkleLT.TrancheWithPercentage[] memory); ``` ### claim Claim airdrop on behalf of eligible recipient. If the vesting end time is in the future, it creates a Lockup Tranched stream, otherwise it transfers the tokens directly to the recipient address. It emits either {ClaimLTWithTransfer} or {ClaimLTWithVesting} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - All requirements from {ISablierLockupTranched.createWithTimestampsLT} must be met. ```solidity function claim( uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof ) external payable override revertIfNot(ClaimType.DEFAULT); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimTo Claim airdrop. If the vesting end time is in the future, it creates a Lockup Tranched stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either {ClaimLTWithTransfer} or {ClaimLTWithVesting} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. - Refer to the requirements in {claim}. ```solidity function claimTo( uint256 index, address to, uint128 amount, bytes32[] calldata merkleProof ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). If the vesting end time is in the future, it creates a Lockup Tranched stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either {ClaimLTWithTransfer} or {ClaimLTWithVesting} event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the {claimViaSig} function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in {claim}. ```solidity function claimViaAttestation( uint256 index, address to, uint128 amount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable override revertIfNot(ClaimType.ATTEST) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature. If the vesting end time is in the future, it creates a Lockup Tranched stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either {ClaimLTWithTransfer} or {ClaimLTWithVesting} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in {claim}. Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 amount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of the recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ### \_calculateStartTimeAndTranches Calculates the vesting start time, and the tranches based on the claim amount and the unlock percentages for each tranche. ```solidity function _calculateStartTimeAndTranches(uint128 claimAmount) private view returns (uint40 vestingStartTime, LockupTranched.Tranche[] memory tranches); ``` ### \_postProcessClaim Post-processes the claim execution by creating the stream or transferring the tokens directly and emitting an event. ```solidity function _postProcessClaim(uint256 index, address recipient, address to, uint128 amount, bool viaSig) private; ``` --- ## SablierMerkleVCA Source: https://docs.sablier.com/reference/airdrops/contracts/contract.SablierMerkleVCA # SablierMerkleVCA [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierMerkleVCA.sol) **Inherits:** [ISablierMerkleVCA](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA), [SablierMerkleSignature](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleSignature) **Title:** SablierMerkleVCA See the documentation in [ISablierMerkleVCA](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA). ## Constants ### AGGREGATE\_AMOUNT Retrieves the total amount of ERC-20 tokens allocated to the campaign. ```solidity uint128 public immutable override AGGREGATE_AMOUNT ``` ### UNLOCK\_PERCENTAGE Retrieves the percentage of the full amount that will unlock immediately at the start time. The value is denominated as a fixed-point number where 1e18 is 100%. ```solidity UD60x18 public immutable override UNLOCK_PERCENTAGE ``` ### VESTING\_END\_TIME Retrieves the time when the VCA airdrop is fully vested, as a Unix timestamp. ```solidity uint40 public immutable override VESTING_END_TIME ``` ### VESTING\_START\_TIME Retrieves the time when the VCA airdrop begins to unlock, as a Unix timestamp. ```solidity uint40 public immutable override VESTING_START_TIME ``` ## State Variables ### isRedistributionEnabled Retrieves a bool indicating whether the redistribution of forgone tokens is enabled or not. ```solidity bool public override isRedistributionEnabled ``` ### totalForgoneAmount Retrieves the total amount of tokens forgone by early claimers. ```solidity uint128 public override totalForgoneAmount ``` ### totalRedistributionAmountPaid Retrieves the total amount of redistribution rewards paid out. ```solidity uint128 public override totalRedistributionAmountPaid ``` ### \_fullAmountAllocatedToEarlyClaimers Tracks the full amount allocated to the recipients who claimed before the vesting end time. ```solidity uint128 private _fullAmountAllocatedToEarlyClaimers ``` ## Functions ### constructor Constructs the contract by initializing the immutable state variables. ```solidity constructor( MerkleVCA.ConstructorParams memory campaignParams, address campaignCreator, address comptroller ) SablierMerkleBase(MerkleBase.ConstructorParams({ campaignCreator: campaignCreator, campaignName: campaignParams.campaignName, campaignStartTime: campaignParams.campaignStartTime, claimType: campaignParams.claimType, comptroller: comptroller, expiration: campaignParams.expiration, initialAdmin: campaignParams.initialAdmin, ipfsCID: campaignParams.ipfsCID, merkleRoot: campaignParams.merkleRoot, token: campaignParams.token })); ``` ### calculateClaimAmount Calculates the amount that would be claimed if the claim were made at `claimTime`. This is for informational purposes only. To actually claim the airdrop, a Merkle proof is required. ```solidity function calculateClaimAmount(uint128 fullAmount, uint40 claimTime) external view override returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `fullAmount` | `uint128` | The amount of tokens allocated to a user, denominated in the token's decimals. | | `claimTime` | `uint40` | A hypothetical time at which to make the claim. Zero is a sentinel value for `block.timestamp`. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The amount that would be claimed, denominated in the token's decimals. | ### calculateForgoneAmount Calculates the amount that would be forgone if the claim were made at `claimTime`. This is for informational purposes only. Reverts if the claim time is less than the vesting start time, since the claim cannot be made, no amount can be forgone. ```solidity function calculateForgoneAmount(uint128 fullAmount, uint40 claimTime) external view override returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `fullAmount` | `uint128` | The amount of tokens allocated to a user, denominated in the token's decimals. | | `claimTime` | `uint40` | A hypothetical time at which to make the claim. Zero is a sentinel value for `block.timestamp`. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The amount that would be forgone, denominated in the token's decimals. | ### calculateRedistributionRewards Calculates the redistribution rewards for a given full amount. Notes: - Reverts if redistribution is not enabled. - If `AGGREGATE_AMOUNT` is set lower than actual total allocations in the Merkle tree, this might return 0 rather than reverting. ```solidity function calculateRedistributionRewards(uint128 fullAmount) external view override returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `fullAmount` | `uint128` | The amount of tokens that the redistribution rewards are to be calculated for. | ### claimTo Claim airdrop. If the vesting end time is in the future, it calculates the claim amount to transfer to the `to` address, otherwise it transfers the full amount. If the redistribution is enabled, it calculates the reward amount based on the total amount of tokens forgone by early claimers and transfers it to the recipients claiming after the vesting end time. It emits a {ClaimVCA} event, and a {RedistributeReward} event if the redistribution is enabled. Notes: - There can be a race condition among recipients if: 1. `AGGREGATE_AMOUNT` is set lower than the actual total allocations in the Merkle tree. 2. The campaign is not sufficiently funded with the actual total allocations. - The rewards are transferred to the recipients at the time of claiming. If the campaign creator turns the redistribution on after the vesting end time, the recipients who have already claimed the full amount would miss on the rewards while subsequent recipients would get them. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - The claim amount must be greater than zero. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. ```solidity function claimTo( uint256 index, address to, uint128 fullAmount, bytes32[] calldata merkleProof ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `fullAmount` | `uint128` | The total amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). If the vesting end time is in the future, it calculates the claim amount to transfer to the `to` address, otherwise it transfers the full amount. It emits a {ClaimVCA} event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the {claimViaSig} function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in {claimTo}. ```solidity function claimViaAttestation( uint256 index, address to, uint128 fullAmount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable override revertIfNot(ClaimType.ATTEST) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `fullAmount` | `uint128` | The total amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature. If the vesting end time is in the future, it calculates the claim amount to transfer to the `to` address, otherwise it transfers the full amount. It emits a {ClaimVCA} event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in {claimTo} except that there are no restrictions on `msg.sender`. Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 fullAmount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable override revertIfNot(ClaimType.DEFAULT) notZeroAddress(to); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of the recipient. | | `fullAmount` | `uint128` | The total amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ### enableRedistribution Enable the redistribution of forgone tokens among recipients claiming after the vesting end time, proportional to their allocation amount. Once enabled, it cannot be disabled. Notes: - It is recommended to fund the campaign with the actual total allocation in the Merkle tree (ideally equivalent to `AGGREGATE_AMOUNT`) to avoid race conditions among the recipients. Requirements: - `msg.sender` must be the admin. - `VESTING_END_TIME` must be in the future. - Redistribution must not be already enabled. ```solidity function enableRedistribution() external override onlyAdmin; ``` ### \_calculateClaimAmount See the documentation for the user-facing functions that call this internal function. ```solidity function _calculateClaimAmount(uint128 fullAmount, uint40 claimTime) private view returns (uint128); ``` ### \_calculateRedistributionRewards Calculates the redistribution rewards for a given full amount. ```solidity function _calculateRedistributionRewards(uint256 fullAmount) private view returns (uint128 rewards); ``` ### \_postProcessClaim Post-processes the claim execution by handling the tokens transfer and emitting an event. ```solidity function _postProcessClaim(uint256 index, address recipient, address to, uint128 fullAmount, bool viaSig) private; ``` --- ## IAdminable Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.IAdminable # IAdminable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IAdminable.sol) **Title:** IAdminable Contract module that provides a basic access control mechanism, with an admin that can be granted exclusive access to specific functions. The inheriting contract must set the initial admin in the constructor. ## Functions ### admin The address of the admin account or contract. ```solidity function admin() external view returns (address); ``` ### transferAdmin Transfers the contract admin to a new address. Notes: - Does not revert if the admin is the same. - This function can potentially leave the contract without an admin, thereby removing any functionality that is only available to the admin. Requirements: - `msg.sender` must be the contract admin. ```solidity function transferAdmin(address newAdmin) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newAdmin` | `address` | The address of the new admin. | ## Events ### TransferAdmin Emitted when the admin is transferred. ```solidity event TransferAdmin(address indexed oldAdmin, address indexed newAdmin); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `oldAdmin` | `address` | The address of the old admin. | | `newAdmin` | `address` | The address of the new admin. | --- ## IComptrollerable Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.IComptrollerable # IComptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IComptrollerable.sol) **Title:** IComptrollerable Contract module that provides a setter and getter for the Sablier Comptroller. ## Functions ### comptroller Retrieves the address of the comptroller contract. ```solidity function comptroller() external view returns (ISablierComptroller); ``` ### setComptroller Sets the comptroller to a new address. Emits a [SetComptroller](#setcomptroller) event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a [TransferFeesToComptroller](#transferfeestocomptroller) event. ```solidity function transferFeesToComptroller() external; ``` ## Events ### SetComptroller Emitted when the comptroller address is set by the admin. ```solidity event SetComptroller(ISablierComptroller oldComptroller, ISablierComptroller newComptroller); ``` ### TransferFeesToComptroller Emitted when the fees are transferred to the comptroller contract. ```solidity event TransferFeesToComptroller(address indexed comptroller, uint256 feeAmount); ``` --- ## ISablierFactoryMerkleBase Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase # ISablierFactoryMerkleBase [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFactoryMerkleBase.sol) **Inherits:** IComptrollerable **Title:** ISablierFactoryMerkleBase Common interface between factories that deploy campaign contracts. The contracts are deployed using CREATE2. ## Functions ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity function nativeToken() external view returns (address); ``` ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for [nativeToken](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase#nativetoken). Emits a [SetNativeToken](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase#setnativetoken) event. Requirements: - `msg.sender` must be the comptroller. - `newNativeToken` must not be zero address. - The native token must not be already set. ```solidity function setNativeToken(address newNativeToken) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ## Events ### SetNativeToken Emitted when the native token address is set by the comptroller. ```solidity event SetNativeToken(address indexed comptroller, address nativeToken); ``` --- ## ISablierFactoryMerkleExecute Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleExecute # ISablierFactoryMerkleExecute [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFactoryMerkleExecute.sol) **Inherits:** [ISablierFactoryMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase) **Title:** ISablierFactoryMerkleExecute A factory that deploys MerkleExecute campaign contracts. See the documentation in [ISablierMerkleExecute](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleExecute). ## Functions ### computeMerkleExecute Computes the deterministic address where [SablierMerkleExecute](/reference/airdrops/contracts/contract.SablierMerkleExecute) campaign will be deployed. Reverts if the requirements from [createMerkleExecute](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleExecute#createmerkleexecute) are not met. ```solidity function computeMerkleExecute( address campaignCreator, MerkleExecute.ConstructorParams calldata campaignParams ) external view returns (address merkleExecute); ``` ### createMerkleExecute Creates a new MerkleExecute campaign for claim-and-execute distribution of tokens. Emits a [CreateMerkleExecute](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleExecute#createmerkleexecute) event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. - The create function does not validate if the `campaignParams.selector` is a function implemented by the target contract. In that case, the `claimAndExecute` function will revert. - If the target contract does not implement the `campaignParams.selector` but has `fallback`, the `claimAndExecute` call may silently succeed. If fallback does not transfer claim tokens, the claim tokens will be left in the campaign contract. These tokens can be clawbacked by the campaign creator. Requirements: - `campaignParams.token` must not be the forbidden native token. - `campaignParams.target` must be a contract. ```solidity function createMerkleExecute( MerkleExecute.ConstructorParams calldata campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external returns (ISablierMerkleExecute merkleExecute); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleExecute.ConstructorParams` | Struct encapsulating the [SablierMerkleExecute](/reference/airdrops/contracts/contract.SablierMerkleExecute) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleExecute` | `ISablierMerkleExecute` | The address of the newly created MerkleExecute campaign. | ## Events ### CreateMerkleExecute Emitted when a [SablierMerkleExecute](/reference/airdrops/contracts/contract.SablierMerkleExecute) campaign is created. ```solidity event CreateMerkleExecute( ISablierMerkleExecute indexed merkleExecute, MerkleExecute.ConstructorParams campaignParams, uint256 aggregateAmount, uint256 recipientCount, address comptroller, uint256 minFeeUSD ); ``` --- ## ISablierFactoryMerkleInstant Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleInstant # ISablierFactoryMerkleInstant [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFactoryMerkleInstant.sol) **Inherits:** [ISablierFactoryMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase) **Title:** ISablierFactoryMerkleInstant A factory that deploys MerkleInstant campaign contracts. See the documentation in [ISablierMerkleInstant](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant). ## Functions ### computeMerkleInstant Computes the deterministic address where [SablierMerkleInstant](/reference/airdrops/contracts/contract.SablierMerkleInstant) campaign will be deployed. Reverts if the requirements from [createMerkleInstant](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleInstant#createmerkleinstant) are not met. ```solidity function computeMerkleInstant( address campaignCreator, MerkleInstant.ConstructorParams calldata campaignParams ) external view returns (address merkleInstant); ``` ### createMerkleInstant Creates a new MerkleInstant campaign for instant distribution of tokens. Emits a [CreateMerkleInstant](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleInstant#createmerkleinstant) event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. Requirements: - `campaignParams.token` must not be the forbidden native token. ```solidity function createMerkleInstant( MerkleInstant.ConstructorParams calldata campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external returns (ISablierMerkleInstant merkleInstant); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleInstant.ConstructorParams` | Struct encapsulating the [SablierMerkleInstant](/reference/airdrops/contracts/contract.SablierMerkleInstant) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleInstant` | `ISablierMerkleInstant` | The address of the newly created MerkleInstant contract. | ## Events ### CreateMerkleInstant Emitted when a [SablierMerkleInstant](/reference/airdrops/contracts/contract.SablierMerkleInstant) campaign is created. ```solidity event CreateMerkleInstant( ISablierMerkleInstant indexed merkleInstant, MerkleInstant.ConstructorParams campaignParams, uint256 aggregateAmount, uint256 recipientCount, address comptroller, uint256 minFeeUSD ); ``` --- ## ISablierFactoryMerkleLL Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLL # ISablierFactoryMerkleLL [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFactoryMerkleLL.sol) **Inherits:** [ISablierFactoryMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase) **Title:** ISablierFactoryMerkleLL A factory that deploys MerkleLL campaign contracts. See the documentation in [ISablierMerkleLL](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL). ## Functions ### computeMerkleLL Computes the deterministic address where [SablierMerkleLL](/reference/airdrops/contracts/contract.SablierMerkleLL) campaign will be deployed. Reverts if the requirements from [createMerkleLL](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLL#createmerklell) are not met. ```solidity function computeMerkleLL( address campaignCreator, MerkleLL.ConstructorParams calldata campaignParams ) external view returns (address merkleLL); ``` ### createMerkleLL Creates a new Merkle Lockup campaign with a Lockup Linear distribution. Emits a [CreateMerkleLL](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLL#createmerklell) event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. - A value of zero for `campaignParams.granularity` would store the granularity as 1 second. Requirements: - `campaignParams.token` must not be the forbidden native token. ```solidity function createMerkleLL( MerkleLL.ConstructorParams memory campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external returns (ISablierMerkleLL merkleLL); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleLL.ConstructorParams` | Struct encapsulating the [SablierMerkleLL](/reference/airdrops/contracts/contract.SablierMerkleLL) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleLL` | `ISablierMerkleLL` | The address of the newly created Merkle Lockup contract. | ## Events ### CreateMerkleLL Emitted when a [SablierMerkleLL](/reference/airdrops/contracts/contract.SablierMerkleLL) campaign is created. ```solidity event CreateMerkleLL( ISablierMerkleLL indexed merkleLL, MerkleLL.ConstructorParams campaignParams, uint256 aggregateAmount, uint256 recipientCount, address comptroller, uint256 minFeeUSD ); ``` --- ## ISablierFactoryMerkleLT Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLT # ISablierFactoryMerkleLT [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFactoryMerkleLT.sol) **Inherits:** [ISablierFactoryMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase) **Title:** ISablierFactoryMerkleLT A factory that deploys MerkleLT campaign contracts. See the documentation in [ISablierMerkleLT](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT). ## Functions ### isPercentagesSum100 Verifies if the sum of percentages in `tranches` equals 100%, i.e., 1e18. This is a helper function for the frontend. It is not used anywhere in the contracts. ```solidity function isPercentagesSum100(MerkleLT.TrancheWithPercentage[] calldata tranches) external pure returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `tranches` | `MerkleLT.TrancheWithPercentage[]` | The tranches with their respective unlock percentages. | **Returns** | Name | Type | Description | | --- | --- | --- | | `result` | `bool` | True if the sum of percentages equals 100%, otherwise false. | ### computeMerkleLT Computes the deterministic address where [SablierMerkleLT](/reference/airdrops/contracts/contract.SablierMerkleLT) campaign will be deployed. Reverts if the requirements from [createMerkleLT](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLT#createmerklelt) are not met. ```solidity function computeMerkleLT( address campaignCreator, MerkleLT.ConstructorParams calldata campaignParams ) external view returns (address merkleLT); ``` ### createMerkleLT Creates a new Merkle Lockup campaign with a Lockup Tranched distribution. Emits a [CreateMerkleLT](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleLT#createmerklelt) event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - A value of zero for `campaignParams.expiration` means the campaign does not expire. Requirements: - `campaignParams.token` must not be the forbidden native token. - The sum of percentages of the tranches must equal 100%. ```solidity function createMerkleLT( MerkleLT.ConstructorParams calldata campaignParams, uint256 aggregateAmount, uint256 recipientCount ) external returns (ISablierMerkleLT merkleLT); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleLT.ConstructorParams` | Struct encapsulating the [SablierMerkleLT](/reference/airdrops/contracts/contract.SablierMerkleLT) parameters. | | `aggregateAmount` | `uint256` | The total amount of ERC-20 tokens to be distributed to all recipients. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleLT` | `ISablierMerkleLT` | The address of the newly created Merkle Lockup contract. | ## Events ### CreateMerkleLT Emitted when a [SablierMerkleLT](/reference/airdrops/contracts/contract.SablierMerkleLT) campaign is created. ```solidity event CreateMerkleLT( ISablierMerkleLT indexed merkleLT, MerkleLT.ConstructorParams campaignParams, uint256 aggregateAmount, uint256 totalDuration, uint256 recipientCount, address comptroller, uint256 minFeeUSD ); ``` --- ## ISablierFactoryMerkleVCA Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleVCA # ISablierFactoryMerkleVCA [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFactoryMerkleVCA.sol) **Inherits:** [ISablierFactoryMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleBase) **Title:** ISablierFactoryMerkleVCA A factory that deploys MerkleVCA campaign contracts. See the documentation in [ISablierMerkleVCA](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA). ## Functions ### computeMerkleVCA Computes the deterministic address where [SablierMerkleVCA](/reference/airdrops/contracts/contract.SablierMerkleVCA) campaign will be deployed. Reverts if the requirements from [createMerkleVCA](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleVCA#createmerklevca) are not met. ```solidity function computeMerkleVCA( address campaignCreator, MerkleVCA.ConstructorParams calldata campaignParams ) external view returns (address merkleVCA); ``` ### createMerkleVCA Creates a new MerkleVCA campaign for variable distribution of tokens. Emits a [CreateMerkleVCA](/reference/airdrops/contracts/interfaces/interface.ISablierFactoryMerkleVCA#createmerklevca) event. Notes: - The contract is created with CREATE2. - The campaign's fee will be set to the min USD fee unless a custom fee is set for `msg.sender`. - Users interested into funding the campaign before its deployment must meet the below requirements, otherwise the campaign deployment will revert. Requirements: - `campaignParams.token` must not be the forbidden native token. - `campaignParams.aggregateAmount` must be greater than 0. - Both `campaignParams.vestingStartTime` and `campaignParams.vestingEndTime` must be greater than 0. - `campaignParams.vestingEndTime` must be greater than `campaignParams.vestingStartTime`. - `campaignParams.expiration` must be greater than 0. - `campaignParams.expiration` must be at least 1 week beyond the end time to ensure loyal recipients have enough time to claim. - `campaignParams.unlockPercentage` must not be greater than 1e18, equivalent to 100%. ```solidity function createMerkleVCA( MerkleVCA.ConstructorParams memory campaignParams, uint256 recipientCount ) external returns (ISablierMerkleVCA merkleVCA); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `campaignParams` | `MerkleVCA.ConstructorParams` | Struct encapsulating the [SablierMerkleVCA](/reference/airdrops/contracts/contract.SablierMerkleVCA) parameters. | | `recipientCount` | `uint256` | The total number of recipient addresses eligible for the airdrop. | **Returns** | Name | Type | Description | | --- | --- | --- | | `merkleVCA` | `ISablierMerkleVCA` | The address of the newly created MerkleVCA campaign. | ## Events ### CreateMerkleVCA Emitted when a [SablierMerkleVCA](/reference/airdrops/contracts/contract.SablierMerkleVCA) campaign is created. ```solidity event CreateMerkleVCA( ISablierMerkleVCA indexed merkleVCA, MerkleVCA.ConstructorParams campaignParams, uint256 recipientCount, address comptroller, uint256 minFeeUSD ); ``` --- ## ISablierMerkleBase Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase # ISablierMerkleBase [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleBase.sol) **Inherits:** IAdminable **Title:** ISablierMerkleBase Common interface between campaign contracts. ## Functions ### CAMPAIGN\_START\_TIME The timestamp at which campaign starts and claim begins. This is an immutable state variable. ```solidity function CAMPAIGN_START_TIME() external view returns (uint40); ``` ### CLAIM\_TYPE Retrieves the claim type supported by the campaign. This is an immutable state variable. ```solidity function CLAIM_TYPE() external view returns (ClaimType); ``` ### COMPTROLLER Retrieves the address of the comptroller contract. ```solidity function COMPTROLLER() external view returns (address); ``` ### EXPIRATION The cut-off point for the campaign, as a Unix timestamp. A value of zero means there is no expiration. This is an immutable state variable. ```solidity function EXPIRATION() external view returns (uint40); ``` ### IS\_SABLIER\_MERKLE Returns `true` indicating that this campaign contract is deployed using the Sablier Factory. This is a constant state variable. ```solidity function IS_SABLIER_MERKLE() external view returns (bool); ``` ### MERKLE\_ROOT The root of the Merkle tree used to validate the proofs of inclusion. This is an immutable state variable. ```solidity function MERKLE_ROOT() external view returns (bytes32); ``` ### TOKEN The ERC-20 token to distribute. This is an immutable state variable. ```solidity function TOKEN() external view returns (IERC20); ``` ### calculateMinFeeWei Calculates the minimum fee in wei required to claim the airdrop. ```solidity function calculateMinFeeWei() external view returns (uint256); ``` ### campaignName Retrieves the name of the campaign. ```solidity function campaignName() external view returns (string memory); ``` ### firstClaimTime Retrieves the timestamp when the first claim is made, and zero if no claim was made yet. ```solidity function firstClaimTime() external view returns (uint40); ``` ### hasClaimed Returns a flag indicating whether a claim has been made for a given index. Uses a bitmap to save gas. ```solidity function hasClaimed(uint256 index) external view returns (bool); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient to check. | ### hasExpired Returns a flag indicating whether the campaign has expired. ```solidity function hasExpired() external view returns (bool); ``` ### ipfsCID The content identifier for indexing the campaign on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. ```solidity function ipfsCID() external view returns (string memory); ``` ### minFeeUSD Retrieves the min USD fee required to claim the airdrop, denominated in 8 decimals. The denomination is based on Chainlink's 8-decimal format for USD prices, where 1e8 is $1. ```solidity function minFeeUSD() external view returns (uint256); ``` ### clawback Claws back the unclaimed tokens. Emits a [Clawback](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase#clawback) event. Requirements: - `msg.sender` must be the admin. - No claim must be made, OR The current timestamp must not exceed 7 days after the first claim, OR The campaign must be expired. ```solidity function clawback(address to, uint128 amount) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `to` | `address` | The address to receive the tokens. | | `amount` | `uint128` | The amount of tokens to claw back. | ### lowerMinFeeUSD Lowers the min USD fee. Emits a [LowerMinFeeUSD](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase#lowerminfeeusd) event. Requirements: - `msg.sender` must be the comptroller. - The new fee must be less than the current [minFeeUSD](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase#minfeeusd). ```solidity function lowerMinFeeUSD(uint256 newMinFeeUSD) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newMinFeeUSD` | `uint256` | The new min USD fee to set, denominated in 8 decimals. | ### sponsor Sponsors the claim fees for eligible recipients. Emits a [Sponsor](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase#sponsor) event. Notes: - This function only makes the payment. The claim fees are updated only after the payment has been verified off-chain. - Refer to the Sablier website in order to sponsor with the correct token, otherwise the sponsorship may be ignored. Requirements: - `biller` must not be the zero address. - `amount` must be greater than zero. - `token` must not be the zero address and must be a valid ERC20 token. - `msg.sender` must have approved the contract to spend the tokens. ```solidity function sponsor(IERC20 token, uint128 amount, address biller) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The ERC-20 token to transfer. | | `amount` | `uint128` | The amount of tokens to transfer. | | `biller` | `address` | The address to receive the tokens. | ## Events ### Clawback Emitted when the admin claws back the unclaimed tokens. ```solidity event Clawback(address indexed admin, address indexed to, uint128 amount); ``` ### LowerMinFeeUSD Emitted when the min USD fee is lowered by the comptroller. ```solidity event LowerMinFeeUSD(address indexed comptroller, uint256 newMinFeeUSD, uint256 previousMinFeeUSD); ``` ### Sponsor Emitted when campaign owner sponsors the claim fees for eligible recipients. ```solidity event Sponsor(address indexed caller, IERC20 indexed token, uint128 amount, address indexed biller); ``` --- ## ISablierMerkleExecute Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleExecute # ISablierMerkleExecute [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleExecute.sol) **Inherits:** [ISablierMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase) **Title:** ISablierMerkleExecute MerkleExecute enables an airdrop distribution model where eligible users claim tokens and immediately execute a call on a target contract (useful for staking, lending pool deposits). This is achieved by approving the target contract to spend user's tokens, followed by a call using the stored function selector combined with user-provided arguments. ## Functions ### SELECTOR The function selector to call on the target contract. This is an immutable state variable. ```solidity function SELECTOR() external view returns (bytes4); ``` ### TARGET The address of the target contract to call with the claim amount. This is an immutable state variable. ```solidity function TARGET() external view returns (address); ``` ### claimAndExecute Claim airdrop and execute the call to the target contract. It emits a [ClaimExecute](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleExecute#claimexecute) event. Notes: - The function approves the exact claim amount to the [TARGET](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleExecute#target), executes the call, then revokes the approval. - The function does not forward native tokens to the target contract, so calls to targets that require a native token payment will revert. Requirements: - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - `msg.sender` must be the airdrop recipient. - The external call to the target contract must succeed. - The target contract must transfer the entire claim amount from the campaign. ```solidity function claimAndExecute( uint256 index, uint128 amount, bytes32[] calldata merkleProof, bytes calldata selectorArguments ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of `msg.sender` in the Merkle tree. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `selectorArguments` | `bytes` | The function ABI-encoded arguments for [SELECTOR](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleExecute#selector). | ## Events ### ClaimExecute Emitted when a claim is executed on behalf of an eligible recipient. ```solidity event ClaimExecute(uint256 index, address indexed recipient, uint128 amount, address indexed target); ``` --- ## ISablierMerkleInstant Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant # ISablierMerkleInstant [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleInstant.sol) **Inherits:** [ISablierMerkleSignature](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature) **Title:** ISablierMerkleInstant MerkleInstant enables an airdrop model where eligible users receive the tokens as soon as they claim. ## Functions ### claim Claim airdrop on behalf of eligible recipient and transfer it to the recipient address. It emits a [ClaimInstant](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claiminstant) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. ```solidity function claim(uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimTo Claim airdrop and transfer the tokens to the `to` address. It emits a [ClaimInstant](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claiminstant) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claim). ```solidity function claimTo(uint256 index, address to, uint128 amount, bytes32[] calldata merkleProof) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). It emits a [ClaimInstant](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claiminstant) event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the [claimViaSig](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claimviasig) function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claim). ```solidity function claimViaAttestation( uint256 index, address to, uint128 amount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature, and transfer the tokens to the `to` address. It emits a [ClaimInstant](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claiminstant) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleInstant#claim). Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 amount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of the recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ## Events ### ClaimInstant Emitted when an airdrop is claimed on behalf of an eligible recipient. ```solidity event ClaimInstant(uint256 index, address indexed recipient, uint128 amount, address to, bool viaSig); ``` --- ## ISablierMerkleLL Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL # ISablierMerkleLL [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleLL.sol) **Inherits:** [ISablierMerkleSignature](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature), [ISablierMerkleLockup](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLockup) **Title:** ISablierMerkleLL MerkleLL enables an airdrop model with a vesting period powered by the Lockup Linear model. ## Functions ### VESTING\_CLIFF\_DURATION Retrieves the cliff duration of the vesting stream, in seconds. ```solidity function VESTING_CLIFF_DURATION() external view returns (uint40); ``` ### VESTING\_CLIFF\_UNLOCK\_PERCENTAGE Retrieves the percentage of the claim amount due to be unlocked at the vesting cliff time, as a fixed-point number where 1e18 is 100%. ```solidity function VESTING_CLIFF_UNLOCK_PERCENTAGE() external view returns (UD60x18); ``` ### VESTING\_GRANULARITY Retrieves the smallest step in time between two consecutive token unlocks. Zero is a sentinel value for 1 second. ```solidity function VESTING_GRANULARITY() external view returns (uint40); ``` ### VESTING\_START\_TIME Retrieves the start time of the vesting stream, as a Unix timestamp. Zero is a sentinel value for `block.timestamp`. ```solidity function VESTING_START_TIME() external view returns (uint40); ``` ### VESTING\_START\_UNLOCK\_PERCENTAGE Retrieves the percentage of the claim amount due to be unlocked at the vesting start time, as a fixed-point number where 1e18 is 100%. ```solidity function VESTING_START_UNLOCK_PERCENTAGE() external view returns (UD60x18); ``` ### VESTING\_TOTAL\_DURATION Retrieves the total duration of the vesting stream, in seconds. ```solidity function VESTING_TOTAL_DURATION() external view returns (uint40); ``` ### claim Claim airdrop on behalf of eligible recipient. If the vesting end time is in the future, it creates a Lockup Linear stream, otherwise it transfers the tokens directly to the recipient address. It emits either [ClaimLLWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithtransfer) or [ClaimLLWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithvesting) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - All requirements from {ISablierLockupLinear.createWithTimestampsLL} must be met. ```solidity function claim(uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimTo Claim airdrop. If the vesting end time is in the future, it creates a Lockup Linear stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either [ClaimLLWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithtransfer) or [ClaimLLWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithvesting) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claim). ```solidity function claimTo(uint256 index, address to, uint128 amount, bytes32[] calldata merkleProof) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). If the vesting end time is in the future, it creates a Lockup Linear stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either [ClaimLLWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithtransfer) or [ClaimLLWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithvesting) event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the [claimViaSig](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimviasig) function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claim). ```solidity function claimViaAttestation( uint256 index, address to, uint128 amount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature. If the vesting end time is in the future, it creates a Lockup Linear stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either [ClaimLLWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithtransfer) or [ClaimLLWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claimllwithvesting) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLL#claim). Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 amount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of the recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ## Events ### ClaimLLWithTransfer Emitted when an airdrop is claimed using direct transfer on behalf of an eligible recipient. ```solidity event ClaimLLWithTransfer(uint256 index, address indexed recipient, uint128 amount, address to, bool viaSig); ``` ### ClaimLLWithVesting Emitted when an airdrop is claimed using Lockup Linear stream on behalf of an eligible recipient. ```solidity event ClaimLLWithVesting( uint256 index, address indexed recipient, uint128 amount, uint256 indexed streamId, address to, bool viaSig ); ``` --- ## ISablierMerkleLT Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT # ISablierMerkleLT [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleLT.sol) **Inherits:** [ISablierMerkleLockup](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLockup), [ISablierMerkleSignature](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature) **Title:** ISablierMerkleLT MerkleLT enables an airdrop model with a vesting period powered by the Lockup Tranched model. ## Functions ### VESTING\_START\_TIME Retrieves the start time of the vesting stream, as a Unix timestamp. Zero is a sentinel value for `block.timestamp`. ```solidity function VESTING_START_TIME() external view returns (uint40); ``` ### tranchesWithPercentages Retrieves the tranches with their respective unlock percentages and durations. ```solidity function tranchesWithPercentages() external view returns (MerkleLT.TrancheWithPercentage[] memory); ``` ### claim Claim airdrop on behalf of eligible recipient. If the vesting end time is in the future, it creates a Lockup Tranched stream, otherwise it transfers the tokens directly to the recipient address. It emits either [ClaimLTWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithtransfer) or [ClaimLTWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithvesting) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - All requirements from {ISablierLockupTranched.createWithTimestampsLT} must be met. ```solidity function claim(uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimTo Claim airdrop. If the vesting end time is in the future, it creates a Lockup Tranched stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either [ClaimLTWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithtransfer) or [ClaimLTWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithvesting) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claim). ```solidity function claimTo(uint256 index, address to, uint128 amount, bytes32[] calldata merkleProof) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). If the vesting end time is in the future, it creates a Lockup Tranched stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either [ClaimLTWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithtransfer) or [ClaimLTWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithvesting) event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the [claimViaSig](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimviasig) function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claim). ```solidity function claimViaAttestation( uint256 index, address to, uint128 amount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of `msg.sender`. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature. If the vesting end time is in the future, it creates a Lockup Tranched stream with `to` address as the stream recipient, otherwise it transfers the tokens directly to the `to` address. It emits either [ClaimLTWithTransfer](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithtransfer) or [ClaimLTWithVesting](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claimltwithvesting) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in [claim](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLT#claim). Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 amount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address to which Lockup stream or ERC-20 tokens will be sent on behalf of the recipient. | | `amount` | `uint128` | The amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ## Events ### ClaimLTWithTransfer Emitted when an airdrop is claimed using direct transfer on behalf of an eligible recipient. ```solidity event ClaimLTWithTransfer(uint256 index, address indexed recipient, uint128 amount, address to, bool viaSig); ``` ### ClaimLTWithVesting Emitted when an airdrop is claimed using Lockup Tranched stream on behalf of an eligible recipient. ```solidity event ClaimLTWithVesting( uint256 index, address indexed recipient, uint128 amount, uint256 indexed streamId, address to, bool viaSig ); ``` --- ## ISablierMerkleLockup Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleLockup # ISablierMerkleLockup [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleLockup.sol) **Inherits:** [ISablierMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase) **Title:** ISablierMerkleLockup MerkleLockup enables Airstreams (a portmanteau of "airdrop" and "stream"), an airdrop model where the tokens are vested over time, as opposed to being unlocked at once. The vesting is provided by Sablier Lockup. Common interface between MerkleLL and MerkleLT. ## Functions ### SABLIER\_LOCKUP The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. ```solidity function SABLIER_LOCKUP() external view returns (ISablierLockup); ``` ### STREAM\_CANCELABLE A flag indicating whether the streams can be canceled. This is an immutable state variable. ```solidity function STREAM_CANCELABLE() external view returns (bool); ``` ### STREAM\_TRANSFERABLE A flag indicating whether the stream NFTs are transferable. This is an immutable state variable. ```solidity function STREAM_TRANSFERABLE() external view returns (bool); ``` ### claimedStreams Retrieves the stream IDs associated with the airdrops claimed by the provided recipient. In practice, most campaigns will only have one stream per recipient. ```solidity function claimedStreams(address recipient) external view returns (uint256[] memory); ``` ### streamShape Retrieves the shape of the Lockup stream created upon claiming. ```solidity function streamShape() external view returns (string memory); ``` --- ## ISablierMerkleSignature Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature # ISablierMerkleSignature [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleSignature.sol) **Inherits:** [ISablierMerkleBase](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleBase) **Title:** ISablierMerkleSignature Abstract contract providing helper functions for verifying EIP-712 and EIP-1271 signatures for Merkle campaigns. ## Functions ### attestor Retrieves the attestor address used for creating attestation signatures. ```solidity function attestor() external view returns (address); ``` ### domainSeparator The domain separator, as required by EIP-712 and EIP-1271, used for signing claims to prevent replay attacks across different campaigns. ```solidity function domainSeparator() external view returns (bytes32); ``` ### setAttestor Sets the attestor address used for verifying attestation signatures. Emits a [SetAttestor](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature#setattestor) event. Requirements: - `msg.sender` must be either the comptroller or the campaign admin. ```solidity function setAttestor(address newAttestor) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newAttestor` | `address` | The new attestor address. If zero, the attestor from the comptroller will be used. | ## Events ### SetAttestor Emitted when the address of the attestor is set in this contract. ```solidity event SetAttestor(address indexed caller, address indexed previousAttestor, address indexed newAttestor); ``` --- ## ISablierMerkleVCA Source: https://docs.sablier.com/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA # ISablierMerkleVCA [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierMerkleVCA.sol) **Inherits:** [ISablierMerkleSignature](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleSignature) **Title:** ISablierMerkleVCA VCA stands for Variable Claim Amount, and is an airdrop model where the claim amount increases linearly until the airdrop period ends. Claiming early results in forgoing the remaining amount, whereas claiming after the period grants the full amount that was allocated. ## Functions ### AGGREGATE\_AMOUNT Retrieves the total amount of ERC-20 tokens allocated to the campaign. ```solidity function AGGREGATE_AMOUNT() external view returns (uint128); ``` ### UNLOCK\_PERCENTAGE Retrieves the percentage of the full amount that will unlock immediately at the start time. The value is denominated as a fixed-point number where 1e18 is 100%. ```solidity function UNLOCK_PERCENTAGE() external view returns (UD60x18); ``` ### VESTING\_END\_TIME Retrieves the time when the VCA airdrop is fully vested, as a Unix timestamp. ```solidity function VESTING_END_TIME() external view returns (uint40); ``` ### VESTING\_START\_TIME Retrieves the time when the VCA airdrop begins to unlock, as a Unix timestamp. ```solidity function VESTING_START_TIME() external view returns (uint40); ``` ### calculateClaimAmount Calculates the amount that would be claimed if the claim were made at `claimTime`. This is for informational purposes only. To actually claim the airdrop, a Merkle proof is required. ```solidity function calculateClaimAmount(uint128 fullAmount, uint40 claimTime) external view returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `fullAmount` | `uint128` | The amount of tokens allocated to a user, denominated in the token's decimals. | | `claimTime` | `uint40` | A hypothetical time at which to make the claim. Zero is a sentinel value for `block.timestamp`. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The amount that would be claimed, denominated in the token's decimals. | ### calculateForgoneAmount Calculates the amount that would be forgone if the claim were made at `claimTime`. This is for informational purposes only. Reverts if the claim time is less than the vesting start time, since the claim cannot be made, no amount can be forgone. ```solidity function calculateForgoneAmount(uint128 fullAmount, uint40 claimTime) external view returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `fullAmount` | `uint128` | The amount of tokens allocated to a user, denominated in the token's decimals. | | `claimTime` | `uint40` | A hypothetical time at which to make the claim. Zero is a sentinel value for `block.timestamp`. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The amount that would be forgone, denominated in the token's decimals. | ### calculateRedistributionRewards Calculates the redistribution rewards for a given full amount. Notes: - Reverts if redistribution is not enabled. - If `AGGREGATE_AMOUNT` is set lower than actual total allocations in the Merkle tree, this might return 0 rather than reverting. ```solidity function calculateRedistributionRewards(uint128 fullAmount) external view returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `fullAmount` | `uint128` | The amount of tokens that the redistribution rewards are to be calculated for. | ### isRedistributionEnabled Retrieves a bool indicating whether the redistribution of forgone tokens is enabled or not. ```solidity function isRedistributionEnabled() external view returns (bool); ``` ### totalForgoneAmount Retrieves the total amount of tokens forgone by early claimers. ```solidity function totalForgoneAmount() external view returns (uint128); ``` ### totalRedistributionAmountPaid Retrieves the total amount of redistribution rewards paid out. ```solidity function totalRedistributionAmountPaid() external view returns (uint128); ``` ### claimTo Claim airdrop. If the vesting end time is in the future, it calculates the claim amount to transfer to the `to` address, otherwise it transfers the full amount. If the redistribution is enabled, it calculates the reward amount based on the total amount of tokens forgone by early claimers and transfers it to the recipients claiming after the vesting end time. It emits a [ClaimVCA](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA#claimvca) event, and a [RedistributeReward](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA#redistributereward) event if the redistribution is enabled. Notes: - There can be a race condition among recipients if: 1. `AGGREGATE_AMOUNT` is set lower than the actual total allocations in the Merkle tree. 2. The campaign is not sufficiently funded with the actual total allocations. - The rewards are transferred to the recipients at the time of claiming. If the campaign creator turns the redistribution on after the vesting end time, the recipients who have already claimed the full amount would miss on the rewards while subsequent recipients would get them. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - The current time must be greater than or equal to the campaign start time. - The campaign must not have expired. - `msg.value` must not be less than the value returned by {COMPTROLLER.calculateMinFeeWei}. - The `index` must not be claimed already. - The Merkle proof must be valid. - The claim amount must be greater than zero. - `msg.sender` must be the airdrop recipient. - The `to` must not be the zero address. ```solidity function claimTo(uint256 index, address to, uint128 fullAmount, bytes32[] calldata merkleProof) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `fullAmount` | `uint128` | The total amount of ERC-20 tokens allocated to the recipient. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | ### claimViaAttestation Claim airdrop using an external attestation from a trusted attestor (e.g., KYC verifier). If the vesting end time is in the future, it calculates the claim amount to transfer to the `to` address, otherwise it transfers the full amount. It emits a [ClaimVCA](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA#claimvca) event. Notes: - The attestation must be an EIP-712 signature from the attestor. - See the example in the [claimViaSig](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA#claimviasig) function. - If the attestor is not set in the campaign, the attestor from the comptroller is used. Requirements: - `msg.sender` must be the airdrop recipient. - `CLAIM_TYPE` must be `ATTEST`. - The `to` must not be the zero address. - The attestor must not be the zero address. - The `expireAt` timestamp must not be in the past. - The attestation signature must be valid. - Refer to the requirements in [claimTo](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA#claimto). ```solidity function claimViaAttestation( uint256 index, address to, uint128 fullAmount, uint40 expireAt, bytes32[] calldata merkleProof, bytes calldata attestation ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the `msg.sender` in the Merkle tree. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of `msg.sender`. | | `fullAmount` | `uint128` | The total amount of ERC-20 tokens allocated to the `msg.sender`. | | `expireAt` | `uint40` | The timestamp after which the attestation signature is no longer valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `attestation` | `bytes` | The EIP-712 signature from the attestor. | ### claimViaSig Claim airdrop on behalf of eligible recipient using an EIP-712 or EIP-1271 signature. If the vesting end time is in the future, it calculates the claim amount to transfer to the `to` address, otherwise it transfers the full amount. It emits a [ClaimVCA](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA#claimvca) event. Requirements: - `CLAIM_TYPE` must be `DEFAULT`. - If `recipient` is an EOA, it must match the recovered signer. - If `recipient` is a contract, it must implement the IERC-1271 interface. - The `to` must not be the zero address. - The `validFrom` must be less than or equal to the current block timestamp. - Refer to the requirements in [claimTo](/reference/airdrops/contracts/interfaces/interface.ISablierMerkleVCA#claimto) except that there are no restrictions on `msg.sender`. Below is the example of typed data to be signed by the airdrop recipient, referenced from [https://docs.metamask.io/wallet/how-to/sign-data/#example](https://docs.metamask.io/wallet/how-to/sign-data/#example). ```json types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], Claim: [ { name: "index", type: "uint256" }, { name: "recipient", type: "address" }, { name: "to", type: "address" }, { name: "amount", type: "uint128" }, { name: "validFrom", type: "uint40" }, ], }, domain: { name: "Sablier Airdrops Protocol", chainId: 1, // Chain on which the contract is deployed verifyingContract: "0xTheAddressOfThisContract", // The address of this contract }, primaryType: "Claim", message: { index: 2, // The index of the signer in the Merkle tree recipient: "0xTheAddressOfTheRecipient", // The address of the airdrop recipient to: "0xTheAddressReceivingTheTokens", // The address where recipient wants to transfer the tokens amount: "1000000000000000000000", // The amount of tokens allocated to the recipient validFrom: 1752425637 // The timestamp from which the claim signature is valid }, ``` ```solidity function claimViaSig( uint256 index, address recipient, address to, uint128 fullAmount, uint40 validFrom, bytes32[] calldata merkleProof, bytes calldata signature ) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `index` | `uint256` | The index of the recipient in the Merkle tree. | | `recipient` | `address` | The address of the airdrop recipient who is providing the signature. | | `to` | `address` | The address receiving the ERC-20 tokens on behalf of the recipient. | | `fullAmount` | `uint128` | The total amount of ERC-20 tokens allocated to the recipient. | | `validFrom` | `uint40` | The timestamp from which the claim signature is valid. | | `merkleProof` | `bytes32[]` | The proof of inclusion in the Merkle tree. | | `signature` | `bytes` | The EIP-712 or EIP-1271 signature from the airdrop recipient. | ### enableRedistribution Enable the redistribution of forgone tokens among recipients claiming after the vesting end time, proportional to their allocation amount. Once enabled, it cannot be disabled. Notes: - It is recommended to fund the campaign with the actual total allocation in the Merkle tree (ideally equivalent to `AGGREGATE_AMOUNT`) to avoid race conditions among the recipients. Requirements: - `msg.sender` must be the admin. - `VESTING_END_TIME` must be in the future. - Redistribution must not be already enabled. ```solidity function enableRedistribution() external; ``` ## Events ### ClaimVCA Emitted when an airdrop is claimed on behalf of an eligible recipient. ```solidity event ClaimVCA( uint256 index, address indexed recipient, uint128 claimAmount, uint128 forgoneAmount, address to, bool viaSig ); ``` ### EnableRedistribution Emitted when the redistribution is enabled. ```solidity event EnableRedistribution(); ``` ### RedistributeReward Emitted when a recipient receives rewards from the forgone tokens pool. ```solidity event RedistributeReward(uint256 index, address indexed recipient, uint128 amount, address to); ``` --- ## Errors Source: https://docs.sablier.com/reference/airdrops/contracts/libraries/library.Errors # Errors [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/Errors.sol) **Title:** Errors Library containing all custom errors the protocol may revert with. ## Errors ### SablierFactoryMerkleBase\_ForbidNativeToken Thrown when trying to create a campaign with native token. ```solidity error SablierFactoryMerkleBase_ForbidNativeToken(address nativeToken); ``` ### SablierFactoryMerkleBase\_NativeTokenAlreadySet Thrown when trying to set the native token address when it is already set. ```solidity error SablierFactoryMerkleBase_NativeTokenAlreadySet(address nativeToken); ``` ### SablierFactoryMerkleBase\_NativeTokenZeroAddress Thrown when trying to set zero address as native token. ```solidity error SablierFactoryMerkleBase_NativeTokenZeroAddress(); ``` ### SablierFactoryMerkleExecute\_TargetNotContract Thrown when trying to create a merkle execute campaign with a target that is not a contract. ```solidity error SablierFactoryMerkleExecute_TargetNotContract(address target); ``` ### SablierFactoryMerkleLT\_TotalPercentageNotOneHundred Thrown when trying to create an LT campaign with tranches' unlock percentages not adding up to 100%. ```solidity error SablierFactoryMerkleLT_TotalPercentageNotOneHundred(uint64 totalPercentage); ``` ### SablierFactoryMerkleVCA\_AggregateAmountZero Thrown when trying to create a Merkle VCA campaign with zero aggregate amount. ```solidity error SablierFactoryMerkleVCA_AggregateAmountZero(); ``` ### SablierFactoryMerkleVCA\_ExpirationTimeZero Thrown if expiration time is zero. ```solidity error SablierFactoryMerkleVCA_ExpirationTimeZero(); ``` ### SablierFactoryMerkleVCA\_ExpirationTooEarly Thrown if expiration time is within 1 week from the vesting end time. ```solidity error SablierFactoryMerkleVCA_ExpirationTooEarly(uint40 vestingEndTime, uint40 expiration); ``` ### SablierFactoryMerkleVCA\_StartTimeZero Thrown if the start time is zero. ```solidity error SablierFactoryMerkleVCA_StartTimeZero(); ``` ### SablierFactoryMerkleVCA\_UnlockPercentageTooHigh Thrown if the unlock percentage is greater than 100%. ```solidity error SablierFactoryMerkleVCA_UnlockPercentageTooHigh(UD60x18 unlockPercentage); ``` ### SablierFactoryMerkleVCA\_VestingEndTimeNotGreaterThanVestingStartTime Thrown if vesting end time is not greater than the vesting start time. ```solidity error SablierFactoryMerkleVCA_VestingEndTimeNotGreaterThanVestingStartTime( uint40 vestingStartTime, uint40 vestingEndTime ); ``` ### SablierMerkleBase\_CallerNotComptroller Thrown when caller is not the comptroller. ```solidity error SablierMerkleBase_CallerNotComptroller(address comptroller, address caller); ``` ### SablierMerkleBase\_CampaignExpired Thrown when trying to claim after the campaign has expired. ```solidity error SablierMerkleBase_CampaignExpired(uint256 blockTimestamp, uint40 expiration); ``` ### SablierMerkleBase\_CampaignNotStarted Thrown when trying to claim before the campaign start time. ```solidity error SablierMerkleBase_CampaignNotStarted(uint256 blockTimestamp, uint40 campaignStartTime); ``` ### SablierMerkleBase\_ClawbackNotAllowed Thrown when trying to clawback when the current timestamp is over the grace period and the campaign has not expired. ```solidity error SablierMerkleBase_ClawbackNotAllowed(uint256 blockTimestamp, uint40 expiration, uint40 firstClaimTime); ``` ### SablierMerkleBase\_FeeTransferFailed Thrown if fee transfer fails. ```solidity error SablierMerkleBase_FeeTransferFailed(address feeRecipient, uint256 feeAmount); ``` ### SablierMerkleBase\_IndexClaimed Thrown when trying to claim the same index more than once. ```solidity error SablierMerkleBase_IndexClaimed(uint256 index); ``` ### SablierMerkleBase\_InsufficientFeePayment Thrown when trying to claim without paying the min fee. ```solidity error SablierMerkleBase_InsufficientFeePayment(uint256 feePaid, uint256 minFeeWei); ``` ### SablierMerkleBase\_InvalidProof Thrown when trying to claim with an invalid Merkle proof. ```solidity error SablierMerkleBase_InvalidProof(); ``` ### SablierMerkleBase\_NewMinFeeUSDNotLower Thrown when trying to set a new min USD fee that is higher than the current fee. ```solidity error SablierMerkleBase_NewMinFeeUSDNotLower(uint256 currentMinFeeUSD, uint256 newMinFeeUSD); ``` ### SablierMerkleBase\_SponsorAmountZero Thrown when trying to sponsor with a zero amount. ```solidity error SablierMerkleBase_SponsorAmountZero(); ``` ### SablierMerkleBase\_ToZeroAddress Thrown when trying to claim to the zero address. ```solidity error SablierMerkleBase_ToZeroAddress(); ``` ### SablierMerkleBase\_UnsupportedClaimType Thrown when trying to call a claim function not supported in the campaign. ```solidity error SablierMerkleBase_UnsupportedClaimType(ClaimType claimTypeRequired, ClaimType claimTypeSupported); ``` ### SablierMerkleExecute\_NotFullAmountTransferred Thrown when the transferred amount is not equal to the claim amount during `claimAndExecute`. ```solidity error SablierMerkleExecute_NotFullAmountTransferred(uint256 amountTransferred, uint256 claimAmount); ``` ### SablierMerkleSignature\_AttestationExpired Thrown when the attestation signature has expired. ```solidity error SablierMerkleSignature_AttestationExpired(uint256 expireAt, uint256 blockTimestamp); ``` ### SablierMerkleSignature\_AttestorNotSet Thrown when the attestor returns the zero address. ```solidity error SablierMerkleSignature_AttestorNotSet(); ``` ### SablierMerkleSignature\_CallerNotAuthorized Thrown when caller is not the comptroller or campaign admin. ```solidity error SablierMerkleSignature_CallerNotAuthorized(address caller, address campaignAdmin, address comptroller); ``` ### SablierMerkleSignature\_InvalidSignature Thrown when claiming with an invalid EIP-712 or EIP-1271 signature. ```solidity error SablierMerkleSignature_InvalidSignature(); ``` ### SablierMerkleSignature\_SignatureNotYetValid Thrown when trying to claim with a signature that is not yet valid. ```solidity error SablierMerkleSignature_SignatureNotYetValid(uint40 validFrom, uint40 blockTimestamp); ``` ### SablierMerkleVCA\_ClaimAmountZero Thrown when the claim amount is zero. ```solidity error SablierMerkleVCA_ClaimAmountZero(address recipient); ``` ### SablierMerkleVCA\_RedistributionAlreadyEnabled Thrown when trying to switch to redistribute strategy when already using it. ```solidity error SablierMerkleVCA_RedistributionAlreadyEnabled(); ``` ### SablierMerkleVCA\_RedistributionNotEnabled Thrown when trying to calculate the rewards amount without redistribution enabled. ```solidity error SablierMerkleVCA_RedistributionNotEnabled(); ``` ### SablierMerkleVCA\_VestingEndTimeNotInFuture Thrown when trying to enable redistribution after the vesting end time. ```solidity error SablierMerkleVCA_VestingEndTimeNotInFuture(uint256 vestingEndTime, uint256 blockTimestamp); ``` ### SablierMerkleVCA\_VestingNotStarted Thrown when calculating the forgone amount with claim time less than the vesting start time. ```solidity error SablierMerkleVCA_VestingNotStarted(uint40 claimTime, uint40 vestingStartTime); ``` --- ## SignatureHash Source: https://docs.sablier.com/reference/airdrops/contracts/libraries/library.SignatureHash # SignatureHash [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/SignatureHash.sol) **Title:** SignatureHash Library containing the hashes for the EIP-712 and EIP-1271 signatures. ## Constants ### CLAIM\_TYPEHASH The struct type hash for the claim signature. ```solidity bytes32 public constant CLAIM_TYPEHASH = keccak256("Claim(uint256 index,address recipient,address to,uint128 amount,uint40 validFrom)") ``` ### DOMAIN\_TYPEHASH The domain type hash for computing the domain separator. ```solidity bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)") ``` ### IDENTITY\_TYPEHASH The struct type hash for the attestation signature. ```solidity bytes32 public constant IDENTITY_TYPEHASH = keccak256("Identity(address recipient,uint40 expireAt)") ``` ### PROTOCOL\_NAME The protocol name for the EIP-712 and EIP-1271 signatures. ```solidity bytes32 public constant PROTOCOL_NAME = keccak256("Sablier Airdrops Protocol") ``` --- ## ClaimType Source: https://docs.sablier.com/reference/airdrops/contracts/types/enum.ClaimType # ClaimType [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleBase.sol) Enum representing the type of claim functions supported by a Merkle campaign. **Notes:** - value0: DEFAULT Activates `claim`, `claimTo`, and `claimViaSig` functions. - value1: ATTEST Activates `claimViaAttestation` function only. - value2: EXECUTE Activates `claimAndExecute` function only. ```solidity enum ClaimType { DEFAULT, ATTEST, EXECUTE } ``` --- ## MerkleBase Source: https://docs.sablier.com/reference/airdrops/contracts/types/library.MerkleBase # MerkleBase [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleBase.sol) ## Structs ### ConstructorParams Struct encapsulating the constructor parameters of [SablierMerkleBase](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleBase) contract. The fields are arranged alphabetically. ```solidity struct ConstructorParams { address campaignCreator; string campaignName; uint40 campaignStartTime; ClaimType claimType; address comptroller; uint40 expiration; address initialAdmin; string ipfsCID; bytes32 merkleRoot; IERC20 token; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `campaignCreator` | `address` | The address of campaign creator which should be the same as the `msg.sender`. | | `campaignName` | `string` | The name of the campaign. | | `campaignStartTime` | `uint40` | The start time of the campaign, as a Unix timestamp. | | `claimType` | `ClaimType` | The type of claim functions to be enabled in the campaign. | | `comptroller` | `address` | The address of the comptroller contract. | | `expiration` | `uint40` | The expiration of the campaign, as a Unix timestamp. A value of zero means the campaign does not expire. | | `initialAdmin` | `address` | The initial admin of the campaign. | | `ipfsCID` | `string` | The content identifier for indexing the contract on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. | | `merkleRoot` | `bytes32` | The Merkle root of the claim data. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | --- ## MerkleExecute Source: https://docs.sablier.com/reference/airdrops/contracts/types/library.MerkleExecute # MerkleExecute [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleExecute.sol) ## Structs ### ConstructorParams Struct encapsulating the constructor parameters of Merkle Execute campaigns. The fields are arranged alphabetically. ```solidity struct ConstructorParams { string campaignName; uint40 campaignStartTime; uint40 expiration; address initialAdmin; string ipfsCID; bytes32 merkleRoot; bytes4 selector; address target; IERC20 token; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `campaignName` | `string` | The name of the campaign. | | `campaignStartTime` | `uint40` | The start time of the campaign, as a Unix timestamp. | | `expiration` | `uint40` | The expiration of the campaign, as a Unix timestamp. A value of zero means the campaign does not expire. | | `initialAdmin` | `address` | The initial admin of the campaign. | | `ipfsCID` | `string` | The content identifier for indexing the contract on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. | | `merkleRoot` | `bytes32` | The Merkle root of the claim data. | | `selector` | `bytes4` | The function selector to call on the target contract when users claim tokens. | | `target` | `address` | The address of the target contract (staking contract, lending pool) to which the function selector will be called when users claim tokens. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | --- ## MerkleInstant Source: https://docs.sablier.com/reference/airdrops/contracts/types/library.MerkleInstant # MerkleInstant [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleInstant.sol) ## Structs ### ConstructorParams Struct encapsulating the constructor parameters of Merkle Instant campaigns. The fields are arranged alphabetically. ```solidity struct ConstructorParams { string campaignName; uint40 campaignStartTime; ClaimType claimType; uint40 expiration; address initialAdmin; string ipfsCID; bytes32 merkleRoot; IERC20 token; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `campaignName` | `string` | The name of the campaign. | | `campaignStartTime` | `uint40` | The start time of the campaign, as a Unix timestamp. | | `claimType` | `ClaimType` | The type of claim functions supported by the campaign. | | `expiration` | `uint40` | The expiration of the campaign, as a Unix timestamp. A value of zero means the campaign does not expire. | | `initialAdmin` | `address` | The initial admin of the campaign. | | `ipfsCID` | `string` | The content identifier for indexing the contract on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. | | `merkleRoot` | `bytes32` | The Merkle root of the claim data. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | --- ## MerkleLL Source: https://docs.sablier.com/reference/airdrops/contracts/types/library.MerkleLL # MerkleLL [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleLL.sol) ## Structs ### ConstructorParams Struct encapsulating the constructor parameters of Merkle Lockup Linear campaigns. The fields are arranged alphabetically. ```solidity struct ConstructorParams { string campaignName; uint40 campaignStartTime; bool cancelable; ClaimType claimType; uint40 cliffDuration; UD60x18 cliffUnlockPercentage; uint40 expiration; uint40 granularity; address initialAdmin; string ipfsCID; ISablierLockup lockup; bytes32 merkleRoot; string shape; UD60x18 startUnlockPercentage; IERC20 token; uint40 totalDuration; bool transferable; uint40 vestingStartTime; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `campaignName` | `string` | The name of the campaign. | | `campaignStartTime` | `uint40` | The start time of the campaign, as a Unix timestamp. | | `cancelable` | `bool` | Indicates if the Lockup stream will be cancelable after claiming. | | `claimType` | `ClaimType` | The type of claim functions supported by the campaign. | | `cliffDuration` | `uint40` | The cliff duration of the vesting stream, in seconds. | | `cliffUnlockPercentage` | `UD60x18` | The percentage of the claim amount due to be unlocked at the vesting cliff time, as a fixed-point number where 1e18 is 100%. | | `expiration` | `uint40` | The expiration of the campaign, as a Unix timestamp. A value of zero means the campaign does not expire. | | `granularity` | `uint40` | The smallest step in time between two consecutive token unlocks. Zero is a sentinel value for 1 second. | | `initialAdmin` | `address` | The initial admin of the campaign. | | `ipfsCID` | `string` | The content identifier for indexing the contract on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `merkleRoot` | `bytes32` | The Merkle root of the claim data. | | `shape` | `string` | The shape of the vesting stream, used for differentiating between streams in the UI. | | `startUnlockPercentage` | `UD60x18` | The percentage of the claim amount due to be unlocked at the vesting start time, as a fixed-point number where 1e18 is 100%. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `totalDuration` | `uint40` | The total duration of the vesting stream, in seconds. | | `transferable` | `bool` | Indicates if the Lockup stream will be transferable after claiming. | | `vestingStartTime` | `uint40` | The start time of the vesting stream, as a Unix timestamp. Zero is a sentinel value for `block.timestamp`. | --- ## MerkleLT Source: https://docs.sablier.com/reference/airdrops/contracts/types/library.MerkleLT # MerkleLT [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleLT.sol) ## Structs ### ConstructorParams Struct encapsulating the constructor parameters of Merkle Lockup Tranched campaigns. The fields are arranged alphabetically. ```solidity struct ConstructorParams { string campaignName; uint40 campaignStartTime; bool cancelable; ClaimType claimType; uint40 expiration; address initialAdmin; string ipfsCID; ISablierLockup lockup; bytes32 merkleRoot; string shape; IERC20 token; MerkleLT.TrancheWithPercentage[] tranchesWithPercentages; bool transferable; uint40 vestingStartTime; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `campaignName` | `string` | The name of the campaign. | | `campaignStartTime` | `uint40` | The start time of the campaign, as a Unix timestamp. | | `cancelable` | `bool` | Indicates if the Lockup stream will be cancelable after claiming. | | `claimType` | `ClaimType` | The type of claim functions supported by the campaign. | | `expiration` | `uint40` | The expiration of the campaign, as a Unix timestamp. A value of zero means the campaign does not expire. | | `initialAdmin` | `address` | The initial admin of the campaign. | | `ipfsCID` | `string` | The content identifier for indexing the contract on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `merkleRoot` | `bytes32` | The Merkle root of the claim data. | | `shape` | `string` | The shape of Lockup stream, used for differentiating between streams in the UI. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `tranchesWithPercentages` | `MerkleLT.TrancheWithPercentage[]` | The tranches with their respective unlock percentages, which are documented in {MerkleLT.TrancheWithPercentage}. | | `transferable` | `bool` | Indicates if the Lockup stream will be transferable after claiming. | | `vestingStartTime` | `uint40` | The start time of the vesting stream, as a Unix timestamp. Zero is a sentinel value for `block.timestamp`. | ### TrancheWithPercentage Struct encapsulating the unlock percentage and duration of a tranche. Since users may have different amounts allocated, this struct makes it possible to calculate the amounts at claim time. An 18-decimal format is used to represent percentages: 100% = 1e18. For more information, see the PRBMath documentation on UD2x18: [https://github.com/PaulRBerg/prb-math](https://github.com/PaulRBerg/prb-math) ```solidity struct TrancheWithPercentage { // slot 0 UD2x18 unlockPercentage; uint40 duration; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `unlockPercentage` | `UD2x18` | The percentage designated to be unlocked in this tranche. | | `duration` | `uint40` | The time difference in seconds between this tranche and the previous one. | --- ## MerkleLockup Source: https://docs.sablier.com/reference/airdrops/contracts/types/library.MerkleLockup # MerkleLockup [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleLockup.sol) ## Structs ### ConstructorParams Struct encapsulating the constructor parameters of [SablierMerkleLockup](/reference/airdrops/contracts/abstracts/abstract.SablierMerkleLockup) contract. The fields are arranged alphabetically. ```solidity struct ConstructorParams { bool cancelable; ISablierLockup lockup; string shape; bool transferable; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `cancelable` | `bool` | Whether Lockup stream will be cancelable after claiming. | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `shape` | `string` | The shape of the vesting stream, used for differentiating between streams in the UI. | | `transferable` | `bool` | Whether Lockup stream will be transferable after claiming. | --- ## MerkleVCA Source: https://docs.sablier.com/reference/airdrops/contracts/types/library.MerkleVCA # MerkleVCA [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/MerkleVCA.sol) ## Structs ### ConstructorParams Struct encapsulating the constructor parameters of Merkle VCA campaigns. The fields are arranged alphabetically. ```solidity struct ConstructorParams { uint128 aggregateAmount; string campaignName; uint40 campaignStartTime; ClaimType claimType; bool enableRedistribution; uint40 expiration; address initialAdmin; string ipfsCID; bytes32 merkleRoot; IERC20 token; UD60x18 unlockPercentage; uint40 vestingEndTime; uint40 vestingStartTime; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `aggregateAmount` | `uint128` | The total amount of ERC-20 tokens to be distributed to all recipients. If its value is set lower than actual total allocations in the Merkle tree, it can either cause a race condition among the recipients or rewards would be calculated as 0 if its too low. As a campaign creator, it is recommended to set the value to the actual total allocations. | | `campaignName` | `string` | The name of the campaign. | | `campaignStartTime` | `uint40` | The start time of the campaign, as a Unix timestamp. | | `claimType` | `ClaimType` | The type of claim functions supported by the campaign. | | `enableRedistribution` | `bool` | Enable redistribution of forgone tokens at deployment. | | `expiration` | `uint40` | The expiration of the campaign, as a Unix timestamp. | | `initialAdmin` | `address` | The initial admin of the campaign. | | `ipfsCID` | `string` | The content identifier for indexing the contract on IPFS. An empty value may break certain UI features that depend upon the IPFS CID. | | `merkleRoot` | `bytes32` | The Merkle root of the claim data. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `unlockPercentage` | `UD60x18` | The percentage of the full amount that will unlock immediately at the start time, denominated as fixed-point number where 1e18 is 100%. | | `vestingEndTime` | `uint40` | Vesting end time, as a Unix timestamp. | | `vestingStartTime` | `uint40` | Vesting start time, as a Unix timestamp. | --- ## Diagrams Source: https://docs.sablier.com/reference/airdrops/diagrams # Diagrams ## Airstream Campaigns In an airstream campaign, there is a vesting of tokens which is powered by Sablier Lockup protocol. A typical airstream campaign creation flow looks like the following: And this is how the claim flow looks like for recipients: If recipient claims after the vesting end date, then no stream is created and the full allocation is transferred directly to the recipient's wallet. ## Instant Airdrop Campaigns In an instant airdrop campaign, there is no vesting and airdropped tokens are claimed directly to the users' wallets. A typical instant airdrop campaign creation flow looks like the following: And this is how the claim flow looks like for recipients: ## Variable Claim Airdrop Campaigns In a variable claim airdrop campaign, there is a vesting of tokens similar to Airstream campaigns, however, the user only receives an amount of token depending on the time elapsed since the start of the campaign. The forfeited amount of tokens is returned back to the project. And this is how a typical claim flow looks like for recipients: ## Clawback For campaign admins, we offer `clawback` functionality which can be used to retrieve unclaimed funds after expiration. In case of variable claim campaign, `clawback` can be used to retrieve forfeited tokens. There is also a grace period that ends 7 days after the first claim is made. During the grace period, admin can `clawback` to return funds from the campaign contract. This is useful in case there had been an accidental transfer of funds. ```mermaid sequenceDiagram actor Campaign admin Campaign admin ->> FactoryMerkleLL: createMerkleLL() FactoryMerkleLL -->> MerkleLL: Deploy a new contract ``` ```mermaid sequenceDiagram actor Airdrop recipient Airdrop recipient ->> MerkleLL: claim() MerkleLL -->> SablierLockup: Create vesting stream MerkleLL -->> SablierLockup: Transfer tokens SablierLockup -->> Airdrop recipient: Mint Stream NFT ``` ```mermaid sequenceDiagram actor Airdrop recipient Airdrop recipient ->> MerkleLL: claim() MerkleLL -->> Airdrop recipient: Transfer tokens ``` ```mermaid sequenceDiagram actor Campaign admin Campaign admin ->> FactoryMerkleInstant: createMerkleInstant() FactoryMerkleInstant -->> MerkleInstant: Deploy a new contract ``` ```mermaid sequenceDiagram actor Airdrop recipient Airdrop recipient ->> MerkleInstant: claim() MerkleInstant -->> Airdrop recipient: Transfer tokens ``` ```mermaid sequenceDiagram actor Campaign admin Campaign admin ->> FactoryMerkleVCA: createMerkleVCA() FactoryMerkleVCA -->> MerkleVCA: Deploy a new contract ``` ```mermaid sequenceDiagram actor Airdrop recipient Airdrop recipient ->> MerkleVCA: claimTo() MerkleVCA -->> Airdrop recipient: Transfer vested tokens MerkleVCA -->> MerkleVCA: unvested tokens are kept for Campaign admin ``` ```mermaid sequenceDiagram actor Campaign admin Campaign admin ->> Merkle Campaign: clawback() Merkle Campaign -->> Campaign admin: Transfer unclaimed/forfeited tokens ``` --- ## Access Control Source: https://docs.sablier.com/reference/bob/access-control # Access Control With the exception of the [admin functions](/concepts/governance#bob), all functions in Bob can be triggered by users. The Comptroller has no control over any vault or deposited tokens. | Action | Anyone | Share Holder | Comptroller | | --- | :---: | :---: | :---: | | Create Vault | ✅ | ✅ | ✅ | | Enter | ✅ | ✅ | ✅ | | Enter with Native | ✅ | ✅ | ✅ | | Sync Price | ✅ | ✅ | ✅ | | Unstake via Adapter | ✅ | ✅ | ✅ | | Redeem | ❌ | ✅ | ❌ | | Transfer Shares | ❌ | ✅ | ❌ | | Set Default Adapter | ❌ | ❌ | ✅ | | Set Native Token | ❌ | ❌ | ✅ | ## Create Vault Anyone can create a vault. ## Enter Anyone can deposit tokens into an active vault. ## Sync Price Anyone can trigger a price sync on any active vault. ## Redeem Only a share holder can redeem from a settled or expired vault. ```mermaid sequenceDiagram actor Anyone Anyone ->> SablierBob: createVault() ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> SablierBob: enter() Anyone -->> SablierBob: Transfer tokens ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> SablierBob: syncPriceFromOracle() ``` ```mermaid sequenceDiagram actor ShareHolder ShareHolder ->> SablierBob: redeem() SablierBob -->> ShareHolder: Transfer tokens ``` --- ## Comptrollerable Source: https://docs.sablier.com/reference/bob/contracts/abstracts/abstract.Comptrollerable # Comptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/Comptrollerable.sol) **Inherits:** [IComptrollerable](/reference/bob/contracts/interfaces/interface.IComptrollerable) **Title:** Comptrollerable See the documentation in [IComptrollerable](/reference/bob/contracts/interfaces/interface.IComptrollerable). ## State Variables ### comptroller Retrieves the address of the comptroller contract. ```solidity ISablierComptroller public override comptroller ``` ## Functions ### onlyComptroller Reverts if called by any account other than the comptroller. ```solidity modifier onlyComptroller() ; ``` ### constructor ```solidity constructor(address initialComptroller) ; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### setComptroller Sets the comptroller to a new address. Emits a {SetComptroller} event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a {TransferFeesToComptroller} event. ```solidity function transferFeesToComptroller() external override; ``` ### \_checkComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _checkComptroller() private view; ``` ### \_setComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _setComptroller( ISablierComptroller previousComptroller, ISablierComptroller newComptroller, bytes4 minimalInterfaceId ) private; ``` --- ## SablierBobState Source: https://docs.sablier.com/reference/bob/contracts/abstracts/abstract.SablierBobState # SablierBobState [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierBobState.sol) **Inherits:** [ISablierBobState](/reference/bob/contracts/interfaces/interface.ISablierBobState) **Title:** SablierBobState See the documentation in [ISablierBobState](/reference/bob/contracts/interfaces/interface.ISablierBobState). ## State Variables ### \_defaultAdapters Default adapters mapped by token address. ```solidity mapping(IERC20 token => ISablierBobAdapter adapter) internal _defaultAdapters ``` ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity address public override nativeToken ``` ### nextVaultId Counter for vault IDs, incremented every time a new vault is created. ```solidity uint256 public override nextVaultId ``` ### \_vaults Vaults mapped by unsigned integers. ```solidity mapping(uint256 vaultId => Bob.Vault vault) internal _vaults ``` ## Functions ### notNull Checks that `vaultId` does not reference a null vault. ```solidity modifier notNull(uint256 vaultId) ; ``` ### constructor Initializes the state variables. ```solidity constructor() ; ``` ### getAdapter Returns the adapter configured for a specific vault. Reverts if `vaultId` references a null vault. ```solidity function getAdapter(uint256 vaultId) external view override notNull(vaultId) returns (ISablierBobAdapter adapter); ``` ### getDefaultAdapterFor Returns the default adapter for a given token. Zero address means no adapter is set. ```solidity function getDefaultAdapterFor(IERC20 token) external view override returns (ISablierBobAdapter adapter); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The ERC-20 token to query the default adapter for. | **Returns** | Name | Type | Description | | --- | --- | --- | | `adapter` | `ISablierBobAdapter` | The default adapter for the token. | ### getExpiry Returns the timestamp when the vault expires. Reverts if `vaultId` references a null vault. ```solidity function getExpiry(uint256 vaultId) external view override notNull(vaultId) returns (uint40 expiry); ``` ### getLastSyncedAt Returns the timestamp when the oracle price was last synced for a vault. Reverts if `vaultId` references a null vault. ```solidity function getLastSyncedAt(uint256 vaultId) external view override notNull(vaultId) returns (uint40 lastSyncedAt); ``` ### getLastSyncedPrice Returns the oracle price stored for a vault. Reverts if `vaultId` references a null vault. ```solidity function getLastSyncedPrice(uint256 vaultId) external view override notNull(vaultId) returns (uint128 lastSyncedPrice); ``` ### getOracle Returns the oracle address set for a vault. Reverts if `vaultId` references a null vault. ```solidity function getOracle(uint256 vaultId) external view override notNull(vaultId) returns (AggregatorV3Interface oracle); ``` ### getShareToken Returns the address of the ERC-20 share token for a vault. Reverts if `vaultId` references a null vault. ```solidity function getShareToken(uint256 vaultId) external view override notNull(vaultId) returns (IBobVaultShare shareToken); ``` ### getTargetPrice Returns the target price at which the vault settles. Reverts if `vaultId` references a null vault. ```solidity function getTargetPrice(uint256 vaultId) external view override notNull(vaultId) returns (uint128 targetPrice); ``` ### getUnderlyingToken Returns the ERC-20 token accepted for deposits in a vault. Reverts if `vaultId` references a null vault. ```solidity function getUnderlyingToken(uint256 vaultId) external view override notNull(vaultId) returns (IERC20 token); ``` ### isStakedInAdapter Returns whether the vault tokens are staked in an adapter. Reverts if `vaultId` references a null vault. ```solidity function isStakedInAdapter(uint256 vaultId) external view override notNull(vaultId) returns (bool stakedInAdapter); ``` ### statusOf Returns the vault status. Reverts if `vaultId` references a null vault. ```solidity function statusOf(uint256 vaultId) external view override notNull(vaultId) returns (Bob.Status status); ``` ### \_statusOf Retrieves the vault's status without performing a null check. ```solidity function _statusOf(uint256 vaultId) internal view returns (Bob.Status); ``` ### \_notNull Reverts if `vaultId` references a null vault. A private function is used instead of inlining this logic in a modifier because Solidity copies modifiers into every function that uses them. ```solidity function _notNull(uint256 vaultId) private view; ``` --- ## BobVaultShare Source: https://docs.sablier.com/reference/bob/contracts/contract.BobVaultShare # BobVaultShare [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/BobVaultShare.sol) **Inherits:** ERC20, [IBobVaultShare](/reference/bob/contracts/interfaces/interface.IBobVaultShare) **Title:** BobVaultShare ERC-20 token representing shares in a Bob vault. Each vault has its own BobVaultShare deployed. Only the SablierBob contract can mint and burn tokens. When shares are transferred, wstETH attribution is updated proportionally via the adapter. ## Constants ### SABLIER\_BOB Returns the address of the Bob contract with the authority to mint and burn tokens. This is an immutable state variable. ```solidity address public immutable override SABLIER_BOB ``` ### VAULT\_ID Returns the vault ID this share token represents. This is an immutable state variable. ```solidity uint256 public immutable override VAULT_ID ``` ### \_DECIMALS The number of decimals. ```solidity uint8 internal immutable _DECIMALS ``` ## Functions ### onlySablierBob Reverts if caller is not the Bob contract. ```solidity modifier onlySablierBob() ; ``` ### onlyVault Reverts if the vault ID is not equal to {VAULT\_ID}. ```solidity modifier onlyVault(uint256 vaultId) ; ``` ### constructor Deploys the vault share token. ```solidity constructor( string memory name_, string memory symbol_, uint8 decimals_, address sablierBob, uint256 vaultId ) ERC20(name_, symbol_); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `name_` | `string` | The name of the token (e.g., "Sablier Bob WETH Vault #1"). | | `symbol_` | `string` | The symbol of the token (e.g., "WETH-500000000000-1792790393-1" with $5000 target price) with the first number being the target price denominated in Chainlink's 8-decimal format, where 1e8 is$1. | | `decimals_` | `uint8` | The number of decimals. | | `sablierBob` | `address` | The address of the SablierBob contract. | | `vaultId` | `uint256` | The ID of the vault this share token represents. | ### decimals Returns the number of decimals used by the token. ```solidity function decimals() public view override(ERC20, IERC20Metadata) returns (uint8); ``` ### mint Mints `amount` tokens to `to`. Emits a {Transfer} event. Requirements: - The caller must be the SablierBob contract. - `vaultId` must be equal to the {VAULT\_ID}. ```solidity function mint(uint256 vaultId, address to, uint256 amount) external override onlySablierBob onlyVault(vaultId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The vault ID that this share token represents. | | `to` | `address` | The address to mint tokens to. | | `amount` | `uint256` | The amount of tokens to mint. | ### burn Burns `amount` tokens from `from`. Emits a {Transfer} event. Requirements: - The caller must be the SablierBob contract. - `vaultId` must be equal to the {VAULT\_ID}. - `from` must have at least `amount` tokens. ```solidity function burn(uint256 vaultId, address from, uint256 amount) external override onlySablierBob onlyVault(vaultId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The vault ID that this share token represents. | | `from` | `address` | The address to burn tokens from. | | `amount` | `uint256` | The amount of tokens to burn. | ### \_update Override to notify SablierBob when shares are transferred (not minted/burned). This allows the adapter to update wstETH attribution proportionally. ```solidity function _update(address from, address to, uint256 amount) internal override; ``` --- ## SablierBob Source: https://docs.sablier.com/reference/bob/contracts/contract.SablierBob # SablierBob [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierBob.sol) **Inherits:** [Comptrollerable](/reference/bob/contracts/abstracts/abstract.Comptrollerable), [ISablierBob](/reference/bob/contracts/interfaces/interface.ISablierBob), ReentrancyGuard, [SablierBobState](/reference/bob/contracts/abstracts/abstract.SablierBobState) **Title:** SablierBob See the documentation in [ISablierBob](/reference/bob/contracts/interfaces/interface.ISablierBob). ## Functions ### onlyActive Modifier to check that the vault is active. ```solidity modifier onlyActive(uint256 vaultId) ; ``` ### constructor ```solidity constructor(address initialComptroller) [Comptrollerable](/docs/reference/05-bob/contracts/abstracts/abstract.Comptrollerable.md)(initialComptroller) SablierBobState(); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### calculateMinFeeWei Calculates the minimum fee in wei required to redeem from the given vault ID. Returns 0 for vaults with an adapter, since the fee is taken from yield generated. Reverts if `vaultId` references a null vault. ```solidity function calculateMinFeeWei(uint256 vaultId) external view override notNull(vaultId) returns (uint256 minFeeWei); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The vault ID for the query. | ### createVault Creates a new vault with the specified parameters. Emits a {CreateVault} event. Notes: - A new ERC-20 share token is deployed for each vault to represent user's share of deposits in the vault. - The default adapter for the token is copied as the vault adapter. Any change in the default adapter does not affect existing vaults. - Vault creator is responsible for choosing a valid oracle. They should use Chainlink oracles, as the integration is based on their API. Requirements: - `token` must not be the zero address. - `token` must implement `symbol()` and `decimals()` functions. - `expiry` must be in the future. - `oracle` must implement the Chainlink's {AggregatorV3Interface} interface. - `oracle` must return a positive price when `latestRoundData()` is called. - `oracle` must return a non-zero value no greater than 36 when `decimals()` is called. - `targetPrice` must not be zero or greater than the current price returned by the provided oracle. ```solidity function createVault( IERC20 token, AggregatorV3Interface oracle, uint40 expiry, uint128 targetPrice ) external override returns (uint256 vaultId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The address of the ERC-20 token that will be accepted for deposits. | | `oracle` | `AggregatorV3Interface` | The address of the price feed oracle for the deposit token. | | `expiry` | `uint40` | The Unix timestamp when the vault expires. | | `targetPrice` | `uint128` | The target price at which the vault settles, denominated in Chainlink's 8-decimal format for USD prices, where 1e8 is $1. | **Returns** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the newly created vault. | ### enter Enter into a vault by depositing tokens into it and minting share tokens to the caller. Emits an {Enter} event. Notes: - If an adapter is configured for the vault, tokens are automatically staked for yield using the adapter. - Share tokens are minted 1:1 with the deposited amount. Requirements: - `vaultId` must not reference a null vault. - The vault must have ACTIVE status. - `amount` must be greater than zero. - The caller must have approved this contract to transfer `amount` tokens. ```solidity function enter(uint256 vaultId, uint128 amount) external override nonReentrant notNull(vaultId) onlyActive(vaultId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to deposit into. | | `amount` | `uint128` | The amount of tokens to deposit. | ### enterWithNativeToken Enter into a vault by depositing native token (such as ETH, POL, etc.) into it and minting share tokens to the caller. Emits an {Enter} event. Notes: - `msg.value` is used as the deposit amount. - See notes for {enter}. Requirements: - See requirements for {enter}. - `msg.value` must be greater than zero and must not exceed `type(uint128).max`. ```solidity function enterWithNativeToken(uint256 vaultId) external payable override nonReentrant notNull(vaultId) onlyActive(vaultId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to deposit into. | ### redeem Redeem the tokens by burning user shares. Emits a {Redeem} event. Notes: - If no adapter is configured for the vault, a fee in the native token is applied. - If an adapter is configured for the vault, a fee, in the deposit token, is deducted from yield generated by the adapter. - If unstake via Lido withdrawal queue contract is triggered, redeem will revert until the withdrawal from the Lido queue is finalized. Requirements: - `vaultId` must not reference a null vault. - Either block timestamp must be greater than or equal to the vault expiry or the latest price from the oracle must be greater than or equal to the target price. - The share balance of the caller must be greater than zero. - If no adapter is configured for the vault, `msg.value` must be greater than or equal to the min fee required in the native token. ```solidity function redeem(uint256 vaultId) external payable override nonReentrant notNull(vaultId) returns (uint128 transferAmount, uint128 feeAmountDeductedFromYield); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to redeem from. | **Returns** | Name | Type | Description | | --- | --- | --- | | `transferAmount` | `uint128` | The amount of tokens transferred to the caller, after fees are deducted (only applicable if adapter is set). | | `feeAmountDeductedFromYield` | `uint128` | The fee amount deducted from the yield. Zero if no adapter is set. | ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for {nativeToken}. Emits a {SetNativeToken} event. Requirements: - `msg.sender` must be the comptroller. - `newNativeToken` must not be zero address. - The native token must not be already set. ```solidity function setNativeToken(address newNativeToken) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ### setDefaultAdapter Sets the default adapter for a specific token. Emits a {SetDefaultAdapter} event. Notes: - This only affects future vaults. Requirements: - The caller must be the comptroller. - If new adapter is not zero address, it must implement [ISablierBobAdapter](/reference/bob/contracts/interfaces/interface.ISablierBobAdapter) interface. ```solidity function setDefaultAdapter(IERC20 token, ISablierBobAdapter newAdapter) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The token address to set the adapter for. | | `newAdapter` | `ISablierBobAdapter` | The address of the new adapter. | ### syncPriceFromOracle Fetches the latest price from the oracle set for a vault and updates it in the vault storage. Emits a {SyncPriceFromOracle} event. Notes: - Oracle staleness is not validated on-chain when calling this function. Any price returned by the oracle is accepted. - Useful for syncing the price from oracle without calling {redeem} or {enter}. This function can be called by anyone to settle vault when the price is above the target price. Requirements: - `vaultId` must not reference a null vault. - The vault must have ACTIVE status. - The oracle must return a positive price. ```solidity function syncPriceFromOracle(uint256 vaultId) external override nonReentrant notNull(vaultId) onlyActive(vaultId) returns (uint128 latestPrice); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to sync. | **Returns** | Name | Type | Description | | --- | --- | --- | | `latestPrice` | `uint128` | The latest price fetched from the oracle, denominated in Chainlink's 8-decimal format for USD prices, where 1e8 is $1. | ### unstakeTokensViaAdapter Unstake all tokens from the adapter for a given vault. Emits an {UnstakeFromAdapter} event. Requirements: - `vaultId` must not reference a null vault. - The adapter set in the vault must not be zero address. - Either block timestamp must be greater than or equal to the vault expiry or the latest price from the oracle must be greater than or equal to the target price. - The vault must not have been unstaked already. - The amount staked must be greater than zero. ```solidity function unstakeTokensViaAdapter(uint256 vaultId) external override nonReentrant notNull(vaultId) returns (uint128 amountReceivedFromAdapter); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `amountReceivedFromAdapter` | `uint128` | The amount of tokens received from the adapter. | ### onShareTransfer Called by adapter when share tokens for a given vault are transferred between users. This is required for accounting of the yield generated by the adapter. Requirements: - The caller must be the share token contract stored in the given vault. - The calculated wstETH transfer amount must not be zero. ```solidity function onShareTransfer( uint256 vaultId, address from, address to, uint256 amount, uint256 fromBalanceBefore ) external override; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `from` | `address` | The address transferring share tokens. | | `to` | `address` | The address receiving share tokens. | | `amount` | `uint256` | The number of share tokens being transferred. | | `fromBalanceBefore` | `uint256` | The number of share tokens the sender had before the transfer. | ### \_enter Common function to enter into a vault by depositing tokens into it and minting share tokens to caller. ```solidity function _enter(uint256 vaultId, address from, uint128 amount, IERC20 token) private; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to deposit into. | | `from` | `address` | The address holding the vault token when calling this function. In case of native token deposits, the vault tokens are held by this contract. | | `amount` | `uint128` | The amount of tokens to deposit. | | `token` | `IERC20` | The ERC-20 token accepted by the vault. | ### \_revertIfSettledOrExpired Private function that reverts if the vault is settled or expired. ```solidity function _revertIfSettledOrExpired(uint256 vaultId) private view; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | ### \_syncPriceFromOracle Private function to fetch the latest oracle price and update it in the vault storage. ```solidity function _syncPriceFromOracle(uint256 vaultId) private returns (uint128 latestPrice); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `latestPrice` | `uint128` | The latest price from the oracle. | ### \_unstakeFullAmountViaAdapter Private function to unstake all tokens using the adapter. ```solidity function _unstakeFullAmountViaAdapter( uint256 vaultId, ISablierBobAdapter adapter ) private returns (uint128 amountReceivedFromAdapter); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `adapter` | `ISablierBobAdapter` | The adapter to use for unstaking. | **Returns** | Name | Type | Description | | --- | --- | --- | | `amountReceivedFromAdapter` | `uint128` | The amount of tokens received from the adapter after unstaking. | --- ## SablierLidoAdapter Source: https://docs.sablier.com/reference/bob/contracts/contract.SablierLidoAdapter # SablierLidoAdapter [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierLidoAdapter.sol) **Inherits:** [Comptrollerable](/reference/bob/contracts/abstracts/abstract.Comptrollerable), ERC165, [ISablierLidoAdapter](/reference/bob/contracts/interfaces/interface.ISablierLidoAdapter) **Title:** SablierLidoAdapter Lido yield adapter for the SablierBob protocol. This adapter stakes WETH as wstETH to earn Lido staking rewards. ## Constants ### CURVE\_POOL Returns the address of the Curve stETH/ETH pool. This is an immutable state variable. ```solidity address public immutable override CURVE_POOL ``` ### LIDO\_WITHDRAWAL\_QUEUE Returns the address of the Lido withdrawal queue contract. This is an immutable state variable. ```solidity address public immutable override LIDO_WITHDRAWAL_QUEUE ``` ### MAX\_FEE Returns the maximum yield fee, denominated in UD60x18, where 1e18 = 100%. This is a constant state variable. ```solidity UD60x18 public constant override MAX_FEE = UD60x18.wrap(0.2e18) ``` ### MAX\_SLIPPAGE\_TOLERANCE Returns the maximum slippage tolerance that can be set, denominated in UD60x18, where 1e18 = 100%. This is a constant state variable. ```solidity UD60x18 public constant override MAX_SLIPPAGE_TOLERANCE = UD60x18.wrap(0.05e18) ``` ### STETH Returns the address of the stETH contract. This is an immutable state variable. ```solidity address public immutable override STETH ``` ### STETH\_ETH\_ORACLE Returns the address of the Chainlink stETH/ETH oracle used in the calculation of `minEthOut` slippage. This is an immutable state variable. ```solidity address public immutable override STETH_ETH_ORACLE ``` ### WETH Returns the address of the WETH contract. This is an immutable state variable. ```solidity address public immutable override WETH ``` ### WSTETH Returns the address of the wstETH contract. This is an immutable state variable. ```solidity address public immutable override WSTETH ``` ### SABLIER\_BOB Returns the address of the SablierBob contract. This is an immutable state variable. ```solidity address public immutable override SABLIER_BOB ``` ## State Variables ### feeOnYield Returns the current global fee on yield for new vaults, denominated in UD60x18, where 1e18 = 100%. ```solidity UD60x18 public override feeOnYield ``` ### slippageTolerance Returns the current slippage tolerance for Curve swaps, denominated in UD60x18, where 1e18 = 100%. ```solidity UD60x18 public override slippageTolerance ``` ### \_lidoWithdrawalRequestIds Lido withdrawal request IDs for each vault. ```solidity mapping(uint256 vaultId => uint256[] requestIds) internal _lidoWithdrawalRequestIds ``` ### \_userWstETH wstETH amount held for each user in each vault. ```solidity mapping(uint256 vaultId => mapping(address user => uint128 wstETHAmount)) internal _userWstETH ``` ### \_vaultTotalWstETH Total wstETH amount held in each vault. ```solidity mapping(uint256 vaultId => uint128 totalWstETH) internal _vaultTotalWstETH ``` ### \_vaultYieldFee Yield fee snapshotted for each vault at creation time. ```solidity mapping(uint256 vaultId => UD60x18 fee) internal _vaultYieldFee ``` ### \_wethReceivedAfterUnstaking Total WETH received after unstaking all tokens in a vault. ```solidity mapping(uint256 vaultId => uint128 wethReceived) internal _wethReceivedAfterUnstaking ``` ## Functions ### constructor Deploys the Lido adapter. ```solidity constructor( address initialComptroller, address sablierBob, address curvePool, address lidoWithdrawalQueue, address steth, address stethEthOracle, address weth, address wsteth, UD60x18 initialSlippageTolerance, UD60x18 initialYieldFee ) [Comptrollerable](/docs/reference/05-bob/contracts/abstracts/abstract.Comptrollerable.md)(initialComptroller); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | | `sablierBob` | `address` | The address of the SablierBob contract. | | `curvePool` | `address` | The address of the Curve stETH/ETH pool. | | `lidoWithdrawalQueue` | `address` | The address of the Lido withdrawal queue contract. | | `steth` | `address` | The address of the stETH contract. | | `stethEthOracle` | `address` | The address of the Chainlink's stETH/ETH oracle. | | `weth` | `address` | The address of the WETH contract. | | `wsteth` | `address` | The address of the wstETH contract. | | `initialSlippageTolerance` | `UD60x18` | The initial slippage tolerance for Curve swaps as UD60x18. | | `initialYieldFee` | `UD60x18` | The initial yield fee as UD60x18. | ### onlySablierBob Reverts if the caller is not SablierBob. ```solidity modifier onlySablierBob() ; ``` ### getLidoWithdrawalRequestIds Returns the Lido withdrawal request IDs for a vault. Multiple request IDs may be generated for a vault if the total amount exceeds the Lido enforced per-withdrawal limit. ```solidity function getLidoWithdrawalRequestIds(uint256 vaultId) external view override returns (uint256[] memory); ``` ### getTotalYieldBearingTokenBalance Returns the total amount of yield-bearing tokens held in a vault. ```solidity function getTotalYieldBearingTokenBalance(uint256 vaultId) external view override returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The total amount of yield-bearing tokens in the vault. | ### getVaultYieldFee Returns the yield fee stored for a specific vault. ```solidity function getVaultYieldFee(uint256 vaultId) external view override returns (UD60x18); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `UD60x18` | The yield fee for the vault denominated in UD60x18, where 1e18 = 100%. | ### getWethReceivedAfterUnstaking Returns the total WETH received after unstaking for a vault. ```solidity function getWethReceivedAfterUnstaking(uint256 vaultId) external view override returns (uint256); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | ### getYieldBearingTokenBalanceFor Returns the amount of yield-bearing tokens held for a specific user in a vault. ```solidity function getYieldBearingTokenBalanceFor(uint256 vaultId, address user) external view override returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `user` | `address` | The address of the user. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The amount of yield-bearing tokens the user has claim to. | ### supportsInterface See {IERC165-supportsInterface}. ```solidity function supportsInterface(bytes4 interfaceId) public view override(ERC165, IERC165) returns (bool); ``` ### processRedemption Processes a user's token redemption by calculating the transfer amount, clearing the user's yield-bearing token balance, and returning the amounts. Notes: - The user's yield-bearing token balance is decremented after calculating the transfer amount. This does not decrement the vault total as it is used in the calculation of the transfer amount for other users. Requirements: - The caller must be the SablierBob contract. ```solidity function processRedemption( uint256 vaultId, address user, uint128 shareBalance ) external override onlySablierBob returns (uint128 transferAmount, uint128 feeAmountDeductedFromYield); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `user` | `address` | The address of the user. | | `shareBalance` | `uint128` | The user's share balance in the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `transferAmount` | `uint128` | The amount to transfer to the user. | | `feeAmountDeductedFromYield` | `uint128` | The fee amount taken from the yield. | ### registerVault Register a new vault with the adapter and snapshot the current fee on yield. Requirements: - The caller must be the SablierBob contract. ```solidity function registerVault(uint256 vaultId) external override onlySablierBob; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the newly created vault. | ### requestLidoWithdrawal Requests a native Lido withdrawal for a vault's staked tokens, bypassing the Curve swap. Emits a {RequestLidoWithdrawal} event. Notes: - This unwraps the vault's wstETH to stETH and submits it to Lido's withdrawal queue. - Once called, the Curve swap is permanently disabled for `vaultId`. - After the queue finalizes the withdrawal, ETH can be redeemed by calling {unstakeFullAmount}. - Large amounts are automatically split into multiple requests to comply with Lido's per-request limit. Requirements: - The caller must be the comptroller. - The status of the vault must not be ACTIVE. - The vault must still be staked in the adapter (not already unstaked via Curve). - A withdrawal request must not have already been requested for this vault. - The vault must have wstETH to withdraw. - The total amount to withdraw must not be less than the minimum amount per request. ```solidity function requestLidoWithdrawal(uint256 vaultId) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | ### setSlippageTolerance Sets the slippage tolerance for Curve swaps. Emits a {SetSlippageTolerance} event. Notes: - This affects all vaults. Requirements: - The caller must be the comptroller. - `newSlippageTolerance` must not exceed MAX\_SLIPPAGE\_TOLERANCE. ```solidity function setSlippageTolerance(UD60x18 newTolerance) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newTolerance` | `UD60x18` | The new slippage tolerance as UD60x18. | ### setYieldFee Sets the fee on yield for future vaults. Emits a {SetYieldFee} event. Notes: - This only affects future vaults, fee is not updated for existing vaults. Requirements: - The caller must be the comptroller. - `newFee` must not exceed MAX\_FEE. ```solidity function setYieldFee(UD60x18 newFee) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newFee` | `UD60x18` | The new yield fee as UD60x18 where 1e18 = 100%. | ### stake Stakes tokens deposited by a user in a vault, converting them to yield-bearing tokens. Emits a {Stake} event. Requirements: - The caller must be the SablierBob contract. - The tokens must have been transferred to this contract. ```solidity function stake(uint256 vaultId, address user, uint256 amount) external override onlySablierBob; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `user` | `address` | The address of the user depositing the tokens. | | `amount` | `uint256` | The amount of tokens to stake. | ### unstakeFullAmount Converts all yield-bearing tokens in a vault back to deposit tokens after settlement. Emits an {UnstakeFullAmount} event. Notes: - This should only be called once per vault after settlement. Requirements: - The caller must be the SablierBob contract. ```solidity function unstakeFullAmount(uint256 vaultId) external override onlySablierBob returns (uint128 totalWstETH, uint128 amountReceivedFromUnstaking); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `totalWstETH` | `uint128` | wrappedTokenBalance The total amount of yield-bearing tokens that were in the vault. | | `amountReceivedFromUnstaking` | `uint128` | The total amount of tokens received from unstaking the yield-bearing tokens. | ### updateStakedTokenBalance Updates staked token balance of a user when vault shares are transferred. Requirements: - The caller must be the SablierBob contract. - `userShareBalanceBeforeTransfer` must not be zero. - The calculated wstETH transfer amount must not be zero. ```solidity function updateStakedTokenBalance( uint256 vaultId, address from, address to, uint256 shareAmountTransferred, uint256 userShareBalanceBeforeTransfer ) external override onlySablierBob; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `from` | `address` | The address transferring vault shares. | | `to` | `address` | The address receiving vault shares. | | `shareAmountTransferred` | `uint256` | The number of vault shares being transferred. | | `userShareBalanceBeforeTransfer` | `uint256` | The sender's vault share balance before the transfer. | ### \_claimLidoWithdrawals Claims finalized Lido withdrawals for a vault and wraps the received ETH into WETH. ```solidity function _claimLidoWithdrawals(uint256 vaultId) private returns (uint128 wethReceived); ``` ### \_swapWstETHToWeth Swap wstETH to WETH using Curve exchange, with oracle-based slippage protection. ```solidity function _swapWstETHToWeth(uint128 wstETHAmount) private returns (uint128 wethReceived); ``` ### receive ```solidity receive() external payable; ``` --- ## ICurveStETHPool Source: https://docs.sablier.com/reference/bob/contracts/interfaces/external/interface.ICurveStETHPool # ICurveStETHPool [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/external/ICurveStETHPool.sol) **Title:** ICurveStETHPool Minimal interface for the Curve stETH/ETH pool. The pool has two tokens: ETH (index 0) and stETH (index 1). ## Functions ### get\_dy Get the amount of output coin for a given input. ```solidity function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256 dy); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `i` | `int128` | The index of the input coin. | | `j` | `int128` | The index of the output coin. | | `dx` | `uint256` | The amount of input coin. | **Returns** | Name | Type | Description | | --- | --- | --- | | `dy` | `uint256` | The expected amount of output coin. | ### exchange Exchange between two tokens in the pool. ```solidity function exchange(int128 i, int128 j, uint256 dx, uint256 minDy) external payable returns (uint256 dy); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `i` | `int128` | The index of the input coin (0 = ETH, 1 = stETH). | | `j` | `int128` | The index of the output coin (0 = ETH, 1 = stETH). | | `dx` | `uint256` | The amount of input coin to exchange. | | `minDy` | `uint256` | The minimum amount of output coin to receive. | **Returns** | Name | Type | Description | | --- | --- | --- | | `dy` | `uint256` | The actual amount of output coin received. | --- ## ILidoWithdrawalQueue Source: https://docs.sablier.com/reference/bob/contracts/interfaces/external/interface.ILidoWithdrawalQueue # ILidoWithdrawalQueue [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/external/ILidoWithdrawalQueue.sol) **Title:** ILidoWithdrawalQueue Minimal interface for Lido's WithdrawalQueueERC721 contract. Used as a fallback unstaking path when the Curve pool is unavailable. ## Functions ### MAX\_STETH\_WITHDRAWAL\_AMOUNT Maximum amount of stETH that can be withdrawn in a single request. ```solidity function MAX_STETH_WITHDRAWAL_AMOUNT() external view returns (uint256); ``` ### MIN\_STETH\_WITHDRAWAL\_AMOUNT Minimum amount of stETH that can be withdrawn in a single request. ```solidity function MIN_STETH_WITHDRAWAL_AMOUNT() external view returns (uint256); ``` ### findCheckpointHints Finds the list of hints for the given `_requestIds` searching among the checkpoints with indices in the range `[_firstIndex, _lastIndex]`. - Array of request IDs should be sorted. - `_firstIndex` should be greater than 0, because checkpoint list is 1-based array. - `_lastIndex` should be less than or equal to `getLastCheckpointIndex()`. ```solidity function findCheckpointHints( uint256[] calldata _requestIds, uint256 _firstIndex, uint256 _lastIndex ) external view returns (uint256[] memory hintIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `_requestIds` | `uint256[]` | IDs of the requests sorted in the ascending order to get hints for. | | `_firstIndex` | `uint256` | Left boundary of the search range. Should be greater than 0. | | `_lastIndex` | `uint256` | Right boundary of the search range. Should be less than or equal to `getLastCheckpointIndex()`. | **Returns** | Name | Type | Description | | --- | --- | --- | | `hintIds` | `uint256[]` | Array of hints used to find required checkpoint for the request. | ### getLastCheckpointIndex Length of the checkpoint array. Last possible value for the hint. ```solidity function getLastCheckpointIndex() external view returns (uint256); ``` ### claimWithdrawals Claim a batch of withdrawal requests if they are finalized sending locked ETH to the owner. Reverts if any of the following conditions are met: - `requestIds` and `hints` arrays length differs. - Any `requestId` or `hint` in arguments are not valid. - Any request is not finalized or already claimed. - `msg.sender` is not an owner of the requests. ```solidity function claimWithdrawals(uint256[] calldata _requestIds, uint256[] calldata _hints) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `_requestIds` | `uint256[]` | Array of request IDs to claim. | | `_hints` | `uint256[]` | Checkpoint hint for each ID. Can be obtained with `findCheckpointHints()` | ### requestWithdrawals Request the batch of stETH for withdrawal. Approvals for the passed amounts should be done before. ```solidity function requestWithdrawals( uint256[] calldata _amounts, address _owner ) external returns (uint256[] memory requestIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `_amounts` | `uint256[]` | Array of stETH amount values. The standalone withdrawal request will be created for each item in the passed list. | | `_owner` | `address` | Address that will be able to manage the created requests. If `address(0)` is passed, `msg.sender` will be used as owner. | **Returns** | Name | Type | Description | | --- | --- | --- | | `requestIds` | `uint256[]` | Array of the created withdrawal request IDs. | --- ## IStETH Source: https://docs.sablier.com/reference/bob/contracts/interfaces/external/interface.IStETH # IStETH [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/external/IStETH.sol) **Inherits:** IERC20 **Title:** IStETH Minimal interface for Lido's stETH. ## Functions ### submit Send funds to the Lido pool with the optional referral parameter and mints stETH. The amount of stETH minted equals the amount of ETH sent. ```solidity function submit(address referral) external payable returns (uint256 amount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `referral` | `address` | The referral address can be zero. | **Returns** | Name | Type | Description | | --- | --- | --- | | `amount` | `uint256` | The amount of stETH minted. | --- ## IWETH9 Source: https://docs.sablier.com/reference/bob/contracts/interfaces/external/interface.IWETH9 # IWETH9 [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/external/IWETH9.sol) **Inherits:** IERC20 **Title:** IWETH9 Minimal interface for Wrapped Ether. ## Functions ### deposit Deposits ETH and mints WETH. ```solidity function deposit() external payable; ``` ### withdraw Burns WETH and withdraws ETH. ```solidity function withdraw(uint256 amount) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `amount` | `uint256` | The amount of WETH to burn. | --- ## IWstETH Source: https://docs.sablier.com/reference/bob/contracts/interfaces/external/interface.IWstETH # IWstETH [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/external/IWstETH.sol) **Inherits:** IERC20 **Title:** IWstETH Minimal interface for Lido's wstETH. ## Functions ### getStETHByWstETH Returns the amount of stETH for a given amount of wstETH. ```solidity function getStETHByWstETH(uint256 wstETHAmount) external view returns (uint256); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `wstETHAmount` | `uint256` | The amount of wstETH. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint256` | The equivalent amount of stETH. | ### getWstETHByStETH Returns the amount of wstETH for a given amount of stETH. ```solidity function getWstETHByStETH(uint256 stETHAmount) external view returns (uint256); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `stETHAmount` | `uint256` | The amount of stETH. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint256` | The equivalent amount of wstETH. | ### wrap Wraps stETH to wstETH. ```solidity function wrap(uint256 stETHAmount) external returns (uint256 wstETHAmount); ``` ### unwrap Unwraps wstETH to stETH. ```solidity function unwrap(uint256 wstETHAmount) external returns (uint256 stETHAmount); ``` --- ## IBobVaultShare Source: https://docs.sablier.com/reference/bob/contracts/interfaces/interface.IBobVaultShare # IBobVaultShare [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IBobVaultShare.sol) **Inherits:** IERC20Metadata **Title:** IBobVaultShare Interface for the ERC-20 token representing shares in a Bob vault. ## Functions ### SABLIER\_BOB Returns the address of the Bob contract with the authority to mint and burn tokens. This is an immutable state variable. ```solidity function SABLIER_BOB() external view returns (address); ``` ### VAULT\_ID Returns the vault ID this share token represents. This is an immutable state variable. ```solidity function VAULT_ID() external view returns (uint256); ``` ### mint Mints `amount` tokens to `to`. Emits a {Transfer} event. Requirements: - The caller must be the SablierBob contract. - `vaultId` must be equal to the [VAULT\_ID](/reference/bob/contracts/interfaces/interface.IBobVaultShare#vault_id). ```solidity function mint(uint256 vaultId, address to, uint256 amount) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The vault ID that this share token represents. | | `to` | `address` | The address to mint tokens to. | | `amount` | `uint256` | The amount of tokens to mint. | ### burn Burns `amount` tokens from `from`. Emits a {Transfer} event. Requirements: - The caller must be the SablierBob contract. - `vaultId` must be equal to the [VAULT\_ID](/reference/bob/contracts/interfaces/interface.IBobVaultShare#vault_id). - `from` must have at least `amount` tokens. ```solidity function burn(uint256 vaultId, address from, uint256 amount) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The vault ID that this share token represents. | | `from` | `address` | The address to burn tokens from. | | `amount` | `uint256` | The amount of tokens to burn. | --- ## IComptrollerable Source: https://docs.sablier.com/reference/bob/contracts/interfaces/interface.IComptrollerable # IComptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IComptrollerable.sol) **Title:** IComptrollerable Contract module that provides a setter and getter for the Sablier Comptroller. ## Functions ### comptroller Retrieves the address of the comptroller contract. ```solidity function comptroller() external view returns (ISablierComptroller); ``` ### setComptroller Sets the comptroller to a new address. Emits a [SetComptroller](#setcomptroller) event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a [TransferFeesToComptroller](#transferfeestocomptroller) event. ```solidity function transferFeesToComptroller() external; ``` ## Events ### SetComptroller Emitted when the comptroller address is set by the admin. ```solidity event SetComptroller(ISablierComptroller oldComptroller, ISablierComptroller newComptroller); ``` ### TransferFeesToComptroller Emitted when the fees are transferred to the comptroller contract. ```solidity event TransferFeesToComptroller(address indexed comptroller, uint256 feeAmount); ``` --- ## ISablierBob Source: https://docs.sablier.com/reference/bob/contracts/interfaces/interface.ISablierBob # ISablierBob [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierBob.sol) **Inherits:** IComptrollerable, [ISablierBobState](/reference/bob/contracts/interfaces/interface.ISablierBobState) **Title:** ISablierBob Price-gated vaults that unlock deposited tokens when the price returned by the oracle is greater than or equal to the target price set by the vault creator. The tokens are also unlocked if the vault expires. When a vault is configured with a adapter, the protocol automatically stakes the tokens via adapter and earns yield on the deposit amount. ## Functions ### calculateMinFeeWei Calculates the minimum fee in wei required to redeem from the given vault ID. Returns 0 for vaults with an adapter, since the fee is taken from yield generated. Reverts if `vaultId` references a null vault. ```solidity function calculateMinFeeWei(uint256 vaultId) external view returns (uint256 minFeeWei); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The vault ID for the query. | ### createVault Creates a new vault with the specified parameters. Emits a [CreateVault](/reference/bob/contracts/interfaces/interface.ISablierBob#createvault) event. Notes: - A new ERC-20 share token is deployed for each vault to represent user's share of deposits in the vault. - The default adapter for the token is copied as the vault adapter. Any change in the default adapter does not affect existing vaults. - Vault creator is responsible for choosing a valid oracle. They should use Chainlink oracles, as the integration is based on their API. Requirements: - `token` must not be the zero address. - `token` must implement `symbol()` and `decimals()` functions. - `expiry` must be in the future. - `oracle` must implement the Chainlink's {AggregatorV3Interface} interface. - `oracle` must return a positive price when `latestRoundData()` is called. - `oracle` must return a non-zero value no greater than 36 when `decimals()` is called. - `targetPrice` must not be zero or greater than the current price returned by the provided oracle. ```solidity function createVault( IERC20 token, AggregatorV3Interface oracle, uint40 expiry, uint128 targetPrice ) external returns (uint256 vaultId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The address of the ERC-20 token that will be accepted for deposits. | | `oracle` | `AggregatorV3Interface` | The address of the price feed oracle for the deposit token. | | `expiry` | `uint40` | The Unix timestamp when the vault expires. | | `targetPrice` | `uint128` | The target price at which the vault settles, denominated in Chainlink's 8-decimal format for USD prices, where 1e8 is $1. | **Returns** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the newly created vault. | ### enter Enter into a vault by depositing tokens into it and minting share tokens to the caller. Emits an [Enter](/reference/bob/contracts/interfaces/interface.ISablierBob#enter) event. Notes: - If an adapter is configured for the vault, tokens are automatically staked for yield using the adapter. - Share tokens are minted 1:1 with the deposited amount. Requirements: - `vaultId` must not reference a null vault. - The vault must have ACTIVE status. - `amount` must be greater than zero. - The caller must have approved this contract to transfer `amount` tokens. ```solidity function enter(uint256 vaultId, uint128 amount) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to deposit into. | | `amount` | `uint128` | The amount of tokens to deposit. | ### enterWithNativeToken Enter into a vault by depositing native token (such as ETH, POL, etc.) into it and minting share tokens to the caller. Emits an [Enter](/reference/bob/contracts/interfaces/interface.ISablierBob#enter) event. Notes: - `msg.value` is used as the deposit amount. - See notes for [enter](/reference/bob/contracts/interfaces/interface.ISablierBob#enter). Requirements: - See requirements for [enter](/reference/bob/contracts/interfaces/interface.ISablierBob#enter). - `msg.value` must be greater than zero and must not exceed `type(uint128).max`. ```solidity function enterWithNativeToken(uint256 vaultId) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to deposit into. | ### onShareTransfer Called by adapter when share tokens for a given vault are transferred between users. This is required for accounting of the yield generated by the adapter. Requirements: - The caller must be the share token contract stored in the given vault. - The calculated wstETH transfer amount must not be zero. ```solidity function onShareTransfer( uint256 vaultId, address from, address to, uint256 amount, uint256 fromBalanceBefore ) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `from` | `address` | The address transferring share tokens. | | `to` | `address` | The address receiving share tokens. | | `amount` | `uint256` | The number of share tokens being transferred. | | `fromBalanceBefore` | `uint256` | The number of share tokens the sender had before the transfer. | ### redeem Redeem the tokens by burning user shares. Emits a [Redeem](/reference/bob/contracts/interfaces/interface.ISablierBob#redeem) event. Notes: - If no adapter is configured for the vault, a fee in the native token is applied. - If an adapter is configured for the vault, a fee, in the deposit token, is deducted from yield generated by the adapter. - If unstake via Lido withdrawal queue contract is triggered, redeem will revert until the withdrawal from the Lido queue is finalized. Requirements: - `vaultId` must not reference a null vault. - Either block timestamp must be greater than or equal to the vault expiry or the latest price from the oracle must be greater than or equal to the target price. - The share balance of the caller must be greater than zero. - If no adapter is configured for the vault, `msg.value` must be greater than or equal to the min fee required in the native token. ```solidity function redeem(uint256 vaultId) external payable returns (uint128 transferAmount, uint128 feeAmountDeductedFromYield); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to redeem from. | **Returns** | Name | Type | Description | | --- | --- | --- | | `transferAmount` | `uint128` | The amount of tokens transferred to the caller, after fees are deducted (only applicable if adapter is set). | | `feeAmountDeductedFromYield` | `uint128` | The fee amount deducted from the yield. Zero if no adapter is set. | ### setDefaultAdapter Sets the default adapter for a specific token. Emits a [SetDefaultAdapter](/reference/bob/contracts/interfaces/interface.ISablierBob#setdefaultadapter) event. Notes: - This only affects future vaults. Requirements: - The caller must be the comptroller. - If new adapter is not zero address, it must implement [ISablierBobAdapter](/reference/bob/contracts/interfaces/interface.ISablierBobAdapter) interface. ```solidity function setDefaultAdapter(IERC20 token, ISablierBobAdapter newAdapter) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The token address to set the adapter for. | | `newAdapter` | `ISablierBobAdapter` | The address of the new adapter. | ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for {nativeToken}. Emits a [SetNativeToken](/reference/bob/contracts/interfaces/interface.ISablierBob#setnativetoken) event. Requirements: - `msg.sender` must be the comptroller. - `newNativeToken` must not be zero address. - The native token must not be already set. ```solidity function setNativeToken(address newNativeToken) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ### syncPriceFromOracle Fetches the latest price from the oracle set for a vault and updates it in the vault storage. Emits a [SyncPriceFromOracle](/reference/bob/contracts/interfaces/interface.ISablierBob#syncpricefromoracle) event. Notes: - Oracle staleness is not validated on-chain when calling this function. Any price returned by the oracle is accepted. - Useful for syncing the price from oracle without calling [redeem](/reference/bob/contracts/interfaces/interface.ISablierBob#redeem) or [enter](/reference/bob/contracts/interfaces/interface.ISablierBob#enter). This function can be called by anyone to settle vault when the price is above the target price. Requirements: - `vaultId` must not reference a null vault. - The vault must have ACTIVE status. - The oracle must return a positive price. ```solidity function syncPriceFromOracle(uint256 vaultId) external returns (uint128 latestPrice); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault to sync. | **Returns** | Name | Type | Description | | --- | --- | --- | | `latestPrice` | `uint128` | The latest price fetched from the oracle, denominated in Chainlink's 8-decimal format for USD prices, where 1e8 is $1. | ### unstakeTokensViaAdapter Unstake all tokens from the adapter for a given vault. Emits an [UnstakeFromAdapter](/reference/bob/contracts/interfaces/interface.ISablierBob#unstakefromadapter) event. Requirements: - `vaultId` must not reference a null vault. - The adapter set in the vault must not be zero address. - Either block timestamp must be greater than or equal to the vault expiry or the latest price from the oracle must be greater than or equal to the target price. - The vault must not have been unstaked already. - The amount staked must be greater than zero. ```solidity function unstakeTokensViaAdapter(uint256 vaultId) external returns (uint128 amountReceivedFromAdapter); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `amountReceivedFromAdapter` | `uint128` | The amount of tokens received from the adapter. | ## Events ### CreateVault Emitted when a new vault is created. ```solidity event CreateVault( uint256 indexed vaultId, IERC20 indexed token, AggregatorV3Interface indexed oracle, ISablierBobAdapter adapter, IBobVaultShare shareToken, uint128 targetPrice, uint40 expiry ); ``` ### Enter Emitted when a user deposits tokens into a vault. ```solidity event Enter(uint256 indexed vaultId, address indexed user, uint128 amountReceived, uint128 sharesMinted); ``` ### Redeem Emitted when a user redeems their shares from a settled vault. ```solidity event Redeem( uint256 indexed vaultId, address indexed user, uint128 amountReceived, uint128 sharesBurned, uint256 fee ); ``` ### SetDefaultAdapter Emitted when the comptroller sets a new default adapter for a token. ```solidity event SetDefaultAdapter(IERC20 indexed token, ISablierBobAdapter indexed adapter); ``` ### SetNativeToken Emitted when the native token address is set by the comptroller. ```solidity event SetNativeToken(address indexed comptroller, address nativeToken); ``` ### SyncPriceFromOracle Emitted when a vault's price is synced from the oracle. ```solidity event SyncPriceFromOracle( uint256 indexed vaultId, AggregatorV3Interface indexed oracle, uint128 latestPrice, uint40 syncedAt ); ``` ### UnstakeFromAdapter Emitted when tokens staked in the adapter for a given vault are unstaked. ```solidity event UnstakeFromAdapter( uint256 indexed vaultId, ISablierBobAdapter indexed adapter, uint128 wrappedTokenUnstakedAmount, uint128 amountReceivedFromAdapter ); ``` --- ## ISablierBobAdapter Source: https://docs.sablier.com/reference/bob/contracts/interfaces/interface.ISablierBobAdapter # ISablierBobAdapter [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierBobAdapter.sol) **Inherits:** IComptrollerable, IERC165 **Title:** ISablierBobAdapter Base interface for adapters used by the SablierBob protocol for generating yield. ## Functions ### MAX\_FEE Returns the maximum yield fee, denominated in UD60x18, where 1e18 = 100%. This is a constant state variable. ```solidity function MAX_FEE() external view returns (UD60x18); ``` ### SABLIER\_BOB Returns the address of the SablierBob contract. This is an immutable state variable. ```solidity function SABLIER_BOB() external view returns (address); ``` ### feeOnYield Returns the current global fee on yield for new vaults, denominated in UD60x18, where 1e18 = 100%. ```solidity function feeOnYield() external view returns (UD60x18); ``` ### getTotalYieldBearingTokenBalance Returns the total amount of yield-bearing tokens held in a vault. ```solidity function getTotalYieldBearingTokenBalance(uint256 vaultId) external view returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The total amount of yield-bearing tokens in the vault. | ### getVaultYieldFee Returns the yield fee stored for a specific vault. ```solidity function getVaultYieldFee(uint256 vaultId) external view returns (UD60x18); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `UD60x18` | The yield fee for the vault denominated in UD60x18, where 1e18 = 100%. | ### getYieldBearingTokenBalanceFor Returns the amount of yield-bearing tokens held for a specific user in a vault. ```solidity function getYieldBearingTokenBalanceFor(uint256 vaultId, address user) external view returns (uint128); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `user` | `address` | The address of the user. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint128` | The amount of yield-bearing tokens the user has claim to. | ### processRedemption Processes a user's token redemption by calculating the transfer amount, clearing the user's yield-bearing token balance, and returning the amounts. Notes: - The user's yield-bearing token balance is decremented after calculating the transfer amount. This does not decrement the vault total as it is used in the calculation of the transfer amount for other users. Requirements: - The caller must be the SablierBob contract. ```solidity function processRedemption( uint256 vaultId, address user, uint128 shareBalance ) external returns (uint128 transferAmount, uint128 feeAmountDeductedFromYield); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `user` | `address` | The address of the user. | | `shareBalance` | `uint128` | The user's share balance in the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `transferAmount` | `uint128` | The amount to transfer to the user. | | `feeAmountDeductedFromYield` | `uint128` | The fee amount taken from the yield. | ### registerVault Register a new vault with the adapter and snapshot the current fee on yield. Requirements: - The caller must be the SablierBob contract. ```solidity function registerVault(uint256 vaultId) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the newly created vault. | ### setYieldFee Sets the fee on yield for future vaults. Emits a [SetYieldFee](/reference/bob/contracts/interfaces/interface.ISablierBobAdapter#setyieldfee) event. Notes: - This only affects future vaults, fee is not updated for existing vaults. Requirements: - The caller must be the comptroller. - `newFee` must not exceed MAX\_FEE. ```solidity function setYieldFee(UD60x18 newFee) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newFee` | `UD60x18` | The new yield fee as UD60x18 where 1e18 = 100%. | ### stake Stakes tokens deposited by a user in a vault, converting them to yield-bearing tokens. Emits a [Stake](/reference/bob/contracts/interfaces/interface.ISablierBobAdapter#stake) event. Requirements: - The caller must be the SablierBob contract. - The tokens must have been transferred to this contract. ```solidity function stake(uint256 vaultId, address user, uint256 amount) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `user` | `address` | The address of the user depositing the tokens. | | `amount` | `uint256` | The amount of tokens to stake. | ### unstakeFullAmount Converts all yield-bearing tokens in a vault back to deposit tokens after settlement. Emits an [UnstakeFullAmount](/reference/bob/contracts/interfaces/interface.ISablierBobAdapter#unstakefullamount) event. Notes: - This should only be called once per vault after settlement. Requirements: - The caller must be the SablierBob contract. ```solidity function unstakeFullAmount(uint256 vaultId) external returns (uint128 wrappedTokenBalance, uint128 amountReceivedFromUnstaking); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | **Returns** | Name | Type | Description | | --- | --- | --- | | `wrappedTokenBalance` | `uint128` | The total amount of yield-bearing tokens that were in the vault. | | `amountReceivedFromUnstaking` | `uint128` | The total amount of tokens received from unstaking the yield-bearing tokens. | ### updateStakedTokenBalance Updates staked token balance of a user when vault shares are transferred. Requirements: - The caller must be the SablierBob contract. - `userShareBalanceBeforeTransfer` must not be zero. - The calculated wstETH transfer amount must not be zero. ```solidity function updateStakedTokenBalance( uint256 vaultId, address from, address to, uint256 shareAmountTransferred, uint256 userShareBalanceBeforeTransfer ) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | | `from` | `address` | The address transferring vault shares. | | `to` | `address` | The address receiving vault shares. | | `shareAmountTransferred` | `uint256` | The number of vault shares being transferred. | | `userShareBalanceBeforeTransfer` | `uint256` | The sender's vault share balance before the transfer. | ## Events ### SetYieldFee Emitted when the comptroller sets a new yield fee. ```solidity event SetYieldFee(UD60x18 previousFee, UD60x18 newFee); ``` ### Stake Emitted when tokens are staked for a user in a vault. ```solidity event Stake(uint256 indexed vaultId, address indexed user, uint256 depositAmount, uint256 wrappedStakedAmount); ``` ### TransferStakedTokens Emitted when staked token attribution is transferred between users. ```solidity event TransferStakedTokens(uint256 indexed vaultId, address indexed from, address indexed to, uint256 amount); ``` ### UnstakeFullAmount Emitted when all staked tokens in a vault are converted back to the deposit token. ```solidity event UnstakeFullAmount(uint256 indexed vaultId, uint128 totalStakedAmount, uint128 amountReceivedFromUnstaking); ``` --- ## ISablierBobState Source: https://docs.sablier.com/reference/bob/contracts/interfaces/interface.ISablierBobState # ISablierBobState [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierBobState.sol) **Title:** ISablierBobState Contract with state variables for the [SablierBob](/reference/bob/contracts/contract.SablierBob) contract, their respective getters and modifiers. ## Functions ### getAdapter Returns the adapter configured for a specific vault. Reverts if `vaultId` references a null vault. ```solidity function getAdapter(uint256 vaultId) external view returns (ISablierBobAdapter adapter); ``` ### getDefaultAdapterFor Returns the default adapter for a given token. Zero address means no adapter is set. ```solidity function getDefaultAdapterFor(IERC20 token) external view returns (ISablierBobAdapter adapter); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The ERC-20 token to query the default adapter for. | **Returns** | Name | Type | Description | | --- | --- | --- | | `adapter` | `ISablierBobAdapter` | The default adapter for the token. | ### getExpiry Returns the timestamp when the vault expires. Reverts if `vaultId` references a null vault. ```solidity function getExpiry(uint256 vaultId) external view returns (uint40 expiry); ``` ### getLastSyncedAt Returns the timestamp when the oracle price was last synced for a vault. Reverts if `vaultId` references a null vault. ```solidity function getLastSyncedAt(uint256 vaultId) external view returns (uint40 lastSyncedAt); ``` ### getLastSyncedPrice Returns the oracle price stored for a vault. Reverts if `vaultId` references a null vault. ```solidity function getLastSyncedPrice(uint256 vaultId) external view returns (uint128 lastSyncedPrice); ``` ### getOracle Returns the oracle address set for a vault. Reverts if `vaultId` references a null vault. ```solidity function getOracle(uint256 vaultId) external view returns (AggregatorV3Interface oracle); ``` ### getShareToken Returns the address of the ERC-20 share token for a vault. Reverts if `vaultId` references a null vault. ```solidity function getShareToken(uint256 vaultId) external view returns (IBobVaultShare shareToken); ``` ### getTargetPrice Returns the target price at which the vault settles. Reverts if `vaultId` references a null vault. ```solidity function getTargetPrice(uint256 vaultId) external view returns (uint128 targetPrice); ``` ### getUnderlyingToken Returns the ERC-20 token accepted for deposits in a vault. Reverts if `vaultId` references a null vault. ```solidity function getUnderlyingToken(uint256 vaultId) external view returns (IERC20 token); ``` ### isStakedInAdapter Returns whether the vault tokens are staked in an adapter. Reverts if `vaultId` references a null vault. ```solidity function isStakedInAdapter(uint256 vaultId) external view returns (bool stakedInAdapter); ``` ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity function nativeToken() external view returns (address); ``` ### nextVaultId Counter for vault IDs, incremented every time a new vault is created. ```solidity function nextVaultId() external view returns (uint256); ``` ### statusOf Returns the vault status. Reverts if `vaultId` references a null vault. ```solidity function statusOf(uint256 vaultId) external view returns (Bob.Status status); ``` --- ## ISablierLidoAdapter Source: https://docs.sablier.com/reference/bob/contracts/interfaces/interface.ISablierLidoAdapter # ISablierLidoAdapter [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLidoAdapter.sol) **Inherits:** [ISablierBobAdapter](/reference/bob/contracts/interfaces/interface.ISablierBobAdapter) **Title:** ISablierLidoAdapter Interface for the Lido yield adapter that stakes WETH as wstETH and unstakes it via Curve. Extends the base adapter interface with Lido and Curve specific functionalities. ## Functions ### CURVE\_POOL Returns the address of the Curve stETH/ETH pool. This is an immutable state variable. ```solidity function CURVE_POOL() external view returns (address); ``` ### LIDO\_WITHDRAWAL\_QUEUE Returns the address of the Lido withdrawal queue contract. This is an immutable state variable. ```solidity function LIDO_WITHDRAWAL_QUEUE() external view returns (address); ``` ### MAX\_SLIPPAGE\_TOLERANCE Returns the maximum slippage tolerance that can be set, denominated in UD60x18, where 1e18 = 100%. This is a constant state variable. ```solidity function MAX_SLIPPAGE_TOLERANCE() external view returns (UD60x18); ``` ### STETH Returns the address of the stETH contract. This is an immutable state variable. ```solidity function STETH() external view returns (address); ``` ### STETH\_ETH\_ORACLE Returns the address of the Chainlink stETH/ETH oracle used in the calculation of `minEthOut` slippage. This is an immutable state variable. ```solidity function STETH_ETH_ORACLE() external view returns (address); ``` ### WETH Returns the address of the WETH contract. This is an immutable state variable. ```solidity function WETH() external view returns (address); ``` ### WSTETH Returns the address of the wstETH contract. This is an immutable state variable. ```solidity function WSTETH() external view returns (address); ``` ### getLidoWithdrawalRequestIds Returns the Lido withdrawal request IDs for a vault. Multiple request IDs may be generated for a vault if the total amount exceeds the Lido enforced per-withdrawal limit. ```solidity function getLidoWithdrawalRequestIds(uint256 vaultId) external view returns (uint256[] memory); ``` ### getWethReceivedAfterUnstaking Returns the total WETH received after unstaking for a vault. ```solidity function getWethReceivedAfterUnstaking(uint256 vaultId) external view returns (uint256); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | ### slippageTolerance Returns the current slippage tolerance for Curve swaps, denominated in UD60x18, where 1e18 = 100%. ```solidity function slippageTolerance() external view returns (UD60x18); ``` ### requestLidoWithdrawal Requests a native Lido withdrawal for a vault's staked tokens, bypassing the Curve swap. Emits a [RequestLidoWithdrawal](/reference/bob/contracts/interfaces/interface.ISablierLidoAdapter#requestlidowithdrawal) event. Notes: - This unwraps the vault's wstETH to stETH and submits it to Lido's withdrawal queue. - Once called, the Curve swap is permanently disabled for `vaultId`. - After the queue finalizes the withdrawal, ETH can be redeemed by calling {unstakeFullAmount}. - Large amounts are automatically split into multiple requests to comply with Lido's per-request limit. Requirements: - The caller must be the comptroller. - The status of the vault must not be ACTIVE. - The vault must still be staked in the adapter (not already unstaked via Curve). - A withdrawal request must not have already been requested for this vault. - The vault must have wstETH to withdraw. - The total amount to withdraw must not be less than the minimum amount per request. ```solidity function requestLidoWithdrawal(uint256 vaultId) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `vaultId` | `uint256` | The ID of the vault. | ### setSlippageTolerance Sets the slippage tolerance for Curve swaps. Emits a [SetSlippageTolerance](/reference/bob/contracts/interfaces/interface.ISablierLidoAdapter#setslippagetolerance) event. Notes: - This affects all vaults. Requirements: - The caller must be the comptroller. - `newSlippageTolerance` must not exceed MAX\_SLIPPAGE\_TOLERANCE. ```solidity function setSlippageTolerance(UD60x18 newTolerance) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newTolerance` | `UD60x18` | The new slippage tolerance as UD60x18. | ## Events ### RequestLidoWithdrawal Emitted when the comptroller requests a Lido native withdrawal for a vault. ```solidity event RequestLidoWithdrawal( uint256 indexed vaultId, address indexed comptroller, uint256 wstETHAmount, uint256 stETHAmount, uint256[] withdrawalRequestIds ); ``` ### SetSlippageTolerance Emitted when the comptroller sets a new slippage tolerance. ```solidity event SetSlippageTolerance(UD60x18 previousTolerance, UD60x18 newTolerance); ``` --- ## Errors Source: https://docs.sablier.com/reference/bob/contracts/libraries/library.Errors # Errors [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/Errors.sol) **Title:** Errors Library containing all custom errors emitted by the Sablier Bob protocol. ## Errors ### BobVaultShare\_OnlySablierBob Thrown when a function is called by an address other than SablierBob. ```solidity error BobVaultShare_OnlySablierBob(address caller, address expectedCaller); ``` ### BobVaultShare\_VaultIdMismatch Thrown when the provided vault ID does not match the share token's vault ID. ```solidity error BobVaultShare_VaultIdMismatch(uint256 providedVaultId, uint256 expectedVaultId); ``` ### SablierBob\_CallerNotShareToken Thrown when `onShareTransfer` is called by an address other than the share token. ```solidity error SablierBob_CallerNotShareToken(uint256 vaultId, address caller); ``` ### SablierBob\_DepositAmountZero Thrown when depositing zero amount in a vault. ```solidity error SablierBob_DepositAmountZero(uint256 vaultId, address user); ``` ### SablierBob\_ExpiryNotInFuture Thrown when trying to create a vault with an expiry timestamp not in the future. ```solidity error SablierBob_ExpiryNotInFuture(uint40 expiry, uint40 currentTime); ``` ### SablierBob\_ForbidNativeToken Thrown when trying to create a vault with the native token. ```solidity error SablierBob_ForbidNativeToken(address nativeToken); ``` ### SablierBob\_InsufficientFeePayment Thrown when trying to redeem with `msg.value` less than the minimum fee required. ```solidity error SablierBob_InsufficientFeePayment(uint256 feePaid, uint256 feeRequired); ``` ### SablierBob\_MsgValueNotZero Thrown when trying to pay a fee in the native token from a vault that uses the adapter. ```solidity error SablierBob_MsgValueNotZero(uint256 vaultId); ``` ### SablierBob\_NativeFeeTransferFailed Thrown when the native token fee transfer to the comptroller fails. ```solidity error SablierBob_NativeFeeTransferFailed(); ``` ### SablierBob\_NativeTokenAlreadySet Thrown when trying to set the native token address when it is already set. ```solidity error SablierBob_NativeTokenAlreadySet(address nativeToken); ``` ### SablierBob\_NativeTokenZeroAddress Thrown when trying to set zero address as native token. ```solidity error SablierBob_NativeTokenZeroAddress(); ``` ### SablierBob\_NewAdapterMissesInterface Thrown when the new adapter does not implement the required interface. ```solidity error SablierBob_NewAdapterMissesInterface(address adapter); ``` ### SablierBob\_NoSharesToRedeem Thrown when trying to exit or redeem with zero share balance. ```solidity error SablierBob_NoSharesToRedeem(uint256 vaultId, address user); ``` ### SablierBob\_TargetPriceZero Thrown when trying to create a vault with a zero target price. ```solidity error SablierBob_TargetPriceZero(); ``` ### SablierBob\_TargetPriceTooLow Thrown when trying to create a vault with a target price that is not greater than the latest price returned by the oracle. ```solidity error SablierBob_TargetPriceTooLow(uint128 targetPrice, uint128 currentPrice); ``` ### SablierBob\_TokenAddressZero Thrown when trying to create a vault with a zero token address. ```solidity error SablierBob_TokenAddressZero(); ``` ### SablierBob\_UnstakeAmountZero Thrown when trying to unstake vault tokens using the adapter but the amount staked is zero. ```solidity error SablierBob_UnstakeAmountZero(uint256 vaultId); ``` ### SablierBob\_VaultAlreadyUnstaked Thrown when trying to unstake vault tokens using the adapter but the vault has already been unstaked. ```solidity error SablierBob_VaultAlreadyUnstaked(uint256 vaultId); ``` ### SablierBob\_VaultHasNoAdapter Thrown when trying to unstake from a vault that has no adapter configured. ```solidity error SablierBob_VaultHasNoAdapter(uint256 vaultId); ``` ### SablierBob\_VaultNotActive Thrown when trying to perform an unauthorized action on a non-active vault. ```solidity error SablierBob_VaultNotActive(uint256 vaultId); ``` ### SablierBob\_VaultStillActive Thrown when trying to perform an unauthorized action on an active vault. ```solidity error SablierBob_VaultStillActive(uint256 vaultId); ``` ### SablierBobState\_Null Thrown when trying to interact with a non-existent vault. ```solidity error SablierBobState_Null(uint256 vaultId); ``` ### SablierEscrow\_BuyTokenZero Thrown when trying to create an order with a zero address for the buy token. ```solidity error SablierEscrow_BuyTokenZero(); ``` ### SablierEscrow\_CallerNotAuthorized Thrown when the caller is not authorized to perform an action on an order. ```solidity error SablierEscrow_CallerNotAuthorized(uint256 orderId, address caller, address expectedCaller); ``` ### SablierEscrow\_ExpiryTimeInPast Thrown when trying to create an order with an expiration timestamp in the past. ```solidity error SablierEscrow_ExpiryTimeInPast(uint40 expiryTime, uint40 currentTime); ``` ### SablierEscrow\_ForbidNativeToken Thrown when trying to create an order with the native token. ```solidity error SablierEscrow_ForbidNativeToken(address nativeToken); ``` ### SablierEscrow\_InsufficientBuyAmount Thrown when trying to accept an order with a buy amount that is below the minimum amount required. ```solidity error SablierEscrow_InsufficientBuyAmount(uint128 buyAmount, uint128 minBuyAmount); ``` ### SablierEscrow\_MinBuyAmountZero Thrown when trying to create an order with a zero buy amount. ```solidity error SablierEscrow_MinBuyAmountZero(); ``` ### SablierEscrow\_NativeTokenAlreadySet Thrown when trying to set the native token address when it is already set. ```solidity error SablierEscrow_NativeTokenAlreadySet(address nativeToken); ``` ### SablierEscrow\_NativeTokenZeroAddress Thrown when trying to set zero address as native token. ```solidity error SablierEscrow_NativeTokenZeroAddress(); ``` ### SablierEscrow\_OrderCancelled Thrown when trying to cancel an order that has already been canceled. ```solidity error SablierEscrow_OrderCancelled(uint256 orderId); ``` ### SablierEscrow\_OrderFilled Thrown when trying to cancel an order that has already been filled. ```solidity error SablierEscrow_OrderFilled(uint256 orderId); ``` ### SablierEscrow\_OrderNotOpen Thrown when trying to fill an order that has either been completed or canceled. ```solidity error SablierEscrow_OrderNotOpen(uint256 orderId, Escrow.Status status); ``` ### SablierEscrow\_SameToken Thrown when trying to create an order with the same sell and buy tokens. ```solidity error SablierEscrow_SameToken(IERC20 token); ``` ### SablierEscrow\_SellAmountZero Thrown when trying to create an order with a zero sell amount. ```solidity error SablierEscrow_SellAmountZero(); ``` ### SablierEscrow\_SellTokenZero Thrown when trying to create an order with a zero address for the sell token. ```solidity error SablierEscrow_SellTokenZero(); ``` ### SablierEscrowState\_NewTradeFeeTooHigh Thrown when trying to set a trade fee that exceeds the maximum allowed. ```solidity error SablierEscrowState_NewTradeFeeTooHigh(UD60x18 newTradeFee, UD60x18 maxTradeFee); ``` ### SablierEscrowState\_Null Thrown when trying to interact with a non-existent order. ```solidity error SablierEscrowState_Null(uint256 orderId); ``` ### SablierLidoAdapter\_LidoWithdrawalAlreadyRequested Thrown when trying to request a Lido withdrawal for a vault that has already requested one. ```solidity error SablierLidoAdapter_LidoWithdrawalAlreadyRequested(uint256 vaultId); ``` ### SablierLidoAdapter\_NoWstETHToWithdraw Thrown when trying to request a Lido withdrawal for a vault with no wstETH. ```solidity error SablierLidoAdapter_NoWstETHToWithdraw(uint256 vaultId); ``` ### SablierLidoAdapter\_OnlySablierBob Thrown when a function is called by an address other than SablierBob. ```solidity error SablierLidoAdapter_OnlySablierBob(address caller, address expectedCaller); ``` ### SablierLidoAdapter\_OraclePriceZero Thrown when the stETH/ETH oracle returns a zero price. ```solidity error SablierLidoAdapter_OraclePriceZero(); ``` ### SablierLidoAdapter\_SlippageExceeded Thrown when the Curve swap output is below the minimum acceptable amount. ```solidity error SablierLidoAdapter_SlippageExceeded(uint256 expected, uint256 actual); ``` ### SablierLidoAdapter\_SlippageToleranceTooHigh Thrown when trying to set a slippage that exceeds the maximum allowed. ```solidity error SablierLidoAdapter_SlippageToleranceTooHigh(UD60x18 tolerance, UD60x18 maxTolerance); ``` ### SablierLidoAdapter\_UserBalanceZero Thrown when trying to update staked token balance but the user's balance is zero. ```solidity error SablierLidoAdapter_UserBalanceZero(uint256 vaultId, address user); ``` ### SablierLidoAdapter\_VaultActive Thrown when trying to request a Lido withdrawal for a vault that is active. ```solidity error SablierLidoAdapter_VaultActive(uint256 vaultId); ``` ### SablierLidoAdapter\_VaultAlreadyUnstaked Thrown when trying to request a Lido withdrawal for a vault that has already been unstaked. ```solidity error SablierLidoAdapter_VaultAlreadyUnstaked(uint256 vaultId); ``` ### SablierLidoAdapter\_WstETHTransferAmountZero Thrown when the calculated wstETH transfer amount rounds down to zero due to floor division. ```solidity error SablierLidoAdapter_WstETHTransferAmountZero(uint256 vaultId, address from, address to); ``` ### SablierLidoAdapter\_WithdrawalAmountBelowMinimum Thrown when the total amount to withdraw is below the minimum amount per request. ```solidity error SablierLidoAdapter_WithdrawalAmountBelowMinimum( uint256 vaultId, uint256 totalAmount, uint256 minimumAmountPerRequest ); ``` ### SablierLidoAdapter\_YieldFeeTooHigh Thrown when trying to set a yield fee that exceeds the maximum allowed. ```solidity error SablierLidoAdapter_YieldFeeTooHigh(UD60x18 fee, UD60x18 maxFee); ``` --- ## Bob Source: https://docs.sablier.com/reference/bob/contracts/types/library.Bob # Bob [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/Bob.sol) Namespace for the structs and enums used in the Sablier Bob protocol. ## Structs ### Vault Struct encapsulating all the configuration and state of a vault. The fields are arranged for gas optimization via tight variable packing. ```solidity struct Vault { // slot 0 IERC20 token; uint40 expiry; uint40 lastSyncedAt; // slot 1 IBobVaultShare shareToken; // slot 2 AggregatorV3Interface oracle; // slot 3 ISablierBobAdapter adapter; bool isStakedInAdapter; // slot 4 uint128 targetPrice; uint128 lastSyncedPrice; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The ERC-20 token accepted for deposits in this vault. | | `expiry` | `uint40` | The Unix timestamp when the vault expires. | | `lastSyncedAt` | `uint40` | The Unix timestamp when the oracle price was last synced. | | `shareToken` | `IBobVaultShare` | The address of ERC-20 token representing shares in this vault. | | `oracle` | `AggregatorV3Interface` | The address of the price oracle for the deposit token, provided by the vault creator. | | `adapter` | `ISablierBobAdapter` | The adapter set for this vault, can be used to take action on the deposit token. | | `isStakedInAdapter` | `bool` | Whether the deposit token is staked with the adapter or not. | | `targetPrice` | `uint128` | The target price at which the vault settles, denoted in 8 decimals where 1e8 is $1. | | `lastSyncedPrice` | `uint128` | The most recent price fetched from the oracle, denoted in 8 decimals where 1e8 is $1. | ## Enums ### Status Enum representing the different statuses of a vault. **Notes:** - value0: ACTIVE Vault is open for deposits. - value1: EXPIRED Vault has end time less than or equal to current timestamp. - value2: SETTLED Vault has target price less than or equal to last synced price. ```solidity enum Status { ACTIVE, EXPIRED, SETTLED } ``` --- ## Diagrams Source: https://docs.sablier.com/reference/bob/diagrams # Diagrams ## Tokens Flow ### Creating a Vault ### Depositing Tokens ### Depositing Tokens (with Lido adapter)") ### Redeeming Shares ### Redeeming Shares (With Lido Adapter)") First user who redeems shares unstakes from Lido adapter. Subsequent users receives WETH directly from the Bob contract. ```mermaid sequenceDiagram actor Anyone Anyone ->> SablierBob: createVault() Create participant BobVaultShare SablierBob ->> BobVaultShare: Deploy share token ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> SablierBob: enter() SablierBob ->> Oracle: latestRoundData() Oracle -->> SablierBob: Sync price Anyone -->> SablierBob: Transfer tokens SablierBob ->> BobVaultShare: mint() BobVaultShare -->> Anyone: Mint shares ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> SablierBob: enter() SablierBob ->> Oracle: latestRoundData() Oracle -->> SablierBob: Sync price Anyone -->> LidoAdapter: Transfer WETH SablierBob ->> LidoAdapter: stake() LidoAdapter ->> WETH: withdraw() WETH -->> LidoAdapter: Unwrap WETH into ETH LidoAdapter ->> Lido: submit() Lido -->> LidoAdapter: Mint stETH LidoAdapter ->> wstETH: wrap() wstETH -->> LidoAdapter: Wrap stETH into wstETH SablierBob ->> BobVaultShare: mint() BobVaultShare -->> Anyone: Mint shares ``` ```mermaid sequenceDiagram actor ShareHolder ShareHolder ->> SablierBob: redeem() SablierBob ->> Oracle: latestRoundData() Oracle -->> SablierBob: Sync price SablierBob ->> BobVaultShare: burn() ShareHolder -->> BobVaultShare: Burn shares SablierBob -->> ShareHolder: Transfer tokens ``` ```mermaid sequenceDiagram actor ShareHolder ShareHolder ->> SablierBob: redeem() SablierBob ->> Oracle: latestRoundData() Oracle -->> SablierBob: Sync price SablierBob ->> LidoAdapter: unstakeFullAmount() LidoAdapter ->> wstETH: unwrap() wstETH -->> LidoAdapter: Unwrap wstETH into stETH LidoAdapter ->> Curve: exchange stETH to ETH Curve -->> LidoAdapter: Transfer ETH LidoAdapter ->> WETH: deposit() WETH -->> LidoAdapter: Wrap ETH into WETH LidoAdapter -->> SablierBob: Transfer WETH SablierBob ->> BobVaultShare: burn() ShareHolder -->> BobVaultShare: Burn shares SablierBob -->> ShareHolder: Transfer WETH ``` ```mermaid sequenceDiagram actor ShareHolder ShareHolder ->> SablierBob: redeem() SablierBob ->> BobVaultShare: burn() ShareHolder -->> BobVaultShare: Burn shares SablierBob -->> ShareHolder: Transfer WETH ``` --- ## Errors Source: https://docs.sablier.com/reference/errors # Errors ## Background The Sablier Protocol handles errors with the convenient and gas-efficient [custom error syntax](https://blog.soliditylang.org/2021/04/21/custom-errors) introduced in Solidity v0.8.4. The error data encoding is identical to the ABI encoding used for functions, e.g.: ```solidity error SablierLockupBase_WithdrawAmountZero(uint256 streamId); ``` Yields the following 4-byte selector in the contract's ABI: ```solidity bytes4(keccak256(bytes("SablierLockupBase_WithdrawAmountZero(uint256)"))) // 0xf747ab7c ``` ## Naming Pattern With the exception of a few generics, all errors in Sablier Protocol adhere to the naming pattern `_`. Incorporating the contract name as a prefix offers context, making it easier for end users to pinpoint the contract responsible for a reverted transaction. This approach is particularly helpful for complex transactions involving multiple contracts. ## Lockup Error List [Click here](/reference/lockup/contracts/libraries/library.Errors) to see the full error list in Lockup protocol. ## Airdrops Error List [Click here](/reference/airdrops/contracts/libraries/library.Errors) to see the full error list in Airdrops protocol. ## Flow Error List [Click here](/reference/flow/contracts/libraries/library.Errors) to see the full error list in Flow protocol. ## Bob Error List [Click here](/reference/bob/contracts/libraries/library.Errors) to see the full error list in Bob protocol. ## Resources - [Custom Errors in Solidity](https://blog.soliditylang.org/2021/04/21/custom-errors/): deep dive into the custom error syntax - [OpenChain](https://openchain.xyz/signatures): signature database - [4byte.directory](https://4byte.directory/): yet another signature database --- ## Access Control Source: https://docs.sablier.com/reference/flow/access-control # Access Control With the exception of the [admin functions](/concepts/governance#flow), all functions in Flow can only be triggered by users. The Comptroller has no control over any stream or any part of the protocol. This article will provide a comprehensive overview of the actions that can be performed on streams once they are created, as well as the corresponding user permissions for each action. :::note Every stream has a sender and a recipient. Recipients can approve third parties to take actions on their behalf. A 'public' caller is any address outside of sender and recipient. ::: ## Overview The table below offers a quick overview of the access control for each action that can be performed on a stream. | Action | Sender | Recipient / Approved third party | Public | | --- | :---: | :---: | :---: | | AdjustRatePerSecond | ✅ | ❌ | ❌ | | Deposit | ✅ | ✅ | ✅ | | Pause | ✅ | ❌ | ❌ | | Refund | ✅ | ❌ | ❌ | | Restart | ✅ | ❌ | ❌ | | Transfer NFT | ❌ | ✅ | ❌ | | Withdraw to any address | ❌ | ✅ | ❌ | | Withdraw to recipient | ✅ | ✅ | ✅ | | Void | ✅ | ✅ | ❌ | ## Adjust rate per second Only the sender can adjust the rate per second of a stream. ## Create stream ## Deposit into a stream Anyone can deposit into a stream. ## Pause Only the sender can pause a stream. ## Refund from a stream Only the sender can refund from a stream. ## Restarting a stream Only the sender can restart a stream. ## Voiding a stream Both Sender and Recipient can void a stream. ## Withdraw from a stream Anyone can call withdraw on a stream as long as `to` address matches the recipient. If recipient/operator is calling withdraw on a stream, they can choose to withdraw to any address. ```mermaid sequenceDiagram actor Sender Sender ->> Flow: adjustRatePerSecond() Flow -->> Flow: update rps ``` ```mermaid sequenceDiagram actor Sender Sender ->> Flow: create() Create actor Recipient Flow -->> Recipient: mint NFT ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> ERC20: approve() Anyone ->> Flow: deposit() Flow ->> ERC20: transferFrom() Anyone -->> Flow: Transfer tokens ``` ```mermaid sequenceDiagram actor Sender Sender ->> Flow: pause() Flow -->> Flow: set rps = 0 ``` ```mermaid sequenceDiagram actor Sender Sender ->> Flow: refund() Flow ->> ERC20: transfer() Flow -->> Sender: Transfer unstreamed tokens ``` ```mermaid sequenceDiagram actor Sender Sender ->> Flow: restart() Flow -->> Flow: set rps > 0 ``` ```mermaid sequenceDiagram actor Sender Sender ->> Flow: void() activate Flow Flow -->> Flow: set rps = 0, Flow -->> Flow: set st = now & sd = cd deactivate Flow actor Recipient Recipient ->> Flow: void() activate Flow Flow -->> Flow: set rps = 0 Flow -->> Flow: set st = now & sd = cd deactivate Flow ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> Flow: withdraw() activate Flow Flow ->> ERC20: transfer() Create actor Recipient Flow -->> Recipient: Transfer tokens deactivate Flow Recipient ->> Flow: withdraw() activate Flow Flow ->> ERC20: transfer() Create actor Any Address Flow -->> Any Address : Transfer tokens deactivate Flow ``` --- ## Batch Source: https://docs.sablier.com/reference/flow/contracts/abstracts/abstract.Batch # Batch [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/Batch.sol) **Inherits:** [IBatch](/reference/flow/contracts/interfaces/interface.IBatch) **Title:** Batch See the documentation in [IBatch](/reference/flow/contracts/interfaces/interface.IBatch). ## Functions ### batch Allows batched calls to self, i.e., `this` contract. Since `msg.value` can be reused across calls, be VERY CAREFUL when using it. Refer to [https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong](https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong) for more information. ```solidity function batch(bytes[] calldata calls) external payable virtual override returns (bytes[] memory results); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `calls` | `bytes[]` | An array of inputs for each call. | **Returns** | Name | Type | Description | | --- | --- | --- | | `results` | `bytes[]` | An array of results from each call. Empty when the calls do not return anything. | --- ## Comptrollerable Source: https://docs.sablier.com/reference/flow/contracts/abstracts/abstract.Comptrollerable # Comptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/Comptrollerable.sol) **Inherits:** [IComptrollerable](/reference/flow/contracts/interfaces/interface.IComptrollerable) **Title:** Comptrollerable See the documentation in [IComptrollerable](/reference/flow/contracts/interfaces/interface.IComptrollerable). ## State Variables ### comptroller Retrieves the address of the comptroller contract. ```solidity ISablierComptroller public override comptroller ``` ## Functions ### onlyComptroller Reverts if called by any account other than the comptroller. ```solidity modifier onlyComptroller() ; ``` ### constructor ```solidity constructor(address initialComptroller) ; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### setComptroller Sets the comptroller to a new address. Emits a {SetComptroller} event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a {TransferFeesToComptroller} event. ```solidity function transferFeesToComptroller() external override; ``` ### \_checkComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _checkComptroller() private view; ``` ### \_setComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _setComptroller( ISablierComptroller previousComptroller, ISablierComptroller newComptroller, bytes4 minimalInterfaceId ) private; ``` --- ## NoDelegateCall Source: https://docs.sablier.com/reference/flow/contracts/abstracts/abstract.NoDelegateCall # NoDelegateCall [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/NoDelegateCall.sol) **Title:** NoDelegateCall This contract implements logic to prevent delegate calls. ## Constants ### ORIGINAL The address of the original contract that was deployed. ```solidity address private immutable ORIGINAL ``` ## Functions ### noDelegateCall Prevents delegate calls. ```solidity modifier noDelegateCall() ; ``` ### constructor Sets the original contract address. ```solidity constructor() ; ``` ### \_preventDelegateCall This function checks whether the current call is a delegate call, and reverts if it is. - A private function is used instead of inlining this logic in a modifier because Solidity copies modifiers into every function that uses them. The `ORIGINAL` address would get copied in every place the modifier is used, which would increase the contract size. By using a function instead, we can avoid this duplication of code and reduce the overall size of the contract. ```solidity function _preventDelegateCall() private view; ``` --- ## SablierFlowState Source: https://docs.sablier.com/reference/flow/contracts/abstracts/abstract.SablierFlowState # SablierFlowState [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierFlowState.sol) **Inherits:** [ISablierFlowState](/reference/flow/contracts/interfaces/interface.ISablierFlowState) **Title:** SablierFlowState See the documentation in [ISablierFlowState](/reference/flow/contracts/interfaces/interface.ISablierFlowState). ## State Variables ### aggregateAmount ```solidity mapping(IERC20 token => uint256 amount) public override aggregateAmount ``` ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity address public override nativeToken ``` ### nextStreamId Counter for stream ids. ```solidity uint256 public override nextStreamId ``` ### nftDescriptor Contract that generates the non-fungible token URI. ```solidity IFlowNFTDescriptor public override nftDescriptor ``` ### \_streams Sablier Flow streams mapped by unsigned integers. ```solidity mapping(uint256 id => Flow.Stream stream) internal _streams ``` ## Functions ### constructor ```solidity constructor(address initialNFTDescriptor) ; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialNFTDescriptor` | `address` | The address of the initial NFT descriptor. | ### notNull Checks that `streamId` does not reference a null stream. ```solidity modifier notNull(uint256 streamId) ; ``` ### notPaused Checks that `streamId` does not reference a paused stream. Note that this implicitly checks that the stream is not voided either. ```solidity modifier notPaused(uint256 streamId) ; ``` ### notVoided Checks that `streamId` does not reference a voided stream. ```solidity modifier notVoided(uint256 streamId) ; ``` ### onlySender Checks the `msg.sender` is the stream's sender. ```solidity modifier onlySender(uint256 streamId) ; ``` ### getBalance Retrieves the balance of the stream, i.e. the total deposited amounts subtracted by the total withdrawn amounts, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function getBalance(uint256 streamId) external view override notNull(streamId) returns (uint128 balance); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getRatePerSecond Retrieves the rate per second of the stream, denoted as a fixed-point number where 1e18 is 1 token per second. Reverts if `streamId` references a null stream. ```solidity function getRatePerSecond(uint256 streamId) external view override notNull(streamId) returns (UD21x18 ratePerSecond); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### getSender Retrieves the stream's sender. Reverts if `streamId` references a null stream. ```solidity function getSender(uint256 streamId) external view override notNull(streamId) returns (address sender); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getSnapshotDebtScaled Retrieves the snapshot debt of the stream, denoted as a fixed-point number where 1e18 is 1 token. Reverts if `streamId` references a null stream. ```solidity function getSnapshotDebtScaled(uint256 streamId) external view override notNull(streamId) returns (uint256 snapshotDebtScaled); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getSnapshotTime Retrieves the snapshot time of the stream, which is a Unix timestamp. Reverts if `streamId` references a null stream. ```solidity function getSnapshotTime(uint256 streamId) external view override notNull(streamId) returns (uint40 snapshotTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### getStream Retrieves the stream entity. Reverts if `streamId` references a null stream. ```solidity function getStream(uint256 streamId) external view override notNull(streamId) returns (Flow.Stream memory stream); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getToken Retrieves the token of the stream. Reverts if `streamId` references a null stream. ```solidity function getToken(uint256 streamId) external view override notNull(streamId) returns (IERC20 token); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### getTokenDecimals Retrieves the token decimals of the stream. Reverts if `streamId` references a null stream. ```solidity function getTokenDecimals(uint256 streamId) external view override notNull(streamId) returns (uint8 tokenDecimals); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### isStream Retrieves a flag indicating whether the stream exists. Does not revert if `streamId` references a null stream. ```solidity function isStream(uint256 streamId) external view override returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isTransferable Retrieves a flag indicating whether the stream NFT is transferable. Reverts if `streamId` references a null stream. ```solidity function isTransferable(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isVoided Retrieves a flag indicating whether the stream is voided. Reverts if `streamId` references a null stream. ```solidity function isVoided(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### \_notNull A private function is used instead of inlining this logic in a modifier because Solidity copies modifiers into every function that uses them. ```solidity function _notNull(uint256 streamId) private view; ``` --- ## FlowNFTDescriptor Source: https://docs.sablier.com/reference/flow/contracts/contract.FlowNFTDescriptor # FlowNFTDescriptor [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/FlowNFTDescriptor.sol) **Inherits:** [IFlowNFTDescriptor](/reference/flow/contracts/interfaces/interface.IFlowNFTDescriptor) **Title:** FlowNFTDescriptor See the documentation in [IFlowNFTDescriptor](/reference/flow/contracts/interfaces/interface.IFlowNFTDescriptor). ## Functions ### tokenURI Produces the URI describing a particular stream NFT. Currently it returns the Sablier logo as an SVG. In the future, it will return an NFT SVG. ```solidity function tokenURI( IERC721Metadata, /* sablierFlow */ uint256 /* streamId */ ) external pure override returns (string memory uri); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `` | `IERC721Metadata` | | | `` | `uint256` | | **Returns** | Name | Type | Description | | --- | --- | --- | | `uri` | `string` | The URI of the ERC721-compliant metadata. | --- ## SablierFlow Source: https://docs.sablier.com/reference/flow/contracts/contract.SablierFlow # SablierFlow [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierFlow.sol) **Inherits:** [Batch](/reference/flow/contracts/abstracts/abstract.Batch), [Comptrollerable](/reference/flow/contracts/abstracts/abstract.Comptrollerable), ERC721, [ISablierFlow](/reference/flow/contracts/interfaces/interface.ISablierFlow), [NoDelegateCall](/reference/flow/contracts/abstracts/abstract.NoDelegateCall), [SablierFlowState](/reference/flow/contracts/abstracts/abstract.SablierFlowState) **Title:** SablierFlow See the documentation in [ISablierFlow](/reference/flow/contracts/interfaces/interface.ISablierFlow). ## Functions ### constructor ```solidity constructor( address initialComptroller, address initialNFTDescriptor ) [Comptrollerable](/docs/reference/04-flow/contracts/abstracts/abstract.Comptrollerable.md)(initialComptroller) ERC721("Sablier Flow NFT", "SAB-FLOW") SablierFlowState(initialNFTDescriptor); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | | `initialNFTDescriptor` | `address` | The address of the initial NFT descriptor. | ### updateMetadata Emits an ERC-4906 event to trigger an update of the NFT metadata. ```solidity modifier updateMetadata(uint256 streamId) ; ``` ### calculateMinFeeWei Calculates the minimum fee in wei required to withdraw from the given stream ID. Reverts if `streamId` references a null stream. ```solidity function calculateMinFeeWei(uint256 streamId) external view override notNull(streamId) returns (uint256 minFeeWei); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### coveredDebtOf Returns the amount of debt covered by the stream balance, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function coveredDebtOf(uint256 streamId) external view override notNull(streamId) returns (uint128 coveredDebt); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### depletionTimeOf Returns the time at which the total debt exceeds stream balance. If the total debt exceeds the stream balance, it returns 0. Reverts on the following conditions: - If `streamId` references a paused or a null stream. - If stream balance is zero. ```solidity function depletionTimeOf(uint256 streamId) external view override notNull(streamId) notPaused(streamId) returns (uint256 depletionTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getRecipient Retrieves the stream's recipient. Reverts if `streamId` references a null stream. ```solidity function getRecipient(uint256 streamId) external view override notNull(streamId) returns (address recipient); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### ongoingDebtScaledOf Returns the amount of debt accrued since the snapshot time until now, denoted as a fixed-point number where 1e18 is 1 token. If the stream is pending, it returns zero. Reverts if `streamId` references a null stream. ```solidity function ongoingDebtScaledOf(uint256 streamId) external view override notNull(streamId) returns (uint256 ongoingDebtScaled); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### refundableAmountOf Returns the amount that the sender can be refunded from the stream, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function refundableAmountOf(uint256 streamId) external view override notNull(streamId) returns (uint128 refundableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### statusOf Returns the stream's status. Reverts if `streamId` references a null stream. Integrators should exercise caution when depending on the return value of this function as streams can be paused and resumed at any moment. ```solidity function statusOf(uint256 streamId) external view override notNull(streamId) returns (Flow.Status status); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### totalDebtOf Returns the total amount owed by the sender to the recipient, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function totalDebtOf(uint256 streamId) external view override notNull(streamId) returns (uint256 totalDebt); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### uncoveredDebtOf Returns the amount of debt not covered by the stream balance, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function uncoveredDebtOf(uint256 streamId) external view override notNull(streamId) returns (uint256 uncoveredDebt); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### withdrawableAmountOf Calculates the amount that the recipient can withdraw from the stream, denoted in token decimals. This is an alias for `coveredDebtOf`. Reverts if `streamId` references a null stream. ```solidity function withdrawableAmountOf(uint256 streamId) external view override notNull(streamId) returns (uint128 withdrawableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawableAmount` | `uint128` | The amount that the recipient can withdraw. | ### adjustRatePerSecond Changes the stream's rate per second. Emits a {AdjustFlowStream} and {MetadataUpdate} event. Notes: - If the snapshot time is not in the future, it updates both the snapshot time and snapshot debt. Requirements: - Must not be delegate called. - `streamId` must not reference a null, paused, or voided stream. - `msg.sender` must be the stream's sender. - `newRatePerSecond` must be greater than zero and must be different from the current rate per second. ```solidity function adjustRatePerSecond( uint256 streamId, UD21x18 newRatePerSecond ) external payable override noDelegateCall notNull(streamId) notPaused(streamId) onlySender(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to adjust. | | `newRatePerSecond` | `UD21x18` | The new rate per second, denoted as a fixed-point number where 1e18 is 1 token per second. | ### create Creates a new Flow stream by setting the snapshot time to `startTime` and leaving the balance to zero. The stream is wrapped in an ERC-721 NFT. Emits a {CreateFlowStream} and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `sender` must not be the zero address. - `recipient` must not be the zero address. - If `startTime` is in the future, the `ratePerSecond` must be greater than zero. - The `token` must not be the native token. - The `token`'s decimals must be less than or equal to 18. ```solidity function create( address sender, address recipient, UD21x18 ratePerSecond, uint40 startTime, IERC20 token, bool transferable ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `sender` | `address` | The address streaming the tokens, which is able to adjust and pause the stream. It doesn't have to be the same as `msg.sender`. | | `recipient` | `address` | The address receiving the tokens. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `startTime` | `uint40` | The timestamp when the stream starts. A sentinel value of zero means the stream will be created with the snapshot time as `block.timestamp`. | | `token` | `IERC20` | The contract address of the ERC-20 token to be streamed. | | `transferable` | `bool` | Boolean indicating if the stream NFT is transferable. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createAndDeposit Creates a new Flow stream by setting the snapshot time to `startTime` and the balance to `amount`. The stream is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateFlowStream}, {DepositFlowStream} and {MetadataUpdate} event. Notes: - Refer to the notes in {create} and {deposit}. Requirements: - Refer to the requirements in {create} and {deposit}. ```solidity function createAndDeposit( address sender, address recipient, UD21x18 ratePerSecond, uint40 startTime, IERC20 token, bool transferable, uint128 amount ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `sender` | `address` | The address streaming the tokens. It doesn't have to be the same as `msg.sender`. | | `recipient` | `address` | The address receiving the tokens. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `startTime` | `uint40` | The timestamp when the stream starts. A sentinel value of zero means the stream will be created with the snapshot time as `block.timestamp`. | | `token` | `IERC20` | The contract address of the ERC-20 token to be streamed. | | `transferable` | `bool` | Boolean indicating if the stream NFT is transferable. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### deposit Makes a deposit in a stream. Emits a {Transfer}, {DepositFlowStream} and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `streamId` must not reference a null or a voided stream. - `amount` must be greater than zero. - `sender` and `recipient` must match the stream's sender and recipient addresses. ```solidity function deposit( uint256 streamId, uint128 amount, address sender, address recipient ) external payable override noDelegateCall notNull(streamId) notVoided(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to deposit to. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | | `sender` | `address` | The stream's sender address. | | `recipient` | `address` | The stream's recipient address. | ### depositAndPause Deposits tokens in a stream and pauses it. Emits a {Transfer}, {DepositFlowStream}, {PauseFlowStream} and {MetadataUpdate} event. Notes: - Refer to the notes in {deposit} and {pause}. Requirements: - Refer to the requirements in {deposit} and {pause}. ```solidity function depositAndPause( uint256 streamId, uint128 amount ) external payable override noDelegateCall notNull(streamId) notPaused(streamId) onlySender(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to deposit to, and then pause. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | ### pause Pauses the stream. Emits a {PauseFlowStream} and {MetadataUpdate} event. Notes: - It updates snapshot debt and snapshot time. - It sets the rate per second to zero. Requirements: - Must not be delegate called. - `streamId` must not reference a null, pending or paused stream. - `msg.sender` must be the stream's sender. ```solidity function pause(uint256 streamId) external payable override noDelegateCall notNull(streamId) notPaused(streamId) onlySender(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to pause. | ### recover Recover the surplus amount of tokens. Notes: - The surplus amount is defined as the difference between the total balance of the contract for the provided ERC-20 token and the sum of balances of all streams created using the same ERC-20 token. Requirements: - `msg.sender` must be the comptroller contract. ```solidity function recover(IERC20 token, address to) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The contract address of the ERC-20 token to recover for. | | `to` | `address` | The address to send the surplus amount. | ### refund Refunds the provided amount of tokens from the stream to the sender's address. Emits a {Transfer}, {RefundFromFlowStream} and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `streamId` must not reference a null stream. - `msg.sender` must be the sender. - `amount` must be greater than zero and must not exceed the refundable amount. ```solidity function refund( uint256 streamId, uint128 amount ) external payable override noDelegateCall notNull(streamId) onlySender(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to refund from. | | `amount` | `uint128` | The amount to refund, denoted in token's decimals. | ### refundAndPause Refunds the provided amount of tokens from the stream to the sender's address. Emits a {Transfer}, {RefundFromFlowStream}, {PauseFlowStream} and {MetadataUpdate} event. Notes: - Refer to the notes in {pause}. Requirements: - Refer to the requirements in {refund} and {pause}. ```solidity function refundAndPause( uint256 streamId, uint128 amount ) external payable override noDelegateCall notNull(streamId) notPaused(streamId) onlySender(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to refund from and then pause. | | `amount` | `uint128` | The amount to refund, denoted in token's decimals. | ### refundMax Refunds the entire refundable amount of tokens from the stream to the sender's address. Emits a {Transfer}, {RefundFromFlowStream} and {MetadataUpdate} event. Requirements: - Refer to the requirements in {refund}. ```solidity function refundMax(uint256 streamId) external payable override noDelegateCall notNull(streamId) onlySender(streamId) updateMetadata(streamId) returns (uint128 refundedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to refund from. | **Returns** | Name | Type | Description | | --- | --- | --- | | `refundedAmount` | `uint128` | The amount refunded to the stream sender, denoted in token's decimals. | ### restart Restarts the stream with the provided rate per second. Emits a {RestartFlowStream} and {MetadataUpdate} event. Notes: - It updates snapshot debt and snapshot time. Requirements: - Must not be delegate called. - `streamId` must not reference a null stream, must be paused, and must not be voided. - `msg.sender` must be the stream's sender. - `ratePerSecond` must be greater than zero. ```solidity function restart( uint256 streamId, UD21x18 ratePerSecond ) external payable override noDelegateCall notNull(streamId) notVoided(streamId) onlySender(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to restart. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | ### restartAndDeposit Restarts the stream with the provided rate per second, and makes a deposit. Emits a {RestartFlowStream}, {Transfer}, {DepositFlowStream} and {MetadataUpdate} event. Notes: - Refer to the notes in {restart} and {deposit}. Requirements: - `amount` must be greater than zero. - Refer to the requirements in {restart}. ```solidity function restartAndDeposit( uint256 streamId, UD21x18 ratePerSecond, uint128 amount ) external payable override noDelegateCall notNull(streamId) notVoided(streamId) onlySender(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to restart. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for {nativeToken}. Emits a {SetNativeToken} event. Requirements: - `msg.sender` must be the comptroller contract. - `newNativeToken` must not be the zero address. - The native token must not be already set. ```solidity function setNativeToken(address newNativeToken) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ### setNFTDescriptor Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. Emits a {SetNFTDescriptor} and {BatchMetadataUpdate} event. Notes: - Does not revert if the NFT descriptor is the same. Requirements: - `msg.sender` must be the comptroller contract. ```solidity function setNFTDescriptor(IFlowNFTDescriptor newNFTDescriptor) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNFTDescriptor` | `IFlowNFTDescriptor` | The address of the new NFT descriptor contract. | ### supportsInterface See {IERC165-supportsInterface}. ```solidity function supportsInterface(bytes4 interfaceId) public view override(IERC165, ERC721) returns (bool); ``` ### tokenURI See {IERC721Metadata-tokenURI}. ```solidity function tokenURI(uint256 streamId) public view override(IERC721Metadata, ERC721) returns (string memory uri); ``` ### transferFromPayable Wrapper for {IERC721.transferFrom} with the `payable` specifier so that it can be called in conjunction with other functions using {IBatch.batch}. Requirements: - Refer to the requirements of `transferFrom` in: [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e4f70216d759d8e6a64144a9e1f7bbeed78e7079/contracts/token/ERC721/IERC721.sol#L75-L91](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e4f70216d759d8e6a64144a9e1f7bbeed78e7079/contracts/token/ERC721/IERC721.sol#L75-L91). ```solidity function transferFromPayable(address from, address to, uint256 streamId) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `from` | `address` | The owner of the stream ID. | | `to` | `address` | The address of the new owner of the stream ID. | | `streamId` | `uint256` | The ID of the stream NFT to transfer. | ### transferTokens A helper to transfer ERC-20 tokens from the caller to the provided address. Useful for paying one-time bonuses. Emits a {Transfer} event. Requirements: - `msg.sender` must have approved this contract to spend at least `amount` tokens. ```solidity function transferTokens(IERC20 token, address to, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The contract address of the ERC-20 token to be transferred. | | `to` | `address` | The address receiving the tokens. | | `amount` | `uint128` | The amount of tokens to transfer, denoted in token's decimals. | ### void Voids a stream. Emits a {VoidFlowStream} and {MetadataUpdate} event. Notes: - It sets snapshot time to the `block.timestamp`. - Voiding an insolvent stream sets the snapshot debt to the stream's balance making the uncovered debt to become zero. - Voiding a solvent stream updates the snapshot debt by adding up ongoing debt. - It sets the rate per second to zero. - A voided stream cannot be restarted. Requirements: - Must not be delegate called. - `streamId` must not reference a null or a voided stream. - `msg.sender` must either be the stream's sender, recipient or an approved third party. ```solidity function void(uint256 streamId) external payable override noDelegateCall notNull(streamId) notVoided(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to void. | ### withdraw Withdraws the provided `amount` to the provided `to` address. Emits a {Transfer}, {WithdrawFromFlowStream} and {MetadataUpdate} event. Notes: - It sets the snapshot time to the `block.timestamp` if `amount` is greater than snapshot debt. Requirements: - Must not be delegate called. - `streamId` must not reference a null stream. - `to` must not be the zero address. - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. - `amount` must be greater than zero and must not exceed the withdrawable amount. - `msg.value` must be greater than or equal to the minimum fee in wei for the stream's sender. ```solidity function withdraw( uint256 streamId, address to, uint128 amount ) external payable override noDelegateCall notNull(streamId) updateMetadata(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | | `amount` | `uint128` | The amount to withdraw, denoted in token's decimals. | ### withdrawMax Withdraws the entire withdrawable amount to the provided `to` address. Emits a {Transfer}, {WithdrawFromFlowStream} and {MetadataUpdate} event. Notes: - Refer to the notes in {withdraw}. Requirements: - Refer to the requirements in {withdraw}. ```solidity function withdrawMax( uint256 streamId, address to ) external payable override noDelegateCall notNull(streamId) updateMetadata(streamId) returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawnAmount` | `uint128` | The amount withdrawn to the recipient, denoted in token's decimals. | ### \_update Overrides the {ERC721.\_update} function to check that the stream is transferable. The transferable flag is ignored if the current owner is 0, as the update in this case is a mint and is allowed. Transfers to the zero address are not allowed, preventing accidental burns. ```solidity function _update( address to, uint256 streamId, address auth ) internal override updateMetadata(streamId) returns (address); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `to` | `address` | The address of the new recipient of the stream. | | `streamId` | `uint256` | ID of the stream to update. | | `auth` | `address` | Optional parameter. If the value is not zero, the overridden implementation will check that `auth` is either the recipient of the stream, or an approved third party. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `address` | The original recipient of the `streamId` before the update. | ### \_coveredDebtOf Calculates the amount of covered debt by the stream balance. ```solidity function _coveredDebtOf(uint256 streamId) private view returns (uint128); ``` ### \_isCallerStreamRecipientOrApproved Checks whether `msg.sender` is the stream's recipient or an approved third party. ```solidity function _isCallerStreamRecipientOrApproved(uint256 streamId, address recipient) private view returns (bool); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | | `recipient` | `address` | | ### \_ongoingDebtScaledOf Calculates the ongoing debt, as a 18-decimals fixed point number, accrued since last snapshot. Return 0 if the stream is paused or `block.timestamp` is less than or equal to snapshot time. ```solidity function _ongoingDebtScaledOf(uint256 streamId) private view returns (uint256); ``` ### \_refundableAmountOf Calculates the refundable amount. ```solidity function _refundableAmountOf(uint256 streamId) private view returns (uint128); ``` ### \_totalDebtOf The total debt is the sum of the snapshot debt and the ongoing debt descaled to token's decimal. This value is independent of the stream's balance. ```solidity function _totalDebtOf(uint256 streamId) private view returns (uint256); ``` ### \_uncoveredDebtOf Calculates the uncovered debt. ```solidity function _uncoveredDebtOf(uint256 streamId) private view returns (uint256); ``` ### \_verifyStreamSenderRecipient Checks whether the provided addresses matches stream's sender and recipient. ```solidity function _verifyStreamSenderRecipient(uint256 streamId, address sender, address recipient) private view; ``` ### \_adjustRatePerSecond See the documentation for the user-facing functions that call this private function. ```solidity function _adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) private; ``` ### \_create See the documentation for the user-facing functions that call this private function. ```solidity function _create( address sender, address recipient, UD21x18 ratePerSecond, uint40 startTime, IERC20 token, bool transferable ) private returns (uint256 streamId); ``` ### \_deposit See the documentation for the user-facing functions that call this private function. ```solidity function _deposit(uint256 streamId, uint128 amount) private; ``` ### \_pause See the documentation for the user-facing functions that call this private function. ```solidity function _pause(uint256 streamId) private; ``` ### \_refund See the documentation for the user-facing functions that call this private function. ```solidity function _refund(uint256 streamId, uint128 amount) private; ``` ### \_restart See the documentation for the user-facing functions that call this private function. ```solidity function _restart(uint256 streamId, UD21x18 ratePerSecond) private; ``` ### \_void See the documentation for the user-facing functions that call this private function. ```solidity function _void(uint256 streamId) private; ``` ### \_withdraw See the documentation for the user-facing functions that call this private function. ```solidity function _withdraw(uint256 streamId, address to, uint128 amount) private; ``` --- ## IBatch Source: https://docs.sablier.com/reference/flow/contracts/interfaces/interface.IBatch # IBatch [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IBatch.sol) This contract implements logic to batch call any function. ## Functions ### batch Allows batched calls to self, i.e., `this` contract. Since `msg.value` can be reused across calls, be VERY CAREFUL when using it. Refer to [https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong](https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong) for more information. ```solidity function batch(bytes[] calldata calls) external payable returns (bytes[] memory results); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `calls` | `bytes[]` | An array of inputs for each call. | **Returns** | Name | Type | Description | | --- | --- | --- | | `results` | `bytes[]` | An array of results from each call. Empty when the calls do not return anything. | --- ## IComptrollerable Source: https://docs.sablier.com/reference/flow/contracts/interfaces/interface.IComptrollerable # IComptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IComptrollerable.sol) **Title:** IComptrollerable Contract module that provides a setter and getter for the Sablier Comptroller. ## Functions ### comptroller Retrieves the address of the comptroller contract. ```solidity function comptroller() external view returns (ISablierComptroller); ``` ### setComptroller Sets the comptroller to a new address. Emits a [SetComptroller](#setcomptroller) event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a [TransferFeesToComptroller](#transferfeestocomptroller) event. ```solidity function transferFeesToComptroller() external; ``` ## Events ### SetComptroller Emitted when the comptroller address is set by the admin. ```solidity event SetComptroller(ISablierComptroller oldComptroller, ISablierComptroller newComptroller); ``` ### TransferFeesToComptroller Emitted when the fees are transferred to the comptroller contract. ```solidity event TransferFeesToComptroller(address indexed comptroller, uint256 feeAmount); ``` --- ## IFlowNFTDescriptor Source: https://docs.sablier.com/reference/flow/contracts/interfaces/interface.IFlowNFTDescriptor # IFlowNFTDescriptor [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IFlowNFTDescriptor.sol) **Title:** IFlowNFTDescriptor This contract generates the URI describing the Sablier Flow stream NFTs. ## Functions ### tokenURI Produces the URI describing a particular stream NFT. Currently it returns the Sablier logo as an SVG. In the future, it will return an NFT SVG. ```solidity function tokenURI(IERC721Metadata sablierFlow, uint256 streamId) external view returns (string memory uri); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `sablierFlow` | `IERC721Metadata` | The address of the Sablier Flow the stream was created in. | | `streamId` | `uint256` | The ID of the stream for which to produce a description. | **Returns** | Name | Type | Description | | --- | --- | --- | | `uri` | `string` | The URI of the ERC721-compliant metadata. | --- ## ISablierFlow Source: https://docs.sablier.com/reference/flow/contracts/interfaces/interface.ISablierFlow # ISablierFlow [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFlow.sol) **Inherits:** IBatch, IComptrollerable, IERC4906, IERC721Metadata, [ISablierFlowState](/reference/flow/contracts/interfaces/interface.ISablierFlowState) **Title:** ISablierFlow Creates and manages Flow streams with linear streaming functions. ## Functions ### calculateMinFeeWei Calculates the minimum fee in wei required to withdraw from the given stream ID. Reverts if `streamId` references a null stream. ```solidity function calculateMinFeeWei(uint256 streamId) external view returns (uint256 minFeeWei); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### coveredDebtOf Returns the amount of debt covered by the stream balance, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function coveredDebtOf(uint256 streamId) external view returns (uint128 coveredDebt); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### depletionTimeOf Returns the time at which the total debt exceeds stream balance. If the total debt exceeds the stream balance, it returns 0. Reverts on the following conditions: - If `streamId` references a paused or a null stream. - If stream balance is zero. ```solidity function depletionTimeOf(uint256 streamId) external view returns (uint256 depletionTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getRecipient Retrieves the stream's recipient. Reverts if `streamId` references a null stream. ```solidity function getRecipient(uint256 streamId) external view returns (address recipient); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### ongoingDebtScaledOf Returns the amount of debt accrued since the snapshot time until now, denoted as a fixed-point number where 1e18 is 1 token. If the stream is pending, it returns zero. Reverts if `streamId` references a null stream. ```solidity function ongoingDebtScaledOf(uint256 streamId) external view returns (uint256 ongoingDebtScaled); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### refundableAmountOf Returns the amount that the sender can be refunded from the stream, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function refundableAmountOf(uint256 streamId) external view returns (uint128 refundableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### statusOf Returns the stream's status. Reverts if `streamId` references a null stream. Integrators should exercise caution when depending on the return value of this function as streams can be paused and resumed at any moment. ```solidity function statusOf(uint256 streamId) external view returns (Flow.Status status); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### totalDebtOf Returns the total amount owed by the sender to the recipient, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function totalDebtOf(uint256 streamId) external view returns (uint256 totalDebt); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### uncoveredDebtOf Returns the amount of debt not covered by the stream balance, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function uncoveredDebtOf(uint256 streamId) external view returns (uint256 uncoveredDebt); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### withdrawableAmountOf Calculates the amount that the recipient can withdraw from the stream, denoted in token decimals. This is an alias for `coveredDebtOf`. Reverts if `streamId` references a null stream. ```solidity function withdrawableAmountOf(uint256 streamId) external view returns (uint128 withdrawableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawableAmount` | `uint128` | The amount that the recipient can withdraw. | ### adjustRatePerSecond Changes the stream's rate per second. Emits a [AdjustFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#adjustflowstream) and {MetadataUpdate} event. Notes: - If the snapshot time is not in the future, it updates both the snapshot time and snapshot debt. Requirements: - Must not be delegate called. - `streamId` must not reference a null, paused, or voided stream. - `msg.sender` must be the stream's sender. - `newRatePerSecond` must be greater than zero and must be different from the current rate per second. ```solidity function adjustRatePerSecond(uint256 streamId, UD21x18 newRatePerSecond) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to adjust. | | `newRatePerSecond` | `UD21x18` | The new rate per second, denoted as a fixed-point number where 1e18 is 1 token per second. | ### create Creates a new Flow stream by setting the snapshot time to `startTime` and leaving the balance to zero. The stream is wrapped in an ERC-721 NFT. Emits a [CreateFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#createflowstream) and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `sender` must not be the zero address. - `recipient` must not be the zero address. - If `startTime` is in the future, the `ratePerSecond` must be greater than zero. - The `token` must not be the native token. - The `token`'s decimals must be less than or equal to 18. ```solidity function create( address sender, address recipient, UD21x18 ratePerSecond, uint40 startTime, IERC20 token, bool transferable ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `sender` | `address` | The address streaming the tokens, which is able to adjust and pause the stream. It doesn't have to be the same as `msg.sender`. | | `recipient` | `address` | The address receiving the tokens. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `startTime` | `uint40` | The timestamp when the stream starts. A sentinel value of zero means the stream will be created with the snapshot time as `block.timestamp`. | | `token` | `IERC20` | The contract address of the ERC-20 token to be streamed. | | `transferable` | `bool` | Boolean indicating if the stream NFT is transferable. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createAndDeposit Creates a new Flow stream by setting the snapshot time to `startTime` and the balance to `amount`. The stream is wrapped in an ERC-721 NFT. Emits a {Transfer}, [CreateFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#createflowstream), [DepositFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#depositflowstream) and {MetadataUpdate} event. Notes: - Refer to the notes in [create](/reference/flow/contracts/interfaces/interface.ISablierFlow#create) and [deposit](/reference/flow/contracts/interfaces/interface.ISablierFlow#deposit). Requirements: - Refer to the requirements in [create](/reference/flow/contracts/interfaces/interface.ISablierFlow#create) and [deposit](/reference/flow/contracts/interfaces/interface.ISablierFlow#deposit). ```solidity function createAndDeposit( address sender, address recipient, UD21x18 ratePerSecond, uint40 startTime, IERC20 token, bool transferable, uint128 amount ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `sender` | `address` | The address streaming the tokens. It doesn't have to be the same as `msg.sender`. | | `recipient` | `address` | The address receiving the tokens. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `startTime` | `uint40` | The timestamp when the stream starts. A sentinel value of zero means the stream will be created with the snapshot time as `block.timestamp`. | | `token` | `IERC20` | The contract address of the ERC-20 token to be streamed. | | `transferable` | `bool` | Boolean indicating if the stream NFT is transferable. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### deposit Makes a deposit in a stream. Emits a {Transfer}, [DepositFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#depositflowstream) and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `streamId` must not reference a null or a voided stream. - `amount` must be greater than zero. - `sender` and `recipient` must match the stream's sender and recipient addresses. ```solidity function deposit(uint256 streamId, uint128 amount, address sender, address recipient) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to deposit to. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | | `sender` | `address` | The stream's sender address. | | `recipient` | `address` | The stream's recipient address. | ### depositAndPause Deposits tokens in a stream and pauses it. Emits a {Transfer}, [DepositFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#depositflowstream), [PauseFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#pauseflowstream) and {MetadataUpdate} event. Notes: - Refer to the notes in [deposit](/reference/flow/contracts/interfaces/interface.ISablierFlow#deposit) and [pause](/reference/flow/contracts/interfaces/interface.ISablierFlow#pause). Requirements: - Refer to the requirements in [deposit](/reference/flow/contracts/interfaces/interface.ISablierFlow#deposit) and [pause](/reference/flow/contracts/interfaces/interface.ISablierFlow#pause). ```solidity function depositAndPause(uint256 streamId, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to deposit to, and then pause. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | ### pause Pauses the stream. Emits a [PauseFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#pauseflowstream) and {MetadataUpdate} event. Notes: - It updates snapshot debt and snapshot time. - It sets the rate per second to zero. Requirements: - Must not be delegate called. - `streamId` must not reference a null, pending or paused stream. - `msg.sender` must be the stream's sender. ```solidity function pause(uint256 streamId) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to pause. | ### recover Recover the surplus amount of tokens. Notes: - The surplus amount is defined as the difference between the total balance of the contract for the provided ERC-20 token and the sum of balances of all streams created using the same ERC-20 token. Requirements: - `msg.sender` must be the comptroller contract. ```solidity function recover(IERC20 token, address to) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The contract address of the ERC-20 token to recover for. | | `to` | `address` | The address to send the surplus amount. | ### refund Refunds the provided amount of tokens from the stream to the sender's address. Emits a {Transfer}, [RefundFromFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#refundfromflowstream) and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `streamId` must not reference a null stream. - `msg.sender` must be the sender. - `amount` must be greater than zero and must not exceed the refundable amount. ```solidity function refund(uint256 streamId, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to refund from. | | `amount` | `uint128` | The amount to refund, denoted in token's decimals. | ### refundAndPause Refunds the provided amount of tokens from the stream to the sender's address. Emits a {Transfer}, [RefundFromFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#refundfromflowstream), [PauseFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#pauseflowstream) and {MetadataUpdate} event. Notes: - Refer to the notes in [pause](/reference/flow/contracts/interfaces/interface.ISablierFlow#pause). Requirements: - Refer to the requirements in [refund](/reference/flow/contracts/interfaces/interface.ISablierFlow#refund) and [pause](/reference/flow/contracts/interfaces/interface.ISablierFlow#pause). ```solidity function refundAndPause(uint256 streamId, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to refund from and then pause. | | `amount` | `uint128` | The amount to refund, denoted in token's decimals. | ### refundMax Refunds the entire refundable amount of tokens from the stream to the sender's address. Emits a {Transfer}, [RefundFromFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#refundfromflowstream) and {MetadataUpdate} event. Requirements: - Refer to the requirements in [refund](/reference/flow/contracts/interfaces/interface.ISablierFlow#refund). ```solidity function refundMax(uint256 streamId) external payable returns (uint128 refundedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to refund from. | **Returns** | Name | Type | Description | | --- | --- | --- | | `refundedAmount` | `uint128` | The amount refunded to the stream sender, denoted in token's decimals. | ### restart Restarts the stream with the provided rate per second. Emits a [RestartFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#restartflowstream) and {MetadataUpdate} event. Notes: - It updates snapshot debt and snapshot time. Requirements: - Must not be delegate called. - `streamId` must not reference a null stream, must be paused, and must not be voided. - `msg.sender` must be the stream's sender. - `ratePerSecond` must be greater than zero. ```solidity function restart(uint256 streamId, UD21x18 ratePerSecond) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to restart. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | ### restartAndDeposit Restarts the stream with the provided rate per second, and makes a deposit. Emits a [RestartFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#restartflowstream), {Transfer}, [DepositFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#depositflowstream) and {MetadataUpdate} event. Notes: - Refer to the notes in [restart](/reference/flow/contracts/interfaces/interface.ISablierFlow#restart) and [deposit](/reference/flow/contracts/interfaces/interface.ISablierFlow#deposit). Requirements: - `amount` must be greater than zero. - Refer to the requirements in [restart](/reference/flow/contracts/interfaces/interface.ISablierFlow#restart). ```solidity function restartAndDeposit(uint256 streamId, UD21x18 ratePerSecond, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to restart. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `amount` | `uint128` | The deposit amount, denoted in token's decimals. | ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for {nativeToken}. Emits a [SetNativeToken](/reference/flow/contracts/interfaces/interface.ISablierFlow#setnativetoken) event. Requirements: - `msg.sender` must be the comptroller contract. - `newNativeToken` must not be the zero address. - The native token must not be already set. ```solidity function setNativeToken(address newNativeToken) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ### setNFTDescriptor Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. Emits a [SetNFTDescriptor](/reference/flow/contracts/interfaces/interface.ISablierFlow#setnftdescriptor) and {BatchMetadataUpdate} event. Notes: - Does not revert if the NFT descriptor is the same. Requirements: - `msg.sender` must be the comptroller contract. ```solidity function setNFTDescriptor(IFlowNFTDescriptor newNFTDescriptor) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNFTDescriptor` | `IFlowNFTDescriptor` | The address of the new NFT descriptor contract. | ### transferFromPayable Wrapper for {IERC721.transferFrom} with the `payable` specifier so that it can be called in conjunction with other functions using {IBatch.batch}. Requirements: - Refer to the requirements of `transferFrom` in: [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e4f70216d759d8e6a64144a9e1f7bbeed78e7079/contracts/token/ERC721/IERC721.sol#L75-L91](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e4f70216d759d8e6a64144a9e1f7bbeed78e7079/contracts/token/ERC721/IERC721.sol#L75-L91). ```solidity function transferFromPayable(address from, address to, uint256 streamId) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `from` | `address` | The owner of the stream ID. | | `to` | `address` | The address of the new owner of the stream ID. | | `streamId` | `uint256` | The ID of the stream NFT to transfer. | ### transferTokens A helper to transfer ERC-20 tokens from the caller to the provided address. Useful for paying one-time bonuses. Emits a {Transfer} event. Requirements: - `msg.sender` must have approved this contract to spend at least `amount` tokens. ```solidity function transferTokens(IERC20 token, address to, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The contract address of the ERC-20 token to be transferred. | | `to` | `address` | The address receiving the tokens. | | `amount` | `uint128` | The amount of tokens to transfer, denoted in token's decimals. | ### void Voids a stream. Emits a [VoidFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#voidflowstream) and {MetadataUpdate} event. Notes: - It sets snapshot time to the `block.timestamp`. - Voiding an insolvent stream sets the snapshot debt to the stream's balance making the uncovered debt to become zero. - Voiding a solvent stream updates the snapshot debt by adding up ongoing debt. - It sets the rate per second to zero. - A voided stream cannot be restarted. Requirements: - Must not be delegate called. - `streamId` must not reference a null or a voided stream. - `msg.sender` must either be the stream's sender, recipient or an approved third party. ```solidity function void(uint256 streamId) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to void. | ### withdraw Withdraws the provided `amount` to the provided `to` address. Emits a {Transfer}, [WithdrawFromFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#withdrawfromflowstream) and {MetadataUpdate} event. Notes: - It sets the snapshot time to the `block.timestamp` if `amount` is greater than snapshot debt. Requirements: - Must not be delegate called. - `streamId` must not reference a null stream. - `to` must not be the zero address. - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. - `amount` must be greater than zero and must not exceed the withdrawable amount. - `msg.value` must be greater than or equal to the minimum fee in wei for the stream's sender. ```solidity function withdraw(uint256 streamId, address to, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | | `amount` | `uint128` | The amount to withdraw, denoted in token's decimals. | ### withdrawMax Withdraws the entire withdrawable amount to the provided `to` address. Emits a {Transfer}, [WithdrawFromFlowStream](/reference/flow/contracts/interfaces/interface.ISablierFlow#withdrawfromflowstream) and {MetadataUpdate} event. Notes: - Refer to the notes in [withdraw](/reference/flow/contracts/interfaces/interface.ISablierFlow#withdraw). Requirements: - Refer to the requirements in [withdraw](/reference/flow/contracts/interfaces/interface.ISablierFlow#withdraw). ```solidity function withdrawMax(uint256 streamId, address to) external payable returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawnAmount` | `uint128` | The amount withdrawn to the recipient, denoted in token's decimals. | ## Events ### AdjustFlowStream Emitted when the rate per second is updated by the sender. ```solidity event AdjustFlowStream( uint256 indexed streamId, uint256 totalDebt, UD21x18 oldRatePerSecond, UD21x18 newRatePerSecond ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `totalDebt` | `uint256` | The total debt at the time of the update, denoted in token's decimals. | | `oldRatePerSecond` | `UD21x18` | The old rate per second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `newRatePerSecond` | `UD21x18` | The new rate per second, denoted as a fixed-point number where 1e18 is 1 token per second. | ### CreateFlowStream Emitted when a Flow stream is created. ```solidity event CreateFlowStream( uint256 streamId, address creator, address indexed sender, address indexed recipient, UD21x18 ratePerSecond, uint40 snapshotTime, IERC20 indexed token, bool transferable ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | | `creator` | `address` | The address creating the stream. | | `sender` | `address` | The address streaming the tokens, which is able to adjust and pause the stream. | | `recipient` | `address` | The address receiving the tokens, as well as the NFT owner. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | | `snapshotTime` | `uint40` | The timestamp when the stream begins accumulating debt. | | `token` | `IERC20` | The contract address of the ERC-20 token to be streamed. | | `transferable` | `bool` | Boolean indicating whether the stream NFT is transferable or not. | ### DepositFlowStream Emitted when a stream is funded. ```solidity event DepositFlowStream(uint256 indexed streamId, address indexed funder, uint128 amount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `funder` | `address` | The address that made the deposit. | | `amount` | `uint128` | The amount of tokens deposited into the stream, denoted in token's decimals. | ### PauseFlowStream Emitted when a stream is paused by the sender. ```solidity event PauseFlowStream( uint256 indexed streamId, address indexed sender, address indexed recipient, uint256 totalDebt ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `sender` | `address` | The stream's sender address. | | `recipient` | `address` | The stream's recipient address. | | `totalDebt` | `uint256` | The amount of tokens owed by the sender to the recipient, denoted in token's decimals. | ### RefundFromFlowStream Emitted when a sender is refunded from a stream. ```solidity event RefundFromFlowStream(uint256 indexed streamId, address indexed sender, uint128 amount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `sender` | `address` | The stream's sender address. | | `amount` | `uint128` | The amount of tokens refunded to the sender, denoted in token's decimals. | ### RestartFlowStream Emitted when a stream is restarted by the sender. ```solidity event RestartFlowStream(uint256 indexed streamId, address indexed sender, UD21x18 ratePerSecond); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `sender` | `address` | The stream's sender address. | | `ratePerSecond` | `UD21x18` | The amount by which the debt is increasing every second, denoted as a fixed-point number where 1e18 is 1 token per second. | ### SetNativeToken Emitted when the native token address is set by the comptroller. ```solidity event SetNativeToken(ISablierComptroller indexed comptroller, address nativeToken); ``` ### SetNFTDescriptor Emitted when the comptroller sets a new NFT descriptor contract. ```solidity event SetNFTDescriptor( ISablierComptroller indexed comptroller, IFlowNFTDescriptor oldNFTDescriptor, IFlowNFTDescriptor newNFTDescriptor ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `comptroller` | `ISablierComptroller` | The address of the current comptroller. | | `oldNFTDescriptor` | `IFlowNFTDescriptor` | The address of the old NFT descriptor contract. | | `newNFTDescriptor` | `IFlowNFTDescriptor` | The address of the new NFT descriptor contract. | ### VoidFlowStream Emitted when a stream is voided by the sender, recipient or an approved operator. ```solidity event VoidFlowStream( uint256 indexed streamId, address indexed sender, address indexed recipient, address caller, uint256 newTotalDebt, uint256 writtenOffDebt ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `sender` | `address` | The stream's sender address. | | `recipient` | `address` | The stream's recipient address. | | `caller` | `address` | The address that performed the void, which can be the sender, recipient or an approved operator. | | `newTotalDebt` | `uint256` | The new total debt, denoted in token's decimals. | | `writtenOffDebt` | `uint256` | The amount of debt written off by the caller, denoted in token's decimals. | ### WithdrawFromFlowStream Emitted when tokens are withdrawn from a stream by a recipient or an approved operator. ```solidity event WithdrawFromFlowStream( uint256 indexed streamId, address indexed to, IERC20 indexed token, address caller, uint128 withdrawAmount ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `to` | `address` | The address that received the withdrawn tokens. | | `token` | `IERC20` | The contract address of the ERC-20 token that was withdrawn. | | `caller` | `address` | The address that performed the withdrawal, which can be the recipient or an approved operator. | | `withdrawAmount` | `uint128` | The amount withdrawn to the recipient, denoted in token's decimals. | --- ## ISablierFlowState Source: https://docs.sablier.com/reference/flow/contracts/interfaces/interface.ISablierFlowState # ISablierFlowState [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierFlowState.sol) **Title:** ISablierFlowState Contract with state variables (storage and constants) for the [SablierFlow](/reference/flow/contracts/contract.SablierFlow) contract, their respective getters and helpful modifiers. ## Functions ### aggregateAmount Retrieves the aggregate amount across all streams, denoted in units of the token's decimals. If tokens are directly transferred to the contract without using the stream creation functions, the ERC-20 balance may be greater than the aggregate amount. ```solidity function aggregateAmount(IERC20 token) external view returns (uint256); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The ERC-20 token for the query. | ### getBalance Retrieves the balance of the stream, i.e. the total deposited amounts subtracted by the total withdrawn amounts, denoted in token's decimals. Reverts if `streamId` references a null stream. ```solidity function getBalance(uint256 streamId) external view returns (uint128 balance); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getRatePerSecond Retrieves the rate per second of the stream, denoted as a fixed-point number where 1e18 is 1 token per second. Reverts if `streamId` references a null stream. ```solidity function getRatePerSecond(uint256 streamId) external view returns (UD21x18 ratePerSecond); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### getSender Retrieves the stream's sender. Reverts if `streamId` references a null stream. ```solidity function getSender(uint256 streamId) external view returns (address sender); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getSnapshotDebtScaled Retrieves the snapshot debt of the stream, denoted as a fixed-point number where 1e18 is 1 token. Reverts if `streamId` references a null stream. ```solidity function getSnapshotDebtScaled(uint256 streamId) external view returns (uint256 snapshotDebtScaled); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getSnapshotTime Retrieves the snapshot time of the stream, which is a Unix timestamp. Reverts if `streamId` references a null stream. ```solidity function getSnapshotTime(uint256 streamId) external view returns (uint40 snapshotTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### getStream Retrieves the stream entity. Reverts if `streamId` references a null stream. ```solidity function getStream(uint256 streamId) external view returns (Flow.Stream memory stream); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getToken Retrieves the token of the stream. Reverts if `streamId` references a null stream. ```solidity function getToken(uint256 streamId) external view returns (IERC20 token); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### getTokenDecimals Retrieves the token decimals of the stream. Reverts if `streamId` references a null stream. ```solidity function getTokenDecimals(uint256 streamId) external view returns (uint8 tokenDecimals); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to make the query for. | ### isStream Retrieves a flag indicating whether the stream exists. Does not revert if `streamId` references a null stream. ```solidity function isStream(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isTransferable Retrieves a flag indicating whether the stream NFT is transferable. Reverts if `streamId` references a null stream. ```solidity function isTransferable(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isVoided Retrieves a flag indicating whether the stream is voided. Reverts if `streamId` references a null stream. ```solidity function isVoided(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity function nativeToken() external view returns (address); ``` ### nextStreamId Counter for stream ids. ```solidity function nextStreamId() external view returns (uint256); ``` **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `uint256` | The next stream ID. | ### nftDescriptor Contract that generates the non-fungible token URI. ```solidity function nftDescriptor() external view returns (IFlowNFTDescriptor); ``` --- ## Errors Source: https://docs.sablier.com/reference/flow/contracts/libraries/library.Errors # Errors [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/Errors.sol) **Title:** Errors Library with custom errors used across the Flow contract. ## Errors ### SablierFlow\_CreateNativeToken Thrown when trying to create a stream with the native token. ```solidity error SablierFlow_CreateNativeToken(address nativeToken); ``` ### SablierFlow\_CreateRatePerSecondZero Thrown when trying to create a pending stream with zero rate per second. ```solidity error SablierFlow_CreateRatePerSecondZero(); ``` ### SablierFlow\_DepositAmountZero Thrown when trying to create a stream with a zero deposit amount. ```solidity error SablierFlow_DepositAmountZero(uint256 streamId); ``` ### SablierFlow\_InsufficientFeePayment Thrown when trying to withdraw with a fee amount less than the minimum fee. ```solidity error SablierFlow_InsufficientFeePayment(uint256 feePaid, uint256 minFeeWei); ``` ### SablierFlow\_InvalidCalculation Thrown when an unexpected error occurs during the calculation of an amount. ```solidity error SablierFlow_InvalidCalculation(uint256 streamId, uint128 availableAmount, uint128 amount); ``` ### SablierFlow\_InvalidTokenDecimals Thrown when trying to create a stream with a token with decimals greater than 18. ```solidity error SablierFlow_InvalidTokenDecimals(address token); ``` ### SablierFlow\_NativeTokenAlreadySet Thrown when trying to set the native token address when it is already set. ```solidity error SablierFlow_NativeTokenAlreadySet(address nativeToken); ``` ### SablierFlow\_NativeTokenZeroAddress Thrown when trying to set zero address as native token. ```solidity error SablierFlow_NativeTokenZeroAddress(); ``` ### SablierFlow\_NewRatePerSecondZero Thrown when trying to adjust the rate per second to zero. ```solidity error SablierFlow_NewRatePerSecondZero(uint256 streamId); ``` ### SablierFlow\_NotStreamRecipient Thrown when the recipient address does not match the stream's recipient. ```solidity error SablierFlow_NotStreamRecipient(address recipient, address streamRecipient); ``` ### SablierFlow\_NotStreamSender Thrown when the sender address does not match the stream's sender. ```solidity error SablierFlow_NotStreamSender(address sender, address streamSender); ``` ### SablierFlow\_NotTransferable Thrown when trying to transfer Stream NFT when transferability is disabled. ```solidity error SablierFlow_NotTransferable(uint256 streamId); ``` ### SablierFlow\_Overdraw Thrown when trying to withdraw an amount greater than the withdrawable amount. ```solidity error SablierFlow_Overdraw(uint256 streamId, uint128 amount, uint128 withdrawableAmount); ``` ### SablierFlow\_RatePerSecondNotDifferent Thrown when trying to change the rate per second with the same rate per second. ```solidity error SablierFlow_RatePerSecondNotDifferent(uint256 streamId, UD21x18 ratePerSecond); ``` ### SablierFlow\_RefundAmountZero Thrown when trying to refund zero tokens from a stream. ```solidity error SablierFlow_RefundAmountZero(uint256 streamId); ``` ### SablierFlow\_RefundOverflow Thrown when trying to refund an amount greater than the refundable amount. ```solidity error SablierFlow_RefundOverflow(uint256 streamId, uint128 refundAmount, uint128 refundableAmount); ``` ### SablierFlow\_SenderZeroAddress Thrown when trying to create a stream with the sender as the zero address. ```solidity error SablierFlow_SenderZeroAddress(); ``` ### SablierFlow\_StreamBalanceZero Thrown when trying to get depletion time of a stream with zero balance. ```solidity error SablierFlow_StreamBalanceZero(uint256 streamId); ``` ### SablierFlow\_StreamNotPaused Thrown when trying to perform a disallowed action on a non-paused stream. ```solidity error SablierFlow_StreamNotPaused(uint256 streamId); ``` ### SablierFlow\_StreamPending Thrown when trying to perform a disallowed action on a pending stream. ```solidity error SablierFlow_StreamPending(uint256 streamId, uint40 snapshotTime); ``` ### SablierFlow\_Unauthorized Thrown when `msg.sender` lacks authorization to perform an action. ```solidity error SablierFlow_Unauthorized(uint256 streamId, address caller); ``` ### SablierFlow\_WithdrawalAddressNotRecipient Thrown when trying to withdraw to an address other than the recipient's. ```solidity error SablierFlow_WithdrawalAddressNotRecipient(uint256 streamId, address caller, address to); ``` ### SablierFlow\_WithdrawAmountZero Thrown when trying to withdraw zero tokens from a stream. ```solidity error SablierFlow_WithdrawAmountZero(uint256 streamId); ``` ### SablierFlow\_WithdrawToZeroAddress Thrown when trying to withdraw to the zero address. ```solidity error SablierFlow_WithdrawToZeroAddress(uint256 streamId); ``` ### SablierFlowState\_Null Thrown when the ID references a null stream. ```solidity error SablierFlowState_Null(uint256 streamId); ``` ### SablierFlowState\_StreamPaused Thrown when trying to perform a disallowed action on a paused stream. ```solidity error SablierFlowState_StreamPaused(uint256 streamId); ``` ### SablierFlowState\_StreamVoided Thrown when trying to perform a disallowed action on a voided stream. ```solidity error SablierFlowState_StreamVoided(uint256 streamId); ``` ### SablierFlowState\_Unauthorized Thrown when `msg.sender` lacks authorization to perform an action. ```solidity error SablierFlowState_Unauthorized(uint256 streamId, address caller); ``` --- ## FlowHelpers Source: https://docs.sablier.com/reference/flow/contracts/libraries/library.FlowHelpers # FlowHelpers [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/FlowHelpers.sol) **Title:** FlowHelpers Library with helper functions in [SablierFlow](/reference/flow/contracts/contract.SablierFlow) contract. ## Functions ### descaleAmount Descales the provided `amount` from 18 decimals fixed-point number to token's decimals number. If `decimals` exceeds 18, it will cause an underflow. ```solidity function descaleAmount(uint256 amount, uint8 decimals) internal pure returns (uint256); ``` ### scaleAmount Scales the provided `amount` from token's decimals number to 18 decimals fixed-point number. If `decimals` exceeds 18, it will cause an underflow. If `amount` exceeds max value of `uint128`, the result may overflow `uint256`. ```solidity function scaleAmount(uint256 amount, uint8 decimals) internal pure returns (uint256); ``` --- ## Flow Source: https://docs.sablier.com/reference/flow/contracts/types/library.Flow # Flow [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/types/DataTypes.sol) ## Structs ### Stream Struct representing Flow streams. The fields are arranged like this to save gas via tight variable packing. ```solidity struct Stream { // slot 0 uint128 balance; UD21x18 ratePerSecond; // slot 1 address sender; uint40 snapshotTime; bool isStream; bool isTransferable; bool isVoided; // slot 2 IERC20 token; uint8 tokenDecimals; // slot 3 uint256 snapshotDebtScaled; } ``` **Properties** | Name | Type | Description | | --- | --- | --- | | `balance` | `uint128` | The amount of tokens that are currently available in the stream, denoted in the token's decimals. This is the sum of deposited amounts minus the sum of withdrawn amounts. | | `ratePerSecond` | `UD21x18` | The payment rate per second, denoted as a fixed-point number where 1e18 is 1 token per second. For example, to stream 1000 tokens per week, this parameter would have the value $(1000 * 10^18) / (7 days in seconds)$. | | `sender` | `address` | The address streaming the tokens, with the ability to pause the stream. | | `snapshotTime` | `uint40` | The Unix timestamp used for the ongoing debt calculation. | | `isStream` | `bool` | Boolean indicating if the struct entity exists. | | `isTransferable` | `bool` | Boolean indicating if the stream NFT is transferable. | | `isVoided` | `bool` | Boolean indicating if the stream is voided. Voiding any stream is non-reversible and it cannot be restarted. Voiding an insolvent stream sets its uncovered debt to zero. | | `token` | `IERC20` | The contract address of the ERC-20 token to stream. | | `tokenDecimals` | `uint8` | The decimals of the ERC-20 token to stream. | | `snapshotDebtScaled` | `uint256` | The amount of tokens that the sender owed to the recipient at snapshot time, denoted as a 18-decimals fixed-point number. This, along with the ongoing debt, can be used to calculate the total debt at any given point in time. | ## Enums ### Status Enum representing the different statuses of a stream. **Notes:** - value0: PENDING Stream scheduled to start in the future. - value1: STREAMING\_SOLVENT Streaming stream with no uncovered debt. - value2: STREAMING\_INSOLVENT Streaming stream with uncovered debt. - value3: PAUSED\_SOLVENT Paused stream with no uncovered debt. - value4: PAUSED\_INSOLVENT Paused stream with uncovered debt. - value5: VOIDED Paused stream with no uncovered debt, which cannot be restarted. ```solidity enum Status { PENDING, STREAMING_SOLVENT, STREAMING_INSOLVENT, PAUSED_SOLVENT, PAUSED_INSOLVENT, VOIDED } ``` --- ## Diagrams Source: https://docs.sablier.com/reference/flow/diagrams # Diagrams ## Token Flows The following three functions lead to tokens flow in and out of a stream: ### Deposit Anyone can deposit into a stream. ### Refund Only sender can refund from the stream that he created. ### Withdraw Anyone can call withdraw on a stream as long as `to` address matches the recipient. If recipient/operator is calling withdraw on a stream, they can choose to withdraw to any address. ## Abbreviations | Abbreviation | Full name | Description | | --- | --- | --- | | bal | Stream balance | Balance of the stream | | cd | Covered debt | Portion of the total debt covered by the stream balance | | elt | Elapsed time | Time elapsed in seconds since the last snapshot | | od | Ongoing debt | Debt accumulated since the last snapshot | | now | Current time | Same as `block.timestamp` | | rps | Rate per second | Rate at which tokens are streamed per second | | sd | Snapshot debt | Debt accumulated until the last snapshot | | st | Snapshot time | Time of the last snapshot | | td | Total debt | Sum of sd and od, also same as sum of cd and ud | | ud | Uncovered debt | Portion of the total debt not covered by the stream balance | ## Storage Layout Flow is a singleton contract that stores all streams created by that contract's users. The following diagrams provide insight into the storage layout of each stream. To see the full list of storage variables, check out [this reference](/reference/flow/contracts/types/library.Flow#structs). ## Debts ### Covered debt ### Ongoing Debt ### Uncovered Debt ### Total Debt ## Refundable Amount ```mermaid sequenceDiagram actor Anyone Anyone ->> Flow: deposit() Anyone -->> Flow: Transfer tokens ``` ```mermaid sequenceDiagram actor Sender Sender ->> Flow: refund() Flow -->> Sender: Transfer unstreamed tokens ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> Flow: withdraw() activate Flow Create actor Recipient Flow -->> Recipient: Transfer streamed tokens deactivate Flow Recipient ->> Flow: withdraw() activate Flow Create actor toAddress Flow -->> toAddress: Transfer streamed tokens deactivate Flow ``` ```mermaid flowchart TD; F["Flow contract"]; S0[(Stream 1)]; b0([bal]) r0([rps]) sd0([sd]) st0([st]) F --> S0; S0 --> b0; S0 --> r0; S0 --> sd0; S0 --> st0; S1[(Stream 2)]; b1([bal]) r1([rps]) sd1([sd]) st1([st]) F --> S1; S1 --> b1; S1 --> r1; S1 --> sd1; S1 --> st1; ``` ```mermaid flowchart TD di0{ }:::blue0 di1{ }:::blue0 cd([cd]) res_0([0 ]) res_bal([bal]) res_sum([td]) cd --> di0 di0 -- "bal = 0" --> res_0 di0 -- "bal > 0" --> di1 di1 -- "ud > 0" --> res_bal di1 -- "ud = 0" --> res_sum ``` ```mermaid flowchart TD rca([od]) di0{ } di1{ } res_00([0 ]) res_01([0 ]) res_rca(["rps ⋅ elt"]) rca --> di0 di0 -- "rps > 0" --> di1 di0 -- "rps == 0" --> res_00 di1 -- "now <= st" --> res_01 di1 -- "now > st" --> res_rca ``` ```mermaid flowchart TD di0{ }:::red1 sd([ud]) res_sd(["td - bal"]) res_zero([0]) sd --> di0 di0 -- "bal < td" --> res_sd di0 -- "bal >= td" --> res_zero ``` ```mermaid flowchart TD rca([td]) di0{ } res_00([sd ]) res_01(["sd + od"]) rca --> di0 di0 -- "rps == 0" --> res_00 di0 -- "rps > 0" --> res_01 ``` ```mermaid flowchart TD ra([Refundable Amount]) res_ra([bal - cd]) ra --> res_ra ``` --- ## Constant Functions Source: https://docs.sablier.com/reference/legacy/contracts/constant-functions # Constant Functions ## Get Stream Returns all properties for the provided stream id. ```solidity 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 tokens are 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 tokens are still allocated to this stream, in the smart contract. - `ratePerSecond`: How much tokens are allocated from the sender to the recipient every second. ### Solidity ```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 ```javascript const sablier = new ethers.Contract(0xabcd..., sablierABI, signerOrProvider); const streamId = 42; const stream = await sablier.getStream(streamId);‌ ``` --- ## Balance Of Returns the real-time balance of an account with regards to a specific stream. ```solidity 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 underlying ERC-20 token. :::info This is the amount of tokens that can be withdrawn from the contract, not the total amount of tokens streamed. If the contract streamed 1,000 tokens to Bob, but Bob withdrew 400 tokens already, this function will return 600 and not 1,000. ::: ### Solidity ```solidity Sablier sablier = Sablier(0xabcd...); uint256 streamId = 42; uint256 senderAddress = 0xcdef...; uint256 balance = sablier.balanceOf(streamId, senderAddress); ``` ### Javascript ```javascript const sablier = new ethers.Contract(0xabcd..., sablierABI, signerOrProvider); const streamId = 42; const senderAddress = 0xcdef...; const balance = await sablier.balanceOf(streamId, senderAddress); ``` --- ## Delta of 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. However, if the clock did not hit the start time of the stream, the value returned is 0 instead. ```solidity 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 ```solidity Sablier sablier = Sablier(0xabcd...); uint256 streamId = 42; uint256 delta = sablier.deltaOf(streamId);‌ ``` ### Ethers.js ```javascript const sablier = new ethers.Contract(0xabcd..., sablierABI, signerOrProvider); 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 just a counter used in this table - the smart contract does not yield error codes, just strings. | Number | 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 unauthorized third-party | | 3 | SafeERC20: low-level call failed | Possibly insufficient allowance, but not necessarily so | | 4 | stream to the zero address | Attempt to stream tokens to the [zero address](https://etherscan.io/address/0x0000000000000000000000000000000000000000) | | 5 | stream to the contract itself | Attempt to stream tokens to the Sablier contract | | 6 | stream to the caller | Happens when the caller attempts to stream tokens to herself | | 7 | deposit is zero | Attempt to stream 0 tokens | | 8 | start time before block.timestamp | Tokens cannot be streamed retroactively | | 9 | stop time before the start time | Negative streaming is not allowed | | 10 | deposit smaller than time delta | The deposit, measured in units of the token, is smaller than the time delta | | 11 | deposit not multiple of time delta | The deposit has a non-zero remainder when divided by the time delta | | 12 | amount is zero | Attempt to withdraw 0 tokens | | 13 | amount exceeds the available balance | Attempt to withdraw more tokens than the available balance | | 14 | recipient balance calculation error | Happens only when streaming an absurdly high number of tokens (close to 2^256) | :::info The contract call could revert with [no reason](https://vmexceptionwhileprocessingtransactionrevert.com/) provided. In this case, you probably did not approve the Lockup contract to spend your token balance, although this is not necessarily the case. Ping us on [Discord](https://discord.sablier.com) if you get stuck. ::: --- ## Non-Constant Functions Source: https://docs.sablier.com/reference/legacy/contracts/non-constant-functions # Non-Constant Functions ## Create stream The create stream function transfers the tokens into the Sablier smart contract, stamping the rules of the stream into the blockchain. As soon as the chain clock hits the start time of the stream, a small portion of tokens starts getting "transferred" from the sender to the recipient once every second. We used scare quotes because what actually happens is not a transfer, but rather an abstract allocation of funds. Every second, the in-contract allowance of the sender decreases, while the recipient's allocation increases, even if the tokens are not transferred to the recipient. Actually transferring the tokens would be excessively expensive in terms of gas costs. ```solidity function createStream(address recipient, uint256 deposit, address tokenAddress, uint256 startTime, uint256 stopTime) returns (uint256) ``` - `msg.sender`: The account who funds the stream, and pays the recipient in real-time. - `recipient`: The account toward which the tokens will be streamed. - `deposit`: The amount of tokens 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. :::caution Before creating a stream, users must first [approve](https://eips.ethereum.org/EIPS/eip-20#approve) the Sablier contract to access their token balance. ::: :::danger The transaction must be processed by the Ethereum blockchain before the start time of the stream, or otherwise the contract will revert 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. In practice, 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. Thus you may have to stream 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, but the latter doesn't. ### Solidity ```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 ```javascript const sablier = new ethers.Contract(0xabcd..., sablierABI, signerOrProvider); // 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, signerOrProvider); // 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 tokens from the Sablier contract to the recipient's account. The withdrawn amount must be less than or equal to the available [balance](/reference/legacy/contracts/constant-functions#balance-of). This function can only be called by the sender or the recipient of the stream, not any third-party. ```solidity function withdrawFromStream(uint256 streamId, uint256 amount) returns (bool); ``` - `streamId`: The id of the stream to withdraw tokens from. - `amount`: The amount of tokens to withdraw. - `RETURN`: True on success, reverts on error. :::info To be able to call this function, you have to wait until the clock goes past the start time of the stream. ::: ### Solidity ```solidity Sablier sablier = Sablier(0xabcd...); uint256 streamId = 42; uint256 amount = 100; require(sablier.withdrawFromStream(streamId, amount), "something went wrong");‌ ``` ### Ethers.js ```javascript ‌const sablier = new ethers.Contract(0xabcd..., sablierABI, signerOrProvider); 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 tokens back to the sender and/or the recipient. If the chain clock did not hit the start time, all the tokens are returned to the sender. If the chain clock did go past the start time, but not past the stop time, the sender and the recipient each get a pro-rata amount. Finally, if the chain clock went past the stop time, all the tokens go to the recipient. This function can be called only by the sender. ```solidity function cancelStream(uint256 streamId) returns (bool); ``` - `streamId`: The id of the stream to cancel. - `RETURN`: True on success, reverts on error. ### Solidity ```solidity Sablier sablier = Sablier(0xabcd...); uint256 streamId = 42; require(sablier.cancelStream(streamId), "something went wrong"); ``` ### Ethers.js ```javascript const sablier = new ethers.Contract(0xabcd..., sablierABI, signerOrProvider); const streamId = 42; const cancelStreamTx = await sablier.cancelStream(streamId); await cancelStreamTx.wait(); ``` --- ## Access Control Source: https://docs.sablier.com/reference/lockup/access-control # Access Control With the exception of the [admin functions](/concepts/governance#lockup), all functions in Lockup can only be triggered by users. The Comptroller has no control over any stream or any part of the protocol. This article will provide a comprehensive overview of the actions that can be performed on streams once they are created, as well as the corresponding user permissions for each action. :::note Every stream has a sender and a recipient. Recipients can approve third parties to take actions on their behalf. A 'public' caller is any address outside of sender and recipient. ::: ## Overview The table below offers a quick overview of the access control for each action that can be performed on a stream. | Action | Sender | Recipient / Approved third party | Public | | --- | :---: | :---: | :---: | | Burn NFT | ❌ | ✅ | ❌ | | Cancel | ✅ | ❌ | ❌ | | Cancel Multiple | ✅ | ❌ | ❌ | | Renounce | ✅ | ❌ | ❌ | | Transfer NFT | ❌ | ✅ | ❌ | | Withdraw to any address | ❌ | ✅ | ❌ | | Withdraw to recipient | ✅ | ✅ | ✅ | | Withdraw Multiple | ✅ | ✅ | ✅ | ## Burn NFT Either the recipient or an approved operator can burn the NFT associated with a stream. #### With Operator: ## Cancel Only the sender can cancel a stream. ## Cancel Multiple Only the sender can cancel multiple streams. ## Renounce Only the sender can renounce a stream. ## Transfer NFT Either the recipient or an approved operator can transfer the NFT associated with a stream. - Only if the stream is transferable. #### With Operator: ## Withdraw Multiple Anybody can withdraw tokens from multiple streams to the recipients of each stream. ## Withdraw to Any Address The tokens in a stream can be withdrawn to any address only by the recipient or an approved third party. #### With Operator: ## Withdraw to Recipient The tokens in a stream can be withdrawn to the recipient by anyone including the sender, recipient, or an approved third party. ```mermaid sequenceDiagram actor Recipient Recipient ->> Lockup: burn() Recipient -->> address(0): Transfer stream NFT ``` ```mermaid sequenceDiagram actor Recipient actor Operator Recipient ->> Lockup: approve(operator) Operator ->> Lockup: burn() Recipient -->> address(0): Transfer stream NFT ``` ```mermaid sequenceDiagram actor Sender Sender ->> Lockup: cancel() Lockup -->> Sender: Transfer unvested tokens ``` ```mermaid sequenceDiagram actor Sender Sender ->> Lockup: cancelMultiple() Lockup -->> Sender: Transfer unvested tokens from multiple streams ``` ```mermaid sequenceDiagram actor Sender Sender ->> Lockup: renounce() ``` ```mermaid sequenceDiagram actor Recipient Recipient ->> Lockup: transfer(toAddress) Create actor toAddress Recipient -->> toAddress: Transfer NFT ``` ```mermaid sequenceDiagram actor Recipient actor Operator Recipient ->> Lockup: approve(operator) Operator ->> Lockup: transfer(toAddress) Create actor toAddress Recipient -->> toAddress: Transfer NFT ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> Lockup: withdrawMultiple() Create actor getRecipient(1) Lockup -->> getRecipient(1): Transfer vested tokens from stream 1 Create actor getRecipient(2) Lockup -->> getRecipient(2): Transfer vested tokens from stream 2 Create actor getRecipient(3) Lockup -->> getRecipient(3): Transfer vested tokens from stream 3 ``` ```mermaid sequenceDiagram actor Recipient Recipient ->> Lockup: withdraw(toAddress) Create actor toAddress Lockup -->> toAddress: Transfer vested tokens ``` ```mermaid sequenceDiagram actor Recipient actor Operator Recipient ->> Lockup: approve(operator) Operator ->> Lockup: withdraw(toAddress) Create actor toAddress Lockup -->> toAddress: Transfer vested tokens ``` ```mermaid sequenceDiagram actor Anyone Anyone ->> Lockup: withdraw(recipient) Create actor Recipient Lockup -->> Recipient: Transfer vested tokens ``` --- ## Batch Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.Batch # Batch [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/Batch.sol) **Inherits:** [IBatch](/reference/lockup/contracts/interfaces/interface.IBatch) **Title:** Batch See the documentation in [IBatch](/reference/lockup/contracts/interfaces/interface.IBatch). ## Functions ### batch Allows batched calls to self, i.e., `this` contract. Since `msg.value` can be reused across calls, be VERY CAREFUL when using it. Refer to [https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong](https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong) for more information. ```solidity function batch(bytes[] calldata calls) external payable virtual override returns (bytes[] memory results); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `calls` | `bytes[]` | An array of inputs for each call. | **Returns** | Name | Type | Description | | --- | --- | --- | | `results` | `bytes[]` | An array of results from each call. Empty when the calls do not return anything. | --- ## Comptrollerable Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.Comptrollerable # Comptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/Comptrollerable.sol) **Inherits:** [IComptrollerable](/reference/lockup/contracts/interfaces/interface.IComptrollerable) **Title:** Comptrollerable See the documentation in [IComptrollerable](/reference/lockup/contracts/interfaces/interface.IComptrollerable). ## State Variables ### comptroller Retrieves the address of the comptroller contract. ```solidity ISablierComptroller public override comptroller ``` ## Functions ### onlyComptroller Reverts if called by any account other than the comptroller. ```solidity modifier onlyComptroller() ; ``` ### constructor ```solidity constructor(address initialComptroller) ; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | ### setComptroller Sets the comptroller to a new address. Emits a {SetComptroller} event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a {TransferFeesToComptroller} event. ```solidity function transferFeesToComptroller() external override; ``` ### \_checkComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _checkComptroller() private view; ``` ### \_setComptroller See the documentation for the user-facing functions that call this private function. ```solidity function _setComptroller( ISablierComptroller previousComptroller, ISablierComptroller newComptroller, bytes4 minimalInterfaceId ) private; ``` --- ## NoDelegateCall Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.NoDelegateCall # NoDelegateCall [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/NoDelegateCall.sol) **Title:** NoDelegateCall This contract implements logic to prevent delegate calls. ## Constants ### ORIGINAL The address of the original contract that was deployed. ```solidity address private immutable ORIGINAL ``` ## Functions ### noDelegateCall Prevents delegate calls. ```solidity modifier noDelegateCall() ; ``` ### constructor Sets the original contract address. ```solidity constructor() ; ``` ### \_preventDelegateCall This function checks whether the current call is a delegate call, and reverts if it is. - A private function is used instead of inlining this logic in a modifier because Solidity copies modifiers into every function that uses them. The `ORIGINAL` address would get copied in every place the modifier is used, which would increase the contract size. By using a function instead, we can avoid this duplication of code and reduce the overall size of the contract. ```solidity function _preventDelegateCall() private view; ``` --- ## SablierLockupDynamic Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.SablierLockupDynamic # SablierLockupDynamic [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierLockupDynamic.sol) **Inherits:** [ISablierLockupDynamic](/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic), [NoDelegateCall](/reference/lockup/contracts/abstracts/abstract.NoDelegateCall), [SablierLockupState](/reference/lockup/contracts/abstracts/abstract.SablierLockupState) **Title:** SablierLockupDynamic See the documentation in [ISablierLockupDynamic](/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic). ## Functions ### createWithDurationsLD Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of `block.timestamp` and all specified time durations. The segment timestamps are derived from these durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupDynamicStream} and {MetadataUpdate} event. Requirements: - All requirements in {createWithTimestampsLD} must be met for the calculated parameters. ```solidity function createWithDurationsLD( Lockup.CreateWithDurations calldata params, LockupDynamic.SegmentWithDuration[] calldata segmentsWithDuration ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithDurations` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `segmentsWithDuration` | `LockupDynamic.SegmentWithDuration[]` | Segments with durations used to compose the dynamic distribution function. Timestamps are calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createWithTimestampsLD Creates a stream with the provided segment timestamps, implying the end time from the last timestamp. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupDynamicStream} and {MetadataUpdate} event. Notes: - As long as the segment timestamps are arranged in ascending order, it is not an error for some of them to be in the past. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.timestamps.start` must be greater than zero and less than the first segment's timestamp. - `segments` must have at least one segment. - The segment timestamps must be arranged in ascending order. - `params.timestamps.end` must be equal to the last segment's timestamp. - The sum of the segment amounts must equal the deposit amount. - `params.recipient` must not be the zero address. - `params.sender` must not be the zero address. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLD( Lockup.CreateWithTimestamps calldata params, LockupDynamic.Segment[] calldata segments ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `segments` | `LockupDynamic.Segment[]` | Segments used to compose the dynamic distribution function. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### \_createLD See the documentation for the user-facing functions that call this private function. ```solidity function _createLD( bool cancelable, uint128 depositAmount, address recipient, LockupDynamic.Segment[] memory segments, address sender, string memory shape, Lockup.Timestamps memory timestamps, IERC20 token, bool transferable ) private returns (uint256 streamId); ``` --- ## SablierLockupLinear Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.SablierLockupLinear # SablierLockupLinear [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierLockupLinear.sol) **Inherits:** [ISablierLockupLinear](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear), [NoDelegateCall](/reference/lockup/contracts/abstracts/abstract.NoDelegateCall), [SablierLockupState](/reference/lockup/contracts/abstracts/abstract.SablierLockupState) **Title:** SablierLockupLinear See the documentation in [ISablierLockupLinear](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear). ## Functions ### createWithDurationsLL Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of `block.timestamp` and `durations.total`. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupLinearStream} and {MetadataUpdate} event. Requirements: - All requirements in {createWithTimestampsLL} must be met for the calculated parameters. ```solidity function createWithDurationsLL( Lockup.CreateWithDurations calldata params, LockupLinear.UnlockAmounts calldata unlockAmounts, uint40 granularity, LockupLinear.Durations calldata durations ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithDurations` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `unlockAmounts` | `LockupLinear.UnlockAmounts` | Struct encapsulating (i) the amount to unlock at the start time and (ii) the amount to unlock at the cliff time. | | `granularity` | `uint40` | The smallest step in time between two consecutive token unlocks. Zero is a sentinel value for 1 second. | | `durations` | `LockupLinear.Durations` | Struct encapsulating (i) cliff period duration and (ii) total stream duration, both in seconds. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createWithTimestampsLL Creates a stream with the provided start time and end time. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupLinearStream} and {MetadataUpdate} event. Notes: - A cliff time of zero means there is no cliff. - As long as the times are ordered, it is not an error for the start or the cliff time to be in the past. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.timestamps.start` must be greater than zero and less than `params.timestamps.end`. - If set, `cliffTime` must be greater than `params.timestamps.start` and less than `params.timestamps.end`. - `params.recipient` must not be the zero address. - `params.sender` must not be the zero address. - The sum of `params.unlockAmounts.start` and `params.unlockAmounts.cliff` must be less than or equal to deposit amount. - If `params.timestamps.cliff` is not set, the `params.unlockAmounts.cliff` must be zero. - `granularity` must not exceed the streamable range which is `params.timestamps.end - cliffTime` if cliff is set, `params.timestamps.end - params.timestamps.start` otherwise. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLL( Lockup.CreateWithTimestamps calldata params, LockupLinear.UnlockAmounts calldata unlockAmounts, uint40 granularity, uint40 cliffTime ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `unlockAmounts` | `LockupLinear.UnlockAmounts` | Struct encapsulating (i) the amount to unlock at the start time and (ii) the amount to unlock at the cliff time. | | `granularity` | `uint40` | The smallest step in time between two consecutive token unlocks. Zero is a sentinel value for 1 second. | | `cliffTime` | `uint40` | The Unix timestamp for the cliff period's end. A value of zero means there is no cliff. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### \_createLL See the documentation for the user-facing functions that call this private function. ```solidity function _createLL( bool cancelable, uint40 cliffTime, uint128 depositAmount, address recipient, address sender, string memory shape, Lockup.Timestamps memory timestamps, IERC20 token, bool transferable, LockupLinear.UnlockAmounts memory unlockAmounts, uint40 granularity ) private returns (uint256 streamId); ``` --- ## SablierLockupPriceGated Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.SablierLockupPriceGated # SablierLockupPriceGated [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierLockupPriceGated.sol) **Inherits:** [ISablierLockupPriceGated](/reference/lockup/contracts/interfaces/interface.ISablierLockupPriceGated), [NoDelegateCall](/reference/lockup/contracts/abstracts/abstract.NoDelegateCall), [SablierLockupState](/reference/lockup/contracts/abstracts/abstract.SablierLockupState) **Title:** SablierLockupPriceGated See the documentation in [ISablierLockupPriceGated](/reference/lockup/contracts/interfaces/interface.ISablierLockupPriceGated). ## Functions ### createWithTimestampsLPG Creates a stream with the provided start time and end time. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupPriceGatedStream} and {MetadataUpdate} event. Notes: - The recipient can withdraw the full deposited amount when either: 1. The oracle price reaches or exceeds the target price, OR 2. Current time is greater than or equal to the stream's end time. - The sender can cancel the stream when price is less than target price AND end time is in the future. - The function does not check if the provided oracle reports the price for the deposited token. It may be possible that stream creator has used a different token for the oracle. In such cases, integrators and recipients are requested to verify the oracle correctness on their own. - The LPG model does not support a "createWithDuration" function because the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract is at the size limit. If the EVM contract size limit is increased in the future, this function will be added. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.sender` must not be the zero address. - `params.recipient` must not be the zero address. - `params.timestamps.start` must not be zero. - `params.timestamps.start` must be less than `params.timestamps.end`. - `unlockParams.oracle` must implement Chainlink's {AggregatorV3Interface} interface. - `unlockParams.oracle` must return a non-zero value no greater than 36 when the `decimals()` function is called. - `unlockParams.oracle` must return a positive price when the `latestRoundData()` function is called. - `unlockParams.targetPrice` must be greater than the current oracle price. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLPG( Lockup.CreateWithTimestamps calldata params, LockupPriceGated.UnlockParams calldata unlockParams ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `unlockParams` | `LockupPriceGated.UnlockParams` | Struct encapsulating the unlock parameters, documented in {LockupPriceGated}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### \_createLPG See the documentation for the user-facing functions that call this private function. ```solidity function _createLPG( Lockup.CreateWithTimestamps calldata params, LockupPriceGated.UnlockParams calldata unlockParams ) private returns (uint256 streamId); ``` --- ## SablierLockupState Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.SablierLockupState # SablierLockupState [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierLockupState.sol) **Inherits:** [ISablierLockupState](/reference/lockup/contracts/interfaces/interface.ISablierLockupState) **Title:** SablierLockupState See the documentation in [ISablierLockupState](/reference/lockup/contracts/interfaces/interface.ISablierLockupState). ## State Variables ### aggregateAmount ```solidity mapping(IERC20 token => uint256 amount) public override aggregateAmount ``` ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity address public override nativeToken ``` ### nextStreamId Counter for stream IDs, used in the create functions. ```solidity uint256 public override nextStreamId ``` ### nftDescriptor Contract that generates the non-fungible token URI. ```solidity ILockupNFTDescriptor public override nftDescriptor ``` ### \_allowedToHook Mapping of contracts allowed to hook to Sablier when a stream is canceled or when tokens are withdrawn. ```solidity mapping(address recipient => bool allowed) internal _allowedToHook ``` ### \_cliffs Cliff timestamp mapped by stream IDs, used in LL streams. ```solidity mapping(uint256 streamId => uint40 cliffTime) internal _cliffs ``` ### \_granularities Granularity mapped by stream IDs, used in LL streams. ```solidity mapping(uint256 streamId => uint40 granularity) internal _granularities ``` ### \_priceGatedUnlockParams Unlock parameters mapped by stream IDs, used in LPG streams. ```solidity mapping(uint256 streamId => LockupPriceGated.UnlockParams unlockParams) internal _priceGatedUnlockParams ``` ### \_segments Stream segments mapped by stream IDs, used in LD streams. ```solidity mapping(uint256 streamId => LockupDynamic.Segment[] segments) internal _segments ``` ### \_streams Lockup streams mapped by unsigned integers. ```solidity mapping(uint256 id => Lockup.Stream stream) internal _streams ``` ### \_tranches Stream tranches mapped by stream IDs, used in LT streams. ```solidity mapping(uint256 streamId => LockupTranched.Tranche[] tranches) internal _tranches ``` ### \_unlockAmounts Unlock amounts mapped by stream IDs, used in LL streams. ```solidity mapping(uint256 streamId => LockupLinear.UnlockAmounts unlockAmounts) internal _unlockAmounts ``` ## Functions ### modelCheck Checks that actual model and expected model are equal. ```solidity modifier modelCheck(Lockup.Model actualModel, Lockup.Model expectedModel) ; ``` ### notNull Checks that `streamId` does not reference a null stream. ```solidity modifier notNull(uint256 streamId) ; ``` ### constructor ```solidity constructor(address initialNFTDescriptor) ; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialNFTDescriptor` | `address` | The address of the initial NFT descriptor. | ### getCliffTime Retrieves the stream's cliff time, which is a Unix timestamp. A value of zero means there is no cliff. Reverts if `streamId` references either a null stream or a non-LL stream. ```solidity function getCliffTime(uint256 streamId) external view override notNull(streamId) modelCheck(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_LINEAR) returns (uint40 cliffTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getDepositedAmount Retrieves the amount deposited in the stream, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function getDepositedAmount(uint256 streamId) external view override notNull(streamId) returns (uint128 depositedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getEndTime Retrieves the stream's end time, which is a Unix timestamp. Reverts if `streamId` references a null stream. ```solidity function getEndTime(uint256 streamId) external view override notNull(streamId) returns (uint40 endTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getGranularity Retrieves the smallest step in time between two consecutive token unlocks. Reverts if `streamId` references either a null stream or a non-LL stream. ```solidity function getGranularity(uint256 streamId) external view override notNull(streamId) modelCheck(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_LINEAR) returns (uint40 granularity); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getLockupModel Retrieves the distribution models used to create the stream. Reverts if `streamId` references a null stream. ```solidity function getLockupModel(uint256 streamId) external view override notNull(streamId) returns (Lockup.Model lockupModel); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getPriceGatedUnlockParams Retrieves the unlock parameters of a price-gated stream. Reverts if `streamId` references either a null stream or a non-LPG stream. ```solidity function getPriceGatedUnlockParams(uint256 streamId) external view override notNull(streamId) modelCheck(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_PRICE_GATED) returns (LockupPriceGated.UnlockParams memory unlockParams); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `unlockParams` | `LockupPriceGated.UnlockParams` | See the documentation in {LockupPriceGated} type. | ### getRefundedAmount Retrieves the amount refunded to the sender after a cancellation, denoted in units of the token's decimals. This amount is always zero unless the stream was canceled. Reverts if `streamId` references a null stream. ```solidity function getRefundedAmount(uint256 streamId) external view override notNull(streamId) returns (uint128 refundedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getSegments Retrieves the segments used to compose the dynamic distribution function. Reverts if `streamId` references either a null stream or a non-LD stream. ```solidity function getSegments(uint256 streamId) external view override notNull(streamId) modelCheck(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_DYNAMIC) returns (LockupDynamic.Segment[] memory segments); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `segments` | `LockupDynamic.Segment[]` | See the documentation in {LockupDynamic} type. | ### getSender Retrieves the stream's sender. Reverts if `streamId` references a null stream. ```solidity function getSender(uint256 streamId) external view override notNull(streamId) returns (address sender); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getStartTime Retrieves the stream's start time, which is a Unix timestamp. Reverts if `streamId` references a null stream. ```solidity function getStartTime(uint256 streamId) external view override notNull(streamId) returns (uint40 startTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getTranches Retrieves the tranches used to compose the tranched distribution function. Reverts if `streamId` references either a null stream or a non-LT stream. ```solidity function getTranches(uint256 streamId) external view override notNull(streamId) modelCheck(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_TRANCHED) returns (LockupTranched.Tranche[] memory tranches); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `tranches` | `LockupTranched.Tranche[]` | See the documentation in {LockupTranched} type. | ### getUnderlyingToken Retrieves the address of the underlying ERC-20 token being distributed. Reverts if `streamId` references a null stream. ```solidity function getUnderlyingToken(uint256 streamId) external view override notNull(streamId) returns (IERC20 token); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getUnlockAmounts Retrieves the unlock amounts used to compose the linear distribution function. Reverts if `streamId` references either a null stream or a non-LL stream. ```solidity function getUnlockAmounts(uint256 streamId) external view override notNull(streamId) modelCheck(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_LINEAR) returns (LockupLinear.UnlockAmounts memory unlockAmounts); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `unlockAmounts` | `LockupLinear.UnlockAmounts` | See the documentation in {LockupLinear} type. | ### getWithdrawnAmount Retrieves the amount withdrawn from the stream, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function getWithdrawnAmount(uint256 streamId) external view override notNull(streamId) returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isAllowedToHook Retrieves a flag indicating whether the provided address is a contract allowed to hook to Sablier when a stream is canceled or when tokens are withdrawn. See [ISablierLockupRecipient](/reference/lockup/contracts/interfaces/interface.ISablierLockupRecipient) for more information. ```solidity function isAllowedToHook(address recipient) external view returns (bool result); ``` ### isCancelable Retrieves a flag indicating whether the stream can be canceled. When the stream is cold, this flag is always `false`. Reverts if `streamId` references a null stream. ```solidity function isCancelable(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isDepleted Retrieves a flag indicating whether the stream is depleted. Reverts if `streamId` references a null stream. ```solidity function isDepleted(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isStream Retrieves a flag indicating whether the stream exists. Does not revert if `streamId` references a null stream. ```solidity function isStream(uint256 streamId) external view override returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isTransferable Retrieves a flag indicating whether the stream NFT can be transferred. Reverts if `streamId` references a null stream. ```solidity function isTransferable(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### wasCanceled Retrieves a flag indicating whether the stream was canceled. Reverts if `streamId` references a null stream. ```solidity function wasCanceled(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### \_statusOf Retrieves the stream's status without performing a null check. ```solidity function _statusOf(uint256 streamId) internal view returns (Lockup.Status); ``` ### \_streamedAmountOf Calculates the streamed amount of the stream. This function is implemented by child contract. The logic varies according to the distribution model. ```solidity function _streamedAmountOf(uint256 streamId) internal view virtual returns (uint128); ``` ### \_create This function is implemented by [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) and is used in the [SablierLockupDynamic](/reference/lockup/contracts/abstracts/abstract.SablierLockupDynamic), [SablierLockupLinear](/reference/lockup/contracts/abstracts/abstract.SablierLockupLinear) and [SablierLockupTranched](/reference/lockup/contracts/abstracts/abstract.SablierLockupTranched) contracts. It updates state variables based on the stream parameters, mints an NFT to the recipient, bumps stream ID, and transfers the deposit amount. ```solidity function _create( bool cancelable, uint128 depositAmount, Lockup.Model lockupModel, address recipient, address sender, uint256 streamId, Lockup.Timestamps memory timestamps, IERC20 token, bool transferable ) internal virtual; ``` ### \_modelCheck Reverts if actual model and expected model are not equal. ```solidity function _modelCheck(Lockup.Model actualModel, Lockup.Model expectedModel) private pure; ``` ### \_notNull A private function is used instead of inlining this logic in a 3 because Solidity copies modifiers into every function that uses them. ```solidity function _notNull(uint256 streamId) private view; ``` --- ## SablierLockupTranched Source: https://docs.sablier.com/reference/lockup/contracts/abstracts/abstract.SablierLockupTranched # SablierLockupTranched [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/abstracts/SablierLockupTranched.sol) **Inherits:** [ISablierLockupTranched](/reference/lockup/contracts/interfaces/interface.ISablierLockupTranched), [NoDelegateCall](/reference/lockup/contracts/abstracts/abstract.NoDelegateCall), [SablierLockupState](/reference/lockup/contracts/abstracts/abstract.SablierLockupState) **Title:** SablierLockupTranched See the documentation in [ISablierLockupTranched](/reference/lockup/contracts/interfaces/interface.ISablierLockupTranched). ## Functions ### createWithDurationsLT Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of `block.timestamp` and all specified time durations. The tranche timestamps are derived from these durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupTrancheStream} and {MetadataUpdate} event. Requirements: - All requirements in {createWithTimestampsLT} must be met for the calculated parameters. ```solidity function createWithDurationsLT( Lockup.CreateWithDurations calldata params, LockupTranched.TrancheWithDuration[] calldata tranchesWithDuration ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithDurations` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `tranchesWithDuration` | `LockupTranched.TrancheWithDuration[]` | Tranches with durations used to compose the tranched distribution function. Timestamps are calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createWithTimestampsLT Creates a stream with the provided tranche timestamps, implying the end time from the last timestamp. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupTrancheStream} and {MetadataUpdate} event. Notes: - As long as the tranche timestamps are arranged in ascending order, it is not an error for some of them to be in the past. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.timestamps.start` must be greater than zero and less than the first tranche's timestamp. - `tranches` must have at least one tranche. - The tranche timestamps must be arranged in ascending order. - `params.timestamps.end` must be equal to the last tranche's timestamp. - The sum of the tranche amounts must equal the deposit amount. - `params.recipient` must not be the zero address. - `params.sender` must not be the zero address. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLT( Lockup.CreateWithTimestamps calldata params, LockupTranched.Tranche[] calldata tranches ) external payable override noDelegateCall returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `tranches` | `LockupTranched.Tranche[]` | Tranches used to compose the tranched distribution function. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### \_createLT See the documentation for the user-facing functions that call this private function. ```solidity function _createLT( bool cancelable, uint128 depositAmount, address recipient, address sender, string memory shape, Lockup.Timestamps memory timestamps, IERC20 token, bool transferable, LockupTranched.Tranche[] memory tranches ) private returns (uint256 streamId); ``` --- ## LockupNFTDescriptor Source: https://docs.sablier.com/reference/lockup/contracts/contract.LockupNFTDescriptor # LockupNFTDescriptor [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/LockupNFTDescriptor.sol) **Inherits:** [ILockupNFTDescriptor](/reference/lockup/contracts/interfaces/interface.ILockupNFTDescriptor) **Title:** LockupNFTDescriptor See the documentation in [ILockupNFTDescriptor](/reference/lockup/contracts/interfaces/interface.ILockupNFTDescriptor). ## Functions ### tokenURI Produces the URI describing a particular stream NFT. This is a data URI with the JSON contents directly inlined. ```solidity function tokenURI(IERC721Metadata lockup, uint256 streamId) external view override returns (string memory uri); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `IERC721Metadata` | | | `streamId` | `uint256` | The ID of the stream for which to produce a description. | **Returns** | Name | Type | Description | | --- | --- | --- | | `uri` | `string` | The URI of the ERC721-compliant metadata. | ### abbreviateAmount Creates an abbreviated representation of the provided amount, rounded down and prefixed with ">= ". The abbreviation uses these suffixes: - "K" for thousands - "M" for millions - "B" for billions - "T" for trillions For example, if the input is 1,234,567, the output is ">= 1.23M". ```solidity function abbreviateAmount(uint256 amount, uint256 decimals) internal pure returns (string memory); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `amount` | `uint256` | The amount to abbreviate, denoted in units of `decimals`. | | `decimals` | `uint256` | The number of decimals to assume when abbreviating the amount. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `string` | abbreviation The abbreviated representation of the provided amount, as a string. | ### calculateDurationInDays Calculates the stream's duration in days, rounding down. ```solidity function calculateDurationInDays(uint256 startTime, uint256 endTime) internal pure returns (string memory); ``` ### calculateStreamedPercentage Calculates how much of the deposited amount has been streamed so far, as a percentage with 4 implied decimals. ```solidity function calculateStreamedPercentage( uint128 streamedAmount, uint128 depositedAmount ) internal pure returns (uint256); ``` ### generateAccentColor Generates a pseudo-random HSL color by hashing together the `chainid`, the `sablier` address, and the `streamId`. This will be used as the accent color for the SVG. ```solidity function generateAccentColor(address sablier, uint256 streamId) internal view returns (string memory); ``` ### generateAttributes Generates an array of JSON objects that represent the NFT's attributes: - Token symbol - Sender address - Status These attributes are useful for filtering and sorting the NFTs. ```solidity function generateAttributes( string memory tokenSymbol, string memory sender, string memory status ) internal pure returns (string memory); ``` ### generateDescription Generates a string with the NFT's JSON metadata description, which provides a high-level overview. ```solidity function generateDescription( string memory tokenSymbol, string memory lockupStringified, string memory tokenAddress, string memory streamId, bool isTransferable ) internal pure returns (string memory); ``` ### safeTokenDecimals Retrieves the token's decimals safely, defaulting to "0" if an error occurs. Performs a low-level call to handle tokens in which the decimals are not implemented. ```solidity function safeTokenDecimals(address token) internal view returns (uint8); ``` ### stringifyFractionalAmount Converts the provided fractional amount to a string prefixed by a dot. ```solidity function stringifyFractionalAmount(uint256 fractionalAmount) internal pure returns (string memory); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `fractionalAmount` | `uint256` | A numerical value with 2 implied decimals. | ### stringifyPercentage Converts the provided percentage to a string. ```solidity function stringifyPercentage(uint256 percentage) internal pure returns (string memory); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `percentage` | `uint256` | A numerical value with 4 implied decimals. | ### stringifyStatus Retrieves the stream's status as a string. ```solidity function stringifyStatus(Lockup.Status status) internal pure returns (string memory); ``` ## Structs ### TokenURIVars Needed to avoid Stack Too Deep. ```solidity struct TokenURIVars { address token; string tokenSymbol; uint128 depositedAmount; string json; ISablierLockup lockup; string lockupStringified; string status; string svg; uint256 streamedPercentage; } ``` --- ## SablierBatchLockup Source: https://docs.sablier.com/reference/lockup/contracts/contract.SablierBatchLockup # SablierBatchLockup [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierBatchLockup.sol) **Inherits:** [ISablierBatchLockup](/reference/lockup/contracts/interfaces/interface.ISablierBatchLockup) **Title:** SablierBatchLockup See the documentation in [ISablierBatchLockup](/reference/lockup/contracts/interfaces/interface.ISablierBatchLockup). ## Functions ### createWithDurationsLD Creates a batch of LD streams using `createWithDurationsLD`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupDynamic.createWithDurationsLD} must be met for each stream. ```solidity function createWithDurationsLD( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithDurationsLD[] calldata batch ) external override returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithDurationsLD[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupDynamic.createWithDurationsLD}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLD Creates a batch of LD streams using `createWithTimestampsLD`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupDynamic.createWithTimestampsLD} must be met for each stream. ```solidity function createWithTimestampsLD( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLD[] calldata batch ) external override returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLD[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupDynamic.createWithTimestampsLD}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithDurationsLL Creates a batch of LL streams using `createWithDurationsLL`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupLinear.createWithDurationsLL} must be met for each stream. ```solidity function createWithDurationsLL( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithDurationsLL[] calldata batch ) external override returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithDurationsLL[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupLinear.createWithDurationsLL}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLL Creates a batch of LL streams using `createWithTimestampsLL`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupLinear.createWithTimestampsLL} must be met for each stream. ```solidity function createWithTimestampsLL( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLL[] calldata batch ) external override returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLL[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupLinear.createWithTimestampsLL}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLPG Creates a batch of LPG streams using `createWithTimestampsLPG`. Notes: - The LPG model does not support a "createWithDuration" function because the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract is at the size limit. If the EVM contract size limit is increased in the future, this function will be added. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupPriceGated.createWithTimestampsLPG} must be met for each stream. ```solidity function createWithTimestampsLPG( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLPG[] calldata batch ) external override returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLPG[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupPriceGated.createWithTimestampsLPG}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithDurationsLT Creates a batch of LT streams using `createWithDurationsLT`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupTranched.createWithDurationsLT} must be met for each stream. ```solidity function createWithDurationsLT( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithDurationsLT[] calldata batch ) external override returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithDurationsLT[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupTranched.createWithDurationsLT}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLT Creates a batch of LT streams using `createWithTimestampsLT`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupTranched.createWithTimestampsLT} must be met for each stream. ```solidity function createWithTimestampsLT( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLT[] calldata batch ) external override returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLT[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupTranched.createWithTimestampsLT}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### \_approve Helper function to approve a Lockup contract to spend funds from the batchLockup. If the current allowance is insufficient, this function approves Lockup to spend the exact `amount`. The {SafeERC20.forceApprove} function is used to handle special ERC-20 tokens (e.g. USDT) that require the current allowance to be zero before setting it to a non-zero value. ```solidity function _approve(address lockup, IERC20 token, uint256 amount) internal; ``` ### \_handleTransfer Helper function to transfer tokens from the caller to the batchLockup contract and approve the Lockup contract. ```solidity function _handleTransfer(address lockup, IERC20 token, uint256 amount) internal; ``` --- ## SablierLockup Source: https://docs.sablier.com/reference/lockup/contracts/contract.SablierLockup # SablierLockup [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/SablierLockup.sol) **Inherits:** [Batch](/reference/lockup/contracts/abstracts/abstract.Batch), [Comptrollerable](/reference/lockup/contracts/abstracts/abstract.Comptrollerable), ERC721, [ISablierLockup](/reference/lockup/contracts/interfaces/interface.ISablierLockup), [SablierLockupDynamic](/reference/lockup/contracts/abstracts/abstract.SablierLockupDynamic), [SablierLockupLinear](/reference/lockup/contracts/abstracts/abstract.SablierLockupLinear), [SablierLockupPriceGated](/reference/lockup/contracts/abstracts/abstract.SablierLockupPriceGated), [SablierLockupTranched](/reference/lockup/contracts/abstracts/abstract.SablierLockupTranched) **Title:** SablierLockup See the documentation in [ISablierLockup](/reference/lockup/contracts/interfaces/interface.ISablierLockup). ## Functions ### constructor ```solidity constructor( address initialComptroller, address initialNFTDescriptor ) [Comptrollerable](/docs/reference/02-lockup/contracts/abstracts/abstract.Comptrollerable.md)(initialComptroller) ERC721("Sablier Lockup NFT", "SAB-LOCKUP") SablierLockupState(initialNFTDescriptor); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `initialComptroller` | `address` | The address of the initial comptroller contract. | | `initialNFTDescriptor` | `address` | The address of the NFT descriptor contract. | ### calculateMinFeeWei Calculates the minimum fee in wei required to withdraw from the given stream ID. Reverts if `streamId` references a null stream. ```solidity function calculateMinFeeWei(uint256 streamId) external view override notNull(streamId) returns (uint256 minFeeWei); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getRecipient Retrieves the stream's recipient. Reverts if the NFT has been burned. ```solidity function getRecipient(uint256 streamId) external view override returns (address recipient); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isCold Retrieves a flag indicating whether the stream is cold, i.e. settled, canceled, or depleted. Reverts if `streamId` references a null stream. ```solidity function isCold(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isWarm Retrieves a flag indicating whether the stream is warm, i.e. either pending or streaming. Reverts if `streamId` references a null stream. ```solidity function isWarm(uint256 streamId) external view override notNull(streamId) returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### refundableAmountOf Calculates the amount that the sender would be refunded if the stream were canceled, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function refundableAmountOf(uint256 streamId) external view override notNull(streamId) returns (uint128 refundableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### statusOf Retrieves the stream's status. Reverts if `streamId` references a null stream. ```solidity function statusOf(uint256 streamId) external view override notNull(streamId) returns (Lockup.Status status); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### streamedAmountOf Calculates the amount streamed to the recipient, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. Notes: - Upon cancellation of the stream, the amount streamed is calculated as the difference between the deposited amount and the refunded amount. Ultimately, when the stream becomes depleted, the streamed amount is equivalent to the total amount withdrawn. ```solidity function streamedAmountOf(uint256 streamId) external view override notNull(streamId) returns (uint128 streamedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### supportsInterface See {IERC165-supportsInterface}. ```solidity function supportsInterface(bytes4 interfaceId) public view override(IERC165, ERC721) returns (bool); ``` ### tokenURI See {IERC721Metadata-tokenURI}. ```solidity function tokenURI(uint256 streamId) public view override(IERC721Metadata, ERC721) returns (string memory uri); ``` ### withdrawableAmountOf Calculates the amount that the recipient can withdraw from the stream, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function withdrawableAmountOf(uint256 streamId) external view override notNull(streamId) returns (uint128 withdrawableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### allowToHook Allows a recipient contract to hook to Sablier when a stream is canceled or when tokens are withdrawn. Useful for implementing contracts that hold streams on behalf of users, such as vaults or staking contracts. Emits an {AllowToHook} event. Notes: - Does not revert if the contract is already on the allowlist. - This is an irreversible operation. The contract cannot be removed from the allowlist. Requirements: - `msg.sender` must be the comptroller contract. - `recipient` must implement [ISablierLockupRecipient](/reference/lockup/contracts/interfaces/interface.ISablierLockupRecipient). ```solidity function allowToHook(address recipient) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `recipient` | `address` | The address of the contract to allow for hooks. | ### burn Burns the NFT associated with the stream. Emits a {Transfer} and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `streamId` must reference a depleted stream. - The NFT must exist. - `msg.sender` must be either the NFT owner or an approved third party. ```solidity function burn(uint256 streamId) external payable override noDelegateCall notNull(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream NFT to burn. | ### cancel Cancels the stream and refunds any remaining tokens to the sender. Emits a {Transfer}, {CancelLockupStream} and {MetadataUpdate} event. Notes: - If there are any tokens left for the recipient to withdraw, the stream is marked as canceled. Otherwise, the stream is marked as depleted. - If the address is on the allowlist, this function will invoke a hook on the recipient. Requirements: - Must not be delegate called. - The stream must be warm and cancelable. - `msg.sender` must be the stream's sender. ```solidity function cancel(uint256 streamId) public payable override noDelegateCall notNull(streamId) returns (uint128 refundedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to cancel. | **Returns** | Name | Type | Description | | --- | --- | --- | | `refundedAmount` | `uint128` | The amount refunded to the sender, denoted in units of the token's decimals. | ### cancelMultiple Cancels multiple streams and refunds any remaining tokens to the sender. Emits multiple {Transfer}, {CancelLockupStream} and {MetadataUpdate} events. For each reverted cancellation, it emits an [InvalidStreamInCancelMultiple](/reference/lockup/contracts/interfaces/interface.ISablierLockup#invalidstreamincancelmultiple) event. Notes: - This function as a whole will not revert if one or more cancellations revert. A zero amount is returned for reverted streams. - Refer to the notes and requirements from {cancel}. ```solidity function cancelMultiple(uint256[] calldata streamIds) external payable override noDelegateCall returns (uint128[] memory refundedAmounts); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The IDs of the streams to cancel. | **Returns** | Name | Type | Description | | --- | --- | --- | | `refundedAmounts` | `uint128[]` | The amounts refunded to the sender, denoted in units of the token's decimals. | ### recover Recover the surplus amount of tokens. Notes: - The surplus amount is defined as the difference between the total balance of the contract for the provided ERC-20 token and the sum of balances of all streams created using the same ERC-20 token. Requirements: - `msg.sender` must be the comptroller contract. - The surplus amount must be greater than zero. ```solidity function recover(IERC20 token, address to) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The contract address of the ERC-20 token to recover for. | | `to` | `address` | The address to send the surplus amount. | ### renounce Removes the right of the stream's sender to cancel the stream. Emits a {RenounceLockupStream} event. Notes: - This is an irreversible operation. Requirements: - Must not be delegate called. - `streamId` must reference a warm stream. - `msg.sender` must be the stream's sender. - The stream must be cancelable. ```solidity function renounce(uint256 streamId) public payable override noDelegateCall notNull(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to renounce. | ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for {nativeToken}. Notes: - If `newNativeToken` is the zero address, the function does not revert. Requirements: - `msg.sender` must be the comptroller contract. - The current native token must be the zero address. ```solidity function setNativeToken(address newNativeToken) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ### setNFTDescriptor Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. Emits a {SetNFTDescriptor} and {BatchMetadataUpdate} event. Notes: - Does not revert if the NFT descriptor is the same. Requirements: - `msg.sender` must be the comptroller contract. ```solidity function setNFTDescriptor(ILockupNFTDescriptor newNFTDescriptor) external override onlyComptroller; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNFTDescriptor` | `ILockupNFTDescriptor` | The address of the new NFT descriptor contract. | ### withdraw Withdraws the provided amount of tokens from the stream to the `to` address. Emits a {Transfer}, {WithdrawFromLockupStream} and {MetadataUpdate} event. Notes: - If `msg.sender` is not the recipient and the address is on the allowlist, this function will invoke a hook on the recipient. - The minimum fee in wei is calculated for the stream's sender using the **SablierComptroller** contract. Requirements: - Must not be delegate called. - `streamId` must not reference a null or depleted stream. - `to` must not be the zero address. - `amount` must be greater than zero and must not exceed the withdrawable amount. - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. - `msg.value` must be greater than or equal to the minimum fee in wei for the stream's sender. ```solidity function withdraw( uint256 streamId, address to, uint128 amount ) public payable override noDelegateCall notNull(streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | | `amount` | `uint128` | The amount to withdraw, denoted in units of the token's decimals. | ### withdrawMax Withdraws the maximum withdrawable amount from the stream to the provided address `to`. Emits a {Transfer}, {WithdrawFromLockupStream} and {MetadataUpdate} event. Notes: - Refer to the notes in {withdraw}. Requirements: - Refer to the requirements in {withdraw}. ```solidity function withdrawMax(uint256 streamId, address to) external payable override returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawnAmount` | `uint128` | The amount withdrawn, denoted in units of the token's decimals. | ### withdrawMaxAndTransfer Withdraws the maximum withdrawable amount from the stream to the current recipient, and transfers the NFT to `newRecipient`. Emits a {WithdrawFromLockupStream}, {Transfer} and {MetadataUpdate} event. Notes: - If the withdrawable amount is zero, the withdrawal is skipped. - Refer to the notes in {withdraw}. Requirements: - `msg.sender` must be either the NFT owner or an approved third party. - Refer to the requirements in {withdraw}. - Refer to the requirements in {IERC721.transferFrom}. ```solidity function withdrawMaxAndTransfer( uint256 streamId, address newRecipient ) external payable override noDelegateCall notNull(streamId) returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream NFT to transfer. | | `newRecipient` | `address` | The address of the new owner of the stream NFT. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawnAmount` | `uint128` | The amount withdrawn, denoted in units of the token's decimals. | ### withdrawMultiple Withdraws tokens from streams to the recipient of each stream. Emits multiple {Transfer}, {WithdrawFromLockupStream} and {MetadataUpdate} events. For each reverting withdrawal, it emits an [InvalidWithdrawalInWithdrawMultiple](/reference/lockup/contracts/interfaces/interface.ISablierLockup#invalidwithdrawalinwithdrawmultiple) event. Notes: - This function as a whole will not revert if one or more withdrawals revert. - This function attempts to call a hook on the recipient of each stream, unless `msg.sender` is the recipient. - Refer to the notes and requirements from {withdraw}. Requirements: - Must not be delegate called. - There must be an equal number of `streamIds` and `amounts`. ```solidity function withdrawMultiple( uint256[] calldata streamIds, uint128[] calldata amounts ) external payable override noDelegateCall; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The IDs of the streams to withdraw from. | | `amounts` | `uint128[]` | The amounts to withdraw, denoted in units of the token's decimals. | ### \_streamedAmountOf Calculates the streamed amount of the stream. This function is implemented by child contract. The logic varies according to the distribution model. ```solidity function _streamedAmountOf(uint256 streamId) internal view override returns (uint128 streamedAmount); ``` ### \_create This function is implemented by [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) and is used in the [SablierLockupDynamic](/reference/lockup/contracts/abstracts/abstract.SablierLockupDynamic), [SablierLockupLinear](/reference/lockup/contracts/abstracts/abstract.SablierLockupLinear) and [SablierLockupTranched](/reference/lockup/contracts/abstracts/abstract.SablierLockupTranched) contracts. It updates state variables based on the stream parameters, mints an NFT to the recipient, bumps stream ID, and transfers the deposit amount. ```solidity function _create( bool cancelable, uint128 depositAmount, Lockup.Model lockupModel, address recipient, address sender, uint256 streamId, Lockup.Timestamps memory timestamps, IERC20 token, bool transferable ) internal override; ``` ### \_update Overrides the {ERC721.\_update} function to check that the stream is transferable, and emits an ERC-4906 event. There are two cases when the transferable flag is ignored: - If the current owner is 0, then the update is a mint and is allowed. - If `to` is 0, then the update is a burn and is also allowed. ```solidity function _update(address to, uint256 streamId, address auth) internal override returns (address); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `to` | `address` | The address of the new recipient of the stream. | | `streamId` | `uint256` | ID of the stream to update. | | `auth` | `address` | Optional parameter. If the value is not zero, the overridden implementation will check that `auth` is either the recipient of the stream, or an approved third party. | **Returns** | Name | Type | Description | | --- | --- | --- | | `` | `address` | The original recipient of the `streamId` before the update. | ### \_isCallerStreamRecipientOrApproved Checks whether `msg.sender` is the stream's recipient or an approved third party, when the `recipient` is known in advance. ```solidity function _isCallerStreamRecipientOrApproved(uint256 streamId, address recipient) private view returns (bool); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | | `recipient` | `address` | The address of the stream's recipient. | ### \_withdrawableAmountOf See the documentation for the user-facing functions that call this private function. ```solidity function _withdrawableAmountOf(uint256 streamId) private view returns (uint128); ``` ### \_cancel See the documentation for the user-facing functions that call this private function. ```solidity function _cancel(uint256 streamId) private returns (uint128 senderAmount); ``` ### \_withdraw See the documentation for the user-facing functions that call this private function. ```solidity function _withdraw(uint256 streamId, address to, uint128 amount) private; ``` --- ## IBatch Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.IBatch # IBatch [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IBatch.sol) This contract implements logic to batch call any function. ## Functions ### batch Allows batched calls to self, i.e., `this` contract. Since `msg.value` can be reused across calls, be VERY CAREFUL when using it. Refer to [https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong](https://paradigm.xyz/2021/08/two-rights-might-make-a-wrong) for more information. ```solidity function batch(bytes[] calldata calls) external payable returns (bytes[] memory results); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `calls` | `bytes[]` | An array of inputs for each call. | **Returns** | Name | Type | Description | | --- | --- | --- | | `results` | `bytes[]` | An array of results from each call. Empty when the calls do not return anything. | --- ## IComptrollerable Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.IComptrollerable # IComptrollerable [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/IComptrollerable.sol) **Title:** IComptrollerable Contract module that provides a setter and getter for the Sablier Comptroller. ## Functions ### comptroller Retrieves the address of the comptroller contract. ```solidity function comptroller() external view returns (ISablierComptroller); ``` ### setComptroller Sets the comptroller to a new address. Emits a [SetComptroller](#setcomptroller) event. Requirements: - `msg.sender` must be the current comptroller. - The new comptroller must return `true` from {supportsInterface} with the comptroller's minimal interface ID which is defined as the XOR of the following function selectors: 1. {calculateMinFeeWeiFor} 2. {convertUSDFeeToWei} 3. {execute} 4. {getMinFeeUSDFor} ```solidity function setComptroller(ISablierComptroller newComptroller) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newComptroller` | `ISablierComptroller` | The address of the new comptroller contract. | ### transferFeesToComptroller Transfers the fees to the comptroller contract. Emits a [TransferFeesToComptroller](#transferfeestocomptroller) event. ```solidity function transferFeesToComptroller() external; ``` ## Events ### SetComptroller Emitted when the comptroller address is set by the admin. ```solidity event SetComptroller(ISablierComptroller oldComptroller, ISablierComptroller newComptroller); ``` ### TransferFeesToComptroller Emitted when the fees are transferred to the comptroller contract. ```solidity event TransferFeesToComptroller(address indexed comptroller, uint256 feeAmount); ``` --- ## ILockupNFTDescriptor Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ILockupNFTDescriptor # ILockupNFTDescriptor [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ILockupNFTDescriptor.sol) **Title:** ILockupNFTDescriptor This contract generates the URI describing the Sablier stream NFTs. Inspired by Uniswap V3 Positions NFTs. ## Functions ### tokenURI Produces the URI describing a particular stream NFT. This is a data URI with the JSON contents directly inlined. ```solidity function tokenURI(IERC721Metadata sablier, uint256 streamId) external view returns (string memory uri); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `sablier` | `IERC721Metadata` | The address of the Sablier contract the stream was created in. | | `streamId` | `uint256` | The ID of the stream for which to produce a description. | **Returns** | Name | Type | Description | | --- | --- | --- | | `uri` | `string` | The URI of the ERC721-compliant metadata. | --- ## ISablierBatchLockup Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierBatchLockup # ISablierBatchLockup [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierBatchLockup.sol) **Title:** ISablierBatchLockup Helper to batch create Lockup streams. ## Functions ### createWithDurationsLD Creates a batch of LD streams using `createWithDurationsLD`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupDynamic.createWithDurationsLD} must be met for each stream. ```solidity function createWithDurationsLD( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithDurationsLD[] calldata batch ) external returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithDurationsLD[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupDynamic.createWithDurationsLD}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLD Creates a batch of LD streams using `createWithTimestampsLD`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupDynamic.createWithTimestampsLD} must be met for each stream. ```solidity function createWithTimestampsLD( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLD[] calldata batch ) external returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLD[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupDynamic.createWithTimestampsLD}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithDurationsLL Creates a batch of LL streams using `createWithDurationsLL`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupLinear.createWithDurationsLL} must be met for each stream. ```solidity function createWithDurationsLL( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithDurationsLL[] calldata batch ) external returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithDurationsLL[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupLinear.createWithDurationsLL}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLL Creates a batch of LL streams using `createWithTimestampsLL`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupLinear.createWithTimestampsLL} must be met for each stream. ```solidity function createWithTimestampsLL( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLL[] calldata batch ) external returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLL[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupLinear.createWithTimestampsLL}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLPG Creates a batch of LPG streams using `createWithTimestampsLPG`. Notes: - The LPG model does not support a "createWithDuration" function because the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract is at the size limit. If the EVM contract size limit is increased in the future, this function will be added. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupPriceGated.createWithTimestampsLPG} must be met for each stream. ```solidity function createWithTimestampsLPG( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLPG[] calldata batch ) external returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLPG[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupPriceGated.createWithTimestampsLPG}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithDurationsLT Creates a batch of LT streams using `createWithDurationsLT`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupTranched.createWithDurationsLT} must be met for each stream. ```solidity function createWithDurationsLT( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithDurationsLT[] calldata batch ) external returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithDurationsLT[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupTranched.createWithDurationsLT}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ### createWithTimestampsLT Creates a batch of LT streams using `createWithTimestampsLT`. Requirements: - There must be at least one element in `batch`. - All requirements from {ISablierLockupTranched.createWithTimestampsLT} must be met for each stream. ```solidity function createWithTimestampsLT( ISablierLockup lockup, IERC20 token, BatchLockup.CreateWithTimestampsLT[] calldata batch ) external returns (uint256[] memory streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract. | | `token` | `IERC20` | The contract address of the ERC-20 token to be distributed. | | `batch` | `BatchLockup.CreateWithTimestampsLT[]` | An array of structs, each encapsulating a subset of the parameters of {ISablierLockupTranched.createWithTimestampsLT}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The ids of the newly created streams. | ## Events ### CreateLockupBatch Emitted when a batch of Lockup streams are created. ```solidity event CreateLockupBatch(address indexed funder, ISablierLockup indexed lockup, uint256[] streamIds); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `funder` | `address` | The address funding the streams. | | `lockup` | `ISablierLockup` | The address of the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract used to create the streams. | | `streamIds` | `uint256[]` | The ids of the newly created streams, the ones that were successfully created. | --- ## ISablierLockup Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierLockup # ISablierLockup [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLockup.sol) **Inherits:** IBatch, IComptrollerable, IERC4906, IERC721Metadata, [ISablierLockupDynamic](/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic), [ISablierLockupLinear](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear), [ISablierLockupPriceGated](/reference/lockup/contracts/interfaces/interface.ISablierLockupPriceGated), [ISablierLockupTranched](/reference/lockup/contracts/interfaces/interface.ISablierLockupTranched) **Title:** ISablierLockup Interface to manage Lockup streams with various distribution models. ## Functions ### calculateMinFeeWei Calculates the minimum fee in wei required to withdraw from the given stream ID. Reverts if `streamId` references a null stream. ```solidity function calculateMinFeeWei(uint256 streamId) external view returns (uint256 minFeeWei); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getRecipient Retrieves the stream's recipient. Reverts if the NFT has been burned. ```solidity function getRecipient(uint256 streamId) external view returns (address recipient); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isCold Retrieves a flag indicating whether the stream is cold, i.e. settled, canceled, or depleted. Reverts if `streamId` references a null stream. ```solidity function isCold(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isWarm Retrieves a flag indicating whether the stream is warm, i.e. either pending or streaming. Reverts if `streamId` references a null stream. ```solidity function isWarm(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### refundableAmountOf Calculates the amount that the sender would be refunded if the stream were canceled, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function refundableAmountOf(uint256 streamId) external view returns (uint128 refundableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### statusOf Retrieves the stream's status. Reverts if `streamId` references a null stream. ```solidity function statusOf(uint256 streamId) external view returns (Lockup.Status status); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### streamedAmountOf Calculates the amount streamed to the recipient, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. Notes: - Upon cancellation of the stream, the amount streamed is calculated as the difference between the deposited amount and the refunded amount. Ultimately, when the stream becomes depleted, the streamed amount is equivalent to the total amount withdrawn. ```solidity function streamedAmountOf(uint256 streamId) external view returns (uint128 streamedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### withdrawableAmountOf Calculates the amount that the recipient can withdraw from the stream, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function withdrawableAmountOf(uint256 streamId) external view returns (uint128 withdrawableAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### allowToHook Allows a recipient contract to hook to Sablier when a stream is canceled or when tokens are withdrawn. Useful for implementing contracts that hold streams on behalf of users, such as vaults or staking contracts. Emits an [AllowToHook](/reference/lockup/contracts/interfaces/interface.ISablierLockup#allowtohook) event. Notes: - Does not revert if the contract is already on the allowlist. - This is an irreversible operation. The contract cannot be removed from the allowlist. Requirements: - `msg.sender` must be the comptroller contract. - `recipient` must implement [ISablierLockupRecipient](/reference/lockup/contracts/interfaces/interface.ISablierLockupRecipient). ```solidity function allowToHook(address recipient) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `recipient` | `address` | The address of the contract to allow for hooks. | ### burn Burns the NFT associated with the stream. Emits a {Transfer} and {MetadataUpdate} event. Requirements: - Must not be delegate called. - `streamId` must reference a depleted stream. - The NFT must exist. - `msg.sender` must be either the NFT owner or an approved third party. ```solidity function burn(uint256 streamId) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream NFT to burn. | ### cancel Cancels the stream and refunds any remaining tokens to the sender. Emits a {Transfer}, [CancelLockupStream](/reference/lockup/contracts/interfaces/interface.ISablierLockup#cancellockupstream) and {MetadataUpdate} event. Notes: - If there are any tokens left for the recipient to withdraw, the stream is marked as canceled. Otherwise, the stream is marked as depleted. - If the address is on the allowlist, this function will invoke a hook on the recipient. Requirements: - Must not be delegate called. - The stream must be warm and cancelable. - `msg.sender` must be the stream's sender. ```solidity function cancel(uint256 streamId) external payable returns (uint128 refundedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to cancel. | **Returns** | Name | Type | Description | | --- | --- | --- | | `refundedAmount` | `uint128` | The amount refunded to the sender, denoted in units of the token's decimals. | ### cancelMultiple Cancels multiple streams and refunds any remaining tokens to the sender. Emits multiple {Transfer}, [CancelLockupStream](/reference/lockup/contracts/interfaces/interface.ISablierLockup#cancellockupstream) and {MetadataUpdate} events. For each reverted cancellation, it emits an [InvalidStreamInCancelMultiple](/reference/lockup/contracts/interfaces/interface.ISablierLockup#invalidstreamincancelmultiple) event. Notes: - This function as a whole will not revert if one or more cancellations revert. A zero amount is returned for reverted streams. - Refer to the notes and requirements from [cancel](/reference/lockup/contracts/interfaces/interface.ISablierLockup#cancel). ```solidity function cancelMultiple(uint256[] calldata streamIds) external payable returns (uint128[] memory refundedAmounts); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The IDs of the streams to cancel. | **Returns** | Name | Type | Description | | --- | --- | --- | | `refundedAmounts` | `uint128[]` | The amounts refunded to the sender, denoted in units of the token's decimals. | ### recover Recover the surplus amount of tokens. Notes: - The surplus amount is defined as the difference between the total balance of the contract for the provided ERC-20 token and the sum of balances of all streams created using the same ERC-20 token. Requirements: - `msg.sender` must be the comptroller contract. - The surplus amount must be greater than zero. ```solidity function recover(IERC20 token, address to) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The contract address of the ERC-20 token to recover for. | | `to` | `address` | The address to send the surplus amount. | ### renounce Removes the right of the stream's sender to cancel the stream. Emits a [RenounceLockupStream](/reference/lockup/contracts/interfaces/interface.ISablierLockup#renouncelockupstream) event. Notes: - This is an irreversible operation. Requirements: - Must not be delegate called. - `streamId` must reference a warm stream. - `msg.sender` must be the stream's sender. - The stream must be cancelable. ```solidity function renounce(uint256 streamId) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to renounce. | ### setNativeToken Sets the native token address. Once set, it cannot be changed. For more information, see the documentation for {nativeToken}. Notes: - If `newNativeToken` is the zero address, the function does not revert. Requirements: - `msg.sender` must be the comptroller contract. - The current native token must be the zero address. ```solidity function setNativeToken(address newNativeToken) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNativeToken` | `address` | The address of the native token. | ### setNFTDescriptor Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. Emits a [SetNFTDescriptor](/reference/lockup/contracts/interfaces/interface.ISablierLockup#setnftdescriptor) and {BatchMetadataUpdate} event. Notes: - Does not revert if the NFT descriptor is the same. Requirements: - `msg.sender` must be the comptroller contract. ```solidity function setNFTDescriptor(ILockupNFTDescriptor newNFTDescriptor) external; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `newNFTDescriptor` | `ILockupNFTDescriptor` | The address of the new NFT descriptor contract. | ### withdraw Withdraws the provided amount of tokens from the stream to the `to` address. Emits a {Transfer}, [WithdrawFromLockupStream](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdrawfromlockupstream) and {MetadataUpdate} event. Notes: - If `msg.sender` is not the recipient and the address is on the allowlist, this function will invoke a hook on the recipient. - The minimum fee in wei is calculated for the stream's sender using the **SablierComptroller** contract. Requirements: - Must not be delegate called. - `streamId` must not reference a null or depleted stream. - `to` must not be the zero address. - `amount` must be greater than zero and must not exceed the withdrawable amount. - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. - `msg.value` must be greater than or equal to the minimum fee in wei for the stream's sender. ```solidity function withdraw(uint256 streamId, address to, uint128 amount) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | | `amount` | `uint128` | The amount to withdraw, denoted in units of the token's decimals. | ### withdrawMax Withdraws the maximum withdrawable amount from the stream to the provided address `to`. Emits a {Transfer}, [WithdrawFromLockupStream](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdrawfromlockupstream) and {MetadataUpdate} event. Notes: - Refer to the notes in [withdraw](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdraw). Requirements: - Refer to the requirements in [withdraw](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdraw). ```solidity function withdrawMax(uint256 streamId, address to) external payable returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream to withdraw from. | | `to` | `address` | The address receiving the withdrawn tokens. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawnAmount` | `uint128` | The amount withdrawn, denoted in units of the token's decimals. | ### withdrawMaxAndTransfer Withdraws the maximum withdrawable amount from the stream to the current recipient, and transfers the NFT to `newRecipient`. Emits a [WithdrawFromLockupStream](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdrawfromlockupstream), {Transfer} and {MetadataUpdate} event. Notes: - If the withdrawable amount is zero, the withdrawal is skipped. - Refer to the notes in [withdraw](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdraw). Requirements: - `msg.sender` must be either the NFT owner or an approved third party. - Refer to the requirements in [withdraw](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdraw). - Refer to the requirements in {IERC721.transferFrom}. ```solidity function withdrawMaxAndTransfer( uint256 streamId, address newRecipient ) external payable returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream NFT to transfer. | | `newRecipient` | `address` | The address of the new owner of the stream NFT. | **Returns** | Name | Type | Description | | --- | --- | --- | | `withdrawnAmount` | `uint128` | The amount withdrawn, denoted in units of the token's decimals. | ### withdrawMultiple Withdraws tokens from streams to the recipient of each stream. Emits multiple {Transfer}, [WithdrawFromLockupStream](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdrawfromlockupstream) and {MetadataUpdate} events. For each reverting withdrawal, it emits an [InvalidWithdrawalInWithdrawMultiple](/reference/lockup/contracts/interfaces/interface.ISablierLockup#invalidwithdrawalinwithdrawmultiple) event. Notes: - This function as a whole will not revert if one or more withdrawals revert. - This function attempts to call a hook on the recipient of each stream, unless `msg.sender` is the recipient. - Refer to the notes and requirements from [withdraw](/reference/lockup/contracts/interfaces/interface.ISablierLockup#withdraw). Requirements: - Must not be delegate called. - There must be an equal number of `streamIds` and `amounts`. ```solidity function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external payable; ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamIds` | `uint256[]` | The IDs of the streams to withdraw from. | | `amounts` | `uint128[]` | The amounts to withdraw, denoted in units of the token's decimals. | ## Events ### AllowToHook Emitted when the comptroller allows a new recipient contract to hook to Sablier. ```solidity event AllowToHook(ISablierComptroller indexed comptroller, address indexed recipient); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `comptroller` | `ISablierComptroller` | The address of the current comptroller. | | `recipient` | `address` | The address of the recipient contract put on the allowlist. | ### CancelLockupStream Emitted when a stream is canceled. ```solidity event CancelLockupStream( uint256 streamId, address indexed sender, address indexed recipient, IERC20 indexed token, uint128 senderAmount, uint128 recipientAmount ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `sender` | `address` | The address of the stream's sender. | | `recipient` | `address` | The address of the stream's recipient. | | `token` | `IERC20` | The contract address of the ERC-20 token that has been distributed. | | `senderAmount` | `uint128` | The amount of tokens refunded to the stream's sender, denoted in units of the token's decimals. | | `recipientAmount` | `uint128` | The amount of tokens left for the stream's recipient to withdraw, denoted in units of the token's decimals. | ### InvalidStreamInCancelMultiple Emitted when canceling multiple streams and one particular cancellation reverts. ```solidity event InvalidStreamInCancelMultiple(uint256 indexed streamId, bytes revertData); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream that reverted the cancellation. | | `revertData` | `bytes` | The error data returned by the reverted cancel. | ### InvalidWithdrawalInWithdrawMultiple Emitted when withdrawing from multiple streams and one particular withdrawal reverts. ```solidity event InvalidWithdrawalInWithdrawMultiple(uint256 indexed streamId, bytes revertData); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream that reverted the withdrawal. | | `revertData` | `bytes` | The error data returned by the reverted withdraw. | ### RenounceLockupStream Emitted when a sender gives up the right to cancel a stream. ```solidity event RenounceLockupStream(uint256 indexed streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | ### SetNFTDescriptor Emitted when the comptroller sets a new NFT descriptor contract. ```solidity event SetNFTDescriptor( ISablierComptroller indexed comptroller, ILockupNFTDescriptor indexed oldNFTDescriptor, ILockupNFTDescriptor indexed newNFTDescriptor ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `comptroller` | `ISablierComptroller` | The address of the current comptroller. | | `oldNFTDescriptor` | `ILockupNFTDescriptor` | The address of the old NFT descriptor contract. | | `newNFTDescriptor` | `ILockupNFTDescriptor` | The address of the new NFT descriptor contract. | ### WithdrawFromLockupStream Emitted when tokens are withdrawn from a stream. ```solidity event WithdrawFromLockupStream(uint256 indexed streamId, address indexed to, IERC20 indexed token, uint128 amount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the stream. | | `to` | `address` | The address that has received the withdrawn tokens. | | `token` | `IERC20` | The contract address of the ERC-20 token that has been withdrawn. | | `amount` | `uint128` | The amount of tokens withdrawn, denoted in units of the token's decimals. | --- ## ISablierLockupDynamic Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic # ISablierLockupDynamic [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLockupDynamic.sol) **Inherits:** [ISablierLockupState](/reference/lockup/contracts/interfaces/interface.ISablierLockupState) **Title:** ISablierLockupDynamic Creates Lockup streams with dynamic distribution model. ## Functions ### createWithDurationsLD Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of `block.timestamp` and all specified time durations. The segment timestamps are derived from these durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, [CreateLockupDynamicStream](/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic#createlockupdynamicstream) and {MetadataUpdate} event. Requirements: - All requirements in [createWithTimestampsLD](/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic#createwithtimestampsld) must be met for the calculated parameters. ```solidity function createWithDurationsLD( Lockup.CreateWithDurations calldata params, LockupDynamic.SegmentWithDuration[] calldata segmentsWithDuration ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithDurations` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `segmentsWithDuration` | `LockupDynamic.SegmentWithDuration[]` | Segments with durations used to compose the dynamic distribution function. Timestamps are calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createWithTimestampsLD Creates a stream with the provided segment timestamps, implying the end time from the last timestamp. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, [CreateLockupDynamicStream](/reference/lockup/contracts/interfaces/interface.ISablierLockupDynamic#createlockupdynamicstream) and {MetadataUpdate} event. Notes: - As long as the segment timestamps are arranged in ascending order, it is not an error for some of them to be in the past. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.timestamps.start` must be greater than zero and less than the first segment's timestamp. - `segments` must have at least one segment. - The segment timestamps must be arranged in ascending order. - `params.timestamps.end` must be equal to the last segment's timestamp. - The sum of the segment amounts must equal the deposit amount. - `params.recipient` must not be the zero address. - `params.sender` must not be the zero address. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLD( Lockup.CreateWithTimestamps calldata params, LockupDynamic.Segment[] calldata segments ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `segments` | `LockupDynamic.Segment[]` | Segments used to compose the dynamic distribution function. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ## Events ### CreateLockupDynamicStream Emitted when an LD stream is created. ```solidity event CreateLockupDynamicStream( uint256 indexed streamId, Lockup.CreateEventCommon commonParams, LockupDynamic.Segment[] segments ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | | `commonParams` | `Lockup.CreateEventCommon` | Common parameters emitted in Create events across all Lockup models. | | `segments` | `LockupDynamic.Segment[]` | The segments the protocol uses to compose the dynamic distribution function. | --- ## ISablierLockupLinear Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear # ISablierLockupLinear [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLockupLinear.sol) **Inherits:** [ISablierLockupState](/reference/lockup/contracts/interfaces/interface.ISablierLockupState) **Title:** ISablierLockupLinear Creates Lockup streams with linear distribution model. ## Functions ### createWithDurationsLL Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of `block.timestamp` and `durations.total`. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, [CreateLockupLinearStream](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear#createlockuplinearstream) and {MetadataUpdate} event. Requirements: - All requirements in [createWithTimestampsLL](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear#createwithtimestampsll) must be met for the calculated parameters. ```solidity function createWithDurationsLL( Lockup.CreateWithDurations calldata params, LockupLinear.UnlockAmounts calldata unlockAmounts, uint40 granularity, LockupLinear.Durations calldata durations ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithDurations` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `unlockAmounts` | `LockupLinear.UnlockAmounts` | Struct encapsulating (i) the amount to unlock at the start time and (ii) the amount to unlock at the cliff time. | | `granularity` | `uint40` | The smallest step in time between two consecutive token unlocks. Zero is a sentinel value for 1 second. | | `durations` | `LockupLinear.Durations` | Struct encapsulating (i) cliff period duration and (ii) total stream duration, both in seconds. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createWithTimestampsLL Creates a stream with the provided start time and end time. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, [CreateLockupLinearStream](/reference/lockup/contracts/interfaces/interface.ISablierLockupLinear#createlockuplinearstream) and {MetadataUpdate} event. Notes: - A cliff time of zero means there is no cliff. - As long as the times are ordered, it is not an error for the start or the cliff time to be in the past. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.timestamps.start` must be greater than zero and less than `params.timestamps.end`. - If set, `cliffTime` must be greater than `params.timestamps.start` and less than `params.timestamps.end`. - `params.recipient` must not be the zero address. - `params.sender` must not be the zero address. - The sum of `params.unlockAmounts.start` and `params.unlockAmounts.cliff` must be less than or equal to deposit amount. - If `params.timestamps.cliff` is not set, the `params.unlockAmounts.cliff` must be zero. - `granularity` must not exceed the streamable range which is `params.timestamps.end - cliffTime` if cliff is set, `params.timestamps.end - params.timestamps.start` otherwise. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLL( Lockup.CreateWithTimestamps calldata params, LockupLinear.UnlockAmounts calldata unlockAmounts, uint40 granularity, uint40 cliffTime ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `unlockAmounts` | `LockupLinear.UnlockAmounts` | Struct encapsulating (i) the amount to unlock at the start time and (ii) the amount to unlock at the cliff time. | | `granularity` | `uint40` | The smallest step in time between two consecutive token unlocks. Zero is a sentinel value for 1 second. | | `cliffTime` | `uint40` | The Unix timestamp for the cliff period's end. A value of zero means there is no cliff. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ## Events ### CreateLockupLinearStream Emitted when an LL stream is created. ```solidity event CreateLockupLinearStream( uint256 indexed streamId, Lockup.CreateEventCommon commonParams, uint40 cliffTime, uint40 granularity, LockupLinear.UnlockAmounts unlockAmounts ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | | `commonParams` | `Lockup.CreateEventCommon` | Common parameters emitted in Create events across all Lockup models. | | `cliffTime` | `uint40` | The Unix timestamp for the cliff period's end. A value of zero means there is no cliff. | | `granularity` | `uint40` | The smallest step in time between two consecutive token unlocks. | | `unlockAmounts` | `LockupLinear.UnlockAmounts` | Struct encapsulating (i) the amount to unlock at the start time and (ii) the amount to unlock at the cliff time. | --- ## ISablierLockupPriceGated Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierLockupPriceGated # ISablierLockupPriceGated [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLockupPriceGated.sol) **Inherits:** [ISablierLockupState](/reference/lockup/contracts/interfaces/interface.ISablierLockupState) **Title:** ISablierLockupPriceGated Creates Lockup streams with price-gated distribution model. ## Functions ### createWithTimestampsLPG Creates a stream with the provided start time and end time. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, [CreateLockupPriceGatedStream](/reference/lockup/contracts/interfaces/interface.ISablierLockupPriceGated#createlockuppricegatedstream) and {MetadataUpdate} event. Notes: - The recipient can withdraw the full deposited amount when either: 1. The oracle price reaches or exceeds the target price, OR 2. Current time is greater than or equal to the stream's end time. - The sender can cancel the stream when price is less than target price AND end time is in the future. - The function does not check if the provided oracle reports the price for the deposited token. It may be possible that stream creator has used a different token for the oracle. In such cases, integrators and recipients are requested to verify the oracle correctness on their own. - The LPG model does not support a "createWithDuration" function because the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract is at the size limit. If the EVM contract size limit is increased in the future, this function will be added. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.sender` must not be the zero address. - `params.recipient` must not be the zero address. - `params.timestamps.start` must not be zero. - `params.timestamps.start` must be less than `params.timestamps.end`. - `unlockParams.oracle` must implement Chainlink's {AggregatorV3Interface} interface. - `unlockParams.oracle` must return a non-zero value no greater than 36 when the `decimals()` function is called. - `unlockParams.oracle` must return a positive price when the `latestRoundData()` function is called. - `unlockParams.targetPrice` must be greater than the current oracle price. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLPG( Lockup.CreateWithTimestamps calldata params, LockupPriceGated.UnlockParams calldata unlockParams ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `unlockParams` | `LockupPriceGated.UnlockParams` | Struct encapsulating the unlock parameters, documented in {LockupPriceGated}. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ## Events ### CreateLockupPriceGatedStream Emitted when an LPG stream is created. ```solidity event CreateLockupPriceGatedStream( uint256 indexed streamId, AggregatorV3Interface indexed oracle, uint128 targetPrice ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | | `oracle` | `AggregatorV3Interface` | The price feed oracle used for retrieving the latest price. | | `targetPrice` | `uint128` | The price that must be reached to unlock the tokens, denominated in Chainlink's 8-decimal, where 1e8 = $1. | --- ## ISablierLockupRecipient Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierLockupRecipient # ISablierLockupRecipient [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLockupRecipient.sol) **Inherits:** IERC165 **Title:** ISablierLockupRecipient Interface for recipient contracts capable of reacting to cancellations and withdrawals. For this to be able to hook into Sablier, it must fully implement this interface and it must have been allowlisted in the Lockup contract. See \[IERC165-supportsInterface\]([https://eips.ethereum.org/EIPS/eip-165](https://eips.ethereum.org/EIPS/eip-165) --- ## ISablierLockupState Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierLockupState # ISablierLockupState [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLockupState.sol) **Title:** ISablierLockupState Contract with state variables (storage and constants) for the [SablierLockup](/reference/lockup/contracts/contract.SablierLockup) contract, their respective getters and helpful modifiers. ## Functions ### aggregateAmount Retrieves the aggregate amount across all streams, denoted in units of the token's decimals. If tokens are directly transferred to the contract without using the stream creation functions, the ERC-20 balance may be greater than the aggregate amount. ```solidity function aggregateAmount(IERC20 token) external view returns (uint256); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `token` | `IERC20` | The ERC-20 token for the query. | ### getCliffTime Retrieves the stream's cliff time, which is a Unix timestamp. A value of zero means there is no cliff. Reverts if `streamId` references either a null stream or a non-LL stream. ```solidity function getCliffTime(uint256 streamId) external view returns (uint40 cliffTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getDepositedAmount Retrieves the amount deposited in the stream, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function getDepositedAmount(uint256 streamId) external view returns (uint128 depositedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getGranularity Retrieves the smallest step in time between two consecutive token unlocks. Reverts if `streamId` references either a null stream or a non-LL stream. ```solidity function getGranularity(uint256 streamId) external view returns (uint40 granularity); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getEndTime Retrieves the stream's end time, which is a Unix timestamp. Reverts if `streamId` references a null stream. ```solidity function getEndTime(uint256 streamId) external view returns (uint40 endTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getLockupModel Retrieves the distribution models used to create the stream. Reverts if `streamId` references a null stream. ```solidity function getLockupModel(uint256 streamId) external view returns (Lockup.Model lockupModel); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getPriceGatedUnlockParams Retrieves the unlock parameters of a price-gated stream. Reverts if `streamId` references either a null stream or a non-LPG stream. ```solidity function getPriceGatedUnlockParams(uint256 streamId) external view returns (LockupPriceGated.UnlockParams memory unlockParams); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `unlockParams` | `LockupPriceGated.UnlockParams` | See the documentation in {LockupPriceGated} type. | ### getRefundedAmount Retrieves the amount refunded to the sender after a cancellation, denoted in units of the token's decimals. This amount is always zero unless the stream was canceled. Reverts if `streamId` references a null stream. ```solidity function getRefundedAmount(uint256 streamId) external view returns (uint128 refundedAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getSegments Retrieves the segments used to compose the dynamic distribution function. Reverts if `streamId` references either a null stream or a non-LD stream. ```solidity function getSegments(uint256 streamId) external view returns (LockupDynamic.Segment[] memory segments); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `segments` | `LockupDynamic.Segment[]` | See the documentation in {LockupDynamic} type. | ### getSender Retrieves the stream's sender. Reverts if `streamId` references a null stream. ```solidity function getSender(uint256 streamId) external view returns (address sender); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getStartTime Retrieves the stream's start time, which is a Unix timestamp. Reverts if `streamId` references a null stream. ```solidity function getStartTime(uint256 streamId) external view returns (uint40 startTime); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getTranches Retrieves the tranches used to compose the tranched distribution function. Reverts if `streamId` references either a null stream or a non-LT stream. ```solidity function getTranches(uint256 streamId) external view returns (LockupTranched.Tranche[] memory tranches); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `tranches` | `LockupTranched.Tranche[]` | See the documentation in {LockupTranched} type. | ### getUnderlyingToken Retrieves the address of the underlying ERC-20 token being distributed. Reverts if `streamId` references a null stream. ```solidity function getUnderlyingToken(uint256 streamId) external view returns (IERC20 token); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### getUnlockAmounts Retrieves the unlock amounts used to compose the linear distribution function. Reverts if `streamId` references either a null stream or a non-LL stream. ```solidity function getUnlockAmounts(uint256 streamId) external view returns (LockupLinear.UnlockAmounts memory unlockAmounts); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | **Returns** | Name | Type | Description | | --- | --- | --- | | `unlockAmounts` | `LockupLinear.UnlockAmounts` | See the documentation in {LockupLinear} type. | ### getWithdrawnAmount Retrieves the amount withdrawn from the stream, denoted in units of the token's decimals. Reverts if `streamId` references a null stream. ```solidity function getWithdrawnAmount(uint256 streamId) external view returns (uint128 withdrawnAmount); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isAllowedToHook Retrieves a flag indicating whether the provided address is a contract allowed to hook to Sablier when a stream is canceled or when tokens are withdrawn. See [ISablierLockupRecipient](/reference/lockup/contracts/interfaces/interface.ISablierLockupRecipient) for more information. ```solidity function isAllowedToHook(address recipient) external view returns (bool result); ``` ### isCancelable Retrieves a flag indicating whether the stream can be canceled. When the stream is cold, this flag is always `false`. Reverts if `streamId` references a null stream. ```solidity function isCancelable(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isDepleted Retrieves a flag indicating whether the stream is depleted. Reverts if `streamId` references a null stream. ```solidity function isDepleted(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isStream Retrieves a flag indicating whether the stream exists. Does not revert if `streamId` references a null stream. ```solidity function isStream(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### isTransferable Retrieves a flag indicating whether the stream NFT can be transferred. Reverts if `streamId` references a null stream. ```solidity function isTransferable(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | ### nativeToken Retrieves the address of the ERC-20 interface of the native token, if it exists. The native tokens on some chains have a dual interface as ERC-20. For example, on Polygon the $POL token is the native token and has an ERC-20 version at 0x0000000000000000000000000000000000001010. This means that `address(this).balance` returns the same value as `balanceOf(address(this))`. To avoid any unintended behavior, these tokens cannot be used in Sablier. As an alternative, users can use the Wrapped version of the token, i.e. WMATIC, which is a standard ERC-20 token. ```solidity function nativeToken() external view returns (address); ``` ### nextStreamId Counter for stream IDs, used in the create functions. ```solidity function nextStreamId() external view returns (uint256); ``` ### nftDescriptor Contract that generates the non-fungible token URI. ```solidity function nftDescriptor() external view returns (ILockupNFTDescriptor); ``` ### wasCanceled Retrieves a flag indicating whether the stream was canceled. Reverts if `streamId` references a null stream. ```solidity function wasCanceled(uint256 streamId) external view returns (bool result); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The stream ID for the query. | --- ## ISablierLockupTranched Source: https://docs.sablier.com/reference/lockup/contracts/interfaces/interface.ISablierLockupTranched # ISablierLockupTranched [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/interfaces/ISablierLockupTranched.sol) **Inherits:** [ISablierLockupState](/reference/lockup/contracts/interfaces/interface.ISablierLockupState) **Title:** ISablierLockupTranched Creates Lockup streams with tranched distribution model. ## Functions ### createWithDurationsLT Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of `block.timestamp` and all specified time durations. The tranche timestamps are derived from these durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupTrancheStream} and {MetadataUpdate} event. Requirements: - All requirements in [createWithTimestampsLT](/reference/lockup/contracts/interfaces/interface.ISablierLockupTranched#createwithtimestampslt) must be met for the calculated parameters. ```solidity function createWithDurationsLT( Lockup.CreateWithDurations calldata params, LockupTranched.TrancheWithDuration[] calldata tranchesWithDuration ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithDurations` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `tranchesWithDuration` | `LockupTranched.TrancheWithDuration[]` | Tranches with durations used to compose the tranched distribution function. Timestamps are calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ### createWithTimestampsLT Creates a stream with the provided tranche timestamps, implying the end time from the last timestamp. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. Emits a {Transfer}, {CreateLockupTrancheStream} and {MetadataUpdate} event. Notes: - As long as the tranche timestamps are arranged in ascending order, it is not an error for some of them to be in the past. Requirements: - Must not be delegate called. - `params.depositAmount` must be greater than zero. - `params.timestamps.start` must be greater than zero and less than the first tranche's timestamp. - `tranches` must have at least one tranche. - The tranche timestamps must be arranged in ascending order. - `params.timestamps.end` must be equal to the last tranche's timestamp. - The sum of the tranche amounts must equal the deposit amount. - `params.recipient` must not be the zero address. - `params.sender` must not be the zero address. - `msg.sender` must have allowed this contract to spend at least `params.depositAmount` tokens. - `params.token` must not be the native token. - `params.shape.length` must not be greater than 32 characters. ```solidity function createWithTimestampsLT( Lockup.CreateWithTimestamps calldata params, LockupTranched.Tranche[] calldata tranches ) external payable returns (uint256 streamId); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `params` | `Lockup.CreateWithTimestamps` | Struct encapsulating the function parameters, which are documented in {Lockup} type. | | `tranches` | `LockupTranched.Tranche[]` | Tranches used to compose the tranched distribution function. | **Returns** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | ## Events ### CreateLockupTranchedStream Emitted when an LT stream is created. ```solidity event CreateLockupTranchedStream( uint256 indexed streamId, Lockup.CreateEventCommon commonParams, LockupTranched.Tranche[] tranches ); ``` **Parameters** | Name | Type | Description | | --- | --- | --- | | `streamId` | `uint256` | The ID of the newly created stream. | | `commonParams` | `Lockup.CreateEventCommon` | Common parameters emitted in Create events across all Lockup models. | | `tranches` | `LockupTranched.Tranche[]` | The tranches the protocol uses to compose the tranched distribution function. | --- ## Errors Source: https://docs.sablier.com/reference/lockup/contracts/libraries/library.Errors # Errors [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/Errors.sol) **Title:** Errors Library containing all custom errors the protocol may revert with. ## Errors ### SablierBatchLockup\_BatchSizeZero ```solidity error SablierBatchLockup_BatchSizeZero(); ``` ### SablierLockupHelpers\_CliffTimeNotLessThanEndTime Thrown when trying to create a linear stream with a cliff time not strictly less than the end time. ```solidity error SablierLockupHelpers_CliffTimeNotLessThanEndTime(uint40 cliffTime, uint40 endTime); ``` ### SablierLockupHelpers\_CliffTimeZeroUnlockAmountNotZero Thrown when trying to create a stream with a non zero cliff unlock amount when the cliff time is zero. ```solidity error SablierLockupHelpers_CliffTimeZeroUnlockAmountNotZero(uint128 cliffUnlockAmount); ``` ### SablierLockupHelpers\_CreateNativeToken Thrown when trying to create a stream with the native token. ```solidity error SablierLockupHelpers_CreateNativeToken(address nativeToken); ``` ### SablierLockupHelpers\_DepositAmountNotEqualToSegmentAmountsSum Thrown when trying to create a dynamic stream with a deposit amount not equal to the sum of the segment amounts. ```solidity error SablierLockupHelpers_DepositAmountNotEqualToSegmentAmountsSum( uint128 depositAmount, uint128 segmentAmountsSum ); ``` ### SablierLockupHelpers\_DepositAmountNotEqualToTrancheAmountsSum Thrown when trying to create a tranched stream with a deposit amount not equal to the sum of the tranche amounts. ```solidity error SablierLockupHelpers_DepositAmountNotEqualToTrancheAmountsSum( uint128 depositAmount, uint128 trancheAmountsSum ); ``` ### SablierLockupHelpers\_DepositAmountZero Thrown when trying to create a stream with a zero deposit amount. ```solidity error SablierLockupHelpers_DepositAmountZero(); ``` ### SablierLockupHelpers\_EndTimeNotEqualToLastSegmentTimestamp Thrown when trying to create a dynamic stream with end time not equal to the last segment's timestamp. ```solidity error SablierLockupHelpers_EndTimeNotEqualToLastSegmentTimestamp(uint40 endTime, uint40 lastSegmentTimestamp); ``` ### SablierLockupHelpers\_EndTimeNotEqualToLastTrancheTimestamp Thrown when trying to create a tranched stream with end time not equal to the last tranche's timestamp. ```solidity error SablierLockupHelpers_EndTimeNotEqualToLastTrancheTimestamp(uint40 endTime, uint40 lastTrancheTimestamp); ``` ### SablierLockupHelpers\_GranularityTooHigh Thrown when trying to create a linear stream with granularity greater than the streamable range. ```solidity error SablierLockupHelpers_GranularityTooHigh(uint40 granularity, uint40 streamableRange); ``` ### SablierLockupHelpers\_SegmentCountZero Thrown when trying to create a dynamic stream with no segments. ```solidity error SablierLockupHelpers_SegmentCountZero(); ``` ### SablierLockupHelpers\_SegmentTimestampsNotOrdered Thrown when trying to create a dynamic stream with unordered segment timestamps. ```solidity error SablierLockupHelpers_SegmentTimestampsNotOrdered( uint256 index, uint40 previousTimestamp, uint40 currentTimestamp ); ``` ### SablierLockupHelpers\_SenderZeroAddress Thrown when trying to create a stream with the sender as the zero address. ```solidity error SablierLockupHelpers_SenderZeroAddress(); ``` ### SablierLockupHelpers\_ShapeExceeds32Bytes Thrown when trying to create a stream with a shape string exceeding 32 bytes. ```solidity error SablierLockupHelpers_ShapeExceeds32Bytes(uint256 shapeLength); ``` ### SablierLockupHelpers\_StartTimeNotLessThanCliffTime Thrown when trying to create a linear stream with a start time not strictly less than the cliff time, when the cliff time does not have a zero value. ```solidity error SablierLockupHelpers_StartTimeNotLessThanCliffTime(uint40 startTime, uint40 cliffTime); ``` ### SablierLockupHelpers\_StartTimeNotLessThanEndTime Thrown when trying to create a linear stream with a start time not strictly less than the end time. ```solidity error SablierLockupHelpers_StartTimeNotLessThanEndTime(uint40 startTime, uint40 endTime); ``` ### SablierLockupHelpers\_StartTimeNotLessThanFirstSegmentTimestamp Thrown when trying to create a dynamic stream with a start time not strictly less than the first segment timestamp. ```solidity error SablierLockupHelpers_StartTimeNotLessThanFirstSegmentTimestamp( uint40 startTime, uint40 firstSegmentTimestamp ); ``` ### SablierLockupHelpers\_StartTimeNotLessThanFirstTrancheTimestamp Thrown when trying to create a tranched stream with a start time not strictly less than the first tranche timestamp. ```solidity error SablierLockupHelpers_StartTimeNotLessThanFirstTrancheTimestamp( uint40 startTime, uint40 firstTrancheTimestamp ); ``` ### SablierLockupHelpers\_StartTimeZero Thrown when trying to create a stream with a zero start time. ```solidity error SablierLockupHelpers_StartTimeZero(); ``` ### SablierLockupHelpers\_TrancheCountZero Thrown when trying to create a tranched stream with no tranches. ```solidity error SablierLockupHelpers_TrancheCountZero(); ``` ### SablierLockupHelpers\_TrancheTimestampsNotOrdered Thrown when trying to create a tranched stream with unordered tranche timestamps. ```solidity error SablierLockupHelpers_TrancheTimestampsNotOrdered( uint256 index, uint40 previousTimestamp, uint40 currentTimestamp ); ``` ### SablierLockupHelpers\_UnlockAmountsSumTooHigh Thrown when trying to create a stream with the sum of the unlock amounts greater than the deposit amount. ```solidity error SablierLockupHelpers_UnlockAmountsSumTooHigh( uint128 depositAmount, uint128 startUnlockAmount, uint128 cliffUnlockAmount ); ``` ### SablierLockup\_AllowToHookUnsupportedInterface Thrown when trying to allow to hook a contract that doesn't implement the interface correctly. ```solidity error SablierLockup_AllowToHookUnsupportedInterface(address recipient); ``` ### SablierLockup\_AllowToHookZeroCodeSize Thrown when trying to allow to hook an address with no code. ```solidity error SablierLockup_AllowToHookZeroCodeSize(address recipient); ``` ### SablierLockup\_FeeTransferFailed Thrown when the fee transfer fails. ```solidity error SablierLockup_FeeTransferFailed(address comptroller, uint256 feeAmount); ``` ### SablierLockup\_InsufficientFeePayment Thrown when trying to withdraw with a fee amount less than the minimum fee. ```solidity error SablierLockup_InsufficientFeePayment(uint256 feePaid, uint256 minFeeWei); ``` ### SablierLockup\_InvalidHookSelector Thrown when the hook does not return the correct selector. ```solidity error SablierLockup_InvalidHookSelector(address recipient); ``` ### SablierLockup\_NativeTokenAlreadySet Thrown when trying to set the native token address when it is already set. ```solidity error SablierLockup_NativeTokenAlreadySet(address nativeToken); ``` ### SablierLockup\_NotTransferable Thrown when trying to transfer Stream NFT when transferability is disabled. ```solidity error SablierLockup_NotTransferable(uint256 tokenId); ``` ### SablierLockup\_Overdraw Thrown when trying to withdraw an amount greater than the withdrawable amount. ```solidity error SablierLockup_Overdraw(uint256 streamId, uint128 amount, uint128 withdrawableAmount); ``` ### SablierLockup\_WithdrawAmountNotEqualWithdrawableAmount Thrown when trying to withdraw a partial amount from a LPG stream. ```solidity error SablierLockup_WithdrawAmountNotEqualWithdrawableAmount( uint256 streamId, uint128 amount, uint128 withdrawableAmount ); ``` ### SablierLockup\_StreamCanceled Thrown when trying to cancel or renounce a canceled stream. ```solidity error SablierLockup_StreamCanceled(uint256 streamId); ``` ### SablierLockup\_StreamDepleted Thrown when trying to cancel, renounce, or withdraw from a depleted stream. ```solidity error SablierLockup_StreamDepleted(uint256 streamId); ``` ### SablierLockup\_StreamNotCancelable Thrown when trying to cancel or renounce a stream that is not cancelable. ```solidity error SablierLockup_StreamNotCancelable(uint256 streamId); ``` ### SablierLockup\_StreamNotDepleted Thrown when trying to burn a stream that is not depleted. ```solidity error SablierLockup_StreamNotDepleted(uint256 streamId); ``` ### SablierLockup\_StreamSettled Thrown when trying to cancel or renounce a settled stream. ```solidity error SablierLockup_StreamSettled(uint256 streamId); ``` ### SablierLockup\_TargetPriceTooLow Thrown when trying to create a price-gated stream with a target price not greater than the current oracle price. ```solidity error SablierLockup_TargetPriceTooLow(uint128 targetPrice, uint128 latestPrice); ``` ### SablierLockup\_Unauthorized Thrown when `msg.sender` lacks authorization to perform an action. ```solidity error SablierLockup_Unauthorized(uint256 streamId, address caller); ``` ### SablierLockup\_WithdrawalAddressNotRecipient Thrown when trying to withdraw to an address other than the recipient's. ```solidity error SablierLockup_WithdrawalAddressNotRecipient(uint256 streamId, address caller, address to); ``` ### SablierLockup\_WithdrawAmountZero Thrown when trying to withdraw zero tokens from a stream. ```solidity error SablierLockup_WithdrawAmountZero(uint256 streamId); ``` ### SablierLockup\_WithdrawArrayCountsNotEqual Thrown when trying to withdraw from multiple streams and the number of stream IDs does not match the number of withdraw amounts. ```solidity error SablierLockup_WithdrawArrayCountsNotEqual(uint256 streamIdsCount, uint256 amountsCount); ``` ### SablierLockup\_WithdrawToZeroAddress Thrown when trying to withdraw to the zero address. ```solidity error SablierLockup_WithdrawToZeroAddress(uint256 streamId); ``` ### SablierLockupState\_NotExpectedModel Thrown when a function is called on a stream that does not use the expected Lockup model. ```solidity error SablierLockupState_NotExpectedModel(Lockup.Model actualLockupModel, Lockup.Model expectedLockupModel); ``` ### SablierLockupState\_Null Thrown when the ID references a null stream. ```solidity error SablierLockupState_Null(uint256 streamId); ``` --- ## LockupHelpers Source: https://docs.sablier.com/reference/lockup/contracts/libraries/library.LockupHelpers # LockupHelpers [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/LockupHelpers.sol) **Title:** LockupHelpers Library with functions needed to validate input parameters across Lockup streams. ## Functions ### calculateSegmentTimestamps Calculate the timestamps and return the segments. ```solidity function calculateSegmentTimestamps( LockupDynamic.SegmentWithDuration[] memory segmentsWithDuration, uint40 startTime ) public pure returns (LockupDynamic.Segment[] memory segmentsWithTimestamps); ``` ### calculateTrancheTimestamps Calculate the timestamps and return the tranches. ```solidity function calculateTrancheTimestamps( LockupTranched.TrancheWithDuration[] memory tranchesWithDuration, uint40 startTime ) public pure returns (LockupTranched.Tranche[] memory tranchesWithTimestamps); ``` ### checkCreateLD Checks the parameters of the [SablierLockup-\_createLD](/reference/lockup/contracts/abstracts/abstract.SablierLockupDynamic#_createld) function. ```solidity function checkCreateLD( address sender, Lockup.Timestamps memory timestamps, uint128 depositAmount, LockupDynamic.Segment[] memory segments, address token, address nativeToken, string memory shape ) public pure; ``` ### checkCreateLL Checks the parameters of the [SablierLockup-\_createLL](/reference/lockup/contracts/abstracts/abstract.SablierLockupLinear#_createll) function. ```solidity function checkCreateLL( uint40 cliffTime, uint128 depositAmount, uint40 granularity, address nativeToken, address sender, string memory shape, Lockup.Timestamps memory timestamps, address token, LockupLinear.UnlockAmounts memory unlockAmounts ) public pure; ``` ### checkCreateLPG Checks the parameters of the [SablierLockup-\_createLPG](/reference/lockup/contracts/abstracts/abstract.SablierLockupPriceGated#_createlpg) function. ```solidity function checkCreateLPG( address sender, Lockup.Timestamps memory timestamps, uint128 depositAmount, address token, address nativeToken, string memory shape, LockupPriceGated.UnlockParams memory unlockParams ) public view; ``` ### checkCreateLT Checks the parameters of the [SablierLockup-\_createLT](/reference/lockup/contracts/abstracts/abstract.SablierLockupTranched#_createlt) function. ```solidity function checkCreateLT( address sender, Lockup.Timestamps memory timestamps, uint128 depositAmount, LockupTranched.Tranche[] memory tranches, address token, address nativeToken, string memory shape ) public pure; ``` ### \_checkTimestampsAndUnlockAmounts Checks the user-provided timestamps of an LL stream. ```solidity function _checkTimestampsAndUnlockAmounts( uint40 cliffTime, uint128 depositAmount, uint40 granularity, Lockup.Timestamps memory timestamps, LockupLinear.UnlockAmounts memory unlockAmounts ) private pure; ``` ### \_checkCreateStream Checks the user-provided common parameters across Lockup streams. ```solidity function _checkCreateStream( address sender, uint128 depositAmount, uint40 startTime, address token, address nativeToken, string memory shape ) private pure; ``` ### \_checkSegments Checks: 1. The first timestamp is strictly greater than the start time. 2. The timestamps are ordered chronologically. 3. There are no duplicate timestamps. 4. The deposit amount is equal to the sum of all segment amounts. 5. The end time equals the last segment's timestamp. ```solidity function _checkSegments( LockupDynamic.Segment[] memory segments, uint128 depositAmount, Lockup.Timestamps memory timestamps ) private pure; ``` ### \_checkTranches Checks: 1. The first timestamp is strictly greater than the start time. 2. The timestamps are ordered chronologically. 3. There are no duplicate timestamps. 4. The deposit amount is equal to the sum of all tranche amounts. 5. The end time equals the last tranche's timestamp. ```solidity function _checkTranches( LockupTranched.Tranche[] memory tranches, uint128 depositAmount, Lockup.Timestamps memory timestamps ) private pure; ``` --- ## LockupMath Source: https://docs.sablier.com/reference/lockup/contracts/libraries/library.LockupMath # LockupMath [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/LockupMath.sol) **Title:** LockupMath Provides functions for calculating the streamed amounts in Lockup streams. Note that 'streamed' is synonymous with 'vested'. ## Functions ### calculateStreamedAmountLD Calculates the streamed amount of LD streams. The LD streaming model uses the following distribution function: $$ f(x) = x^{exp} \cdot csa + \Sigma(esa) $$ Where: - $x$ is the elapsed time divided by the total duration of the current segment. - $exp$ is the current segment exponent. - $csa$ is the current segment amount. - $\Sigma(esa)$ is the sum of all streamed segments' amounts. Notes: 1. Normalization to 18 decimals is not needed because there is no mix of amounts with different decimals. 2. The stream's start time must be in the past so that the calculations below do not overflow. 3. The stream's end time must be in the future so that the loop below does not panic with an "index out of bounds" error. Assumptions: 4. The sum of all segment amounts does not overflow uint128 and equals the deposited amount. 5. The first segment's timestamp is greater than the start time. 6. The last segment's timestamp equals the end time. 7. The segment timestamps are arranged in ascending order. ```solidity function calculateStreamedAmountLD( uint128 depositedAmount, uint40 endTime, LockupDynamic.Segment[] calldata segments, uint40 startTime, uint128 withdrawnAmount ) external view returns (uint128); ``` ### calculateStreamedAmountLL Calculates the streamed amount of LL streams. The LL streaming model uses the following distribution function: $$ f(x) = \begin{cases} s & \text{if block timestamp} < \text{cliff time} \\ x \cdot sa + s + c & \text{if block timestamp} \geq \text{cliff time} \end{cases} $$ Where: - $sa$ is the streamable amount, i.e. deposited amount minus unlock amounts' sum. - $s$ is the start unlock amount. - $c$ is the cliff unlock amount. - $x$ is the elapsed time percentage with discrete unlocks: $$ x = \frac{\lfloor \text{time elapsed} / \text{granularity} \rfloor \cdot \text{granularity}}{\text{streamable time}} $$ The floor division in the numerator creates discrete unlock steps at every granularity seconds. Assumptions: 1. The sum of the unlock amounts (start and cliff) does not overflow uint128 and is less than or equal to the deposit amount. 2. The start time is before the end time. 3. If the cliff time is not zero, it is after the start time and before the end time. 4. Granularity is less than or equal to the streamable range. 5. Granularity is not zero. ```solidity function calculateStreamedAmountLL( uint40 cliffTime, uint128 depositedAmount, uint40 endTime, uint40 granularity, uint40 startTime, LockupLinear.UnlockAmounts calldata unlockAmounts, uint128 withdrawnAmount ) external view returns (uint128); ``` ### calculateStreamedAmountLPG Calculates the streamed amount of LPG streams. The LPG streaming model uses all-or-nothing unlock based on price threshold: $$ f(x) = \begin{cases} \text{deposited} & \text{if block timestamp} \geq \text{end time or latest price} \geq \text{target price} \\ 0 & \text{otherwise} \end{cases} $$ Assumptions: 1. The stream is not canceled. 2. The oracle is assumed to be returning the correct price. ```solidity function calculateStreamedAmountLPG( uint128 deposited, uint40 endTime, LockupPriceGated.UnlockParams memory unlockParams ) external view returns (uint128); ``` ### calculateStreamedAmountLT Calculates the streamed amount of LT streams. The LT streaming model uses the following distribution function: $$ f(x) = \Sigma(eta) $$ Where: - $\Sigma(eta)$ is the sum of all streamed tranches' amounts. Assumptions: 1. The sum of all tranche amounts does not overflow uint128, and equals the deposited amount. 2. The first tranche's timestamp is greater than the start time. 3. The last tranche's timestamp equals the end time. 4. The tranche timestamps are arranged in ascending order. ```solidity function calculateStreamedAmountLT( uint128 depositedAmount, uint40 endTime, uint40 startTime, LockupTranched.Tranche[] calldata tranches ) external view returns (uint128); ``` --- ## NFTSVG Source: https://docs.sablier.com/reference/lockup/contracts/libraries/library.NFTSVG # NFTSVG [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/NFTSVG.sol) ## Constants ### CARD\_MARGIN ```solidity uint256 internal constant CARD_MARGIN = 16 ``` ## Functions ### generateSVG ```solidity function generateSVG(SVGParams memory params) internal pure returns (string memory); ``` ### generateDefs ```solidity function generateDefs( string memory accentColor, string memory status, string memory cards ) internal pure returns (string memory); ``` ### generateFloatingText ```solidity function generateFloatingText( string memory lockupAddress, string memory tokenAddress, string memory tokenSymbol ) internal pure returns (string memory); ``` ### generateHrefs ```solidity function generateHrefs( uint256 progressXPosition, uint256 statusXPosition, uint256 amountXPosition, uint256 durationXPosition ) internal pure returns (string memory); ``` ## Structs ### SVGParams ```solidity struct SVGParams { string accentColor; string amount; string tokenAddress; string tokenSymbol; string duration; string lockupAddress; string progress; uint256 progressNumerical; string status; } ``` ### SVGVars ```solidity struct SVGVars { string amountCard; uint256 amountWidth; uint256 amountXPosition; string cards; uint256 cardsWidth; string durationCard; uint256 durationWidth; uint256 durationXPosition; string progressCard; uint256 progressWidth; uint256 progressXPosition; string statusCard; uint256 statusWidth; uint256 statusXPosition; } ``` --- ## SVGElements Source: https://docs.sablier.com/reference/lockup/contracts/libraries/library.SVGElements # SVGElements [Git Source](https://github.com/sablier-labs/evm-monorepo/blob/8b6823c019ff7556ac9ad24cbb5ac62821854d2f/src/libraries/SVGElements.sol) ## Constants ### BACKGROUND ```solidity string internal constant BACKGROUND = '' ``` ### BACKGROUND\_COLOR ```solidity string internal constant BACKGROUND_COLOR = "hsl(230,21%,11%)" ``` ### FLOATING\_TEXT ```solidity string internal constant FLOATING_TEXT = '' ``` ### GLOW ```solidity string internal constant GLOW = '' ``` ### LOGO ```solidity string internal constant LOGO = '