Lifecycle Operations
Withdraw, topup, transfer, update, and cancel - params, constraints, and return types for both APIs.
After a stream is created, five operations are available. Each returns InstructionResult: { instructions }. Unlike creation, there is no metadata keypair - sign with just the invoker.
Operations overview
| Operation | Gated by | Who calls | Params |
|---|---|---|---|
withdraw | Always available | Recipient | { id, amount? } |
topup | canTopup: true | Sender | { id, amount } |
transfer | transferableBySender: true | Sender | { id, newRecipient } |
cancel | cancelableBySender: true | Sender | { id } |
update | canUpdateRate: true | Sender | { id, ... } |
withdraw
Withdraw tokens that have vested. Omit amount to withdraw everything available.
import { withdraw, buildTransaction, sign, execute } from "@streamflow/stream/solana/api";
const result = await withdraw({ id: streamId }, recipientInvoker, env);
const built = await buildTransaction(result.instructions, { feePayer: recipientInvoker.publicKey }, env);
await sign(built.transaction, [recipientInvoker]);
await execute(built, env);const { txId } = await client.withdraw({ id: streamId }, { invoker: recipientKeypair });For locks: only works after unlockDate has passed.
topup
Add tokens to extend the stream's duration. Requires canTopup: true at creation.
import { topup } from "@streamflow/stream/solana/api";
const result = await topup(
{ id: streamId, amount: new BN(1_000_000) },
senderInvoker,
{ ...env, isNative: false }, // isNative must match the token used at creation
);
const built = await buildTransaction(result.instructions, { feePayer: senderInvoker.publicKey }, env);
await sign(built.transaction, [senderInvoker]);
await execute(built, env);const { txId } = await client.topup(
{ id: streamId, amount: new BN(1_000_000) },
{ invoker: senderKeypair, isNative: false },
);transfer
Change the recipient wallet. Requires transferableBySender: true at creation.
transfer() embeds its own ComputeBudgetProgram instruction. Do not pass computeLimit to buildTransaction - it would duplicate the compute budget instruction.
import { transfer } from "@streamflow/stream/solana/api";
const result = await transfer({ id: streamId, newRecipient: "NewWalletAddress..." }, senderInvoker, env);
// Note: no computeLimit in buildTransaction options
const built = await buildTransaction(result.instructions, { feePayer: senderInvoker.publicKey }, env);
await sign(built.transaction, [senderInvoker]);
await execute(built, env);const { txId } = await client.transfer(
{ id: streamId, newRecipient: "NewWalletAddress..." },
{ invoker: senderKeypair },
);cancel
Terminate the stream early. Returns unvested tokens to the sender. Requires cancelableBySender: true at creation.
import { cancel } from "@streamflow/stream/solana/api";
const result = await cancel({ id: streamId }, senderInvoker, env);
const built = await buildTransaction(result.instructions, { feePayer: senderInvoker.publicKey }, env);
await sign(built.transaction, [senderInvoker]);
await execute(built, env);const { txId } = await client.cancel({ id: streamId }, { invoker: senderKeypair });Cancel must be the last operation - all other operations fail after a stream is cancelled.
update
Change the stream's release rate or permission flags. Requires canUpdateRate: true on the stream.
canUpdateRate is not exposed in ICreateVestingParams. Streams created via createVesting don't support update unless you use SolanaStreamClient.create() directly with canUpdateRate: true in the raw params.
import { update } from "@streamflow/stream/solana/api";
const result = await update(
{ id: streamId, amountPerPeriod: new BN(200_000) },
senderInvoker,
env,
);
const built = await buildTransaction(result.instructions, { feePayer: senderInvoker.publicKey }, env);
await sign(built.transaction, [senderInvoker]);
await execute(built, env);const { txId } = await client.update(
{
id: streamId,
amountPerPeriod: new BN(200_000),
// other updatable fields:
// transferableBySender, transferableByRecipient, cancelableBySender
// enableAutomaticWithdrawal, withdrawFrequency
},
{ invoker: senderKeypair },
);Querying stream state
Use SolanaStreamClient to read current stream data:
import { SolanaStreamClient, ICluster, StreamDirection, StreamType } from "@streamflow/stream";
const client = new SolanaStreamClient({
clusterUrl: "https://api.mainnet-beta.solana.com",
cluster: ICluster.Mainnet,
});
// Fetch a single stream
const stream = await client.getOne({ id: streamId });
console.log("Recipient:", stream.recipient);
console.log("Deposited:", stream.depositedAmount.toString());
console.log("Withdrawn:", stream.withdrawnAmount.toString());
console.log("Is cancelled:", stream.closed);
// List all streams for a wallet (returns [streamId, Stream][] tuples)
const streams = await client.get({
address: walletAddress,
direction: StreamDirection.Outgoing,
type: StreamType.Vesting,
});