A minimal MVP marketplace where farmers list products and buyers pay using the Stellar Network (XLM).
- Frontend: React + Vite
- Backend: Node.js + Express
- Database: SQLite (local dev, default) / PostgreSQL (production)
- Payments: Stellar Testnet (XLM)
FarmersMarketplace/
├── backend/
│ ├── src/
│ │ ├── index.js # Express app entry
│ │ ├── stellar.js # Stellar SDK helpers
│ │ ├── middleware/auth.js
│ │ ├── db/schema.js # SQLite schema + connection
│ │ └── routes/
│ │ ├── auth.js # register, login
│ │ ├── products.js # CRUD listings
│ │ ├── orders.js # place order + pay
│ │ └── wallet.js # balance, transactions, fund
│ └── package.json
└── frontend/
├── src/
│ ├── App.jsx
│ ├── api/client.js # API wrapper
│ ├── context/AuthContext.jsx
│ ├── components/Navbar.jsx
│ └── pages/
│ ├── Auth.jsx # Login + Register
│ ├── Dashboard.jsx # Farmer: add/view products
│ ├── Marketplace.jsx # Buyer: browse
│ ├── ProductDetail.jsx # Buy flow
│ └── Wallet.jsx # Balance + transactions
└── package.json
cd backend
cp .env.example .env
npm install
npm run devRuns on http://localhost:4000
cd frontend
npm install
npm run devRuns on http://localhost:3000
- Register as a buyer and a farmer (two separate accounts)
- Go to Wallet → click "Fund with Testnet XLM" (uses Stellar Friendbot, free testnet tokens)
- As a farmer, go to Dashboard and list a product priced in XLM
- As a buyer, browse the Marketplace, open a product, set quantity, click Buy Now
- The backend signs and submits a real Stellar transaction on testnet
- View the transaction hash in Wallet → Transaction History or on stellar.expert
Interactive API documentation is available at http://localhost:4000/api/docs when the backend is running.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register | — | Register user |
| POST | /api/auth/login | — | Login |
| GET | /api/products | — | Browse all products |
| GET | /api/products/:id | — | Product detail |
| POST | /api/products | farmer | Create listing |
| GET | /api/products/mine/list | farmer | My listings |
| DELETE | /api/products/:id | farmer | Remove listing |
| POST | /api/orders | buyer | Place + pay order |
| GET | /api/orders | buyer | Order history |
| GET | /api/orders/sales | farmer | Incoming sales |
| GET | /api/wallet | auth | Balance |
| GET | /api/wallet/transactions | auth | TX history |
| POST | /api/wallet/fund | auth | Fund via Friendbot (testnet) |
| GET | /api/contracts/:contractId/state?prefix= | auth | View Soroban contract storage entries (JSON: key, val, durability) |
Schema changes are managed through versioned SQL migration files in backend/migrations/.
cd backend
npm run migrate # apply all pending migrations
npm run migrate:rollback # revert the last applied migrationMigrations run automatically on app startup — no manual step needed for development.
- Migration files:
backend/migrations/NNN_description.sql - Rollback files:
backend/migrations/NNN_description.undo.sql(optional) - Applied migrations are tracked in a
migrationstable in the database - Running
migratetwice is safe — already-applied migrations are skipped
# Up migration
echo "ALTER TABLE products ADD COLUMN featured INTEGER DEFAULT 0;" \
> backend/migrations/002_add_featured.sql
# Rollback (optional)
echo "ALTER TABLE products DROP COLUMN IF EXISTS featured;" \
> backend/migrations/002_add_featured.undo.sql
npm run migrateThe application includes automated database backup functionality to protect against data loss.
Create a timestamped backup of the database:
cd backend
npm run backupThis creates a backup file in backend/backups/ with format market-YYYY-MM-DD.db.
Restore the database from a backup file:
cd backend
npm run restore -- backups/market-2024-01-01.dbImportant: Before restoring, the current database is automatically backed up to market.db.backup.
- Backups run automatically every day at midnight UTC
- Only the last 7 backups are retained (older ones are automatically deleted)
- Backup status and errors are logged using the structured logging system
- Backup files are stored in:
backend/backups/ - File naming convention:
market-YYYY-MM-DD.db - Maximum retention: 7 days
- Quick Restore: Use
npm run restorewith the desired backup file - Emergency Recovery: Copy
market.db.backup(created before restore) back tomarket.db - Complete Reset: Delete
market.dband restart the application (fresh schema)
The backend supports both SQLite (local dev) and PostgreSQL (production), controlled by the DATABASE_URL environment variable.
No extra setup needed. SQLite is used automatically when DATABASE_URL is not set.
- Add
DATABASE_URLto your.env:DATABASE_URL=postgresql://user:password@localhost:5432/farmersmarketplace - The schema is created automatically on first start.
cp backend/.env.example backend/.env
# Edit backend/.env — set JWT_SECRET etc.
docker compose upThis starts:
postgres— PostgreSQL 16 on port 5432backend— Express API on port 4000 (connected to postgres)frontend— React app on port 3000
DATABASE_URL=postgresql://user:pass@host:5432/dbname \
node backend/scripts/migrate-sqlite-to-pg.jsTest Soroban contracts against a local Stellar node using the built-in test harness.
docker-compose -f docker-compose.test.yml up -dThis starts a stellar/quickstart node on port 8000 with Soroban RPC enabled.
cd backend
npm run test:contractsbackend/src/__tests__/helpers/soroban.js exposes:
fundAccount(publicKey)— fund via local FriendbotdeployContract(wasmBuffer, keypair)— upload WASM and create contract instanceinvokeContract(contractId, method, args, keypair)— call a contract function
| Variable | Default | Description |
|---|---|---|
TEST_HORIZON_URL |
http://localhost:8000 |
Local Horizon endpoint |
TEST_SOROBAN_RPC_URL |
http://localhost:8000/soroban/rpc |
Local Soroban RPC |
TEST_NETWORK_PASSPHRASE |
Standalone Network ; February 2017 |
Local network passphrase |
SKIP_CONTRACT_TESTS |
false |
Set to true to skip contract tests in CI without Docker |
Contract tests require a running local Stellar node (Docker). In CI environments where Docker is not available, set SKIP_CONTRACT_TESTS=true to skip the suite without failing the build:
SKIP_CONTRACT_TESTS=true npm run test:contractsWhen skipped in CI, a warning is printed to the log so the omission is visible.
A dedicated nightly CI job (contract-tests-nightly in .github/workflows/ci.yml) runs the full contract test suite on a schedule with Docker available, ensuring these tests are not silently broken.
The script contract/test-futurenet.sh runs a full end-to-end test of the escrow contract on Stellar Futurenet using real XLM transfers.
| Test | Flow | Assertion |
|---|---|---|
| 1 — Happy path | deposit → release | Farmer balance increases by deposit − platform_fee |
| 2 — Dispute refund | deposit → open_dispute → resolve(buyer) | Buyer balance recovers the deposited amount |
| 3 — Dispute to farmer | deposit → open_dispute → resolve(farmer) | Farmer balance increases |
stellarCLI installed and in$PATHcurlandjq- Compiled WASM (see build step below)
# 1. Build the contract WASM
cd contract
cargo build --target wasm32-unknown-unknown --release
# 2. Run the Futurenet E2E test (takes ~2–3 minutes)
./contract/test-futurenet.shThe script:
- Generates four ephemeral Stellar keypairs (admin, buyer, farmer, arbitrator)
- Funds each via Futurenet Friendbot
- Deploys the contract to Futurenet and calls
initialize - Runs the three test flows above
- Asserts balance changes match expected amounts (±100 stroops tolerance for network fees)
- Exits non-zero on any failed assertion
- Cleans up ephemeral keys on exit
| Variable | Default | Description |
|---|---|---|
NETWORK |
futurenet |
Stellar network alias |
WASM_PATH |
auto-detected | Path to compiled .wasm |
FEE_BPS |
250 |
Platform fee in basis points (2.5%) |
DEPOSIT_XLM |
1 |
Deposit amount in XLM |
TIMEOUT_SECS |
7200 |
Escrow timeout offset (seconds from now) |
SKIP_BUILD |
0 |
Set to 1 to skip cargo build |
DEPOSIT_XLM=2 FEE_BPS=500 SKIP_BUILD=1 ./contract/test-futurenet.shThe contract/ directory contains a Soroban smart contract that provides on-chain escrow for marketplace orders.
| Function | Description |
|---|---|
deposit(order_id, buyer, farmer, amount, timeout_unix) |
Lock funds in escrow |
release(order_id) |
Buyer releases funds to farmer |
refund(order_id) |
Anyone refunds buyer after timeout |
get_escrow(order_id) |
Read-only view of an escrow record |
| Error | Meaning |
|---|---|
AlreadyExists |
Duplicate deposit for same order_id |
NotFound |
No escrow record for order_id |
Unauthorized |
Caller not permitted |
NotTimedOut |
Refund called before timeout |
AlreadySettled |
Escrow already released or refunded |
InvalidParties |
buyer and farmer must be different addresses |
cd contract
cargo test --features testutils
cargo build --target wasm32-unknown-unknown --release- #468 — Every function that reads/writes an escrow entry calls
extend_ttl(TTL_MIN=100_000, TTL_MAX=200_000)so entries never expire and lock funds. - #469 —
depositrejects calls wherebuyer == farmerwithEscrowError::InvalidParties. - #470 —
depositpanics iftimeout_unixis not at least 1 hour (3600 s) in the future. - #471 —
deposit,release, andrefundeach emit a Soroban event so the backend can subscribe to the RPC event stream instead of polling.
The project supports English (en.json) and Swahili (sw.json) via react-i18next. A CI script enforces key parity between the two locale files.
cd frontend
node scripts/check-i18n-sync.jsThis script fails if sw.json is missing any keys present in en.json. It is also run as part of the test suite (i18nSync.test.js).
- Add the English string to
src/i18n/en.json. - Add the corresponding Swahili translation to
src/i18n/sw.json. - Run
node scripts/check-i18n-sync.jsto verify parity. - Run
npx vitest run src/test/i18nSync.test.jsto confirm the test passes.
Missing Swahili translations will fall back to the English key string, showing raw translation keys to Swahili users. Always keep both locale files in sync.
- Stellar wallets are auto-created on registration
- All payments use XLM on Stellar Testnet — no real money involved
- SQLite database file (
market.db) is created automatically on first run (whenDATABASE_URLis not set) - To reset SQLite: delete
backend/market.db
See CONTRIBUTING.md for full guidance on:
- Local dev environment setup (Rust toolchain, Stellar CLI)
- Build and test commands
- Lint requirements (
cargo fmt,cargo clippy,cargo audit) - Branch naming and Conventional Commit format
- PR requirements and review process
- Issue workflow and label guide
For security vulnerabilities, follow the process in SECURITY.md instead of opening a public issue.