Create a new payment stream with optional time limit.
pub fn create_stream(
env: Env,
payer: Address,
recipient: Address,
rate_per_second: i128,
initial_balance: i128,
end_time: u64 // 0 = unlimited; > 0 = auto-terminate timestamp
) -> u32Requires: Payer authentication Returns: Stream ID (u32) Errors: Panics if rate or balance is non-positive
Example:
// Unlimited stream
let stream_id = client.create_stream(
&payer, &recipient, &100, &10_000, &0
);
// Time-limited (7 day subscription)
let end_time = current_time + 7 * 24 * 3600;
let stream_id = client.create_stream(
&payer, &recipient, &100, &10_000, &end_time
);Start an inactive stream.
pub fn start_stream(env: Env, stream_id: u32)Requires: Payer authentication Returns: Nothing Errors:
"stream already active"- Cannot start active stream"end_time must be in the future"- end_time in past
Behavior:
- Sets
start_time = now - Sets
is_active = true - Clears
paused_at(if paused) - Validates
end_time > nowif set
Example:
client.start_stream(&stream_id); // Begin accrualCalculate and deduct accrued amounts (internal accounting).
pub fn settle_stream(env: Env, stream_id: u32) -> i128Requires: No authorization Returns: Amount settled (i128) Errors: Panics if stream not found
Behavior:
- Computes accrued:
(time_elapsed) * rate_per_second - Caps at balance (no overpayment)
- Deducts from balance
- Respects
paused_at(uses pause time if paused) - Respects
end_time(caps accrual, auto-deactivates at end)
Example:
let accrued = client.settle_stream(&stream_id);
println!("Amount settled: {}", accrued); // In base unitsPaused Behavior:
client.pause_stream(&stream_id);
env.advance_time(1000); // Pass time
let accrued = client.settle_stream(&stream_id);
assert_eq!(accrued, 0); // No new accrual while pausedCancel active stream and settle accrued amounts immediately.
pub fn cancel_stream(env: Env, stream_id: u32)Requires: Payer authentication
Returns: Nothing
Errors: "cannot cancel inactive stream"
Behavior:
- Immediately settles accrued amounts
- Recipient receives accrued amount
- Payer retains remaining balance
- Stream marked inactive
- Atomic operation (prevents races)
Example:
// 10s into a 100/s stream
client.cancel_stream(&stream_id); // Settles 1000, leaves 9000
let info = client.get_stream_info(&stream_id);
assert!(!info.is_active);
assert_eq!(info.balance, 9000);Freeze accrual without terminating the stream.
pub fn pause_stream(env: Env, stream_id: u32)Requires: Payer authentication Returns: Nothing Errors:
"cannot pause inactive stream""stream already paused"
Behavior:
- Settles accrued amount to pause point
- Sets
paused_at = now - Keeps
is_active = true - Accrual stops until resumed
Example:
client.start_stream(&stream_id);
env.advance_time(5);
client.pause_stream(&stream_id); // Settles 500
// 1 hour passes, stream doesn't accrue
env.advance_time(3600);
client.resume_stream(&stream_id);
env.advance_time(2);
let accrued = client.settle_stream(&stream_id); // Only 200Restart accrual from a paused stream.
pub fn resume_stream(env: Env, stream_id: u32)Requires: Payer authentication Returns: Nothing Errors:
"cannot resume inactive stream""stream is not paused"
Behavior:
- Clears
paused_at - Resets
start_time = now(for future accrual calculation) - Keeps
is_active = true - Accrual resumes
Example:
// After pause
client.resume_stream(&stream_id);
env.advance_time(3);
let accrued = client.settle_stream(&stream_id); // 300 accruedPermanently stop an active stream.
pub fn stop_stream(env: Env, stream_id: u32)Enhanced in v0.2.0:
- Now clears
paused_atwhen stopping
Example:
client.stop_stream(&stream_id); // Final stop
let info = client.get_stream_info(&stream_id);
assert!(!info.is_active);Retrieve stream details (read-only).
pub fn get_stream_info(env: Env, stream_id: u32) -> StreamInfoReturns: StreamInfo struct
StreamInfo Fields:
pub struct StreamInfo {
pub payer: Address, // Payer address
pub recipient: Address, // Recipient address
pub rate_per_second: i128, // Payment rate (base units/s)
pub balance: i128, // Remaining balance
pub start_time: u64, // Stream start or resume timestamp
pub end_time: u64, // Auto-terminate time (0 = unlimited)
pub is_active: bool, // Active vs. stopped
pub paused_at: u64, // Pause timestamp (0 = not paused)
}Example:
let info = client.get_stream_info(&stream_id);
if info.paused_at > 0 {
println!("Stream paused at {}", info.paused_at);
} else if info.is_active {
println!("Stream active, balance: {}", info.balance);
} else {
println!("Stream stopped");
}Remove a fully-settled, inactive stream from storage.
pub fn archive_stream(env: Env, stream_id: u32)Requires: Payer authentication
Returns: Nothing
Errors:
"cannot archive active stream""cannot archive stream with unsettled balance"
Behavior:
- Removes stream from persistent storage
- Must be stopped and fully settled (balance = 0)
- Protects recipient entitlements
Example:
client.stop_stream(&stream_id);
let accrued = client.settle_stream(&stream_id); // Drain balance
client.archive_stream(&stream_id); // Remove from storage
// get_stream_info now panics (stream not found)Get contract version.
pub fn version(_env: Env) -> u32Returns: Version as u32 (major1M + minor1k + patch) Current: 2_000 (v0.2.0)
Example:
let v = client.version();
assert_eq!(v, 2_000); // v0.2.0[Created]
↓ start_stream
[Active]
├─→ pause_stream → [Active, Paused]
│ ↓ resume_stream → [Active]
├─→ settle_stream → [Active or Inactive] (depending on balance/end_time)
├─→ cancel_stream → [Cancelled/Inactive]
└─→ stop_stream → [Inactive]
[Inactive]
├─→ start_stream → [Active] (if not created yet)
└─→ archive_stream → [Archived/Removed]
| Error Message | Function | Cause | Fix |
|---|---|---|---|
"rate and balance must be positive" |
create_stream | Non-positive rate/balance | Use positive values |
"stream already active" |
start_stream | Stream already started | Call start_stream once |
"end_time must be in the future" |
start_stream | end_time ≤ now | Use future timestamp |
"cannot cancel inactive stream" |
cancel_stream | Stream not active | Start stream first |
"cannot pause inactive stream" |
pause_stream | Stream not active | Start stream first |
"stream already paused" |
pause_stream | Already paused | Resume first |
"cannot resume inactive stream" |
resume_stream | Stream not active | Start stream first |
"stream is not paused" |
resume_stream | Not paused | Pause first |
"stream not active" |
stop_stream | Stream already stopped | Call once |
"cannot archive active stream" |
archive_stream | Stream still running | Stop first |
"cannot archive stream with unsettled balance" |
archive_stream | Balance not zero | Settle first |
"stream not found" |
Any | Stream ID invalid | Check ID |
let stream_id = client.create_stream(&payer, &recipient, &100, &1000, &0);
client.start_stream(&stream_id);
env.advance_time(5);
let accrued = client.settle_stream(&stream_id); // 500
client.stop_stream(&stream_id);
client.archive_stream(&stream_id);// Setup
let stream_id = client.create_stream(&payer, &recipient, &50, &500, &0);
client.start_stream(&stream_id);
// Pause during off-hours
client.pause_stream(&stream_id);
// Hours later...
client.resume_stream(&stream_id);
// Cleanup
client.stop_stream(&stream_id);
client.settle_stream(&stream_id);
client.archive_stream(&stream_id);let end_time = now + 30 * 24 * 3600; // 30 days
let stream_id = client.create_stream(&payer, &recipient, &3_33, &3_000, &end_time);
client.start_stream(&stream_id);
// Settles over 30 days
// After 30 days, auto-deactivates
env.advance_time(30 * 24 * 3600);
let accrued = client.settle_stream(&stream_id); // ~3000
let info = client.get_stream_info(&stream_id);
assert!(!info.is_active); // Auto-deactivatedlet stream_id = client.create_stream(&payer, &recipient, &100, &1000, &0);
client.start_stream(&stream_id);
env.advance_time(3);
let balance_before = client.get_stream_info(&stream_id).balance;
client.cancel_stream(&stream_id); // Atomic: settle + deactivate
let info = client.get_stream_info(&stream_id);
assert_eq!(info.balance, balance_before - 300); // Recipient got 300
assert!(!info.is_active);| Version | Features | Breaking Changes |
|---|---|---|
| 0.1.0 | Basic streaming | None (initial) |
| 0.2.0 | Cancel, Pause/Resume, End Time | create_stream signature |
Documentation: See docs/ for detailed specifications
Tests: Run cargo test --lib for 27 comprehensive tests