Full documentation for every PayStream contract function: parameters, return values, errors, and CLI examples.
See also: Error Codes · Stream Status Lifecycle · Storage Layout (for off-chain indexers)
Deep links to Stellar Expert for the deployed testnet contracts. Use these to verify transactions and inspect on-chain state without building custom tooling.
| Contract | Testnet address | Stellar Expert link |
|---|---|---|
| PayStream Token | CDZQHVQHQMHIGJGSQIJVXGPGNZJQDQV4BBKMG7MSIDTJTTBBQYAEUPB |
https://stellar.expert/explore/testnet/contract/CDZQHVQHQMHIGJGSQIJVXGPGNZJQDQV4BBKMG7MSIDTJTTBBQYAEUPB |
| PayStream Stream | CBXKDJUQYQDQKSQT6AKZRQWXDXTQHVXHBQQJQ3WKDXJHPNMRGYAQMRS |
https://stellar.expert/explore/testnet/contract/CBXKDJUQYQDQKSQT6AKZRQWXDXTQHVXHBQQJQ3WKDXJHPNMRGYAQMRS |
The contract addresses above mirror docs/testnet.md. When
testnet.mdis updated as part of a release, update this table to match so the explorer links stay current. A future improvement is to template these URLs fromtestnet.mdso they auto-update on every release.
Mainnet: Placeholder section added until mainnet launch. Mainnet contract IDs and Stellar Explorer links (mainnet network) will be published here after the mainnet prototype (PROD-34 / issue #323) is deployed. See docs/mainnet.md.
Set the contract admin. Must be called once after deployment before any other function.
Emits a contract_initialized event on success so off-chain indexers can determine when and by whom the contract was initialised.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Address that will become the contract admin |
Returns: nothing
Errors:
- Panics if
adminauth fails - Panics with "already initialized" if called more than once (SC-01 / LOW-02)
Example:
stellar contract invoke --id <STREAM_ID> --source <ADMIN_KEY> --network testnet \
-- initialize --admin <ADMIN_ADDRESS>Step 1 of a two-step admin transfer. Current admin nominates a new admin.
Caller: Current admin
| Parameter | Type | Description |
|---|---|---|
new_admin |
Address |
Address being nominated as the next admin |
Returns: nothing
Errors:
- Panics if caller is not the current admin
Example:
stellar contract invoke --id <STREAM_ID> --source <ADMIN_KEY> --network testnet \
-- propose_admin --new_admin <NEW_ADMIN_ADDRESS>Step 2 of a two-step admin transfer. Nominated admin accepts and becomes the new admin.
Caller: Nominated (pending) admin
| Parameter | Type | Description |
|---|---|---|
new_admin |
Address |
Must match the address set by propose_admin |
Returns: nothing
Errors:
- Panics if there is no pending admin
- Panics if
new_admindoes not match the pending admin
Example:
stellar contract invoke --id <STREAM_ID> --source <NEW_ADMIN_KEY> --network testnet \
-- accept_admin --new_admin <NEW_ADMIN_ADDRESS>Admin pauses the entire contract. Blocks create_stream, create_streams_batch, and withdraw.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
nonce |
u64 |
Current admin nonce (replay protection) |
Returns: nothing
Errors:
- Panics if caller is not the admin
- Panics if
noncedoes not match the stored nonce (E009)
Example:
stellar contract invoke --id <STREAM_ID> --source <ADMIN_KEY> --network testnet \
-- pause_contract --nonce 0Admin unpauses the contract, restoring normal operation.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
nonce |
u64 |
Current admin nonce (replay protection) |
Returns: nothing
Errors:
- Panics if caller is not the admin
- Panics if
noncedoes not match the stored nonce (E009)
Example:
stellar contract invoke --id <STREAM_ID> --source <ADMIN_KEY> --network testnet \
-- unpause_contract --nonce 1Admin sets the minimum deposit enforced on create_stream.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Must equal the stored admin |
nonce |
u64 |
Current admin nonce (replay protection) |
amount |
i128 |
New minimum deposit (must be > 0) |
Returns: nothing
Errors:
- Panics if
adminauth fails or does not match stored admin - Panics if
nonceis wrong (E009) - Panics if
amount≤ 0 (E002)
Example:
stellar contract invoke --id <STREAM_ID> --source <ADMIN_KEY> --network testnet \
-- set_min_deposit --admin <ADMIN_ADDRESS> --nonce 2 --amount 100000Admin batch-expires streams that crossed stop_time + GRACE_PERIOD and have no remaining claimable tokens. Ineligible entries are skipped without reverting. The admin nonce is consumed even when every stream is skipped.
Admin configures the fee charged on stream creation. The fee is deducted from the gross deposit before the net amount is locked in escrow. fee_bps is capped at 100 (1%); setting it to 0 disables fees.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Must equal the stored admin |
nonce |
u64 |
Current admin nonce (replay protection) |
stream_ids |
Vec<u64> |
Streams to evaluate; each must be Active, have stop_time > 0, now > stop_time + GRACE_PERIOD, and claimable == 0 to expire |
Returns: nothing
Errors:
- Panics if
adminauth fails or does not match the stored admin - Panics if
noncedoes not match the stored admin nonce (E009) |fee_bps|u16| Fee in basis points, from 0 to 100 | |treasury|Address| Address receiving collected fees |
Errors: Panics if the caller is not the admin, the nonce is wrong, or fee_bps exceeds 100.
Example:
stellar contract invoke --id <STREAM_ID> --source <ADMIN_KEY> --network testnet \
-- expire_streams --admin <ADMIN_ADDRESS> --nonce 3 --stream_ids '[1,2,3]'
-- set_protocol_fee --admin <ADMIN_ADDRESS> --nonce 2 --fee_bps 10 --treasury <TREASURY_ADDRESS>Return the configured protocol fee and treasury. Before configuration, returns (0, None).
Caller: Anyone
Returns: (u16, Option<Address>) — fee in basis points and optional treasury address
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- get_protocol_feeEmployer creates a salary stream and deposits funds into the contract escrow.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Employer address; funds are pulled from here |
employee |
Address |
Employee address; receives streamed tokens |
token_address |
Address |
SEP-41 token contract address |
deposit |
i128 |
Gross amount; configured fee is deducted before the net amount is locked in escrow |
rate_per_second |
i128 |
Tokens streamed per second |
stop_time |
u64 |
Hard stop timestamp (0 = indefinite) |
low_balance_threshold |
Option<i128> |
Emit a low_bal event after any withdraw that leaves less than this remaining (None/0 = disabled) |
cliff_time |
Option<u64> |
Cliff timestamp (PROD-17). Nothing accrues before it; once passed, accrual runs from cliff_time (not from the start). None, or a cliff at/before now, means no cliff |
Returns: u64 — the new stream ID
Cliff behaviour: claimable is 0 while now < cliff_time. Withdrawing,
pausing/resuming or changing the rate before the cliff never moves accrual
earlier than the cliff. Cancelling before the cliff refunds the full deposit to
the employer. The first withdraw / withdraw_all / cancel_stream /
settle_stream call at or after the cliff emits cliff_reached once. Read the
cliff with get_cliff_time(stream_id) -> Option<u64>.
Errors:
- Panics if contract is paused
- E035 if
cliff_time>stop_time(whenstop_timeis non-zero) - E002 if
deposit≤ 0 - E007 if
deposit< minimum deposit - E001 if
rate_per_second≤ 0 - E008 if
rate_per_second> 1,000,000,000 - Panics if
stop_timeis in the past (when non-zero) - Panics if
employer==employee - Panics if token transfer fails (insufficient balance or allowance)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- create_stream \
--employer <EMPLOYER_ADDRESS> \
--employee <EMPLOYEE_ADDRESS> \
--token_address <TOKEN_ID> \
--deposit 1000000 \
--rate_per_second 100 \
--stop_time 0 \
--low_balance_threshold 100000Employer creates a fixed grant that vests linearly over vest_period. The grant is the gross deposit; any configured protocol fee is deducted before the net grant is escrowed. An optional cliff delays all claimability until the cliff duration has elapsed, while vesting accrues from the start time. Paused time is excluded from the vesting period.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Employer funding the grant |
employee |
Address |
Grant recipient |
token_address |
Address |
SEP-41 token contract address |
total_grant |
i128 |
Gross grant amount (must meet minimum deposit) |
vest_period |
u64 |
Vesting duration in seconds; must be greater than zero |
cliff |
Option<u64> |
Optional cliff duration in seconds; must not exceed vest_period |
Returns: u64 — the new stream ID
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- create_linear_vest --employer <EMPLOYER_ADDRESS> \
--employee <EMPLOYEE_ADDRESS> --token_address <TOKEN_ID> \
--total_grant 1000000 --vest_period 31536000 --cliff 7776000Return whether a stream is Payroll or LinearVest. Existing streams without type metadata are treated as Payroll.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
Stream to query |
Returns: StreamType — Payroll or LinearVest
Return the grant amount, vest duration, and optional cliff for a linear vest. Returns None for payroll streams.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
Stream to query |
Returns: Option<LinearVestSchedule> — schedule containing total_grant, vest_period, and cliff
Employer creates multiple salary streams atomically. All streams succeed or all revert.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Employer address |
params |
Vec<StreamParams> |
List of stream parameters (see below) |
StreamParams fields:
| Field | Type | Description |
|---|---|---|
employee |
Address |
Employee address |
token |
Address |
SEP-41 token contract address |
deposit |
i128 |
Gross deposit; any configured protocol fee is deducted before escrow |
rate_per_second |
i128 |
Tokens per second for this stream |
stop_time |
u64 |
Hard stop timestamp (0 = indefinite) |
cliff_time |
Option<u64> |
Optional cliff (PROD-17); same rules as create_stream. Also stored in templates |
Returns: Vec<u64> — list of new stream IDs in the same order as params
Limit: 50 streams per call
Errors: Same per-stream validations as create_stream, including protocol fee deduction; panics if params is empty or contains more than 50 entries (E029)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- create_streams_batch \
--employer <EMPLOYER_ADDRESS> \
--params '[{"employee":"<ADDR1>","token":"<TOKEN_ID>","deposit":500000,"rate_per_second":50,"stop_time":0},{"employee":"<ADDR2>","token":"<TOKEN_ID>","deposit":500000,"rate_per_second":50,"stop_time":0}]'Employee withdraws all claimable tokens earned so far.
Caller: Employee
| Parameter | Type | Description |
|---|---|---|
employee |
Address |
Must match the stream's employee |
stream_id |
u64 |
ID of the stream to withdraw from |
Returns: i128 — amount transferred (0 if nothing claimable)
Errors:
- Panics if contract is paused
- Panics if stream not found
- Panics if caller is not the stream's employee
- Panics if stream is not Active or Exhausted
- E003 if a reentrant withdraw is detected
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYEE_KEY> --network testnet \
-- withdraw --employee <EMPLOYEE_ADDRESS> --stream_id 1Anyone may finalize an Active stream after its stop_time is reached. Any remaining
claimable tokens are transferred to the current stream receipt holder, then the
stream transitions to Exhausted and emits a status event. No caller authorization
is required.
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the stream to settle |
Errors:
- Panics if the stream is not found
- Panics if the stream is not Active
- Panics if
stop_timeis unset or has not been reached
Example:
stellar contract invoke --id <STREAM_ID> --source <KEEPER_KEY> --network testnet \
-- settle_stream --stream_id 1Employee withdraws all claimable tokens from every stream they receive in a single transaction. Auth is checked once. Cancelled and Paused streams are silently skipped; streams with nothing claimable are also skipped.
Caller: Employee
| Parameter | Type | Description |
|---|---|---|
employee |
Address |
Employee address (requires auth, checked once) |
Returns: Vec<i128> — amounts transferred in employee stream-index order, with one entry per stream that paid out; empty if nothing was claimable
Errors:
- Panics if contract is paused
- E003 if a reentrant withdraw is detected on any stream
Gas note: Each stream in the employee's index incurs one token::transfer cross-contract call. For a 10-stream portfolio this is roughly 10× the cost of a single withdraw call; plan accordingly for large indexes.
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYEE_KEY> --network testnet \
-- withdraw_all --employee <EMPLOYEE_ADDRESS>Employer adds more funds to an active stream.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream to top up |
amount |
i128 |
Additional tokens to deposit |
Returns: nothing
Errors:
- Panics if stream not found
- Panics if caller is not the stream's employer
- E005 if stream is Cancelled
- E006 if stream is Exhausted
- Panics if
amount≤ 0 - Panics if token transfer fails
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- top_up --employer <EMPLOYER_ADDRESS> --stream_id 1 --amount 500000Employer updates the low-balance alert threshold for a stream at any time without topping up. Setting the threshold to 0 disables the alert.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer (requires auth) |
stream_id |
u64 |
ID of the stream to update |
threshold |
i128 |
New alert threshold in deposit tokens (≥ 0; 0 disables alert) |
Returns: nothing
Errors:
- Panics if stream not found (E015)
- Panics if caller is not the stream's employer (E018)
- E005 if stream is Cancelled
- E006 if stream is Exhausted
- Panics if
threshold< 0
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- set_low_balance_threshold --employer <EMPLOYER_ADDRESS> --stream_id 1 --threshold 500000Employer pre-authorizes recurring top-ups. After each withdraw / withdraw_all, if deposit - withdrawn < trigger_threshold, the contract pulls topup_amount from the employer via transfer_from (capped so the cumulative total never exceeds max_total). The employer must first approve the stream contract as spender for at least max_total. If the allowance or balance is insufficient, the top-up is skipped and the withdraw still succeeds. Calling again replaces the config and resets the running total.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream |
trigger_threshold |
i128 |
Remaining balance below which a top-up fires (> 0) |
topup_amount |
i128 |
Amount per top-up (> 0, ≤ max_total) |
max_total |
i128 |
Cap on cumulative auto top-ups |
Auth: employer.require_auth()
Returns: nothing
Errors:
- Panics if stream not found or caller is not the employer
- E005 if stream is Cancelled
- E025 if parameters are invalid (
trigger_threshold ≤ 0,topup_amount ≤ 0, ortopup_amount > max_total)
Events: at_set with enabled = true (see events schema)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- set_auto_topup --employer <EMPLOYER_ADDRESS> --stream_id 1 \
--trigger_threshold 100000 --topup_amount 500000 --max_total 5000000Employer revokes a stream's recurring top-up authorization.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream |
Auth: employer.require_auth()
Returns: nothing
Errors:
- Panics if stream not found or caller is not the employer
- E026 if no auto top-up is configured
Events: at_set with enabled = false (see events schema)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- cancel_auto_topup --employer <EMPLOYER_ADDRESS> --stream_id 1Query the recurring auto top-up configuration for a stream. Used by off-chain dashboards to display auto top-up status.
Caller: Anyone (no auth required)
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the stream |
Returns: Option<AutoTopupConfig> — Some(AutoTopupConfig) if configured, or None if no auto top-up is set for the stream.
AutoTopupConfig fields:
| Field | Type | Description |
|---|---|---|
trigger_threshold |
i128 |
Remaining deposit threshold below which an auto top-up is triggered |
topup_amount |
i128 |
Amount pulled from the employer per top-up |
max_total |
i128 |
Lifetime cap on cumulative auto top-up transfers |
total_topped_up |
i128 |
Cumulative tokens deposited via auto top-up so far |
Errors: None (returns None if stream has no config or does not exist)
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- get_auto_topup_config --stream_id 1(Note: get_auto_topup is also supported as an alias.)
Not a public entry point. Runs automatically at the end of withdraw and, per stream, inside withdraw_all. Integrators cannot call it directly; it is documented here so its behaviour is predictable.
| Parameter | Type | Description |
|---|---|---|
stream |
&mut Stream |
Stream being withdrawn from |
now |
u64 |
Current ledger timestamp |
Behaviour: if an AutoTopupConfig exists, the stream has not reached stop_time, and deposit - withdrawn < trigger_threshold, it pulls min(topup_amount, max_total - total_topped_up) from the employer via transfer_from, adds it to deposit, reactivates an Exhausted stream, increments total_topped_up, and emits at_up.
Auth: none of its own — it relies on the employer's prior token approve of the stream contract.
Returns: nothing
Errors: never panics. The top-up is silently skipped (the withdraw still succeeds) when no config exists, the stream has ended, the cap is reached, or the allowance/balance is insufficient.
# 1. Employer approves the stream contract to spend up to max_total
stellar contract invoke --id <TOKEN_ID> --source <EMPLOYER_KEY> --network testnet \
-- approve --from <EMPLOYER_ADDRESS> --spender <STREAM_ID> \
--amount 5000000 --expiration_ledger <LEDGER>
# 2. Configure auto top-up: refill 500000 whenever remaining < 100000, up to 5000000 total
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- set_auto_topup --employer <EMPLOYER_ADDRESS> --stream_id 1 \
--trigger_threshold 100000 --topup_amount 500000 --max_total 5000000
# 3. Once remaining balance would drop below 100000, the employee withdraws
stellar contract invoke --id <STREAM_ID> --source <EMPLOYEE_KEY> --network testnet \
-- withdraw --employee <EMPLOYEE_ADDRESS> --stream_id 1
# 4. Verify the top-up fired: total_topped_up should now be 500000
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- get_auto_topup_config --stream_id 1The withdraw transaction also emits an at_up event (["v1", "at_up", 1] → [500000, 500000]), and get_stream shows deposit increased by 500000.
Employer pauses an active stream. Accrual stops until resume_stream is called.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream to pause |
Returns: nothing
Errors:
- Panics if stream not found
- Panics if caller is not the stream's employer
- Panics if stream is not Active
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- pause_stream --employer <EMPLOYER_ADDRESS> --stream_id 1Employer pauses multiple Active streams atomically. The call reverts entirely if
any stream is missing, belongs to another employer, or is not Active. A status
event is emitted for each stream on success.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must own every stream (requires auth once) |
stream_ids |
Vec<u64> |
IDs of 1–50 streams to pause |
Returns: nothing
Errors:
- Panics if
stream_idsis empty or contains more than 50 entries (E029) - Panics if any stream is not found, does not belong to
employer, or is not Active
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- pause_stream_batch --employer <EMPLOYER_ADDRESS> --stream_ids '[1,2,3]'Employer resumes a paused stream. The last_withdraw_time is reset to now so paused time is excluded from accrual.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream to resume |
Returns: nothing
Errors:
- Panics if stream not found
- Panics if caller is not the stream's employer
- Panics if stream is not Paused
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- resume_stream --employer <EMPLOYER_ADDRESS> --stream_id 1Employer resumes multiple Paused streams atomically. The call reverts entirely if
any stream is missing, belongs to another employer, or is not Paused. Each
stream's accrual clock restarts at the current ledger timestamp, and a status
event is emitted for each stream.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must own every stream (requires auth once) |
stream_ids |
Vec<u64> |
IDs of 1–50 streams to resume |
Returns: nothing
Errors:
- Panics if
stream_idsis empty or contains more than 50 entries (E029) - Panics if any stream is not found, does not belong to
employer, or is not Paused
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- resume_stream_batch --employer <EMPLOYER_ADDRESS> --stream_ids '[1,2,3]'Employer changes the rate_per_second of an Active or Paused stream without cancelling and recreating it.
Before applying the new rate, any tokens accrued since the last withdrawal are settled by resetting last_withdraw_time to the current ledger timestamp. The employee is credited at the old rate for all elapsed time; future accrual uses the new rate.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream to update |
new_rate |
i128 |
New tokens-per-second rate (1 – 1,000,000,000) |
Returns: nothing
Errors:
- Panics if stream not found
- Panics if caller is not the stream's employer
- Panics if stream is not Active or Paused
- E001 if
new_rate≤ 0 - E008 if
new_rate> 1,000,000,000
Emitted event: rate_updated(stream_id, old_rate, new_rate)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- update_rate --employer <EMPLOYER_ADDRESS> --stream_id 1 --new_rate 20Worked Example (numeric):
Initial state:
- Stream created at time T=0 with deposit=10,000, rate=10 tokens/sec, no stop time
- At T=0: withdrawn=0, pending_accrual=0
Period 1 (rate A = 10/s):
- Advance to T=100: 100 seconds elapsed at old rate
claimable()= 100 × 10 = 1,000 tokens
Rate change:
- Call
update_rateat T=100 to set rate B = 20/s - Before applying new rate, accrued 1,000 tokens are banked into
pending_accrual - After update:
pending_accrual= 1,000,rate_per_second= 20,last_withdraw_time= 100
Period 2 (rate B = 20/s):
- Advance to T=150: 50 more seconds at new rate (20/s)
- New accrual = 50 × 20 = 1,000 tokens
- Total
claimable()=pending_accrual+ new accrual = 1,000 + 1,000 = 2,000
Withdraw:
withdraw()at T=150 pays out 2,000 tokens total (covering both periods)- After withdraw:
withdrawn= 2,000,pending_accrual= 0 - Future accrual resumes from T=150 at 20/s
This matches test_update_rate_withdraw_clears_pending_accrual.
Employer changes the hard stop timestamp of an Active stream without cancelling
and recreating it. Set new_stop_time to 0 to make the stream indefinite. A
nonzero timestamp cannot precede last_withdraw_time, so this operation cannot
move the hard stop behind the stream's current accrual baseline.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream to update |
new_stop_time |
u64 |
Unix timestamp in seconds, or 0 for no hard stop |
Returns: nothing
Errors:
- Panics if stream not found
- E017 if the stream is not Active
- E018 if caller is not the stream's employer
- E030 if nonzero
new_stop_timeis beforelast_withdraw_time
Emitted event: stop_time_updated(stream_id, old_stop_time, new_stop_time)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- update_stop_time --employer <EMPLOYER_ADDRESS> --stream_id 1 --new_stop_time 1893456000Employer cancels a stream. The employee receives all earned tokens; the employer is refunded the remainder.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must match the stream's employer |
stream_id |
u64 |
ID of the stream to cancel |
Returns: nothing
Errors:
- Panics if stream not found
- Panics if caller is not the stream's employer
- Panics if stream is already Cancelled or Exhausted
Emitted event: stream_cancelled (enriched — see Events section)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- cancel_stream --employer <EMPLOYER_ADDRESS> --stream_id 1Employer cancels multiple streams atomically in a single transaction. All cancellations succeed or all revert. Cheaper than N individual cancel_stream calls for N ≥ 2.
Caller: Employer
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Must own every stream in the list (requires auth once) |
stream_ids |
Vec<u64> |
IDs of 1–50 streams to cancel |
Returns: nothing
Errors:
- Panics if
stream_idsis empty - Panics with E029 if
stream_idscontains more than 50 entries - Panics if any stream is not found
- Panics if any stream does not belong to
employer - Panics if any stream is already Cancelled or Exhausted (reverts entire batch)
Example:
stellar contract invoke --id <STREAM_ID> --source <EMPLOYER_KEY> --network testnet \
-- cancel_streams_batch --employer <EMPLOYER_ADDRESS> --stream_ids '[1,2,3]'Read the full state of a stream by ID.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the stream to read |
Returns: Stream — the stream struct
Stream fields:
| Field | Type | Description |
|---|---|---|
id |
u64 |
Stream ID |
employer |
Address |
Employer address |
employee |
Address |
Employee address |
token |
Address |
Token contract address |
deposit |
i128 |
Total deposited |
withdrawn |
i128 |
Total already withdrawn |
rate_per_second |
i128 |
Tokens streamed per second |
start_time |
u64 |
Ledger timestamp when stream started |
stop_time |
u64 |
Hard stop timestamp (0 = none) |
last_withdraw_time |
u64 |
Timestamp of last withdrawal or resume |
status |
StreamStatus |
Active / Paused / Cancelled / Exhausted |
locked |
bool |
Reentrancy guard (always false at rest) |
low_balance_threshold |
i128 |
Remaining-deposit threshold for the low_bal event (0 = disabled) |
Errors:
- Panics if stream not found
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- get_stream --stream_id 1Read the full state of up to 50 streams in a single call. Lets off-chain dashboards replace N get_stream calls with one.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_ids |
Vec<u64> |
IDs of the streams to read (max 50) |
Returns: Vec<Stream> — streams in the same order as stream_ids
Errors:
- Panics if
stream_idshas more than 50 entries - Panics if any stream ID is not found (consistent with
get_stream)
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- get_streams_batch --stream_ids '[1, 2, 3]'Query only the status of a stream by ID. Lighter than get_stream for read-heavy off-chain
indexers that only need to know whether a stream is Active, Paused, Cancelled, or Exhausted (SC-06).
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the stream to query |
Returns: StreamStatus — one of Active, Paused, Cancelled, Exhausted
Errors:
- Panics with "stream not found" if no stream exists for
stream_id
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_status --stream_id 1Query how many tokens the employee can withdraw right now.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the stream to query |
Returns: i128 — claimable token amount
Formula:
claimable = min(
(now - last_withdraw_time) * rate_per_second,
deposit - withdrawn
)
Time is capped at stop_time when set. Returns 0 for Cancelled or Exhausted streams.
Errors:
- Panics if stream not found
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- claimable --stream_id 1Each example mirrors a scenario covered in contracts/stream/src/test.rs.
1. Normal accrual — deposit = 1_000, rate_per_second = 10, stop_time = 0, last_withdraw_time = 0, withdrawn = 0, now = 50
elapsed = 50 - 0 = 50
earned = 50 * 10 = 500
remaining = 1_000 - 0 = 1_000
claimable = min(500, 1_000) = 500
2. With stop_time — deposit = 1_000, rate_per_second = 10, stop_time = 60, last_withdraw_time = 0, withdrawn = 0, now = 100
effective_end = min(now, stop_time) = 60
elapsed = 60 - 0 = 60
earned = 60 * 10 = 600
remaining = 1_000 - 0 = 1_000
claimable = min(600, 1_000) = 600 # time after stop_time never accrues
3. After pause/resume — deposit = 10_000, rate_per_second = 10; paused at T=100, resumed at T=200 (resume_stream sets last_withdraw_time = 200), withdrawn = 0, now = 250
elapsed = 250 - 200 = 50 # the paused interval 100..200 is excluded
earned = 50 * 10 = 500
remaining = 10_000 - 0 = 10_000
claimable = min(500, 10_000) = 500
Explorer link (example): <https://stellar.expert/explore/testnet/tx/<TX_HASH>> Replace
<TX_HASH>with the actual transaction hash from yourresume_streaminvocation to inspect it on Stellar Expert.
Resuming resets
last_withdraw_time, so tokens accrued before the pause but not yet withdrawn are not carried over. Employees should withdraw before a stream is paused.
4. Exhausted stream — deposit = 1_000, rate_per_second = 10, fully withdrawn at T=100 (withdrawn = 1_000, status = Exhausted), now = 500
status == Exhausted → claimable = 0
(formula would also give min(400 * 10, 1_000 - 1_000) = min(4_000, 0) = 0)
Query claimable amounts for up to 50 streams in one contract call. Missing stream IDs return 0; all other results use the same calculation as claimable and preserve input order.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_ids |
Vec<u64> |
Stream IDs to query (maximum 50) |
Returns: Vec<i128> — claimable amounts in the same order as stream_ids
Errors:
- Panics if
stream_idshas more than 50 entries - E004 if claimable amount arithmetic overflows
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- claimable_batch --stream_ids '[1, 2, 3]'Query how many tokens would be claimable at an arbitrary timestamp.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the stream to query |
timestamp |
u64 |
Hypothetical ledger timestamp |
Returns: i128 — claimable amount at the given timestamp
Errors:
- Panics if stream not found
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- claimable_at --stream_id 1 --timestamp 1800000000Total number of streams ever created.
Caller: Anyone
Returns: u64
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_countReturn all stream IDs owned by an employer.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Employer address to query |
Returns: Vec<u64>
Migration: Use this canonical name in new clients. streams_by_employer remains available as a deprecated compatibility alias.
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_ids_by_employer --employer <EMPLOYER_ADDRESS>Return all stream IDs paying an employee.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
employee |
Address |
Employee address to query |
Returns: Vec<u64>
Migration: Use this canonical name in new clients. streams_by_employee remains available as a deprecated compatibility alias.
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_ids_by_employee --employee <EMPLOYEE_ADDRESS>Return a page of stream IDs paying an employee, in creation order. The requested limit is capped at 50; an offset at or beyond the total stream count returns an empty vector.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
employee |
Address |
Employee address to query |
offset |
u32 |
Number of stream IDs to skip |
limit |
u32 |
Maximum number of IDs to return (capped at 50) |
Returns: Vec<u64> — stream ID page
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- streams_by_employee_paginated \
--employee <EMPLOYEE_ADDRESS> --offset 0 --limit 20Return all stream IDs created within a time window, using the day-bucket timestamp index. Results are unique, deduplicated, and sorted ascending by stream_id. The effective range is capped at 30 days.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
from_ts |
u64 |
Inclusive lower bound (Unix timestamp in seconds) |
to_ts |
u64 |
Exclusive upper bound (Unix timestamp in seconds) |
Returns: Vec<u64> — unique stream IDs in ascending order for the effective range
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- streams_in_time_range --from_ts 1714569600 --to_ts 1714656000Migration: streams_in_range remains available as a deprecated alias for compatibility.
Return the number of streams owned by an employer. Equivalent to stream_ids_by_employer(employer).len() but avoids loading the full ID vector — useful for pagination and dashboards.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Employer address to query |
Returns: u64 — stream count; 0 if the address has no streams
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_count_by_employer --employer <EMPLOYER_ADDRESS>Return the number of streams paying an employee. Equivalent to stream_ids_by_employee(employee).len() but avoids loading the full ID vector — useful for pagination and dashboards.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
employee |
Address |
Employee address to query |
Returns: u64 — stream count; 0 if the address has no streams
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_count_by_employee --employee <EMPLOYEE_ADDRESS>Return the number of streams funded with a token. The count is maintained in an on-chain token index, so callers do not need to scan all streams.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
token |
Address |
Token contract address to query |
Returns: u64 — stream count; 0 if no streams use the token
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_count_by_token --token <TOKEN_ADDRESS>Return a page of stream IDs funded with a token, in creation order. The requested limit is capped at 50; an offset at or beyond the token's stream count returns an empty vector.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
token |
Address |
Token contract address to query |
offset |
u64 |
Number of stream IDs to skip |
limit |
u64 |
Maximum number of IDs to return (capped at 50) |
Returns: Vec<u64> — stream ID page
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- stream_ids_by_token --token <TOKEN_ADDRESS> --offset 0 --limit 20Return the current admin nonce. Use this to build the next admin transaction.
Caller: Anyone
Returns: u64
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- admin_nonceReturn the current contract admin address.
Off-chain tools use this to discover the admin without decoding raw ledger state.
Caller: Anyone
Returns: Address
Errors:
- Panics with "admin not set" (E021) if the contract has not been initialised.
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- adminAdmin upgrades the contract WASM in-place.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
new_wasm_hash |
BytesN<32> |
Hash of the new WASM blob (must be uploaded first) |
nonce |
u64 |
Current admin nonce (replay protection) |
Returns: nothing
Errors:
- Panics if caller is not the admin
- Panics if
nonceis wrong (E009)
Migration hook called by the admin after an upgrade to confirm the new WASM is operational. Also writes the compile-time CONTRACT_VERSION constant (1 as of the current release) into instance storage so that version() can be queried off-chain.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Must match the stored admin |
Returns: nothing
Side effects:
- Writes
CONTRACT_VERSIONtoDataKey::Versionin instance storage.
Example:
stellar contract invoke --id <STREAM_ID> --source <ADMIN_KEY> --network testnet \
-- migrate --admin <ADMIN_ADDRESS>Return the contract version stored by the last migrate call. Returns 0 if migrate has never been called (pre-upgrade / initial deployment state).
Caller: Anyone
Returns: u32 — current contract version (1 after the first migrate)
Example:
stellar contract invoke --id <STREAM_ID> --source <ANY_KEY> --network testnet \
-- versionTemplates are saved per employer in persistent storage (TTL extended on every
save and read). One employer can never read, use or delete another
employer's template: lookups are keyed by (employer, template_id).
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Template owner (requires auth) |
template_id |
u32 |
Employer-chosen identifier; saving again overwrites |
params |
StreamParams |
Token, deposit, rate, stop time and optional cliff. employee is ignored when creating from the template |
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Template owner (requires auth via create_stream) |
employee |
Address |
Recipient of the new stream |
template_id |
u32 |
Template to use |
Returns: u64 — the new stream ID. Same validations as create_stream.
Errors: E031 if employer has no template with this ID.
| Parameter | Type | Description |
|---|---|---|
employer |
Address |
Template owner (requires auth) |
template_id |
u32 |
Template to delete |
Removes the template and drops it from list_templates. Streams already
created from it are unaffected. Errors: E031 if no such template exists for
this employer.
get_template(employer, template_id) -> Option<StreamParams> and
list_templates(employer) -> Vec<(u32, StreamParams)>. Read-only.
The token implements the SEP-41 interface; see
sep41-compliance.md for the full checklist, events and
deviations. SEP-41 additions in this release: decimals, name, symbol,
the admin-only write-once set_metadata(admin, decimal, name, symbol), the
transfer / approve / burn / mint events, and transfer taking a
MuxedAddress recipient.
Initialise the token with an admin and an initial supply minted to the admin.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Token admin address |
initial_supply |
i128 |
Tokens minted to admin on init |
Returns: nothing
Example:
stellar contract invoke --id <TOKEN_ID> --source <ADMIN_KEY> --network testnet \
-- initialize --admin <ADMIN_ADDRESS> --initial_supply 1000000000Return the total token supply.
Caller: Anyone
Returns: i128
Example:
stellar contract invoke --id <TOKEN_ID> --source <ANY_KEY> --network testnet \
-- total_supplyReturn the token balance of an address.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
owner |
Address |
Address to query |
Returns: i128
Example:
stellar contract invoke --id <TOKEN_ID> --source <ANY_KEY> --network testnet \
-- balance --owner <ADDRESS>Transfer tokens from one address to another.
Caller: from (requires auth)
| Parameter | Type | Description |
|---|---|---|
from |
Address |
Sender |
to |
Address |
Recipient |
amount |
i128 |
Amount to transfer (must be > 0) |
Returns: nothing
Errors:
- Panics if
amount≤ 0 - Panics if
fromhas insufficient balance
Example:
stellar contract invoke --id <TOKEN_ID> --source <FROM_KEY> --network testnet \
-- transfer --from <FROM_ADDRESS> --to <TO_ADDRESS> --amount 1000Approve a spender to transfer tokens on behalf of the owner.
Caller: owner (requires auth)
| Parameter | Type | Description |
|---|---|---|
owner |
Address |
Token owner |
spender |
Address |
Address being approved |
amount |
i128 |
Allowance amount |
expiration_ledger |
u32 |
Last ledger sequence at which the allowance is valid |
Returns: nothing
Errors:
- Panics if
amountis negative - Panics if
amount> 0 andexpiration_ledgeris before the current ledger
Example:
stellar contract invoke --id <TOKEN_ID> --source <OWNER_KEY> --network testnet \
-- approve --owner <OWNER_ADDRESS> --spender <SPENDER_ADDRESS> --amount 5000 --expiration_ledger 1000000Return the allowance granted by owner to spender.
Caller: Anyone
| Parameter | Type | Description |
|---|---|---|
owner |
Address |
Token owner |
spender |
Address |
Approved spender |
Returns: i128 — the remaining allowance; 0 if none or expired (SEP-41).
Use allowance_with_expiration(owner, spender) -> (i128, u32) to also read the
expiration ledger.
Example:
stellar contract invoke --id <TOKEN_ID> --source <ANY_KEY> --network testnet \
-- allowance --owner <OWNER_ADDRESS> --spender <SPENDER_ADDRESS>Transfer tokens on behalf of from using an existing allowance.
Caller: spender (requires auth)
| Parameter | Type | Description |
|---|---|---|
spender |
Address |
Address with an existing allowance |
from |
Address |
Token owner |
to |
Address |
Recipient |
amount |
i128 |
Amount to transfer |
Returns: nothing
Errors:
- Panics if the allowance has expired
- Panics if allowance is insufficient
- Panics if
fromhas insufficient balance - T001 if the recipient balance would overflow
Example:
stellar contract invoke --id <TOKEN_ID> --source <SPENDER_KEY> --network testnet \
-- transfer_from --spender <SPENDER_ADDRESS> --from <FROM_ADDRESS> --to <TO_ADDRESS> --amount 1000Admin mints new tokens to an address, increasing total supply.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Must match the stored admin |
to |
Address |
Recipient of minted tokens |
amount |
i128 |
Amount to mint (must be > 0) |
nonce |
u64 |
Current admin nonce (see admin_nonce); consumed on success |
Returns: nothing
Errors:
- Panics if caller is not the admin
- Panics if
noncedoes not match the stored admin nonce - Panics if
amount≤ 0 - T001 if the recipient balance or total supply would overflow
Example:
stellar contract invoke --id <TOKEN_ID> --source <ADMIN_KEY> --network testnet \
-- mint --admin <ADMIN_ADDRESS> --to <RECIPIENT_ADDRESS> --amount 1000000 --nonce 0Return the admin nonce that the next admin-gated call (mint, mint_to_batch, propose_admin, pause_token, unpause_token, upgrade) must supply. get_admin_nonce is an alias with identical behaviour.
Caller: Anyone
Returns: u64
Example:
stellar contract invoke --id <TOKEN_ID> --source <ANY_KEY> --network testnet \
-- get_admin_nonceAdmin mints tokens to up to 50 recipients in one call. A single admin nonce is consumed for the whole batch, and the batch is all-or-nothing. Emits one mint event per recipient.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Must match the stored admin |
nonce |
u64 |
Current admin nonce; consumed once |
recipients |
Vec<(Address, i128)> |
Recipient / amount pairs (each amount > 0) |
Returns: nothing
Errors:
- Panics if caller is not the admin
- Panics if
noncedoes not match the stored admin nonce - T003 if more than 50 recipients are supplied
- Panics if any amount ≤ 0
- T001 if any balance or the total supply would overflow (whole batch reverts)
Admin pauses or resumes all token movement. While paused, transfer, transfer_from, burn and burn_from panic with T002. Consumes the admin nonce and emits token_paused with data true (pause) or false (unpause).
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
admin |
Address |
Must match the stored admin |
nonce |
u64 |
Current admin nonce |
Returns: nothing
Errors:
- Panics if caller is not the admin
- Panics if
noncedoes not match the stored admin nonce
Example:
stellar contract invoke --id <TOKEN_ID> --source <ADMIN_KEY> --network testnet \
-- pause_token --admin <ADMIN_ADDRESS> --nonce 0Return whether the token is paused. Caller: Anyone. Returns: bool
Step 1 of two-step admin transfer: the current admin nominates a new admin.
Caller: Admin
| Parameter | Type | Description |
|---|---|---|
new_admin |
Address |
Address nominated as the next admin |
nonce |
u64 |
Current admin nonce; consumed on success |
Returns: nothing
Errors:
- Panics if
noncedoes not match the stored admin nonce
Example:
stellar contract invoke --id <TOKEN_ID> --source <ADMIN_KEY> --network testnet \
-- propose_admin --new_admin <NEW_ADMIN_ADDRESS> --nonce 0Step 2 of two-step admin transfer: the nominated address accepts and becomes admin.
Caller: new_admin (requires auth)
| Parameter | Type | Description |
|---|---|---|
new_admin |
Address |
Must match the pending admin |
Returns: nothing
Errors:
- Panics if there is no pending admin
- Panics if
new_admindoes not match the pending admin
Example:
stellar contract invoke --id <TOKEN_ID> --source <NEW_ADMIN_KEY> --network testnet \
-- accept_admin --new_admin <NEW_ADMIN_ADDRESS>fn upgrade(env: Env, new_wasm_hash: BytesN<32>, nonce: u64)Admin replaces the token WASM in-place; balances and total supply are preserved. Requires admin auth; panics with invalid nonce if nonce ≠ admin_nonce().
fn migrate(env: Env, admin: Address)Post-upgrade no-op hook. Requires admin auth and that it matches the stored admin.
Burn tokens from the caller's own balance, reducing total supply.
Caller: from (requires auth)
| Parameter | Type | Description |
|---|---|---|
from |
Address |
Address whose tokens are burned |
amount |
i128 |
Amount to burn (must be > 0) |
Returns: nothing
Errors:
- Panics if
amount≤ 0 - Panics if
fromhas insufficient balance - T002 if the token is paused
Emits burn with topics ("burn", from) and data amount.
Example:
stellar contract invoke --id <TOKEN_ID> --source <FROM_KEY> --network testnet \
-- burn --from <FROM_ADDRESS> --amount 500Burn tokens on behalf of from using an existing allowance.
Caller: spender (requires auth)
| Parameter | Type | Description |
|---|---|---|
spender |
Address |
Address with an existing allowance |
from |
Address |
Token owner whose tokens are burned |
amount |
i128 |
Amount to burn (must be > 0) |
Returns: nothing
Errors:
- Panics if
amount≤ 0 - Panics if the allowance has expired
- Panics if allowance is insufficient
- Panics if
fromhas insufficient balance - T002 if the token is paused
Deducts amount from both from's balance and spender's allowance. Emits burn with topics ("burn", from) and data amount.
Example:
stellar contract invoke --id <TOKEN_ID> --source <SPENDER_KEY> --network testnet \
-- burn_from --spender <SPENDER_ADDRESS> --from <FROM_ADDRESS> --amount 500All events are emitted via env.events().publish(). The topic tuple is (symbol, stream_id) and the data tuple carries the payload described below.
Emitted once per stream, by the first withdraw, withdraw_all,
cancel_stream or settle_stream call at or after the stream's cliff
(PROD-17).
Topics: ("v1", "cliff", stream_id)
Data: cliff_time: u64
Emitted by cancel_stream instead of the generic status event. Carries the exact cash-flow amounts so off-chain indexers can track fund movements without re-simulating the transaction.
| Field | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the cancelled stream (in topics) |
employer |
Address |
Employer address (refund recipient) |
employee |
Address |
Employee address (claimable recipient) |
claimable_paid |
i128 |
Tokens transferred to the employee at cancellation |
refund_paid |
i128 |
Tokens returned to the employer |
Topics: ("cancelled", stream_id)
Data: (employer, employee, claimable_paid, refund_paid)
Emitted by update_rate when the employer changes the stream's rate_per_second.
| Field | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the updated stream (in topics) |
old_rate |
i128 |
Previous rate_per_second value |
new_rate |
i128 |
New rate_per_second value |
Topics: ("rate_upd", stream_id)
Data: (old_rate, new_rate)
Emitted by update_stop_time when the employer changes a stream's hard stop time.
| Field | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the updated stream (in topics) |
old_stop_time |
u64 |
Previous hard stop timestamp (0 means indefinite) |
new_stop_time |
u64 |
New hard stop timestamp (0 means indefinite) |
Topics: ("v1", "stop_upd", stream_id)
Data: (old_stop_time, new_stop_time)
Emitted by withdraw when the stream's remaining deposit (deposit - withdrawn) falls below its non-zero low_balance_threshold. Off-chain systems can subscribe to this event to alert the employer to top up. The threshold is set via create_stream and can be changed via top_up or set_low_balance_threshold.
| Field | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the stream (in topics) |
employer |
Address |
Employer to notify |
remaining |
i128 |
Deposit remaining after the withdraw |
threshold |
i128 |
The stream's low_balance_threshold |
Topics: ("low_bal", stream_id)
Data: (employer, remaining, threshold)
Emitted by expire_streams when an eligible stream is transitioned from Active to Exhausted.
| Field | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the expired stream (in topics) |
Topics: ("expired", stream_id)
Data: empty tuple ()
Emitted by set_low_balance_threshold when the employer updates the low-balance alert threshold for a stream.
| Field | Type | Description |
|---|---|---|
stream_id |
u64 |
ID of the updated stream (in topics) |
old_threshold |
i128 |
Previous low_balance_threshold value |
new_threshold |
i128 |
New low_balance_threshold value |
Topics: ("thresh_up", stream_id)
Data: (old_threshold, new_threshold)
Stream contract panics are prefixed with a stable code defined in contracts/stream/src/types.rs.
Keep this table in sync with that file (see CONTRIBUTING.md).
| Code | Constant | Meaning | Triggered By | Recommended Fix |
|---|---|---|---|---|
| E001 | ERR_ZERO_RATE |
rate_per_second must be > 0 |
create_stream, create_streams_batch, update_rate |
Pass a rate_per_second / new_rate ≥ 1 |
| E002 | ERR_ZERO_DEPOSIT |
deposit / amount must be > 0 |
create_stream, create_streams_batch, set_min_deposit |
Pass a deposit / amount > 0 |
| E003 | ERR_REENTRANT |
Reentrant withdraw detected (stream locked flag set) |
withdraw, withdraw_all |
Do not re-enter withdraw from a token callback; retry in a separate transaction |
| E004 | ERR_OVERFLOW |
Arithmetic overflow in claimable / balance calculation | claimable, claimable_at, withdraw, withdraw_all, top_up |
Use smaller deposit, rate_per_second, or top-up amount values |
| E005 | ERR_STREAM_CANCELLED |
Cannot top up a cancelled stream | top_up, set_auto_topup, set_low_balance_threshold |
Create a new stream instead |
| E006 | ERR_STREAM_EXHAUSTED |
Cannot top up an exhausted stream | top_up, set_low_balance_threshold |
Create a new stream instead |
| E007 | ERR_BELOW_MIN_DEPOSIT |
Deposit below minimum | create_stream, create_streams_batch |
Deposit at least the minimum (default 10_000, changed via set_min_deposit) |
| E008 | ERR_INVALID_RATE |
rate_per_second exceeds maximum (1,000,000,000) |
create_stream, create_streams_batch, update_rate |
Pass a rate ≤ 1,000,000,000 |
| E009 | ERR_BAD_NONCE |
Invalid admin nonce | propose_admin, pause_contract, unpause_contract, set_min_deposit, expire_streams, upgrade, propose_upgrade, execute_upgrade, cancel_upgrade |
Read the current value with admin_nonce and pass it as nonce |
| E009 | ERR_BAD_NONCE |
Invalid admin nonce | propose_admin, pause_contract, unpause_contract, set_min_deposit, set_protocol_fee, upgrade, propose_upgrade, execute_upgrade, cancel_upgrade |
Read the current value with admin_nonce and pass it as nonce |
| E029 | ERR_BATCH_TOO_LARGE |
Batch contains more than 50 streams | create_streams_batch, cancel_streams_batch |
Split the operation into batches of at most 50 streams |
| E030 | ERR_INVALID_STOP_TIME |
Nonzero stop time precedes last withdrawal | update_stop_time |
Use 0 or a timestamp at/after last_withdraw_time |
| T001 | ERR_OVERFLOW (token) |
Token arithmetic overflow | Token mint, mint_to_batch, transfer, transfer_from |
Use smaller amounts |
| T002 | ERR_PAUSED (token) |
Token is paused | Token transfer, transfer_from, burn, burn_from |
Wait for the admin to call unpause_token |
| T003 | ERR_BATCH_TOO_LARGE (token) |
More than 50 recipients | Token mint_to_batch |
Split into batches of at most 50 |
Cancelled and Exhausted are terminal states: no function moves a stream out of them.
stateDiagram-v2
[*] --> Active: create_stream / create_streams_batch
Active --> Paused: pause_stream / pause_stream_batch
Paused --> Active: resume_stream / resume_stream_batch
Active --> Cancelled: cancel_stream / cancel_streams_batch
Paused --> Cancelled: cancel_stream / cancel_streams_batch
Active --> Exhausted: withdraw / withdraw_all / settle_stream
Cancelled --> [*]
Exhausted --> [*]
update_rate, top_up, and set_low_balance_threshold do not change a stream's status.
settle_stream (callable by anyone) triggers the Active → Exhausted transition for streams
whose stop_time has passed and whose deposit is fully streamed. withdraw performs the
same transition automatically when called after stop_time with nothing left to claim.