What it does: SplitLock is a Web3 dApp on Stellar that lets a payer lock a token into an on-chain escrow and later either split it proportionally across multiple recipients or refund it — a reusable "escrow split-payment" primitive for payroll, group payments, dividends, reimbursements, and any scenario where funds should be held safely before distribution.
🌱 New here? Start with
note.md(onboarding) and grab a good first issue. The full task list lives infront-end.md,back-end.md, andcontracts.md.
- A payer creates a payment: they pick a Stellar asset (token contract), an amount, and a list of recipients with shares (e.g. 1:3 splits the amount 25% / 75%).
- The full amount is pulled from the payer into the contract escrow immediately.
- Later, the payer releases — the contract sends each recipient their share (the last recipient absorbs any rounding remainder so the full amount is always distributed) — or refunds the whole amount back to the payer.
- Each payment is viewable by id, and a payer can list all their payments.
All value-moving actions require payer authorization (via Freighter) and a payment can only be settled once (release and refund are mutually exclusive).
splitlock/
├── contracts/ # Soroban smart contract (Rust)
│ ├── src/lib.rs # Payments contract: create / release / refund / get / list / payment_count / stats
│ ├── src/test.rs # 14 unit tests (escrow, refund, double-settle, guards, events)
│ └── Cargo.toml
├── frontend/ # React + Vite + TypeScript dApp
│ └── src/ # App, wallet hook (Freighter), contract client, shared types, error boundary
├── backend/ # Node indexer + REST API (read-only)
│ ├── src/ # config, db (SQLite), stellar RPC client, indexer, server
│ └── openapi.yaml # API contract
├── scripts/ # deploy.sh, test.sh, invoke.sh helpers
├── .github/ # CI, issue/PR templates, dependabot
├── AGENTS.md # guidance for AI coding agents
├── CONTRIBUTING.md # how to pick up work + open a PR
├── SECURITY.md # vulnerability reporting policy
└── README.md
Payments (Soroban / soroban-sdk 23) is the on-chain core. It holds funds and
enforces the rules:
| Method | Auth | What it does |
|---|---|---|
create(payer, token, recipients, shares, amount) |
payer | Pulls amount of token into escrow and records a proportional split. |
release(payment_id) |
payer | Sends each recipient their share; full amount always distributed. |
refund(payment_id) |
payer | Returns the full amount to the payer. |
get(payment_id) |
none | Read-only view of a payment. |
list(payer) |
none | All payment ids created by payer (powers "My Payments"). |
payment_count() |
none | Total payments ever created (used by the indexer). |
stats() |
none | { count, volume } aggregate escrowed volume. |
Validation & safety: rejects empty/duplicate recipients, mismatched
recipient/share counts, non-positive amounts, zero shares, and more than 50
recipients. Release/refund are blocked once a payment is already settled.
Typed #[contracterror] codes make failures easy for clients to map. Emits
events on create/release/refund.
A React + Vite + TypeScript app using @stellar/stellar-sdk and the
Freighter browser wallet. It lets users:
- Connect Freighter (auto-detects, with an install link if missing).
- Create an escrow split-payment with live per-recipient share %, a "split equally" shortcut, and inline validation (address format, positive amount, unique recipients, payer ≠ recipient).
- Look up any payment by id and see its state with a colored status badge.
- Release or refund (signed via Freighter), with a refresh button and Stellar Expert links for transactions/contracts.
- View "My Payments" (newest first) and get friendly error messages mapped from contract failures.
A read-only off-chain service (no signing, no keys) that makes the dApp fast and queryable:
- Indexer (
indexer.js) pollspayment_count()+get(id)and mirrors every payment into SQLite — so the UI/API don't hammer RPC on every load. - REST API (
server.js, Express) over the indexed data:GET /health— liveness + active network/contract.GET /payments— list, filterable bypayer/released, paginated.GET /payments/:id— single payment.GET /stats—{ count, volume, releasedCount }.- CORS + per-IP rate limiting, configurable via env.
- See
backend/openapi.yamlfor the full contract andbackend/docs/operations.mdfor the deploy→invoke flow and incident runbook.
cd contracts
cargo build --target wasm32v1-none --release
cargo testNote:
soroban-env-host 23.0.1pulls a brokened25519-dalek 3.0.0.Cargo.tomlpins a workinged25519-dalekvia a[patch.crates-io]git source. Keep that patch when updating dependencies.WASM output:
contracts/target/wasm32v1-none/release/stellar_dapp_contract.wasm
cd frontend
npm install
VITE_CONTRACT_ADDRESS=<contract_id> VITE_NETWORK=futurenet npm run devSupported VITE_NETWORK values: futurenet (default), testnet.
cd backend
npm install
cp .env.example .env # set CONTRACT_ID + BACKEND_NETWORK
npm run migrate # create the SQLite DB
npm run index # start indexing (in foreground)
# in another shell:
npm start # start the REST API on :3000Requires the Stellar CLI and a funded test account.
./scripts/deploy.sh # Futurenet
NETWORK=testnet ./scripts/deploy.sh # TestnetThe script builds the wasm, deploys, verifies the contract is live, prints the
contract id, and writes it to .contract/<network>.id.
- Deploy the contract → get a contract id.
- Configure
VITE_CONTRACT_ADDRESS(andCONTRACT_IDfor the backend). - Open the dApp, connect Freighter (a Futurenet/Testnet account holding the token).
- Create a payment → funds are held in escrow.
- Release (split to recipients) or Refund (back to payer).
- The indexer picks up the payment and serves it via the REST API.
- Contracts: Rust, Soroban SDK 27,
wasm32v1-none - Frontend: React 18, Vite 5, TypeScript,
@stellar/stellar-sdk13 - Wallet: Freighter
- Backend: Node 20, Express, SQLite (
better-sqlite3),@stellar/stellar-sdk - CI: GitHub Actions (contract fmt/clippy/test/wasm, frontend typecheck/build,
cargo audit+npm audit) - Tooling: Stellar CLI 27
SplitLock is open source and welcomes contributors of all levels. Pick any unchecked item from the issue tracker files and open a PR:
front-end.md— UI/UX, wallet, a11y, toolingback-end.md— deploy scripts, indexer/API, CI, configcontracts.md— contract logic, storage, tests
Read CONTRIBUTING.md for the full workflow, and
SECURITY.md for how to report vulnerabilities. All
contributions are released under the MIT License.
MIT © 2026 Adebanjo Ayo