A full-stack decentralized application (dApp) built on the Stellar Network using Soroban Smart Contracts. This marketplace allows administrators to tokenize real-world assets into fractional shares for users to purchase.
graph TB
subgraph User["Browser"]
FRONTEND["React + Vite Frontend"]
FREIGHTER["Freighter Wallet Extension"]
end
subgraph OffChain["Off-Chain"]
BACKEND["Express.js Backend API<br/>(asset metadata)"]
DATA[(data.json)]
end
subgraph Stellar["Stellar Network"]
RPC["Soroban RPC Endpoint"]
CONTRACT["Soroban Smart Contract<br/>(Rust - RwaMarketplace)"]
TOKEN["Stellar Asset Contract<br/>(Payment Token)"]
end
FRONTEND -->|"1. Fetch asset metadata<br/>GET /api/rwa"| BACKEND
BACKEND -->|"Read/Write"| DATA
FRONTEND -->|"2. Simulate transaction<br/>get_shares / buy_shares"| RPC
RPC -->|"3. Invoke contract"| CONTRACT
FRONTEND -->|"4. Request signing<br/>(XDR)"| FREIGHTER
FREIGHTER -->|"5. Return signed XDR"| FRONTEND
FRONTEND -->|"6. Submit signed tx"| RPC
RPC -->|"7. Execute"| CONTRACT
CONTRACT -->|"8. Transfer tokens"| TOKEN
CONTRACT -->|"9. Update balances"| CONTRACT
RPC -->|"10. Return result"| FRONTEND
ADMIN["Admin"] -->|"Deploy / Init<br/>via Soroban CLI"| RPC
ADMIN -->|"Manage metadata<br/>via x-api-key"| BACKEND
style CONTRACT fill:#4a9eff,stroke:#2a6fd1,color:#fff
style FRONTEND fill:#6c5ce7,stroke:#5a4bd1,color:#fff
style FREIGHTER fill:#00b894,stroke:#009874,color:#fff
style BACKEND fill:#fdcb6e,stroke:#e0a800,color:#333
style RPC fill:#ff7675,stroke:#d63031,color:#fff
style TOKEN fill:#74b9ff,stroke:#0984e3,color:#fff
style ADMIN fill:#dfe6e9,stroke:#b2bec3,color:#333
| Step | Description |
|---|---|
| 1 | Frontend fetches asset metadata from the Backend API (GET /api/rwa) |
| 2 | User enters share amount and clicks "Buy Shares" |
| 3 | Frontend builds a buy_shares transaction and simulates it via Soroban RPC |
| 4 | Frontend sends the transaction XDR to Freighter Wallet for signing |
| 5 | User approves in Freighter; signed XDR is returned |
| 6 | Frontend submits the signed transaction to the Soroban RPC endpoint |
| 7 | Soroban Smart Contract executes buy_shares: validates availability, transfers payment tokens from buyer to admin |
| 8 | The Stellar Asset Contract (payment token) transfers the cost to the admin address |
| 9 | Contract updates the buyer's share balance and available shares count |
| 10 | Frontend refreshes the share balance via get_shares (simulate-only, no fee) |
├── contracts/ # Soroban smart contract (Rust)
│ ├── Cargo.toml
│ └── lib.rs
├── backend/ # Off-chain metadata API (Express.js)
│ ├── package.json
│ ├── index.js
│ └── .env.example
├── frontend/ # React + Vite application
│ ├── package.json
│ ├── vite.config.js
│ ├── index.html
│ └── src/
│ ├── main.jsx
│ └── App.jsx
├── .gitignore
└── README.md
- Architecture Overview & Diagrams
- Architecture Decision Records (ADRs) — Technical decisions and rationale
- Security Best Practices Guide — Security guidelines, audit checklist, and incident response
- Performance Benchmarks — Gas costs, API latency, frontend metrics
- Troubleshooting Guide — Common issues and solutions
- Multi-Region Deployment — Deployment strategy and failover
- NFT Certificates
- NFT Quickstart
- FAQ
- Contributors Spotlight — Recognize the people who make this project possible
- Node.js (v18 or higher)
- Rust
- Soroban CLI (
cargo install --locked soroban-cli) - Freighter Wallet browser extension
The backend can be containerized with the provided backend/Dockerfile.
# Build the image
docker build -t rwa-backend ./backend
# Run the container (copy backend/.env.example to backend/.env first)
docker run -p 3001:3001 --env-file ./backend/.env rwa-backendThe container runs as a non-root user and exposes port 3001.
cd contracts
cargo build --target wasm32-unknown-unknown --release
# OR: soroban contract buildcd contracts
cargo testsoroban network add --global testnet \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015"
soroban keys generate --global admin --network testnet
soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/rwa_marketplace.wasm \
--source admin \
--network testnetCopy the returned Contract ID (starts with C).
soroban contract invoke \
--id <YOUR_CONTRACT_ID> \
--source admin \
--network testnet \
-- \
init \
--admin $(soroban keys address admin) \
--payment_token CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC \
--price 10000000 \
--total_shares 100Frontend — copy and fill in frontend/.env.example as frontend/.env:
VITE_CONTRACT_ID=<YOUR_CONTRACT_ID>
VITE_RPC_URL=https://soroban-testnet.stellar.org:443
VITE_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
VITE_API_URL=http://localhost:3001Backend — copy and fill in backend/.env.example as backend/.env:
PORT=3001
CORS_ORIGINS=http://localhost:5173
ADMIN_API_KEY=<generate-a-strong-random-key>
DATA_FILE=data.json# Backend
cd backend
npm install
npm run dev
# Frontend (in a separate terminal)
cd frontend
npm install
npm run devOpen http://localhost:5173, connect your Freighter wallet, and buy shares.
To run the application with rate limiting and basic WAF/DDoS protection, you can use the provided Nginx configuration. Ensure Nginx is installed on your system.
# Start Nginx using the provided configuration
nginx -c $(pwd)/nginx/nginx.confThis will start an Nginx server on http://localhost:80 that proxies requests:
/api/*-> Backend (http://localhost:3001) with rate limiting (10 req/s)/*-> Frontend (http://localhost:5173)
| Function | Description | Auth |
|---|---|---|
init |
Initialize marketplace | Admin |
buy_shares |
Purchase fractional shares (mints NFT certificate per share if configured) | Buyer |
set_nft_contract |
Configure NFT contract for certificate minting | Admin |
get_shares |
Query user balance | None |
get_available_shares |
Query remaining shares | None |
get_total_shares |
Query total shares | None |
get_price |
Query price per share | None |
is_paused |
Check if paused | None |
pause |
Pause marketplace | Admin |
unpause |
Unpause marketplace | Admin |
emergency_withdraw |
Withdraw tokens from contract | Admin |
When users buy shares, they receive SEP-41 compliant NFT certificates representing their ownership. These NFTs can be viewed in wallets, transferred peer-to-peer, and traded on secondary marketplaces.
👉 See NFT Certificates Documentation for setup, deployment, and integration details.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/health |
No | Health check |
GET |
/api/rwa |
No | List all assets |
GET |
/api/rwa/:contractId |
No | Get asset metadata |
POST |
/api/rwa |
x-api-key |
Create/update asset |
PATCH |
/api/rwa/:contractId |
x-api-key |
Partial update (specific fields only) |
DELETE |
/api/rwa/:contractId |
x-api-key |
Delete asset |
Interactive API documentation is available at /api-docs (Swagger UI) and /api-docs.json (raw OpenAPI spec) when the backend is running.
This project includes a render.yaml Blueprint for one-click deployment to Render.
For zero-downtime releases, the repository now includes a blue-green deployment workflow described in docs/blue-green-deployment.md. It uses paired blue and green services, health checks before traffic switches, and rollback support.
| Service | Type | Description |
|---|---|---|
rwa-marketplace-backend |
Web Service (Node) | Express.js metadata API |
rwa-marketplace-frontend |
Static Site | React + Vite dApp |
-
Fork or push this repository to your GitHub account.
-
Go to dashboard.render.com → New → Blueprint.
-
Connect your repository — Render will detect
render.yamlautomatically. -
Set the required environment variables in the Render dashboard:
Backend:
Variable Description CORS_ORIGINSYour frontend URL, e.g. https://rwa-marketplace-frontend.onrender.comADMIN_API_KEYAuto-generated by Render — copy it for API calls Frontend:
Variable Description VITE_CONTRACT_IDYour deployed Soroban contract ID VITE_API_URLYour backend Render URL, e.g. https://rwa-marketplace-backend.onrender.com -
Click Apply — Render builds and deploys both services.
# Backend
cd backend && npm run deploy
# Frontend
cd frontend && npm run deployNote: Free-tier Render services spin down after inactivity. Upgrade to a paid plan for always-on availability.
We appreciate all contributions! See CONTRIBUTORS.md for the full contributor spotlight. To contribute, please review CONTRIBUTING.md.
