Soroban smart contracts for PayStream — decentralized payroll and salary streaming on the Stellar blockchain.
PayStream lets employers stream salaries to employees in real-time, per-second. Instead of waiting for a monthly paycheck, employees earn and can withdraw their salary continuously as they work — fully on-chain, trustless, and transparent.
New here? Create your first testnet stream in 5 minutes with the Quickstart.
- Real-time pay — employees access earned wages any time, not just payday
- Trustless escrow — funds locked on-chain; employer cannot claw back earned salary
- Transparent — every stream, withdrawal, and cancellation is an immutable on-chain event
- Stellar-native — built on Stellar's fast, low-fee infrastructure with Soroban smart contracts
- Flexible — pause, resume, top-up, or cancel streams; optional hard stop time
- Multi-token — each stream can use any SEP-41 compliant token; employer and employee can run concurrent streams in different assets
- Native XLM — stream Stellar's native asset via its Stellar Asset Contract (SAC) address; no custom token deployment needed (see docs/testnet.md)
Solid arrows are contract calls made by each actor; dashed arrows are token transfers.
flowchart LR
Admin([Admin])
Employer([Employer])
Employee([Employee])
SC[StreamContract]
TC[TokenContract<br/>SEP-41 / SAC]
Admin -->|"initialize, propose_admin / accept_admin,<br/>pause_contract / unpause_contract,<br/>set_min_deposit, upgrade, migrate"| SC
Employer -->|"create_stream, create_streams_batch, top_up,<br/>pause_stream, pause_stream_batch, resume_stream, resume_stream_batch, update_rate,<br/>cancel_stream, cancel_streams_batch"| SC
Employee -->|"withdraw, withdraw_all"| SC
SC -->|"transfer"| TC
Employer -.->|"deposit / top-up"| SC
SC -.->|"earned salary<br/>(withdraw, cancel)"| Employee
SC -.->|"unearned refund (cancel)"| Employer
Anyone can call the read-only functions (get_stream, claimable, stream_status, …) and settle_stream.
stateDiagram-v2
[*] --> Active: create_stream
Active --> Paused: pause_stream / pause_stream_batch
Paused --> Active: resume_stream / resume_stream_batch
Active --> Cancelled: cancel_stream
Paused --> Cancelled: cancel_stream
Active --> Exhausted: withdraw / settle_stream (deposit fully paid or stop_time passed)
Cancelled --> [*]
Exhausted --> [*]
See the glossary for terms and the client guide for JavaScript integration examples.
.
├── contracts
│ ├── stream # Core salary streaming and escrow contract
│ │ ├── src
│ │ │ ├── lib.rs # Stream entrypoints
│ │ │ ├── storage.rs # Persistence and claimable calculation
│ │ │ ├── events.rs # On-chain event publishing
│ │ │ ├── types.rs # Domain models and storage keys
│ │ │ └── test.rs # Contract tests
│ │ └── Cargo.toml
│ └── token # Fungible payment token contract
│ ├── src
│ │ ├── lib.rs
│ │ ├── storage.rs
│ │ ├── types.rs
│ │ └── test.rs
│ └── Cargo.toml
├── scripts
│ ├── build.sh
│ ├── deploy-local.sh
│ ├── deploy-testnet.sh
│ ├── export-streams.sh # CSV/JSON export of an employer's streams
│ ├── init-testnet.sh
│ └── sync-labels.sh # Apply .github/labels.yml to the repository
├── Cargo.toml
├── Makefile
├── CONTRIBUTING.md
├── SECURITY.md
└── README.md
- Rust (latest stable)
- Stellar CLI
git clone https://github.com/veracindarella/paystream-contracts.git
cd paystream-contracts
rustup target add wasm32-unknown-unknownmake build
# or: stellar contract buildmake test
# or: cargo testmake fmt-check
make lintBuild and test entirely inside Docker — no local Rust or Stellar CLI installation needed.
Run tests:
docker compose run --rm testBuild contracts only:
docker compose run --rm build stellar contract buildThe cargo-cache volume persists the Cargo registry between runs so subsequent builds are fast.
Full parameter, return value, error, and example documentation: docs/api-reference.md
| Function | Caller | Description |
|---|---|---|
initialize(admin) |
Admin | Set contract admin |
create_stream(employer, employee, token, deposit, rate_per_second, stop_time, low_balance_threshold) |
Employer | Create stream, lock deposit; optional low_bal event threshold |
create_streams_batch(employer, params) |
Employer | Create multiple streams atomically; all succeed or all revert |
withdraw(employee, stream_id) |
Employee | Withdraw all claimable earnings |
withdraw_all(employee) |
Employee | Withdraw from all streams in one transaction |
top_up(employer, stream_id, amount, low_balance_threshold) |
Employer | Add more funds to active stream; optionally update low_bal threshold |
set_low_balance_threshold(employer, stream_id, threshold) |
Employer | Adjust or disable (0) alert threshold without topping up |
pause_stream(employer, stream_id) |
Employer | Pause accrual |
pause_stream_batch(employer, stream_ids) |
Employer | Pause multiple streams atomically; all succeed or all revert (max 50) |
resume_stream(employer, stream_id) |
Employer | Resume accrual |
resume_stream_batch(employer, stream_ids) |
Employer | Resume multiple paused streams atomically; all succeed or all revert (max 50) |
cancel_stream(employer, stream_id) |
Employer | Pay employee earned share, refund remainder |
cancel_streams_batch(employer, stream_ids) |
Employer | Cancel multiple streams atomically; all succeed or all revert |
get_stream(stream_id) |
Anyone | Read stream state |
get_streams_batch(stream_ids) |
Anyone | Read up to 50 streams in one call, in input order |
get_auto_topup_config(stream_id) |
Anyone | Query recurring auto top-up config for a stream |
stream_status(stream_id) |
Anyone | Query stream status only (lightweight) |
claimable(stream_id) |
Anyone | Query withdrawable amount right now |
stream_count() |
Anyone | Total streams created |
stream_count_by_employer(employer) |
Anyone | Number of streams owned by an employer |
stream_count_by_employee(employee) |
Anyone | Number of streams paying an employee |
| Approach | Transactions | Approx. fee |
|---|---|---|
N individual create_stream calls |
N | N × base fee |
One create_streams_batch call |
1 | 1 × base fee + per-stream resource overhead |
create_streams_batch is cheaper for N ≥ 2 because Stellar charges one base fee per transaction. Per-stream resource overhead grows linearly but is far smaller than the per-transaction base fee saved.
Active → Paused → Active
Active → Cancelled
Active → Exhausted (deposit fully streamed, or stop_time passed — via withdraw or settle_stream)
claimable = min(
(now - last_withdraw_time) * rate_per_second,
deposit - withdrawn
)
Time is capped at stop_time if set. Paused time is excluded. See the worked examples.
- API Reference
- FAQ — common integrator questions
- Operations Runbook — admin procedures (nonce, pause, min deposit, admin transfer, upgrade)
- Migration Guide v0.1 → v0.2 — breaking changes, new error codes, and event schema changes
Current testnet deployments (source of truth: docs/testnet.md).
| Contract | Network | Address | Explorer Link |
|---|---|---|---|
| PayStream Token | Stellar Testnet | CDZQHVQHQMHIGJGSQIJVXGPGNZJQDQV4BBKMG7MSIDTJTTBBQYAEUPB |
StellarExpert |
| PayStream Stream | Stellar Testnet | CBXKDJUQYQDQKSQT6AKZRQWXDXTQHVXHBQQJQ3WKDXJHPNMRGYAQMRS |
StellarExpert |
Contract IDs change after every redeployment. When updating docs/testnet.md, update this table in the same PR.
./scripts/build.sh
./scripts/deploy-testnet.sh
export STELLAR_ADMIN_ADDRESS=<YOUR_PUBLIC_KEY>
export TOKEN_CONTRACT_ID=<FROM_DEPLOY>
export STREAM_CONTRACT_ID=<FROM_DEPLOY>
./scripts/init-testnet.shmake deploy-localEach GitHub Release includes two complementary supply-chain verification artifacts:
-
SLSA provenance (
paystream-contracts.intoto.jsonl) — generated byslsa-github-generator. Proves the released WASM files were built by this repository's CI from the tagged commit. -
Cosign keyless signatures (
*.cosign.bundle) — one bundle per WASM artifact, signed with Sigstore keyless signing during the release workflow. The bundle binds the artifact digest to the CI identity via the Sigstore transparency log.
# Install the verifier
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
# Download the WASM and provenance from the release, then verify
TAG=v1.0.0
gh release download "$TAG" -R veracindarella/paystream-contracts
slsa-verifier verify-artifact paystream_stream.wasm paystream_token.wasm \
--provenance-path paystream-contracts.intoto.jsonl \
--source-uri github.com/veracindarella/paystream-contracts \
--source-tag "$TAG"# Install cosign (https://docs.sigstore.dev/cosign/system_config/installation/)
# or: go install github.com/sigstore/cosign/v2/cmd/cosign@latest
TAG=v1.0.0
gh release download "$TAG" -R veracindarella/paystream-contracts
# Verify each WASM against its cosign bundle.
# The --certificate-identity-regexp matches the GitHub Actions workflow URL.
# The --certificate-oidc-issuer must be the Sigstore production OIDC issuer.
for wasm in paystream_stream.wasm paystream_token.wasm; do
cosign verify-blob \
--bundle "${wasm}.cosign.bundle" \
--certificate-identity-regexp "https://github.com/veracindarella/paystream-contracts/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"${wasm}"
done| Layer | Technology |
|---|---|
| Blockchain | Stellar (Soroban) |
| Language | Rust |
| SDK | Soroban SDK v22.0.0 |
| CI/CD | GitHub Actions |
We welcome contributions from the community.
- Developer Setup & Guidelines: Review CONTRIBUTING.md for environment setup, coding standards, and PR workflows.
- Bounty Program: PayStream maintains an active bounty track for open-source contributors across smart contracts, testing, security, and tooling. See docs/bounties.md for open bounties, criteria, and entry-level tasks tagged with
good-first-issue. - First-Time Contributors: Browse open beginner-friendly tasks in the Good First Issues Tracker.
- Questions & Community: Ask questions, share ideas, and show what you've built in GitHub Discussions — categories: Q&A, Ideas, Integrations, Show and Tell, Announcements.
- Roadmap: See docs/roadmap.md for planned milestones through mainnet launch.
See SECURITY.md for the full security policy, vulnerability reporting channels,
and the responsible disclosure rewards programme.
To rotate the admin keypair, follow the key rotation runbook.
Report vulnerabilities through a private GitHub security advisory
(or security@paystream.example — see SEC-32 / #270) — not via public issues.
Built with ❤️ on Stellar