Complete reference for integrating FlowStar's token streaming smart contract into your dApp.
Creates a new payment stream with optional cliff vesting.
Signature:
pub fn create_stream(sender: Address, params: StreamParams) -> u64Parameters:
sender: Address- The account funding and owning the stream (must authorize)params: StreamParams- Stream configuration object containing:recipient: Address- Account receiving the fundstoken: Address- Token contract address (SEP-41)total_amount: i128- Total amount to stream (in token's smallest unit)start_time: u64- Stream start time (UNIX seconds)end_time: u64- Stream end time (UNIX seconds)cliff_time: u64- Time before which no funds unlock (except cliff_amount)cliff_amount: i128- Amount unlocked immediately at cliff (smallest unit)
Returns:
u64- Unique stream ID
Authorization Required:
sendermust authorize the transactionsendermust have approved the streaming contract to transfertotal_amounttokens
Preconditions:
start_time < end_timecliff_time >= start_timecliff_amount <= total_amounttotal_amount > 0- Token contract must be valid SEP-41
Example - CLI:
soroban contract invoke \
--id CXXXXX \
-- \
create_stream \
--sender GXXXXXX \
--recipient GXXXXXX \
--token CXXXXXX \
--total_amount 1000000000 \
--start_time 1700000000 \
--end_time 1702592000 \
--cliff_time 1700000000 \
--cliff_amount 100000000Example - JavaScript (Stellar SDK):
import { Address, Contract, nativeToScVal } from '@stellar/stellar-sdk';
const contract = new Contract(STREAM_CONTRACT_ID);
const params = {
recipient: new Address(recipientAddress).toScVal(),
token: new Address(tokenAddress).toScVal(),
total_amount: nativeToScVal(1000000000n, { type: 'i128' }),
start_time: nativeToScVal(Math.floor(Date.now() / 1000), { type: 'u64' }),
end_time: nativeToScVal(Math.floor(Date.now() / 1000) + 86400 * 30, { type: 'u64' }),
cliff_time: nativeToScVal(Math.floor(Date.now() / 1000), { type: 'u64' }),
cliff_amount: nativeToScVal(100000000n, { type: 'i128' }),
};
const result = await invoke(
'create_stream',
[new Address(senderAddress).toScVal(), nativeToScVal(params, { type: 'map' })],
);Withdraws available funds from a stream to the recipient's account.
Signature:
pub fn withdraw(stream_id: u64, amount: i128) -> Result<(), StreamError>Parameters:
stream_id: u64- Stream ID to withdraw fromamount: i128- Amount to withdraw (in token's smallest unit)
Authorization Required:
- The stream recipient must authorize the transaction
Preconditions:
- Stream must exist
- Recipient must have at least
amountwithdrawable - Stream must not be cancelled
Returns:
Ok(())on successErr(StreamError)on failure
Example - JavaScript:
const streamId = 1n;
const withdrawAmount = 100000000n; // 10 USDC (7 decimals)
const result = await invoke('withdraw', [
nativeToScVal(streamId, { type: 'u64' }),
nativeToScVal(withdrawAmount, { type: 'i128' }),
]);Cancels a stream and returns remaining funds to the sender.
Signature:
pub fn cancel(stream_id: u64) -> Result<(), StreamError>Parameters:
stream_id: u64- Stream ID to cancel
Authorization Required:
- The stream sender must authorize the transaction
Preconditions:
- Stream must exist
- Stream must not already be cancelled
Returns:
Ok(())on successErr(StreamError)on failure
Example - JavaScript:
const streamId = 1n;
const result = await invoke('cancel', [
nativeToScVal(streamId, { type: 'u64' }),
]);Transfers stream ownership to a new recipient.
Signature:
pub fn transfer_stream(stream_id: u64, new_recipient: Address) -> Result<(), StreamError>Parameters:
stream_id: u64- Stream ID to transfernew_recipient: Address- New recipient address
Authorization Required:
- The current stream recipient must authorize the transaction
Preconditions:
- Stream must exist
new_recipientmust be different from current recipient- Stream must not be cancelled
Returns:
Ok(())on successErr(StreamError)on failure
Example - JavaScript:
const streamId = 1n;
const newRecipient = 'GXXXXX...';
const result = await invoke('transfer_stream', [
nativeToScVal(streamId, { type: 'u64' }),
new Address(newRecipient).toScVal(),
]);Adds additional funds to an existing stream.
Signature:
pub fn top_up(stream_id: u64, additional_amount: i128) -> Result<(), StreamError>Parameters:
stream_id: u64- Stream ID to top upadditional_amount: i128- Additional amount to add (smallest unit)
Authorization Required:
- The stream sender must authorize the transaction
- Sender must have approved the contract for
additional_amounttokens
Preconditions:
- Stream must exist
- Stream must not be cancelled
additional_amount > 0
Returns:
Ok(())on successErr(StreamError)on failure
Example - JavaScript:
const streamId = 1n;
const additionalAmount = 50000000n; // Add 5 USDC
const result = await invoke('top_up', [
nativeToScVal(streamId, { type: 'u64' }),
nativeToScVal(additionalAmount, { type: 'i128' }),
]);Extends the stream's time-to-live in storage (required every ~6 months).
Signature:
pub fn bump_stream(stream_id: u64) -> Result<(), StreamError>Parameters:
stream_id: u64- Stream ID to bump
Authorization Required:
- The stream sender must authorize the transaction
Preconditions:
- Stream must exist
Returns:
Ok(())on successErr(StreamError)on failure
Example - JavaScript:
const streamId = 1n;
const result = await invoke('bump_stream', [
nativeToScVal(streamId, { type: 'u64' }),
]);Fetches a stream by ID.
Signature:
pub fn get_stream(stream_id: u64) -> Result<Stream, StreamError>Parameters:
stream_id: u64- Stream ID to retrieve
Returns:
- Stream object with all fields
Err(StreamError::NotFound)if stream doesn't exist
Example:
const streamId = 1n;
const stream = await query('get_stream', [nativeToScVal(streamId, { type: 'u64' })]);Returns the amount available for withdrawal from a stream at current time.
Signature:
pub fn get_withdrawable(stream_id: u64) -> i128Parameters:
stream_id: u64- Stream ID
Returns:
i128- Withdrawable amount (in token's smallest unit)
Example:
const streamId = 1n;
const withdrawable = await query('get_withdrawable', [
nativeToScVal(streamId, { type: 'u64' }),
]);Lists stream IDs sent by an address (paginated).
Signature:
pub fn get_sent_streams(sender: Address, offset: u32, limit: u32) -> Vec<u64>Parameters:
sender: Address- Sender addressoffset: u32- Pagination offsetlimit: u32- Maximum results (recommended: 100)
Returns:
- Vector of stream IDs
Example:
const senderAddress = 'GXXXXX...';
const offset = 0;
const limit = 100;
const streamIds = await query('get_sent_streams', [
new Address(senderAddress).toScVal(),
nativeToScVal(offset, { type: 'u32' }),
nativeToScVal(limit, { type: 'u32' }),
]);Lists stream IDs received by an address (paginated).
Signature:
pub fn get_received_streams(recipient: Address, offset: u32, limit: u32) -> Vec<u64>Parameters:
recipient: Address- Recipient addressoffset: u32- Pagination offsetlimit: u32- Maximum results (recommended: 100)
Returns:
- Vector of stream IDs
Returns total number of streams sent by an address.
Signature:
pub fn get_sent_stream_count(sender: Address) -> u32Parameters:
sender: Address- Sender address
Returns:
u32- Total count
Returns total number of streams received by an address.
Signature:
pub fn get_received_stream_count(recipient: Address) -> u32Parameters:
recipient: Address- Recipient address
Returns:
u32- Total count
All write operations require transaction authorization from a specific account:
import { TransactionBuilder, Address } from '@stellar/stellar-sdk';
// The signer's account must match the required authorizer for the operation
const tx = new TransactionBuilder(account, {
fee: '1000000',
networkPassphrase: 'Test SDF Network ; September 2015',
})
.addOperation(contract.call('create_stream', ...args))
.setTimeout(300)
.build();
// Sign with the authorized account
const signedXdr = await wallet.sign(tx);| Code | Name | Description | Recovery |
|---|---|---|---|
| 1 | NotFound | Stream does not exist | Verify stream ID exists |
| 2 | Unauthorized | Caller is not authorized for this operation | Use correct wallet address |
| 3 | InvalidAmount | Amount is negative or zero | Use positive amount > 0 |
| 4 | InvalidTime | Start/end times are invalid | Ensure start_time < end_time |
| 5 | InvalidCliff | Cliff configuration is invalid | Ensure cliff_time >= start_time |
| 6 | AlreadyCancelled | Stream is already cancelled | Cannot modify cancelled streams |
| 7 | InsufficientFunds | Insufficient balance to execute operation | Add more funds or reduce amount |
| 8 | InvalidToken | Token contract is not valid SEP-41 | Verify token address |
| 9 | TransferFailed | Token transfer failed (likely insufficient allowance) | Approve contract for amount |
| 10 | InsufficientWithdrawable | No funds available to withdraw | Wait for cliff or unlock period |
Approximate gas costs on Stellar's Soroban (in stroops = 0.0000001 XLM):
| Operation | Min Fee | Estimated Fee (15% buffer) | Notes |
|---|---|---|---|
| create_stream | 500,000 | 575,000 | + token approval (~500k) |
| withdraw | 200,000 | 230,000 | Varies by stream state |
| cancel | 150,000 | 172,500 | Varies by amount returned |
| transfer_stream | 100,000 | 115,000 | Quick operation |
| top_up | 200,000 | 230,000 | Similar to withdraw |
| bump_stream | 100,000 | 115,000 | Minimal cost |
| get_stream | 50,000 | N/A | Read-only, no fee |
| get_withdrawable | 50,000 | N/A | Read-only, no fee |
interface Stream {
id: u64;
sender: Address;
recipient: Address;
token: Address;
deposited_amount: i128;
withdrawn_amount: i128;
start_time: u64;
end_time: u64;
cliff_time: u64;
cliff_amount: i128;
amount_per_second: i128;
cancelled: boolean;
}
interface StreamParams {
recipient: Address;
token: Address;
total_amount: i128;
start_time: u64;
end_time: u64;
cliff_time: u64;
cliff_amount: i128;
}