Real payments, on Stellar.
Connect a Freighter wallet, send XLM and USDC on the Stellar testnet, and tip a Soroban smart contract — with an Express service layer and a React frontend.
Payflow is a non-custodial Stellar dApp. The backend never holds a secret key: it builds unsigned transactions, the browser signs them with Freighter, and the signed transaction is submitted to the network. Balances and history are read live from Horizon; the Tip Jar is a Soroban contract written in Rust.
- What it does
- Architecture
- Stack
- Repo layout
- Prerequisites
- Local setup
- API summary
- The Soroban Tip Jar
- Tests
- Security notes
- Contributing
- Connect Freighter — your Stellar public key is your identity (no passwords).
- Fund on testnet — one click funds a new account with Friendbot.
- See live balances — XLM and USDC, read from Horizon.
- Add a USDC trustline — sign a
changeTrusttransaction. - Send payments — build → sign in Freighter → submit a real on-chain XLM/USDC payment.
- Review activity — payment history read from the ledger, with explorer links.
- Tip a smart contract — call
tip()/withdraw()on a Soroban Tip Jar. - Address book — save friendly labels for addresses (the only app-stored data).
Browser (React) Express API Stellar testnet
───────────────── ─────────────── ────────────────
Freighter ──sign──┐ /api/tx/payment ──build XDR──► (unsigned)
│ /api/tx/submit ──submit────► Horizon / Soroban RPC
build ──────────► POST ────► /api/account/:pk ──read──────► Horizon
request │ /api/payments/:pk ──read──────► Horizon
│ /api/tipjar/... ──simulate──► Soroban RPC
signed XDR ────────┘
The signing key stays inside Freighter. The backend only ever sees unsigned XDR (to build) and signed XDR (to submit) — never a secret.
- Backend: Node.js + Express,
@stellar/stellar-sdk(Horizon + Soroban RPC) - Frontend: React + Vite + Tailwind,
@stellar/freighter-api - Contract: Rust +
soroban-sdk(the Tip Jar) - Storage: local JSON address book by default; Postgres when
DATABASE_URLis set - Tests: Jest + Supertest (backend),
cargo test(contract)
src/— Express app, Stellar service layer (lib/stellar.js), routes, contacts modelfrontend/— React client (Freighter connect, balances, send, activity, tip jar)contracts/tip-jar/— the Soroban Tip Jar contract (Rust) + teststests/— backend request tests (Horizon mocked, runs offline)docs/— maintainer notes and Wave issue ideas
- Node.js ≥ 18
- The Freighter browser extension, set to Testnet
- (Only to rebuild the contract) Rust + the Stellar CLI
npm install
cd frontend && npm install && cd ..cp .env.example .env
cp frontend/.env.example frontend/.envThe defaults already target the Stellar testnet. Set TIPJAR_CONTRACT_ID once you've
deployed the contract (see below). DATABASE_URL is optional — without it the address
book uses a local JSON file.
# terminal 1 — API
npm run dev
# terminal 2 — frontend
cd frontend && npm run devOpen the frontend, make sure Freighter is on Testnet, click Connect, then Fund with Friendbot — and send your first payment.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/network |
Network config for the frontend |
GET |
/api/account/:publicKey |
Balances + funded status (Horizon) |
POST |
/api/account/:publicKey/fund |
Fund via Friendbot (testnet) |
GET |
/api/payments/:publicKey |
Payment history (Horizon) |
POST |
/api/tx/payment |
Build an unsigned payment XDR |
POST |
/api/tx/change-trust |
Build an unsigned trustline XDR (USDC) |
POST |
/api/tx/submit |
Submit a signed XDR (Horizon or Soroban RPC) |
GET |
/api/tipjar/balance |
Read the Tip Jar contract balance |
POST |
/api/tx/tip |
Build a tip() invocation |
POST |
/api/tx/tip-withdraw |
Build a withdraw() invocation |
GET/POST/DELETE |
/api/contacts... |
Address book CRUD |
A small Rust contract that holds a token (the native XLM SAC). Anyone can tip(); only the
owner can withdraw(). Build, test, and deploy instructions are in
contracts/tip-jar/README.md. After deploying, set
TIPJAR_CONTRACT_ID in .env and the Tip Jar page lights up. Running the app does not
require Rust — only rebuilding the contract does.
npm test # backend (Horizon mocked — offline, fast)
cd contracts/tip-jar && cargo test # the Soroban contract- The backend is non-custodial — it never sees or stores a secret key.
- All signing happens in Freighter; the API handles only unsigned/signed XDR.
- This targets testnet. Do not point it at mainnet without a security review.
- No secrets are logged;
.envis git-ignored.
See CONTRIBUTING.md. Issues are labelled for newcomers; the codebase
is split into small, well-scoped areas (a route, a page, a contract function) to make
first contributions easy.