Soroban smart contracts for Stellabill — prepaid USDC subscription billing on the Stellar network. This repository contains the on-chain logic for recurring payments, subscriber vaults, and merchant payouts.
- What’s in this repo
- Prerequisites
- Local setup
- Build, test, and deploy
- Contributing (open source)
- Project layout
- License
A single Soroban contract that implements a prepaid subscription vault for recurring USDC billing:
| Concept | Description |
|---|---|
| Subscriber | User who holds a subscription; funds are held in the contract (vault) for that subscription. |
| Merchant | Recipient of recurring payments; can withdraw accumulated USDC. |
| Subscription | Agreement between subscriber and merchant: amount, billing interval, status (active/paused/cancelled), and prepaid balance. |
Main capabilities (current / planned):
| Method | Signature | Auth | Docs |
|---|---|---|---|
version |
version(env: Env) -> u32 |
— | — |
init |
init(env: Env, token: Address, admin: Address, min_topup: i128) |
— | lifecycle |
create_subscription |
create_subscription(env: Env, subscriber: Address, merchant: Address, amount: i128, interval_seconds: u64, expiration: Option<u64>, usage_enabled: bool) -> u32 |
subscriber | lifecycle |
deposit_funds |
deposit_funds(env: Env, subscription_id: u32, subscriber: Address, amount: i128) |
subscriber | lifecycle |
charge_subscription |
charge_subscription(env: Env, subscription_id: u32) |
admin | lifecycle |
batch_charge |
batch_charge(env: Env, subscription_ids: Vec<u32>) -> BatchChargeResult |
admin | lifecycle |
cancel_subscription |
cancel_subscription(env: Env, subscription_id: u32, authorizer: Address) |
subscriber or merchant | lifecycle |
pause_subscription |
pause_subscription(env: Env, subscription_id: u32, authorizer: Address) |
subscriber or merchant | lifecycle |
resume_subscription |
resume_subscription(env: Env, subscription_id: u32, authorizer: Address) |
subscriber or merchant | lifecycle |
withdraw_merchant_funds |
withdraw_merchant_funds(env: Env, merchant: Address, amount: i128) |
merchant | lifecycle |
get_subscription |
get_subscription(env: Env, subscription_id: u32) -> Subscription |
— | — |
Types:
Subscription
| Field | Type | Description |
|---|---|---|
subscriber |
Address |
Owner of the subscription; must auth create and deposit. |
merchant |
Address |
Recipient of charges. |
amount |
i128 |
Charge per interval (in token base units). |
interval_seconds |
u64 |
Minimum time between charges. |
last_payment_timestamp |
u64 |
Ledger timestamp of last successful charge. |
status |
SubscriptionStatus |
Lifecycle state; see state machine below. |
prepaid_balance |
i128 |
Current balance; increased by deposit, decreased by charge. |
expiration |
Option<u64> |
Optional ledger timestamp after which the subscription is treated as expired. None means no expiry. |
usage_enabled |
bool |
Usage-billing flag; reserved for future use. |
SubscriptionStatus — Active, Paused, Cancelled, InsufficientBalance, GracePeriod.
See subscription_lifecycle.md for the full state machine and transition rules.
Error (selected variants — see docs/errors.md for the canonical table with numeric codes):
| Variant | Category | When |
|---|---|---|
Unauthorized |
Auth | Required signer or admin mismatch. |
NotFound |
Not found | Subscription id or resource is missing. |
NotInitialized |
Not found | Contract has not been initialized. |
InvalidAmount |
Invalid args | Amount is zero or negative. |
InvalidStatusTransition |
State | Lifecycle transition not permitted from current status. |
NotActive |
State | Operation requires Active subscription. |
SubscriptionExpired |
State | Subscription has passed its expiration timestamp. |
IntervalNotElapsed |
State | Charge attempted before interval has elapsed. |
InsufficientBalance |
Accounting | Vault balance is insufficient. |
BelowMinimumTopup |
Accounting | Deposit is below the configured min_topup threshold. |
Documentation: Subscription lifecycle and state machine — states, transitions, on-chain representation, and invariants. Error codes — canonical error taxonomy with numeric codes and retry guidance.
Implementation status:
contracts/subscription_vault/src/lib.rscurrently exposes only theversion()stub while the full implementation is rewritten on a future branch. The method table and type definitions above reflect the intended API as methods land. See the inline doc-comment inlib.rsfor context.
- Rust (latest stable, e.g. 1.75+):
https://rustup.rs
rustup default stable - Soroban CLI:
https://developers.stellar.org/docs/tools/soroban-cli/install
Used to build WASM and run tests/deploy. - Stellar / Soroban basics:
https://developers.stellar.org/docs
Optional but helpful for contributing.
git clone https://github.com/YOUR_ORG/stellabill-contracts.git
cd stellabill-contracts(Replace YOUR_ORG with the actual org or user.)
- Install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shthenrustup default stable. - Install Soroban CLI per the official install guide.
rustc --version
cargo --version
soroban --versioncargo build
cargo testFrom the repo root, this builds the workspace and runs the contract unit tests (including subscription_vault tests in contracts/subscription_vault/src/test.rs).
soroban contract buildThis produces the WASM under target/ for deployment to Stellar (e.g. testnet/mainnet via Soroban CLI or your CI/CD).
| Task | Command |
|---|---|
| Build workspace | cargo build |
| Run tests | cargo test |
| Build contract WASM | soroban contract build |
| Run with Soroban CLI (e.g. testnet) | See Stellar docs for soroban contract deploy and invoke. |
We welcome contributions from the community. Here’s how to get started and how we work.
- Read this README and the Stellar / Soroban docs.
- Check GitHub Issues for “good first issue” or “help wanted” labels.
- If you want to change behavior or add a feature, open an issue first so we can align on design.
- Fork the repo on GitHub and clone your fork.
- Create a branch from
main(or default branch):
git checkout -b feature/your-featureorfix/your-fix. - Set up locally as in Local setup. Run
cargo testandcargo buildto ensure everything passes. - Make changes in small, logical commits. Keep messages clear (e.g. “Add admin check to charge_subscription”, “Fix subscription id overflow”).
- Run tests and build before pushing:
cargo test && cargo buildand, if you touch contract interface,soroban contract build. - Push to your fork and open a Pull Request against the upstream
main(or default branch).
- Scope: One logical change per PR when possible (easier review and atomic history).
- Tests: New behavior should be covered by unit tests in the contract crate; existing tests must stay green.
- Docs: If you add or change a public function or type, update the README or inline docs as needed.
- Description: Use the PR description to explain the “why” and how to verify (e.g. steps or test commands).
- Rust: Follow common Rust style (
cargo fmt,cargo clippy). Nounwrap()in contract logic without a clear justification; preferResultand explicit errors. - Soroban: Use
Envfor storage and auth; keep contract functions narrow and well-documented. - Security: Any change that touches auth, token transfers, or admin rights will get extra review. When in doubt, open an issue first.
- Questions: Open a GitHub Discussion or an issue with the “question” label.
- Bugs: Open an issue with steps to reproduce, environment (Rust/Soroban versions), and logs if relevant.
- Ideas: Use Discussions or an issue with “enhancement” so we can track and discuss.
We expect all contributors and maintainers to be respectful and inclusive. By participating, you agree to uphold a constructive and professional environment. Specific CoC details (if any) will be linked in the repo (e.g. CODE_OF_CONDUCT.md or in the GitHub community guidelines).
stellabill-contracts/
├── Cargo.toml # Workspace root; lists contract crates
├── Cargo.lock # Locked dependencies (reproducible builds)
├── README.md # This file
├── .gitignore
├── docs/ # Contract documentation
│ ├── subscription_lifecycle.md # Subscription lifecycle, state machine, on-chain representation
│ ├── subscription_state_machine.md
│ ├── batch_charge.md
│ ├── billing_intervals.md
│ ├── topup_estimation.md
│ └── safe_math.md
└── contracts/
└── subscription_vault/ # Prepaid subscription vault contract
├── Cargo.toml
└── src/
├── lib.rs # Contract logic and types
└── test.rs # Unit tests
See the LICENSE file in this repository (add one if not present). Contributions are accepted under the same license.