StellarStream is a basic payment-streaming MVP for the Stellar ecosystem.
It includes:
- A React dashboard to create and monitor streams
- A Node.js/Express API for stream lifecycle operations
- A Soroban smart contract scaffold for on-chain stream logic
- A backlog folder with implementation task drafts
This repository is intentionally lightweight and easy to extend.
StellarStream models a payment stream where a sender allocates a total amount over a fixed duration.
As time passes, the recipient "vests" value continuously.
Current MVP behavior:
- Create stream
- List streams with live progress
- Cancel stream
- Show computed metrics (active/completed/vested)
- Track and display event history for stream lifecycle actions
Frontend (frontend, port 3000)
- React + Vite app
- Uses
/apiproxy to call backend - Polls stream list every 5 seconds
Backend (backend, port 3001)
- Express REST API
- SQLite database for persistent storage
- Event indexer worker for tracking stream lifecycle
- Computes progress in real time from timestamps
Contract (contracts)
- Soroban contract scaffold in Rust
- Supports
create_stream,claimable,claim, andcancel - Not yet integrated with backend runtime in this MVP
For each stream:
totalAmountstartAtdurationSecondsend = startAt + durationSeconds
At time t:
elapsed = clamp(t - startAt, 0, durationSeconds)ratio = elapsed / durationSecondsvested = totalAmount * ratioremaining = totalAmount - vested
Status rules:
scheduledwhent < startAtactivewhenstartAt <= t < endcompletedwhent >= endcanceledwhen stream was canceled
Base URL:
- Local:
http://localhost:3001 - Frontend proxy:
/api
Purpose:
- Service health check
Response:
service,status,timestamp
Purpose:
- List streams sorted by newest first, with optional filtering and pagination
Query params (optional):
status: scheduled | active | completed | canceledsender: string(exact sender match)recipient: string(exact recipient match)asset: string(exact asset code match)q: string(general search term - searches stream ID, sender, recipient, and asset code, case-insensitive)page: number(integer>= 1)limit: number(integer1..100)
Search behavior:
- The
qparameter performs case-insensitive partial matching across stream ID, sender, recipient, and asset code - Search combines with other filters (all filters are applied together)
- Empty or whitespace-only search terms are ignored
Pagination behavior:
- If both
pageandlimitare omitted, legacy mode applies and all matching rows are returned. - If either
pageorlimitis provided, pagination mode applies with defaultspage=1andlimit=20.
Validation:
- Invalid
status,page, orlimitreturns400.
Response:
data: Stream[](includes computedprogress)total: number(filtered count before pagination)page: number(applied page)limit: number(applied page size)
Purpose:
- Fetch single stream by ID
Response:
data: Stream
Error:
404if stream does not exist
Purpose:
- Fetch all streams for a specific recipient account
Path parameters:
accountId: string(Stellar account ID starting with G, exactly 56 characters)
Validation:
- Account ID must be a valid Stellar account ID format
Response:
data: Stream[](includes computedprogressfor each stream)
Error:
400if account ID is invalid
Purpose:
- Fetch the current allowed asset allowlist
Response:
data: string[](normalized asset codes)
Purpose:
- Create a new stream
Request JSON:
sender: stringrecipient: stringassetCode: stringtotalAmount: numberdurationSeconds: number(minimum 60)startAt?: number(unix seconds)
Validation:
- Sender/recipient must be non-trivial strings
- Asset length must be 2..12
- Amount must be positive
- Duration must be at least 60 seconds
Response:
201withdata: Stream
Purpose:
- Cancel an existing stream
Response:
data: Streamwith canceled state
Error:
404if stream does not exist
Purpose:
- Returns implementation backlog items shown in UI
Response:
data: OpenIssue[]
Purpose:
- Fetch event history timeline for a specific stream
Response:
data: StreamEvent[](ordered by timestamp ascending)
Event types:
created: Stream was createdclaimed: Tokens were claimed from the streamcanceled: Stream was canceledstart_time_updated: Start time was modified
Each event includes:
id: Event IDstreamId: Associated stream IDeventType: Type of eventtimestamp: Unix timestamp when event occurredactor: Account that triggered the event (optional)amount: Amount involved (optional, for created/claimed)metadata: Additional context (optional)
Contract file:
contracts/src/lib.rs
Data:
NextStreamIdStream(stream_id) -> Stream
Implemented methods:
create_stream(...) -> u64get_stream(stream_id) -> Streamclaimable(stream_id, at_time) -> i128claim(stream_id, recipient, amount) -> i128cancel(stream_id, sender)
Important note:
claimcurrently updates accounting only.- Token transfer wiring is planned as next implementation step.
Prerequisites:
- Node.js 18+
- npm 9+
- Optional for contract work: Rust + Soroban toolchain
From repo root:
npm run install:all
npm run dev:backend
npm run dev:frontendManual alternative:
cd backend && npm install && npm run dev
cd frontend && npm install && npm run devOpen:
- Frontend:
http://localhost:3000 - Backend:
http://localhost:3001
Build:
npm run buildDeploy the Soroban contract to Stellar testnet.
- soroban-cli installed
- Rust toolchain with
wasm32-unknown-unknowntarget - Stellar testnet account with secret key
Set the SECRET_KEY environment variable and run:
SECRET_KEY="S..." npm run deploy:contractOr use the script directly:
SECRET_KEY="S..." ./scripts/deploy.shThe script will:
- Build the contract
- Deploy to Stellar testnet
- Output the contract ID
- Save the contract ID to
contracts/contract_id.txt
Required:
SECRET_KEY- Stellar account secret key for deployment (must have testnet XLM for fees)
Optional:
NETWORK_PASSPHRASE- Network passphrase (defaults to testnet:"Test SDF Network ; September 2015")RPC_URL- RPC endpoint URL (defaults tohttps://soroban-testnet.stellar.org:443)
- Copy the contract ID from the output or
contracts/contract_id.txt - Set
CONTRACT_IDin your backend.envfile - Ensure
SERVER_PRIVATE_KEYis set in your backend.envfile - Restart your backend service
Copy backend/.env.example to backend/.env and fill in the values before starting the server.
The backend validates all environment variables at startup. If a required variable is missing or malformed, the process exits immediately with a descriptive error message rather than failing silently at runtime.
| Mode | When to use | How to enable |
|---|---|---|
| Soroban enabled (default) | Full on-chain integration — contract deployed, indexer running | Set CONTRACT_ID and SERVER_PRIVATE_KEY |
| Soroban disabled | Local UI/API development without a deployed contract | Set SOROBAN_DISABLED=true |
⚠️ SOROBAN_DISABLED=trueis for local development only. Never set it in production or staging.
| Variable | Required | Default | Description |
|---|---|---|---|
SOROBAN_DISABLED |
No | false |
Set to "true" to skip Soroban checks and run off-chain |
CONTRACT_ID |
Yes (unless SOROBAN_DISABLED=true) |
— | Soroban contract ID from deployment (56 chars, starts with C) |
SERVER_PRIVATE_KEY |
Yes (unless SOROBAN_DISABLED=true) |
— | Stellar secret key for signing transactions (56 chars, starts with S) |
PORT |
No | 3001 |
Port the Express API listens on |
RPC_URL |
No | https://soroban-testnet.stellar.org:443 |
Soroban RPC endpoint |
NETWORK_PASSPHRASE |
No | Test SDF Network ; September 2015 |
Stellar network passphrase |
ALLOWED_ASSETS |
No | USDC,XLM |
Comma-separated list of allowed asset codes |
DB_PATH |
No | backend/data/streams.db |
Path to the SQLite database file |
WEBHOOK_DESTINATION_URL |
No | — | HTTP(S) URL for stream lifecycle webhook delivery |
WEBHOOK_SIGNING_SECRET |
No | — | Secret for HMAC-SHA256 webhook payload signing |
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_API_URL |
No | /api |
Backend API base URL |
- Header:
X-Webhook-Signature - Format:
sha256=<hex-digest> - Digest input: raw JSON request body string
- Algorithm: HMAC-SHA256 using
WEBHOOK_SIGNING_SECRET
If WEBHOOK_DESTINATION_URL is set without WEBHOOK_SIGNING_SECRET, webhooks are delivered unsigned and a warning is logged at startup.
The server validates config before doing anything else:
- Missing
CONTRACT_IDorSERVER_PRIVATE_KEY(Soroban enabled) → exits with a message explaining how to deploy the contract and where to set the value. - Malformed
CONTRACT_ID(not 56 chars / not starting withC) → exits with a format hint. - Malformed
SERVER_PRIVATE_KEY(not 56 chars / not starting withS) → exits with a format hint. - Invalid
RPC_URLorWEBHOOK_DESTINATION_URL→ exits with the bad value shown. SOROBAN_DISABLED=true→ logs a notice and skips all Soroban checks; the event indexer does not start.
See backend/src/config/validateEnv.ts for the full validation logic.
Root:
.gitignore: ignore rules for Node/Rust/local files.package.json: root helper scripts (install/build/dev delegates).README.md: project documentation.
GitHub templates:
.github/ISSUE_TEMPLATE/config.yml: issue template behavior..github/ISSUE_TEMPLATE/project-task.md: reusable issue template file.
Scripts:
scripts/deploy.sh: builds and deploys the Soroban contract to testnet.scripts/generate-contract-bindings.sh: generates TypeScript bindings from a deployed contract.
Docs:
docs/CONTRACT_BINDINGS.md: full workflow for generating and consuming contract bindings.
Backend:
backend/package.json: backend dependencies and scripts.backend/tsconfig.json: backend TypeScript compiler config.backend/src/index.ts: API server, route handlers, request validation.backend/src/config/validateEnv.ts: startup environment variable validation.backend/src/services/streamStore.ts: stream store + progress math + Soroban integration.backend/src/services/db.ts: SQLite database initialization and schema.backend/src/services/eventHistory.ts: event recording and retrieval functions.backend/src/services/indexer.ts: background worker for indexing contract events.backend/src/services/auth.ts: authentication middleware and JWT handling.backend/src/services/openIssues.ts: backlog entries returned by API.
Frontend:
frontend/index.html: Vite HTML entry.frontend/package.json: frontend dependencies and scripts.frontend/postcss.config.js: PostCSS plugin config.frontend/tailwind.config.js: Tailwind config (kept for styling extension).frontend/tsconfig.json: frontend TypeScript config.frontend/tsconfig.node.json: TS config for Vite/node-side files.frontend/vite.config.ts: dev server config + backend API proxy.frontend/src/main.tsx: React app bootstrap.frontend/src/App.tsx: top-level layout, polling, metrics, handlers.frontend/src/index.css: app styles.frontend/src/services/api.ts: typed API client functions.frontend/src/services/contractClient.ts: thin wrapper around generated contract client.frontend/src/contracts/generated/: gitignored — TypeScript bindings generated by npm run gen:bindings.frontend/src/types/stream.ts: shared frontend data types.frontend/src/components/CreateStreamForm.tsx: stream creation form.frontend/src/components/StreamsTable.tsx: stream list and cancel actions.frontend/src/components/StreamTimeline.tsx: event history timeline display.frontend/src/components/IssueBacklog.tsx: backlog panel renderer.
Contract:
contracts/Cargo.toml: Rust crate and Soroban dependency config.contracts/src/lib.rs: Soroban contract implementation scaffold.
- Contract is not fully connected to backend execution path yet.
- Wallet sign/transaction flow is not active yet in UI.
- No authentication layer on write endpoints.
- Test coverage and CI can be expanded.
- Event indexer polls every 10 seconds (configurable).
- Contract bindings
(frontend/src/contracts/generated/)must be regenerated locally after each deployment.
- Move stream source of truth from memory to Soroban state.
- Add wallet-authenticated transaction signing flow.
- Wire
frontend/src/services/contractClient.tsusing generated bindings to call create_stream, claim, and cancel directly from the frontend. - Add contract tests and backend integration tests.
- Enhance event history with claim events from contract.
- Add real-time event notifications via WebSockets.
- Automate binding regeneration in CI after each contract deployment.