Soroban smart contracts for StreamPay — continuous payment streaming on the Stellar network.
This repo contains the on-chain logic for creating, starting, stopping, and settling payment streams. Contracts are written in Rust using the Soroban SDK.
create_stream(payer, recipient, rate_per_second, initial_balance, recipient_can_stop)— Create a new stream (payer must auth). Setrecipient_can_stop = trueto allow the recipient to also stop the stream; defaults tofalse(payer-only).start_stream(stream_id)— Start an existing stream.stop_stream(stream_id, stopper)— Stop an active stream.stoppermust be the payer (always allowed) or the recipient (only whenrecipient_can_stopwas set at creation).stoppermust authorise the call.settle_stream(stream_id)— Compute and deduct streamed amount since last settlement; returns amount.batch_settle(stream_ids)— Settle multiple streams in a single call; returns one settled amount per input id.archive_stream(stream_id)— Remove a fully-settled, inactive stream from storage (payer must auth).get_stream_info(stream_id)— Read stream metadata (payer, recipient, rate, balance, timestamps, active, recipient_can_stop).version()— Returns the contract version as au32(no auth required).
batch_settleis all-or-nothing. If any stream id is missing or any item panics, the entire invocation reverts and no settlement updates are committed.- Inactive streams settle to
0, matchingsettle_stream. - The contract caps each batch at
25stream ids to keep Soroban resource usage predictable. Off-chain indexers and payroll processors should chunk larger workloads into multiple transactions.
Streams are stored in Soroban persistent storage with per-stream TTL
management. Each stream is an independent ledger entry that can expire
independently. The contract instance storage holds only the next_id counter.
The counter is 1-based; once it rolls over, the contract stores 0 as an
exhausted sentinel and rejects further stream creation with stream id overflow.
See docs/factory-pattern.md for the full design rationale and future factory
pattern graduation path.
The on-chain version uses a packed u32 scheme: major * 1_000_000 + minor * 1_000 + patch.
| Semver | u32 |
|---|---|
| 0.1.0 | 1 000 |
| 1.0.0 | 1 000 000 |
| 1.2.3 | 1 002 003 |
When releasing, update both Cargo.toml version and the VERSION const in src/lib.rs.
- Rust (stable, with
rustfmt) - Optional: Stellar CLI for deployment
Note: this crate uses soroban-sdk version 22.0 (see Cargo.toml).
- Build optimized WASM (recommended via Docker builder included):
# Build with local toolchain (WASM output in target/)
cargo build --release --target wasm32-unknown-unknown
# OR use deterministic Docker builder (produces streampay_contracts.wasm)
docker build -f docker/Dockerfile.build -t streampay-wasm .
docker run --rm -v "$(pwd)":/work streampay-wasm- Run unit tests locally:
cargo test- Deploy to Futurenet/Testnet using
sorobanCLI (example):
# install soroban CLI if not installed
curl -sSf https://soroban.stellar.org/install.sh | bash
# set the network (futurenet/testnet)
soroban config set network futurenet
# upload contract and get contract id (example paths)
soroban contract publish --wasm target/wasm32-unknown-unknown/release/streampay_contracts.wasm
# note the contract id printed by the publish command- Example invocation (replace
<CONTRACT_ID>and addresses):
# create_stream(payer, recipient, rate_per_second, initial_balance)
soroban contract invoke --wasm target/wasm32-unknown-unknown/release/streampay_contracts.wasm \
--id <CONTRACT_ID> --fn create_stream --args <PAYER_ADDRESS> <RECIPIENT_ADDRESS> 100 10000
# start_stream(stream_id)
soroban contract invoke --id <CONTRACT_ID> --fn start_stream --args 1- The exact
sorobanCLI flags depend on the CLI version; consultsoroban --help. - For deterministic WASM builds in CI, use the provided
docker/Dockerfile.buildwhich pins the Rust toolchain. - Ensure the
soroban-sdkversion inCargo.tomlis compatible with yoursorobanCLI and network.
-
Clone and enter the repo
git clone <repo-url> cd streampay-contracts
-
Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup component add rustfmt
-
Verify setup
cargo fmt --all -- --check cargo build cargo test
| Command | Description |
|---|---|
cargo build |
Build the contract |
cargo test |
Run unit tests |
cargo fmt |
Format code |
cargo fmt --all -- --check |
Check formatting (CI) |
./scripts/check-wasm-size.sh |
Check optimized WASM size |
On every push/PR to main, GitHub Actions runs:
- Format check:
cargo fmt --all -- --check - Build:
cargo build - Tests:
cargo test - WASM size check: Reports optimized contract size and warns if approaching limits
Ensure all checks pass before merging. See docs/resource-limits.md for details on Soroban resource constraints.
Tagged releases follow semver. Each release includes an optimized WASM artifact and SHA-256 checksum.
See docs/RELEASE.md for the full release process, including how to verify WASM builds.
streampay-contracts/
├── src/
│ └── lib.rs # Contract and tests
├── docker/
│ └── Dockerfile.build # Deterministic WASM builder
├── .github/workflows/
│ ├── ci.yml # Format, build, test
│ └── release.yml # Tagged release workflow
├── docs/
│ └── RELEASE.md # Release process guide
├── cliff.toml # Changelog generator config
├── rust-toolchain.toml # Pinned Rust version
├── Cargo.toml
├── CHANGELOG.md
└── README.md
MIT
| Doc | Description |
|---|---|
docs/timestamp-accrual.md |
Ledger timestamp assumptions: validator behavior, coarse granularity, accrual edge cases, off-chain UX rounding |
SECURITY.md |
Security policy and responsible disclosure |
CONTRIBUTING.md |
Contribution guidelines |
CHANGELOG.md |
Release history |