# Sablier Merkle Airdrops Documentation > Merkle Airdrops is useful to distribute tokens to a large number of users efficiently. ## 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) --- ## 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. --- ## 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. --- ## 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). --- ## 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) | --- ## 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 ``` --- ## Contact Us Source: https://docs.sablier.com/support/contact # Contact Us If you need help with the Sablier protocols, the apps, or anything else, you can reach us through any of the following channels. ## Email For customer support, partnership inquiries, or anything that doesn't fit a public channel, email us at [contact@sablier.com](mailto:contact@sablier.com). We typically reply within one business day. ## Discord For real-time questions and community discussion, join our [Discord server](https://discord.sablier.com). The team and community members are active there and happy to help. - `#support` — for help with the apps and protocols - `#dev` — for integration and smart contract questions ## GitHub If you've found a bug or want to request a feature, open an issue in the relevant repository under the [`sablier-labs`](https://github.com/sablier-labs) organization on GitHub. --- ## FAQ Source: https://docs.sablier.com/support/faq # FAQ ### Where can I access the Sablier Protocol? You can access Sablier through the following web interfaces: - [app.sablier.com](https://app.sablier.com) - [app.safe.global](https://app.safe.global/share/safe-app?appUrl=https%3A%2F%2Fapp.sablier.com%2F&chain=arb1) (if you are using a Safe multisig wallet) ### What is token streaming? An alternative wording, coined by Andreas Antonopoulos in 2017. Just like you can stream movies on Netflix or music on Spotify, so you can stream tokens by the second on Sablier. ### How does streaming work on Sablier Lockup? Imagine Alice wants to stream 3000 DAI to Bob during the whole month of January. 1. Alice deposits the 3000 DAI in Lockup before Jan 1, setting the end time to Feb 1. 2. Bob's allocation of the DAI deposit increases every second beginning Jan 1. 3. On Jan 10, Bob will have earned approximately 1000 DAI. He can send a transaction to withdraw the tokens. 4. If at any point during January Alice wishes to get back her tokens, she can cancel the stream and recover what has not been streamed yet. ### How does Lockup calculate the payment rate? Dividing the deposit amount by the difference between the stop time and the start time gives us a payment rate per second. Lockup uses this rate to transfer a small portion of tokens from the sender to the recipient once every second. For instance, if the payment rate was 0.01 DAI per second, the recipient would receive: $0.01 * 60 = 0.6$ DAI / minute, $0.01 * 60 * 60 = 36$ DAI / hour, $0.01 * 60 * 60 * 24 = 864$ DAI / day ### How does streaming work on Sablier Flow? Imagine Alice wants to stream 3000 DAI on a monthly basis to Bob. 1. Alice creates a stream on Flow with a rate of 3000 DAI per month. 2. Alice deposits 200 DAI in Flow. 3. On Jan 10, Bob is owed 1000 DAI, but there is only 200 DAI in the stream. So he can only withdraw 200 DAI. 4. Alice deposits another 2800 DAI in Flow, and Bob can now withdraw the remaining 800 DAI. 5. On Feb 1, Bob is able to withdraw 2800 DAI. 6. The stream will continue indefinitely until it is paused (by Alice) or voided (by either Alice or Bob). ### How can I create a stream? You will need an EVM wallet (e.g. [Metamask](https://metamask.io), [Rainbow](https://rainbow.me), etc.), some ETH (or the network's token to pay gas fees) and an ERC-20 token like DAI. Then, choose your favorite interface for accessing the Sablier Protocol (such as [app.sablier.com](https://app.sablier.com)) and fill in the recipient's address, the deposit amount and the total duration. Alternatively, you can see [here](/guides/lockup/etherscan) how to manually create a stream using [Etherscan](https://etherscan.io). ### Can I create a stream with non-transferable tokens? Yes, it is possible to deposit non-transferable tokens in Sablier, but you need to whitelist the following two contracts: 1. `SablierLockup` 2. `SablierBatchLockup` You can get the addresses of the above contracts on the [Lockup Deployments](/guides/lockup/deployments) page. Pick the chain on which your token is deployed. A relevant diagram can be found [here](/reference/lockup/diagrams). ### Where are the tokens held? In the Sablier smart contracts. You can verify this assertion by inspecting Etherscan or any other blockchain explorer. ### How can recipients access their tokens? As the tokens are being streamed at the smart contract level, recipients can consider Sablier their real-time wallet for digital currency. To make withdrawals, recipients can: 1. Use a web interface (e.g. [app.sablier.com](https://app.sablier.com)). 2. Call the contract directly on a blockchain explorer. ### Can I cancel streams? Yes, both as a sender and a recipient. If the stream is canceled before the start time, the whole deposit amount is returned in full to you. If the stream is canceled while the stream is active, the smart contracts calculate how much has been streamed, transfer that to the recipient and return the remainder to you. If the stream is canceled after the stream has stopped, the smart contracts transfers all the remaining funds (if any) to the recipient. ### Can I modify the streaming rate? On Lockup, once a stream is created, it is set in stone on the Ethereum blockchain. Whereas on Flow, the streaming rate per second can be adjusted anytime by the sender. ### How are vested airdrops different from a batch of normal streams? Creating streams through the form (manual or CSV) will immediately start the vesting period. You click to create the transaction, pay for the gas, and you start all the streams for your recipients. Due to how block gas limits work, you can only fit a limited number of streams (usually 60) in a single block before running out of space. This is great for small distributions like paying your contractors or a set of investors. Meanwhile, vested airdrops leverage a "lazy creation" of streams. You deploy a contract that stores a list of recipients and the streams they are entitled to claim. Every recipient will manually claim their allocation by creating a stream. This allows for millions of recipients in your distribution campaign, with each recipient triggering the stream creation one after the other (as opposed to all at once). | Feature | Groups | Airdrops | | --- | --- | --- | | Maximum number of streams | ~280 (different limit per chain) | Millions | | Gas to create streams | Sender paying | Each recipient paying | | Gas to deploy Airdrop contract | N/A | Sender paying | | Streams show up onchain | Immediately | Lazily | | Good for | Investors, Employees | Large communities | ### What is real-time finance? A term coined by us to emphasize the wide-ranging use cases for the Sablier Protocol. We like to think about work as an attempt to rethink the way trust is established in financial contracts. --- ## Ethereum Source: https://docs.sablier.com/support/how-to # Ethereum ## Airdrops ### How to Create a Sablier Vested Airdrop Campaign [YouTube video player](https://www.youtube.com/watch?v=xDTLGGewjNU) ### How to create a Sablier Instant Airdrop Campaign [YouTube video player](https://www.youtube.com/watch?v=cgEFYDtL3RU) ### How to Claim a Sablier Vested Airdrop [YouTube video player](https://www.youtube.com/watch?v=vZTDZV8VaaA) ### How to Claim a Sablier Instant Airdrop [YouTube video player](https://www.youtube.com/watch?v=vTwqteJ9oAI) ## Vesting ### How to Withdraw From a Vesting Stream [YouTube video player](https://www.youtube.com/watch?v=XIcYied15BM) ### How to Create a Vesting Stream [YouTube video player](https://www.youtube.com/watch?v=0rgYaZhA0is) ### How to Batch-Cancel Streams [YouTube video player](https://www.youtube.com/watch?v=fRlUAMx2YFA) ### How to Create Vesting Streams Using the CSV Upload Feature [YouTube video player](https://www.youtube.com/watch?v=oqjx2muvcX4) ### How to Create a Vesting Stream Using a Safe Multisig Wallet [YouTube video player](https://www.youtube.com/watch?v=z94OHVk-BXU) ### How to Trade Vesting Streams on OpenSea [YouTube video player](https://www.youtube.com/watch?v=S_TTU3VyKOU) ### Distribution Shapes & Settings [YouTube video player](https://www.youtube.com/watch?v=37CZzlnOSRI) ## Payments ### How to Withdraw From Payment Streams [YouTube video player](https://www.youtube.com/watch?v=u8cGre6WW3E) ### How to Create Payment Streams [YouTube video player](https://www.youtube.com/watch?v=z8LFPedx8Kk) ### How to Pay Your Team Automatically with Sablier [YouTube video player](https://www.youtube.com/watch?v=ffqGPQz8oQY) ### How to Top Up a Sablier Payment Stream [YouTube video player](https://www.youtube.com/watch?v=lVSuWR-xC98) ### How to Manage Multiple Payment Streams [YouTube video player](https://www.youtube.com/watch?v=uzZH9UUjtZI) ## Others ### How to Create a zkSync Proposal Involving a Vesting Stream [YouTube video player](https://www.youtube.com/watch?v=3mWXLR8qtOs) ### Creating a Vesting Stream Using Fireblocks [YouTube video player](https://www.youtube.com/watch?v=40UZyctH5tY) ### Withdrawing Funds From Vesting Stream Using Fireblocks [YouTube video player](https://www.youtube.com/watch?v=t92yJmT3HkY) ### Launching and Vesting Tokens Made Easy with CreateMyToken and Sablier [YouTube video player](https://www.youtube.com/watch?v=izl9mlsXhpI) # Solana ### How to Claim a Solana-Based Sablier Airdrop [YouTube video player](https://www.youtube.com/watch?v=hKQ3eXdVk7k) ### How to Withdraw From Solana-Based Sablier Streams [YouTube video player](https://www.youtube.com/watch?v=xL4_yncbHYk) --- ## Technical Guides Source: https://docs.sablier.com/support/technical-guides # Technical Guides ## Guides for Apps Hands-on guides for app-specific features: - [CSV Support](/apps/guides/csv-support) (for streams and airdrops) - [URL Schemes](/apps/guides/url-schemes) ## Guides for Contracts - [Interactions with Etherscan](/guides/lockup/etherscan) - [Lockup Stream Management](/guides/lockup/examples/stream-management/withdraw) - And more... ## Guides for Indexers See the dedicated [API guides](/api/overview). - [Getting Started](/api/getting-started) - [Streams Indexer](/api/streams/indexers) ---