All articles
solanastreamflowvestingtoken-lockairdropsdkprograms

Streamflow: Solana programs for vesting, locks, and airdrops

Streamflow’s Solana programs power vesting, token locks, airdrop distributors, and more. How the protocol stack fits together, mainnet program IDs, and how to create streams and airdrops with the JS SDK.

Share
devrels.xyz/a/175short link

Streamflow: on-chain vesting, locks, and airdrops on Solana.

Every serious token launch eventually needs the same boring machinery: lock team supply, vest investors, drip ecosystem grants, airdrop to a huge list without melting the fee payer. Building that yourself is a multi-audit research project. Streamflow already ships it as Solana programs plus SDKs.

Product: app.streamflow.finance. Human docs: docs.streamflow.finance. Builder surface: js-sdk / typedoc. Agent-oriented overview: llms.txt.

What “Streamflow programs” means

Streamflow describes three layers (Architecture & Features):

  1. Protocol — interoperable Solana programs (the escrow / stream / distributor contracts)
  2. Infrastructure — JS/Rust SDKs, APIs, oracles
  3. Application the app and business suite

For builders, “programs” are the on-chain half: accounts that hold SPL tokens and release them under rules you set at creation time. The app is optional; most integrations call the SDK, which builds instructions against those programs.

Mainnet program IDs (from the JS SDK)

Constants live in packages/stream/solana/constants.ts:

text
Core Stream program (mainnet):
  strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m

Aligned unlocks (price-based):
  aSTRM2NKoKxNnkmLWk9sz3k74gKBk9t7bpPrTGxMszH

Partner / fee oracle program (mainnet):
  pardpVtPjC8nLj1Dwncew62mUzfChdCX1EaoZe8oCAa

Devnet core stream (SDK map):
  HqDGZjaVRXJ9MGRQEw7qDc2rAr6iH1n1kAQdCZaCMfMZ

Escrow PDAs use seeds such as strm / contract (see the same constants file). Always prefer the SDK over hand-rolling PDA derivation unless you are writing a CPI crate (rust-sdk).

Product map → program surfaces

Product intentOn-chain ideaSDK package
Vesting / continuous unlockStream with period + amountPerPeriod (+ cliff)@streamflow/stream
Token lock (cliff dump)Stream configured as almost-all cliff@streamflow/stream
Price-based unlockAligned stream + oracle@streamflow/stream + oracle API
Mass airdropDistributor + merkle claims@streamflow/distributor
Token staking poolsSeparate staking product / CLIApp docs + staking-cli

Lock vs vest in plain language (Token Lock, Vesting): a lock holds until a condition/date then releases; vesting releases over a schedule. Both are streams under the hood with different parameter shapes.

Install and client

bash
npm i @streamflow/stream
# airdrops:
npm i @streamflow/common @streamflow/distributor
typescript
import { StreamflowSolana, getBN, getNumberFromBN } from "@streamflow/stream";

const client = new StreamflowSolana.SolanaStreamClient(
  "https://api.mainnet-beta.solana.com",
);

// create / withdraw / cancel params always carry a signer-ish invoker
const solanaCreateParams = {
  sender: wallet, // SignerWalletAdapter | Keypair
  isNative: false, // true → wSOL path
};

Timestamps in the SDK are unix seconds. Amounts are BN in base units — use getBN(uiAmount, decimals).

Create a vesting stream

Example pattern from the official stream package README: 20% cliff, rest unlocked daily over two weeks.

typescript
import { getBN } from "@streamflow/stream";

const decimals = 9;
const totalAmount = getBN(1000, decimals);
const cliffAmount = getBN(200, decimals);
const day = 60 * 60 * 24;
const twoWeeks = day * 14;
const amountPerPeriod = totalAmount.sub(cliffAmount).divn(twoWeeks);
const start = Math.floor(Date.now() / 1000) + 60;

const createVestingParams = {
  recipient: "<RECIPIENT_PUBKEY>",
  tokenId: "<MINT>",
  start,
  amount: totalAmount,
  period: day,
  cliff: start,
  cliffAmount,
  amountPerPeriod,
  name: "Team grant — Alice",
  canTopup: false,
};

// signs + sends via client
const { ixs, tx, metadata } = await client.create(
  createVestingParams,
  solanaCreateParams,
);

// or instructions only (you build/sign the tx)
const prepared = await client.prepareCreateStreamInstructions(
  createVestingParams,
  { sender: wallet.publicKey },
);

Lifecycle ops on an existing stream id: withdraw, cancel, topup, transfer, update, plus getOne / get / search helpers — all documented in the stream README.

Create a token lock

App-labeled locks are streams where almost the entire deposit is cliff and permissions are locked down (no topup, no cancel, not transferable by sender, etc.). Sketch:

typescript
const totalAmount = getBN(1000, 9);
const cliffAmount = totalAmount.subn(1); // app lock heuristic
const unlockAt = Math.floor(Date.now() / 1000) + 90 * day;

const createTokenLockParams = {
  recipient: "<RECIPIENT>",
  tokenId: "<MINT>",
  start: unlockAt,
  amount: totalAmount,
  period: 1,
  cliff: unlockAt,
  cliffAmount,
  amountPerPeriod: new BN(1),
  name: "LP lock — Q3",
  canTopup: false,
  cancelableBySender: false,
  cancelableByRecipient: false,
  transferableBySender: false,
  transferableByRecipient: false,
};

await client.create(createTokenLockParams, solanaCreateParams);

LP locks and NFT locks are product variants on the same idea — see app guides for LP lock and Quicklock.

Price-based (aligned) streams

Unlock rate can track an oracle between minPrice/maxPrice and minPercentage/maxPercentage. Streamflow hosts oracles:

bash
# resolve oracle for a mint
curl -sS "https://oracle-api-public.streamflow.finance/oracle/<MINT>"
# OpenAPI-ish docs:
# https://oracle-api-public.streamflow.finance/docs

Creation uses ICreateAlignedStreamData with oracleType ("test" for Streamflow oracles, or Pyth) and priceOracle. Aligned txs need a higher compute budget (SDK sets ~300k). Details in the stream README section “Price-based Stream (Aligned)”.

Airdrops: Distributor program

Large recipient sets should not be N vesting streams. The Distributor path builds a merkle tree, one distributor account, and per-claimant claims. Recipients pay gas on claim; sender pays a more constant setup cost.

typescript
import { ICluster } from "@streamflow/common";
import { SolanaDistributorClient } from "@streamflow/distributor/solana";

const client = new SolanaDistributorClient({
  clusterUrl: "https://api.mainnet-beta.solana.com",
  cluster: ICluster.Mainnet,
});

// CSV columns (raw amounts, decimals already applied):
// pubkey,amount_unlocked,amount_locked,category

// Public API can build the merkle tree (API key from Streamflow):
// POST https://api-public.streamflow.finance/v2/api/airdrops/
// Header: x-api-key: <token>

// then client.prepareCreateInstructions / create with distributorParams:
// mint, unlockPeriod, startVestingTs, endVestingTs, clawbackStartTs, ...

Claim / clawback / search: distributor README. Public API reference: api-public.streamflow.finance/v2/docs. Operational CLIs also exist (bulk-streams-cli, batch cancel, etc.) when you are outside a web app.

Reading streams in a product

  • getOne(id) — single contract
  • get({ address }) — streams for a wallet
  • Search helpers with filters on mint / sender / recipient (SDK memcmp offsets are published in constants)
  • Unlocked amount helpers in the stream package — do not re-implement unlock math if the SDK already exposes it

Withdraw available amount can use the sentinel BN documented as WITHDRAW_AVAILABLE_AMOUNT in constants (max u64 magic number meaning “all unlocked”).

When to use the app vs the SDK

  • App — one-off locks, CSV bulk vesting, public proof links, token dashboard, non-engineer operators
  • SDK — wallets, launchpads, payroll bots, DAO tools, custom UIs, CPI from your program (Rust SDK)
  • Distributor API + SDK — airdrops at thousands–millions of rows

Security / ops notes

  • Streamflow publishes audit links from the js-sdk README (Notion audit index + partner oracle audit PDF). Read them before mainnet treasury size.
  • Many contracts are effectively immutable after create for lock-style configs — design cancel/transfer flags carefully up front.
  • Browser bundles need Node polyfills (node:crypto, etc.) — Vite/Webpack plugins are called out in the monorepo README.
  • Fees and Business vs Individual pricing: costs article.

Builder path this week

  1. Skim llms.txt and the architecture article.
  2. Install @streamflow/stream, point SolanaStreamClient at devnet, create a tiny vesting stream to yourself.
  3. Implement recipient UX: list streams → show unlocked → withdraw.
  4. If you need > a few hundred recipients, switch to Distributor + public airdrop API rather than N creates.
  5. For CPI from an Anchor program, open streamflow-sdk on docs.rs.

Resources

Bottom line

Streamflow’s Solana programs turn token operations into accounts and instructions: one stream model for vesting and locks, aligned streams when price should drive unlock rate, and a distributor when the recipient list is enormous. Start from @streamflow/stream, keep program IDs and amount math in the SDK’s world, and only drop to raw accounts when you are indexing or CPIing. The spreadsheet does not need to be the source of truth for unlocks anymore — the program is.

Keep reading

Get new articles in your inbox

Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.

Streamflow: Solana programs for vesting, locks, and airdrops | devrels.xyz