Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Sablier Protocol
https://github.com/sablier-labs/docs
Admin
Documentation for Sablier's token streaming, vesting, and airdrop protocols on Ethereum and Solana.
Tokens:
793,700
Snippets:
3,943
Trust Score:
9.6
Update:
6 months ago
Context
Skills
Chat
Benchmark
64
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Sablier Protocol Documentation Sablier is a powerful onchain token distribution protocol consisting of persistent, non-upgradeable smart contracts that enable token streaming on Ethereum and other EVM blockchains, as well as Solana. The protocol facilitates continuous, real-time payments on a per-second basis, eliminating the trust requirements and processing delays inherent in traditional lump-sum payment systems. This innovative approach transforms time itself into the trust-binding mechanism, enabling seamless vesting schedules, payroll automation, airdrops, and subscription-based payments. The Sablier ecosystem comprises three main protocol components: **Lockup** (for vesting and vested airdrops), **Merkle Airdrops** (for on-chain token distribution campaigns with a REST-based Merkle API service), and **Flow** (for continuous streaming payments). The protocol prioritizes security, censorship resistance, self-custody, and functionality without trusted intermediaries. Deployed contracts guarantee 100% uptime as long as the underlying blockchain exists, maintaining their immutable, non-upgradeable design that prevents any party from pausing contracts, reversing transactions, or altering user streams. GraphQL indexers from The Graph (per-chain) and Envio (multi-chain, single endpoint) provide unified data access across contract versions. On Solana, the SolSab program collection provides Lockup Linear streams and Merkle Instant airdrops, adapted to Solana's account-based architecture and SPL/Token2022 token standards. ## API Endpoints and Indexers ### Query Lockup Streams via GraphQL (The Graph) Retrieve stream data from Sablier Lockup contracts using The Graph's subgraph indexer. ```bash # Query all active streams for a specific recipient curl -X POST https://api.thegraph.com/subgraphs/name/sablier-labs/sablier-v2 \ -H "Content-Type: application/json" \ -d '{ "query": "{ streams(where: { recipient: \"0x1234...\", status: STREAMING }, first: 10) { id tokenId asset { id symbol decimals } sender { id } recipient { id } depositAmount withdrawnAmount startTime endTime cliffTime cancelable renounced } }" }' # Expected response: # { # "data": { # "streams": [ # { # "id": "1-420-123", # "tokenId": "123", # "asset": { # "id": "0x...", # "symbol": "DAI", # "decimals": "18" # }, # "sender": { "id": "0xabc..." }, # "recipient": { "id": "0x1234..." }, # "depositAmount": "1000000000000000000000", # "withdrawnAmount": "250000000000000000000", # "startTime": "1704067200", # "endTime": "1735689600", # "cliffTime": "0", # "cancelable": true, # "renounced": false # } # ] # } # } ``` ### Query Flow Streams via GraphQL (Envio) Access Flow protocol data through Envio's multi-chain GraphQL indexer. Unlike The Graph (which requires separate endpoints per chain), Envio provides a single endpoint that aggregates data across all chains where Sablier is deployed. ```bash # Query active Flow streams with pagination curl -X POST https://indexer.bigdevenergy.link/[chain-id]/v1/graphql \ -H "Content-Type: application/json" \ -d '{ "query": "query GetFlowStreams { Stream(where: { recipient: { _eq: \"0x5678...\" }, voided: { _eq: false } }, limit: 20, order_by: { streamId: desc }) { streamId sender recipient tokenAddress ratePerSecond snapshotTime snapshotDebt depositedAmount voided transferable } }" }' # Expected response: # { # "data": { # "Stream": [ # { # "streamId": "456", # "sender": "0xdef...", # "recipient": "0x5678...", # "tokenAddress": "0x...", # "ratePerSecond": "11574074074074", # "snapshotTime": "1704153600", # "snapshotDebt": "0", # "depositedAmount": "30000000000000000000", # "voided": false, # "transferable": true # } # ] # } # } ``` ### Query Airdrop Campaigns via GraphQL Retrieve Merkle Airdrop campaign data including eligibility and claim status. ```bash # Query campaign details and user claims curl -X POST https://api.thegraph.com/subgraphs/name/sablier-labs/sablier-merkle \ -H "Content-Type: application/json" \ -d '{ "query": "{ campaign(id: \"1-0xABC...\") { id campaignAddress name ipfsCID merkleRoot token { id symbol decimals } admin aggregateAmount clawbackTime expiration recipientCount claimedCount claimedAmount claims(first: 100) { id index recipient claimTime amount } } }" }' # Expected response: # { # "data": { # "campaign": { # "id": "1-0xABC...", # "campaignAddress": "0xABC...", # "name": "Token Airdrop Q1 2025", # "ipfsCID": "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX", # "merkleRoot": "0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123", # "token": { # "id": "0x...", # "symbol": "DAI", # "decimals": "18" # }, # "admin": "0xBeeF...", # "aggregateAmount": "100000000000000000000000000", # "expiration": "1711929600", # "recipientCount": "10000", # "claimedCount": "3472", # "claimedAmount": "34720000000000000000000000", # "claims": [ # { # "id": "1-0xABC...-0", # "index": "0", # "recipient": "0x111...", # "claimTime": "1704240000", # "amount": "10000000000000000000" # } # ] # } # } # } ``` ### Merkle API for Airdrop Campaigns The Merkle API is a REST service that handles Merkle tree generation, validation, and proof retrieval for airdrop campaigns. ```bash # Create a new Merkle tree for an airdrop campaign curl -X POST https://api.sablier.com/v1/merkle/create \ -H "Content-Type: application/json" \ -d '{ "recipients": [ { "address": "0x1234...", "amount": "1000000000000000000" }, { "address": "0x5678...", "amount": "2000000000000000000" } ], "token": "0x6B175474E89094C44Da98b954EedeAC495271d0F", "chainId": 1 }' # Expected response: # { # "merkleRoot": "0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123", # "ipfsCID": "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX", # "treeUrl": "https://ipfs.io/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX" # } # Get eligibility and Merkle proof for a recipient curl -X GET "https://api.sablier.com/v1/merkle/eligibility?campaignId=0xABC...&address=0x1234..." # Expected response: # { # "eligible": true, # "index": 0, # "amount": "1000000000000000000", # "proof": [ # "0xabcd...", # "0xef01...", # "0x2345..." # ] # } ``` ## Smart Contract Integration ### Create Lockup Linear Stream Deploy a stream with linear vesting over a defined period. ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud60x18 } from "@prb/math/src/UD60x18.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { Broker, Lockup, LockupLinear } from "@sablier/lockup/src/types/DataTypes.sol"; contract LockupLinearStreamCreator { IERC20 public constant DAI = IERC20(0x68194a729C2450ad26072b3D33ADaCbcef39D574); ISablierLockup public constant LOCKUP = ISablierLockup(0xC2Da366fD67423b500cDF4712BdB41d0995b0794); function createStream(uint128 totalAmount) public returns (uint256 streamId) { // Approve the Lockup contract to spend DAI DAI.approve(address(LOCKUP), totalAmount); // Define stream parameters Lockup.CreateWithDurations memory params = Lockup.CreateWithDurations({ sender: msg.sender, recipient: address(0xCAFE), totalAmount: totalAmount, asset: DAI, cancelable: true, transferable: true, broker: Broker({ account: address(0), fee: ud60x18(0) }) }); // Define durations: 52 weeks total, no cliff LockupLinear.Durations memory durations = LockupLinear.Durations({ cliff: 0, total: 52 weeks }); // Define unlock amounts: no immediate unlock LockupLinear.UnlockAmounts memory unlockAmounts = LockupLinear.UnlockAmounts({ start: 0, cliff: 0 }); // Create the stream streamId = LOCKUP.createWithDurationsLL(params, unlockAmounts, durations); } } // Usage: // 1. Deploy contract // 2. Call createStream(1000e18) to create a 1000 DAI stream over 52 weeks // 3. Returns streamId which can be used to track/manage the stream ``` ### Create Flow Stream Set up continuous streaming with no end date for payroll or subscriptions. ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UD21x18, ud21x18 } from "@prb/math/src/UD21x18.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; contract FlowStreamCreator { IERC20 public constant USDC = IERC20(0xf08A50178dfcDe18524640EA6618a1f965821715); ISablierFlow public constant FLOW = ISablierFlow(0x52ab22e769E31564E17D524B683264B65662A014); // Helper: Calculate rate per second for specific amount over duration function calculateRatePerSecond( uint256 amount, uint256 duration ) internal pure returns (UD21x18) { // For 1000 USDC per month: amount = 1000e6, duration = 30 days return ud21x18(amount / duration); } // Create stream for 1000 USDC per month function createStream_1K_PerMonth() external returns (uint256 streamId) { // Approve FLOW contract to spend USDC USDC.approve(address(FLOW), type(uint256).max); // Calculate rate: 1000 USDC over 30 days UD21x18 ratePerSecond = calculateRatePerSecond(1000e6, 30 days); // Create the stream streamId = FLOW.create({ sender: msg.sender, recipient: address(0xCAFE), ratePerSecond: ratePerSecond, token: USDC, transferable: true }); // Deposit initial funds (e.g., 3 months worth) FLOW.deposit(streamId, 3000e6, msg.sender, address(0xCAFE)); } // Stream management: pause/restart function pauseStream(uint256 streamId) external { FLOW.pause(streamId); } function restartStream(uint256 streamId) external { FLOW.restart(streamId, calculateRatePerSecond(1000e6, 30 days)); } // Withdraw accumulated tokens function withdrawFromStream(uint256 streamId, uint128 amount) external { FLOW.withdraw(streamId, address(0xCAFE), amount); } } // Usage: // 1. Deploy contract // 2. Call createStream_1K_PerMonth() - creates unlimited stream at 1000 USDC/month rate // 3. Stream continues indefinitely, accumulating debt if sender balance runs low // 4. Sender can deposit more funds or pause/adjust stream as needed ``` ### Create Merkle Airdrop Campaign Deploy a Merkle-based airdrop with vested token distribution. ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ud2x18 } from "@prb/math/src/UD2x18.sol"; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { ISablierMerkleLL } from "@sablier/airdrops/src/interfaces/ISablierMerkleLL.sol"; import { ISablierMerkleFactory } from "@sablier/airdrops/src/interfaces/ISablierMerkleFactory.sol"; import { MerkleBase, MerkleLL } from "@sablier/airdrops/src/types/DataTypes.sol"; contract MerkleAirdropCreator { IERC20 public constant DAI = IERC20(0x68194a729C2450ad26072b3D33ADaCbcef39D574); ISablierMerkleFactory public constant FACTORY = ISablierMerkleFactory(0x4ECd5A688b0365e61c1a764E8BF96A7C5dF5d35F); ISablierLockup public constant LOCKUP = ISablierLockup(0xC2Da366fD67423b500cDF4712BdB41d0995b0794); function createMerkleLL() public returns (ISablierMerkleLL merkleLL) { // Approve factory to spend DAI for aggregate amount uint256 aggregateAmount = 100_000_000e18; // 100M DAI DAI.approve(address(FACTORY), aggregateAmount); // Configure base parameters MerkleBase.ConstructorParams memory baseParams = MerkleBase.ConstructorParams({ asset: DAI, cancelable: false, expiration: uint40(block.timestamp + 12 weeks), initialAdmin: address(0xBeeF), ipfsCID: "QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX", merkleRoot: bytes32(0x4e07408562bedb8b60ce05c1decfe3ad16b722309875f562c03d02d7aaacb123), name: "My First Campaign", transferable: true }); // Configure vesting schedule // 0.01% unlocked at start, 0.01% after 30 day cliff, linear vest over 90 days MerkleLL.Schedule memory schedule = MerkleLL.Schedule({ startTime: 0, // Vesting starts at claim time startPercentage: ud2x18(0.01e18), // 0.01% unlocked immediately cliffDuration: 30 days, cliffPercentage: ud2x18(0.01e18), // 0.01% after cliff totalDuration: 90 days }); // Create the campaign merkleLL = FACTORY.createMerkleLL({ baseParams: baseParams, lockup: LOCKUP, cancelable: false, transferable: true, schedule: schedule, aggregateAmount: aggregateAmount, recipientCount: 10_000 }); } // Recipients claim their airdrop by calling the campaign contract function claimExample( ISablierMerkleLL campaign, uint256 index, address recipient, uint128 amount, bytes32[] calldata merkleProof ) external returns (uint256 streamId) { streamId = campaign.claim(index, recipient, amount, merkleProof); // Returns the Lockup stream ID created for the recipient } } // Workflow: // 1. Generate Merkle tree with recipient addresses and amounts // 2. Store tree data on IPFS, get CID // 3. Deploy campaign using createMerkleLL() // 4. Recipients call claim() with their proof to receive vested tokens // 5. After expiration + grace period, admin can clawback unclaimed tokens ``` ### Stream Management Functions Manage existing Lockup streams with withdrawal, cancellation, and transfer operations. ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; contract StreamManager { ISablierLockup public immutable lockup; constructor(address lockupContract) { lockup = ISablierLockup(lockupContract); } // Withdraw vested tokens from stream function withdrawFromStream(uint256 streamId, uint128 amount) external { lockup.withdraw(streamId, msg.sender, amount); } // Withdraw maximum available amount function withdrawMaxFromStream(uint256 streamId) external returns (uint128 withdrawn) { withdrawn = lockup.withdrawMax(streamId, msg.sender); } // Cancel stream and retrieve remaining tokens (sender only) function cancelStream(uint256 streamId) external { lockup.cancel(streamId); } // Renounce cancelability (sender only, irreversible) function renounceStream(uint256 streamId) external { lockup.renounce(streamId); } // Transfer stream NFT to new recipient function transferStream(uint256 streamId, address newRecipient) external { lockup.safeTransferFrom(msg.sender, newRecipient, streamId); } // Check withdrawable amount function checkWithdrawable(uint256 streamId) external view returns (uint128) { return lockup.withdrawableAmountOf(streamId); } // Get stream status function getStreamStatus(uint256 streamId) external view returns (string memory) { // Returns: "PENDING", "STREAMING", "SETTLED", "CANCELED", "DEPLETED" return lockup.statusOf(streamId); } // Batch withdraw from multiple streams function batchWithdraw( uint256[] calldata streamIds, uint128[] calldata amounts ) external { require(streamIds.length == amounts.length, "Length mismatch"); for (uint256 i = 0; i < streamIds.length; i++) { lockup.withdraw(streamIds[i], msg.sender, amounts[i]); } } } // Usage examples: // withdrawFromStream(123, 1000e18) - withdraw 1000 tokens from stream 123 // cancelStream(456) - cancel stream 456 (sender only, refunds unvested) // transferStream(789, 0xNEW...) - transfer stream NFT to new recipient // checkWithdrawable(123) - returns amount available for withdrawal ``` ### Direct Contract Queries Query stream data directly from smart contracts without indexers. ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; import { ISablierLockup } from "@sablier/lockup/src/interfaces/ISablierLockup.sol"; import { ISablierFlow } from "@sablier/flow/src/interfaces/ISablierFlow.sol"; contract StreamDataReader { ISablierLockup public immutable lockup; ISablierFlow public immutable flow; constructor(address lockupContract, address flowContract) { lockup = ISablierLockup(lockupContract); flow = ISablierFlow(flowContract); } // Lockup stream queries function getLockupStreamInfo(uint256 streamId) external view returns ( address sender, address recipient, uint128 depositAmount, uint128 withdrawnAmount, uint40 startTime, uint40 endTime, bool cancelable, bool renounced ) { sender = lockup.getSender(streamId); recipient = lockup.getRecipient(streamId); depositAmount = lockup.getDepositedAmount(streamId); withdrawnAmount = lockup.getWithdrawnAmount(streamId); startTime = lockup.getStartTime(streamId); endTime = lockup.getEndTime(streamId); (cancelable, renounced) = lockup.getCancelableRenounced(streamId); } // Flow stream queries function getFlowStreamInfo(uint256 streamId) external view returns ( address sender, address recipient, uint128 ratePerSecond, uint128 balance, uint128 totalDebt, bool isPaused, bool isVoided ) { sender = flow.getSender(streamId); recipient = flow.getRecipient(streamId); ratePerSecond = flow.getRatePerSecond(streamId); balance = flow.getBalance(streamId); totalDebt = flow.totalDebtOf(streamId); isPaused = flow.isPaused(streamId); isVoided = flow.isVoided(streamId); } // Calculate streamed amount at current time function getStreamedAmount(uint256 streamId) external view returns (uint128) { return lockup.streamedAmountOf(streamId); } // Get refundable amount for sender function getRefundableAmount(uint256 streamId) external view returns (uint128) { return lockup.refundableAmountOf(streamId); } } // Usage: // Deploy with Lockup and Flow contract addresses // Call getLockupStreamInfo(123) to get complete stream data // Call getStreamedAmount(123) to calculate current vested amount // Call getFlowStreamInfo(456) to query Flow stream details ``` ## Frontend Integration ### React + GraphQL Code Generator Setup Configure a modern frontend stack with type-safe GraphQL queries. For a pre-configured starter project, check out the [Sablier Frontend Sandbox](https://github.com/sablier-labs/sandbox). ```typescript // Install dependencies // npm install @tanstack/react-query graphql graphql-request // npm install -D @graphql-codegen/cli @graphql-codegen/typescript // codegen.yml - GraphQL Code Generator configuration import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: 'https://api.thegraph.com/subgraphs/name/sablier-labs/sablier-v2', documents: ['src/**/*.graphql'], generates: { './src/graphql/generated.ts': { plugins: ['typescript', 'typescript-operations', 'typescript-react-query'], config: { fetcher: 'graphql-request', }, }, }, }; export default config; // src/queries/streams.graphql - Define queries // query GetUserStreams($recipient: String!, $first: Int!) { // streams(where: { recipient: $recipient, status: STREAMING }, first: $first) { // id // tokenId // asset { symbol decimals } // sender { id } // depositAmount // withdrawnAmount // startTime // endTime // } // } // src/hooks/useUserStreams.ts - Type-safe React hook import { useQuery } from '@tanstack/react-query'; import { GraphQLClient } from 'graphql-request'; import { GetUserStreamsQuery, GetUserStreamsQueryVariables } from '../graphql/generated'; const client = new GraphQLClient('https://api.thegraph.com/subgraphs/name/sablier-labs/sablier-v2'); export function useUserStreams(recipient: string, first: number = 10) { return useQuery({ queryKey: ['streams', recipient, first], queryFn: async () => { const query = ` query GetUserStreams($recipient: String!, $first: Int!) { streams(where: { recipient: $recipient, status: STREAMING }, first: $first) { id tokenId asset { symbol decimals } sender { id } depositAmount withdrawnAmount startTime endTime } } `; return client.request<GetUserStreamsQuery>(query, { recipient, first }); }, enabled: !!recipient, }); } // src/components/StreamList.tsx - Component usage import { useUserStreams } from '../hooks/useUserStreams'; import { useAccount } from 'wagmi'; export function StreamList() { const { address } = useAccount(); const { data, isLoading, error } = useUserStreams(address || ''); if (isLoading) return <div>Loading streams...</div>; if (error) return <div>Error loading streams: {error.message}</div>; if (!data?.streams.length) return <div>No active streams</div>; return ( <div> {data.streams.map(stream => ( <div key={stream.id}> <h3>Stream #{stream.tokenId}</h3> <p>Asset: {stream.asset.symbol}</p> <p>Deposited: {stream.depositAmount} / {stream.asset.decimals}</p> <p>Withdrawn: {stream.withdrawnAmount}</p> <p>Sender: {stream.sender.id}</p> </div> ))} </div> ); } ``` ### Viem Contract Integration Interact with Sablier contracts using viem for type-safe contract calls. ```typescript // npm install viem @sablier/lockup @sablier/flow import { createPublicClient, createWalletClient, http, parseEther } from 'viem'; import { mainnet } from 'viem/chains'; import { privateKeyToAccount } from 'viem/accounts'; // Import ABIs import { ISablierLockup } from '@sablier/lockup/artifacts'; import { IERC20 } from '@openzeppelin/contracts/build/contracts/IERC20.json'; const LOCKUP_ADDRESS = '0xC2Da366fD67423b500cDF4712BdB41d0995b0794'; const DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; // Setup clients const publicClient = createPublicClient({ chain: mainnet, transport: http(), }); const account = privateKeyToAccount('0x...'); const walletClient = createWalletClient({ account, chain: mainnet, transport: http(), }); // Create a Lockup Linear stream async function createLockupStream(recipientAddress: string, amount: bigint) { // 1. Approve DAI spending const approvalHash = await walletClient.writeContract({ address: DAI_ADDRESS, abi: IERC20.abi, functionName: 'approve', args: [LOCKUP_ADDRESS, amount], }); await publicClient.waitForTransactionReceipt({ hash: approvalHash }); // 2. Create stream parameters const params = { sender: account.address, recipient: recipientAddress, totalAmount: amount, asset: DAI_ADDRESS, cancelable: true, transferable: true, broker: { account: '0x0000000000000000000000000000000000000000', fee: 0n }, }; const durations = { cliff: 0, total: 31536000, // 365 days in seconds }; const unlockAmounts = { start: 0n, cliff: 0n, }; // 3. Create the stream const { request } = await publicClient.simulateContract({ address: LOCKUP_ADDRESS, abi: ISablierLockup.abi, functionName: 'createWithDurationsLL', args: [params, unlockAmounts, durations], account: account.address, }); const hash = await walletClient.writeContract(request); const receipt = await publicClient.waitForTransactionReceipt({ hash }); // Extract streamId from logs const streamId = receipt.logs[0].topics[1]; // Simplified extraction return { streamId, transactionHash: hash }; } // Withdraw from stream async function withdrawFromStream(streamId: bigint, amount: bigint) { const hash = await walletClient.writeContract({ address: LOCKUP_ADDRESS, abi: ISablierLockup.abi, functionName: 'withdraw', args: [streamId, account.address, amount], }); return publicClient.waitForTransactionReceipt({ hash }); } // Query withdrawable amount async function getWithdrawableAmount(streamId: bigint): Promise<bigint> { return publicClient.readContract({ address: LOCKUP_ADDRESS, abi: ISablierLockup.abi, functionName: 'withdrawableAmountOf', args: [streamId], }); } // Usage example: // const result = await createLockupStream('0xRecipient...', parseEther('1000')); // console.log('Created stream:', result.streamId); // const withdrawable = await getWithdrawableAmount(result.streamId); // await withdrawFromStream(result.streamId, withdrawable); ``` ## Integration Patterns and Use Cases The Sablier Protocol serves diverse token distribution use cases through its three core systems. **Lockup** is ideal for employee vesting schedules, investor token vesting, grant distributions, and vested airdrops where tokens unlock gradually over time according to predefined curves (linear, dynamic with multiple segments, or tranched). Organizations can create streams with cliff periods, cancelability options, and transferable NFTs representing stream ownership. **Flow** enables continuous payment streams without end dates, perfect for payroll automation, subscription services, real-time grants, and ongoing contractor payments. Unlike Lockup's fixed schedules, Flow streams can be paused, adjusted, and topped up as needed, accumulating debt if underfunded. **Merkle Airdrops** facilitate gas-efficient token distributions to thousands of recipients through Merkle tree proofs, supporting both instant claims and vested distributions that create Lockup streams upon claiming. The Merkle API service provides REST endpoints for tree generation, eligibility checks, and proof retrieval, available as an open-source Rust backend that integrators can self-host or access through Sablier's hosted service. Integration approaches vary by use case. Most applications should use the GraphQL indexers for comprehensive data access: **Envio** offers a single multi-chain endpoint aggregating all chains, while **The Graph** provides per-chain subgraphs with proven reliability since 2019. Both indexers enable pagination, historical queries, multi-stream management, and unified APIs across contract versions with caching for optimal performance. Direct smart contract interaction is recommended for decentralized applications prioritizing censorship resistance, though it limits access to current stream state without historical data or recipient stream lists. The recommended frontend stack combines React + GraphQL Code Generator + TanStack Query for type-safe queries with automatic code generation (see the [sablier-labs/sandbox](https://github.com/sablier-labs/sandbox) repository for a pre-configured starter). Smart contracts integrate via Solidity imports from NPM packages (`@sablier/lockup`, `@sablier/flow`, `@sablier/airdrops`), enabling programmatic stream creation, batch operations, and hook implementation for recipient contracts. All Sablier contracts are non-upgradeable and maintain 100% uptime guarantees, making them reliable primitives for building trustless financial applications on EVM chains and Solana (via the SolSab programs). For AI-assisted development and LLM integrations, Sablier provides protocol-specific documentation files at https://docs.sablier.com/llms-lockup.txt, https://docs.sablier.com/llms-flow.txt, https://docs.sablier.com/llms-airdrops.txt, and https://docs.sablier.com/llms-full.txt. These plain text markdown resources enable efficient consumption by language models and AI agents. Any documentation page can be accessed in plain text format by appending `.md` to the URL (e.g., https://docs.sablier.com/guides/building-with-llms.md), and the [/llms.txt file](https://docs.sablier.com/llms.txt) follows the emerging standard for LLM-accessible content discovery. This makes Sablier documentation highly accessible for AI-powered development tools, chatbots, and automated integration builders.