Skip to content

Program Reference

Complete reference for every on-chain instruction: parameters, account inputs, validation rules, error codes, events, and working TypeScript examples.

Program ID (devnet): 6VkmhxbTH9dnzAE7Scpxn6R3HeXYtY4oZffAFMAYvECk

  1. Account Types
  2. PDA Seeds
  3. Instructions
  4. Error Reference
  5. Event Reference

Created on create_stream, closed on final withdraw or cancel.

FieldTypeDescription
creatorPubkeyWallet that funded the stream
recipientPubkeyWallet that receives vested tokens
mintPubkeySPL Token mint address
vaultPubkeyEscrow token account PDA
amountu64Total tokens locked
amount_withdrawnu64Tokens claimed so far
start_timei64Unix timestamp when vesting begins
end_timei64Unix timestamp when fully vested
cliff_timei64Unix timestamp of cliff (0 = no cliff)
vesting_countu64Nonce for PDA uniqueness
cancelledboolWhether stream was cancelled by creator
bumpu8Stream PDA bump seed
vault_bumpu8Vault PDA bump seed

Size: 187 bytes

Created on create_milestone_stream, closed on withdraw_milestone or cancel_milestone.

FieldTypeDescription
creatorPubkeyWallet that funded the stream
recipientPubkeyWallet that receives vested tokens
mintPubkeySPL Token mint address
vaultPubkeyEscrow token account PDA
amountu64Total tokens locked
amount_withdrawnu64Tokens claimed so far
milestone_authorityPubkeyWallet authorized to trigger milestone
milestone_reachedboolWhether milestone has been triggered
cancelledboolWhether stream was cancelled by creator
vesting_countu64Nonce for PDA uniqueness
bumpu8MilestoneStream PDA bump seed
vault_bumpu8Vault PDA bump seed

Size: 196 bytes

One per creator wallet, lazily created. Tracks the next sequential nonce.

FieldTypeDescription
creatorPubkeyCreator wallet address
vesting_countu64Next sequential nonce (starts at 0)

Size: 48 bytes

Custom PDA token account with the stream PDA as authority.

FieldTypeDescription
mintPubkeySPL Token mint
amountu64Tokens held in escrow
authorityPubkeyStream or MilestoneStream PDA

PDASeedsNotes
CreatorConfig["creator_config", creator]One per creator
StreamAccount["stream", creator, recipient, mint, vesting_count]Nonce from CreatorConfig
Vault["vault", stream.key()]Escrow token account
MilestoneStream["milestone-stream", creator, recipient, mint, vesting_count]Nonce from CreatorConfig
import { PublicKey } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import {
PROGRAM_ID,
getStreamPda,
getMilestoneStreamPda,
getVaultPda,
getCreatorConfigPda,
} from "@solana-tdp/sdk";
const creator = new PublicKey("...");
const recipient = new PublicKey("...");
const mint = new PublicKey("...");
const vestingCount = new BN(0);
const [streamPDA, bump] = getStreamPda(creator, recipient, mint, vestingCount, PROGRAM_ID);
const [vaultPDA, vaultBump] = getVaultPda(streamPDA, PROGRAM_ID);
const [configPDA] = getCreatorConfigPda(creator, PROGRAM_ID);
const [milestonePDA] = getMilestoneStreamPda(creator, recipient, mint, vestingCount, PROGRAM_ID);

Initialize a new time-based vesting stream. Tokens transfer from creator’s token account into a vault PDA.

Caller: Creator (must sign)

Parameters:

NameTypeDescription
amountu64Total tokens to lock in the stream
start_timei64Unix timestamp when vesting begins
end_timei64Unix timestamp when fully vested
cliff_timei64Unix timestamp of cliff (0 = no cliff)

Accounts:

NameWritableSignerDescription
senderyesyesCreator’s wallet
recipientnonoRecipient’s wallet
mintnonoSPL Token mint
creator_configyesnoCreatorConfig PDA (init_if_needed)
streamyesnoStreamAccount PDA (init)
vaultyesnoVault token account PDA (init)
sender_tokenyesnoCreator’s token account
token_programnonoToken program
system_programnonoSystem program
rentnonoRent sysvar

Validation:

ConditionError
amount == 0ZeroAmount
end_time <= start_timeInvalidTimeRange
cliff_time != 0 && (cliff_time <= start_time || cliff_time > end_time)InvalidCliffTime
end_time - start_time < 60DurationTooShort
Creator token balance < amountInsufficientBalance
start_time <= clock.unix_timestampStartTimeInPast
Mint owner is not an SPL Token mintUnsupportedTokenProgram
Mint has a transfer-hook extension (Token-2022 only)TokenHasTransferHook

Events: StreamCreated

Example:

import { BN, Program } from "@coral-xyz/anchor";
import { Transaction, Keypair } from "@solana/web3.js";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import {
getStreamPda,
getVaultPda,
getCreatorConfigPda,
getCreateStreamAccounts,
PROGRAM_ID,
} from "@solana-tdp/sdk";
import type { SolanaTdp } from "@solana-tdp/sdk";
async function createStream(
program: Program<SolanaTdp>,
creator: Keypair,
recipient: PublicKey,
mint: PublicKey,
amount: number,
startTime: number,
endTime: number,
cliffTime: number,
) {
const [streamPDA] = getStreamPda(creator.publicKey, recipient, mint, new BN(0), PROGRAM_ID);
const [vaultPDA] = getVaultPda(streamPDA, PROGRAM_ID);
const [configPDA] = getCreatorConfigPda(creator.publicKey, PROGRAM_ID);
const senderToken = getAssociatedTokenAddressSync(mint, creator.publicKey);
const accounts = getCreateStreamAccounts(
creator.publicKey,
recipient,
mint,
streamPDA,
vaultPDA,
senderToken,
configPDA,
);
const tx = await program.methods
.createStream({
amount: new BN(amount),
startTime: new BN(startTime),
endTime: new BN(endTime),
cliffTime: new BN(cliffTime),
})
.accountsPartial(accounts)
.transaction();
const txSig = await program.provider.sendAndConfirm(tx, [creator]);
return { txSig, streamPDA, vaultPDA };
}

Let the recipient claim a specific amount of vested tokens. Calculates claimable amount based on current clock time and the vesting curve.

Caller: Recipient (must sign)

Parameters:

NameTypeDescription
amountu64Amount to claim (must be > 0 and <= claimable)

Accounts:

NameWritableSignerDescription
recipientyesyesRecipient’s wallet
streamyesnoStreamAccount PDA
vaultyesnoVault token account PDA
recipient_tokenyesnoRecipient’s ATA (init_if_needed)
senderyesnoCreator’s wallet (rent return)
mintnonoSPL Token mint
token_programnonoToken program
associated_token_programnonoAssociated Token program
system_programnonoSystem program

Validation:

ConditionError
Stream is cancelledAlreadyCancelled
clock.unix_timestamp < cliff_timeCliffNotReached
amount == 0ZeroAmount
Calculated claimable == 0NothingToWithdraw
amount > claimableExceedsClaimable

Vesting formula:

if clock < cliff_time: claimable = 0
else:
elapsed = clock - start_time
duration = end_time - start_time
vested = min(amount * elapsed / duration, amount)
claimable = vested - amount_withdrawn

Events: TokensClaimed (always), StreamCompleted (on final withdrawal)

Example:

import { BN } from "@coral-xyz/anchor";
import { getWithdrawAccounts, getClaimable, fetchStream } from "@solana-tdp/sdk";
async function withdraw(
program: Program<SolanaTdp>,
recipient: Keypair,
creator: PublicKey,
streamPDA: PublicKey,
vaultPDA: PublicKey,
mint: PublicKey,
) {
const recipientToken = getAssociatedTokenAddressSync(mint, recipient.publicKey);
// Optional: query the stream to compute claimable
const stream = await fetchStream(program.provider.connection, streamPDA);
const clock = await program.provider.connection.getBlockTime(
await program.provider.connection.getSlot(),
);
const claimable = getClaimable(stream!.account, clock);
console.log(`Claimable: ${claimable.toString()}`);
// Withdraw the full claimable amount
const accounts = getWithdrawAccounts(
recipient.publicKey,
streamPDA,
vaultPDA,
recipientToken,
creator,
mint,
);
const tx = await program.methods
.withdraw({ amount: claimable })
.accountsPartial(accounts)
.transaction();
const txSig = await program.provider.sendAndConfirm(tx, [recipient]);
return txSig;
}

Let the creator cancel an active stream. Recipient receives vested (including unclaimed) tokens, creator receives the unvested portion. Both accounts are closed.

Caller: Creator (must sign)

Parameters: none

Accounts:

NameWritableSignerDescription
senderyesyesCreator’s wallet
recipientnonoRecipient’s wallet
streamyesnoStreamAccount PDA
vaultyesnoVault token account PDA
sender_tokenyesnoCreator’s token account
recipient_tokenyesnoRecipient’s ATA (init_if_needed)
mintnonoSPL Token mint
token_programnonoToken program
associated_token_programnonoAssociated Token program
system_programnonoSystem program

Validation:

ConditionError
Caller is not the creatorUnauthorized
Stream is already cancelledAlreadyCancelled
clock.unix_timestamp >= end_timeStreamExpired

Split logic:

vested = calculate_vested(clock)
recipient_share = vested - amount_withdrawn
creator_share = amount - vested

Creator pays for recipient’s ATA creation if it doesn’t exist.

Events: StreamCancelled

Example:

import { getCancelAccounts } from "@solana-tdp/sdk";
async function cancelStream(
program: Program<SolanaTdp>,
creator: Keypair,
recipient: PublicKey,
streamPDA: PublicKey,
vaultPDA: PublicKey,
mint: PublicKey,
) {
const senderToken = getAssociatedTokenAddressSync(mint, creator.publicKey);
const recipientToken = getAssociatedTokenAddressSync(mint, recipient);
const accounts = getCancelAccounts(
creator.publicKey,
recipient,
streamPDA,
vaultPDA,
senderToken,
recipientToken,
mint,
);
const tx = await program.methods.cancel().accountsPartial(accounts).transaction();
const txSig = await program.provider.sendAndConfirm(tx, [creator]);
return txSig;
}

Initialize a new milestone-gated vesting stream. No time parameters — withdrawal is gated by a milestone authority triggering release. Full amount is released at once.

Caller: Creator (must sign)

Parameters:

NameTypeDescription
amountu64Total tokens to lock in the stream

Accounts:

NameWritableSignerDescription
senderyesyesCreator’s wallet
recipientnonoRecipient’s wallet
milestone_authoritynonoAuthority that can trigger milestone
creator_configyesnoCreatorConfig PDA (init_if_needed)
streamyesnoMilestoneStreamAccount PDA (init)
vaultyesnoVault token account PDA (init)
sender_tokenyesnoCreator’s token account
mintnonoSPL Token mint
token_programnonoToken program
system_programnonoSystem program
rentnonoRent sysvar

Validation:

ConditionError
amount == 0ZeroAmount
Creator token balance < amountInsufficientBalance
Mint owner is not an SPL Token mintUnsupportedTokenProgram
Mint has a transfer-hook extension (Token-2022 only)TokenHasTransferHook

Events: MilestoneStreamCreated

Example:

import { BN } from "@coral-xyz/anchor";
import {
getMilestoneStreamPda,
getVaultPda,
getCreatorConfigPda,
getCreateMilestoneStreamAccounts,
PROGRAM_ID,
} from "@solana-tdp/sdk";
async function createMilestoneStream(
program: Program<SolanaTdp>,
creator: Keypair,
recipient: PublicKey,
milestoneAuthority: PublicKey,
mint: PublicKey,
amount: number,
) {
const [streamPDA] = getMilestoneStreamPda(
creator.publicKey,
recipient,
mint,
new BN(0),
PROGRAM_ID,
);
const [vaultPDA] = getVaultPda(streamPDA, PROGRAM_ID);
const [configPDA] = getCreatorConfigPda(creator.publicKey, PROGRAM_ID);
const senderToken = getAssociatedTokenAddressSync(mint, creator.publicKey);
const accounts = getCreateMilestoneStreamAccounts(
creator.publicKey,
recipient,
milestoneAuthority,
configPDA,
streamPDA,
vaultPDA,
senderToken,
mint,
);
const tx = await program.methods
.createMilestoneStream({ amount: new BN(amount) })
.accountsPartial(accounts)
.transaction();
const txSig = await program.provider.sendAndConfirm(tx, [creator]);
return { txSig, streamPDA, vaultPDA };
}

Let the milestone authority mark a milestone stream as reached. Once triggered, the recipient can withdraw all tokens. One-way operation — cannot be undone.

Caller: MilestoneAuthority (must sign)

Parameters: none

Accounts:

NameWritableSignerDescription
milestone_authoritynoyesThe designated milestone authority
streamyesnoMilestoneStreamAccount PDA

Validation:

ConditionError
Caller is not milestone_authorityUnauthorized
Status is CancelledAlreadyCancelled
milestone_reached == trueFullyVested

Events: MilestoneTriggered

Example:

import { getTriggerMilestoneAccounts } from "@solana-tdp/sdk";
async function triggerMilestone(
program: Program<SolanaTdp>,
milestoneAuthority: Keypair,
streamPDA: PublicKey,
) {
const accounts = getTriggerMilestoneAccounts(milestoneAuthority.publicKey, streamPDA);
const tx = await program.methods.triggerMilestone().accountsPartial(accounts).transaction();
const txSig = await program.provider.sendAndConfirm(tx, [milestoneAuthority]);
return txSig;
}

Let the recipient withdraw the full stream amount after the milestone has been triggered.

Caller: Recipient (must sign)

Parameters: none

Accounts:

NameWritableSignerDescription
recipientyesyesRecipient’s wallet
streamyesnoMilestoneStreamAccount PDA
vaultyesnoVault token account PDA
recipient_tokenyesnoRecipient’s ATA (init_if_needed)
senderyesnoCreator’s wallet (rent return)
mintnonoSPL Token mint
token_programnonoToken program
associated_token_programnonoAssociated Token program
system_programnonoSystem program

Validation:

ConditionError
Status is CancelledAlreadyCancelled
milestone_reached == falseNothingToWithdraw
amount_withdrawn > 0FullyVested

Events: MilestoneCompleted

Example:

import { getWithdrawMilestoneAccounts } from "@solana-tdp/sdk";
async function withdrawMilestone(
program: Program<SolanaTdp>,
recipient: Keypair,
creator: PublicKey,
streamPDA: PublicKey,
vaultPDA: PublicKey,
mint: PublicKey,
) {
const recipientToken = getAssociatedTokenAddressSync(mint, recipient.publicKey);
const accounts = getWithdrawMilestoneAccounts(
recipient.publicKey,
streamPDA,
vaultPDA,
recipientToken,
creator,
mint,
);
const tx = await program.methods.withdrawMilestone().accountsPartial(accounts).transaction();
const txSig = await program.provider.sendAndConfirm(tx, [recipient]);
return txSig;
}

Let the creator cancel a milestone stream before the milestone is triggered. Creator receives the full amount back.

Caller: Creator (must sign)

Parameters: none

Accounts:

NameWritableSignerDescription
senderyesyesCreator’s wallet
streamyesnoMilestoneStreamAccount PDA
vaultyesnoVault token account PDA
sender_tokenyesnoCreator’s token account
mintnonoSPL Token mint
token_programnonoToken program
associated_token_programnonoAssociated Token program
system_programnonoSystem program

Validation:

ConditionError
Caller is not the creatorUnauthorized
Status is CancelledAlreadyCancelled
milestone_reached == trueFullyVested

Events: MilestoneCancelled

Example:

import { getCancelMilestoneAccounts } from "@solana-tdp/sdk";
async function cancelMilestone(
program: Program<SolanaTdp>,
creator: Keypair,
streamPDA: PublicKey,
vaultPDA: PublicKey,
mint: PublicKey,
) {
const senderToken = getAssociatedTokenAddressSync(mint, creator.publicKey);
const accounts = getCancelMilestoneAccounts(
creator.publicKey,
streamPDA,
vaultPDA,
senderToken,
mint,
);
const tx = await program.methods.cancelMilestone().accountsPartial(accounts).transaction();
const txSig = await program.provider.sendAndConfirm(tx, [creator]);
return txSig;
}

#CodeNameMessage
06000ZeroAmountAmount must be greater than zero
16001InvalidTimeRangestart_time must be before end_time
26002InvalidCliffTimecliff_time must be between start_time and end_time
36003DurationTooShortStream duration must be at least 60 seconds
46004InsufficientBalanceSender does not have enough token balance
56005UnsupportedTokenProgramUnsupported token program. Only SPL Token is supported.
66006TokenHasTransferHookToken-2022 mint has transfer-hook extension
76007CliffNotReachedCliff time has not been reached yet
86008NothingToWithdrawNo tokens available to withdraw
96009AlreadyCancelledStream is already cancelled
106010FullyVestedStream is fully vested; no tokens remain to cancel.
116011StartTimeInPaststart_time must be in the future
126012StreamExpiredCancel after end_time — use withdraw instead
136013ExceedsClaimableRequested amount exceeds claimable tokens
146014UnauthorizedCaller is not authorized for this action
156015MilestoneAlreadyTriggeredMilestone has already been triggered
166016AlreadyWithdrawnTokens have already been withdrawn from this milestone.
176017ArithmeticOverflowArithmetic overflow in vesting calculation.

Events are the authoritative on-chain record. Since accounts are closed on completion/cancel, indexers should capture events to reconstruct stream history.

Parse events with the SDK:

import { parseEvents, findEvent } from "@solana-tdp/sdk";
const events = await parseEvents(program.provider, program, txSig);
const created = findEvent(events, "StreamCreated");
console.log(created.data); // { stream, creator, recipient, mint, amount, ... }
EventFieldsEmitted By
StreamCreatedstream, creator, recipient, mint, amount, start_time, cliff_time, end_timecreate_stream
TokensClaimedstream, recipient, amount, claimed, total_claimedwithdraw
StreamCompletedstream, recipient, total_amountwithdraw (final)
StreamCancelledstream, creator, recipient, vested_to_recipient, returned_to_creatorcancel
MilestoneStreamCreatedstream, creator, recipient, mint, amount, milestone_authoritycreate_milestone_stream
MilestoneTriggeredstream, milestone_authoritytrigger_milestone
MilestoneCompletedstream, recipient, amountwithdraw_milestone
MilestoneCancelledstream, creator, recipient, returned_to_creatorcancel_milestone