Price-Based Locks
Oracle-driven unlock conditions - tokens release when a price target is met.
A price-based lock releases all tokens when an oracle reports that the token price has reached a specified threshold (maxPrice). Instead of a time-based unlockDate, the protocol polls an on-chain oracle continuously.
Price-based locks require SolanaStreamClient directly. The createLock and createLockBatch composable wrappers only support time-based locks.
How it works
The protocol classifies a stream as a price-based lock when these fields satisfy the isDynamicLock() criteria: minPrice > 0, maxPrice - minPrice <= 1, minPercentage === 0, and maxPercentage === 100. You must set all of them explicitly - the SDK does not default any of them.
| Parameter | Required value | Purpose |
|---|---|---|
minPrice | maxPrice - 1 | Creates a step function - nothing releases below this |
maxPrice | target price | Full unlock triggers when oracle price reaches this |
minPercentage | 0 | 0% releases below minPrice |
maxPercentage | 100 | 100% releases at maxPrice |
oracleType | "pyth" or "switchboard" | Which oracle program to use |
priceOracle | oracle account public key | Price feed for the token pair |
When the oracle reports currentPrice >= maxPrice, the entire amount becomes claimable.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient | string | ✓ | Recipient wallet address |
tokenId | string | ✓ | Token mint address |
amount | BN | ✓ | Amount to lock |
maxPrice | number | ✓ | Price at which full unlock triggers (USD) |
oracleType | "pyth" | "switchboard" | ✓ | On-chain oracle program to use |
priceOracle | string | ✓ | Oracle account public key for the token's price feed |
name | string | ✓ | Display name |
start | number | ✓ | Unix timestamp when the lock begins |
cliff | number | - | Same as start for a lock |
partner | string | - | Partner address for custom fee rates |
tokenProgramId | string | - | For Token-2022 mints |
Code example
import { SolanaStreamClient, ICluster } from "@streamflow/stream";
import { Keypair } from "@solana/web3.js";
import BN from "bn.js";
import fs from "node:fs";
const client = new SolanaStreamClient({
clusterUrl: "https://api.mainnet-beta.solana.com",
cluster: ICluster.Mainnet,
});
const secret = JSON.parse(fs.readFileSync(process.env.KEYPAIR_PATH!, "utf-8"));
const invoker = Keypair.fromSecretKey(Uint8Array.from(secret));
const start = Math.floor(Date.now() / 1000);
const { txId, metadataId } = await client.create(
{
recipient: "RecipientWalletAddress...",
tokenId: "TokenMintAddress...",
amount: new BN(1_000_000_000), // 1000 tokens at 6 decimals
// Schedule - price-based lock uses a single synthetic period
start,
cliff: start,
period: 1,
cliffAmount: new BN(1_000_000_000).subn(1), // amount - 1
amountPerPeriod: new BN(1), // dust amount
// Lock flags - all hardcoded false for locks
canTopup: false,
cancelableBySender: false,
cancelableByRecipient: false,
transferableBySender: false,
transferableByRecipient: false,
automaticWithdrawal: false,
withdrawalFrequency: 0,
name: "Price Lock - Unlock at $5",
// Price oracle configuration - all four fields required for lock classification
maxPrice: 5, // unlock when oracle price >= $5
minPrice: 4, // must be maxPrice - 1
minPercentage: 0, // must be 0 for lock classification
maxPercentage: 100, // must be 100 for lock classification
oracleType: "pyth", // or "switchboard"
// Pyth SOL/USD feed on mainnet:
priceOracle: "H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG",
},
{
sender: invoker,
isNative: false,
},
);
console.log("Price lock stream ID:", metadataId);
console.log("Transaction:", txId);priceOracle must be the specific oracle feed account for your token pair, not the oracle program ID. For Pyth, find the correct feed address at pyth.network/price-feeds. For Switchboard, use app.switchboard.xyz.
Querying a price-based lock
After creation, use client.getOne() to read stream state. stream.isAligned === true indicates it's a price-based variant. Use the isAligned() type guard to access price-specific fields:
import { StreamType, isAligned } from "@streamflow/stream";
const stream = await client.getOne({ id: metadataId });
console.log("Is lock:", stream.type === StreamType.Lock); // true
console.log("Is price-based:", stream.isAligned); // true
if (isAligned(stream)) {
// AlignedStream - price fields are now available
console.log("Max price:", stream.maxPrice); // 5
console.log("Oracle type:", stream.oracleType); // "pyth"
console.log("Oracle:", stream.priceOracle);
}