Welcome! This guide will help you understand the project architecture and how to contribute effectively.
- Project Overview
- Architecture
- Data Models
- User Flows
- API Design
- Stellar Integration
- Development Setup
- Picking an Issue
- Submitting a PR
Stellance is a decentralized freelance payment platform that eliminates traditional payment barriers for freelancers worldwide. Instead of using PayPal, Upwork, or banks (which charge high fees and exclude many users), we use the Stellar blockchain for instant, low-cost, cross-border payments.
- High Fees: Platforms like Upwork take 20% of earnings
- Banking Barriers: Millions of freelancers can't receive international payments
- Slow Payouts: Traditional payments take 3-7 days
- Lack of Trust: No transparent escrow system
- ✅ Instant payments (3-5 seconds on Stellar)
- ✅ Low fees (<2% platform fee + $0.0001 network fee)
- ✅ No bank required (just a Stellar wallet)
- ✅ Trustless escrow (smart contract holds funds until work is approved)
- ✅ Global access (works anywhere Stellar is accessible)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ Next.js Web │ ◄─────► │ NestJS API │ ◄─────► │ Stellar │
│ (Frontend) │ HTTP │ (Backend) │ SDK │ Network │
│ │ │ │ │ (Blockchain) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
│
▼
┌─────────────────┐
│ │
│ PostgreSQL │
│ (Database) │
│ │
└─────────────────┘
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | Next.js 16 (App Router) | User interface, wallet connection |
| Backend | NestJS | API, business logic, Stellar integration |
| Database | PostgreSQL + Prisma | Store users, jobs, contracts, payments |
| Blockchain | Stellar (XLM/USDC) | Payment rails, escrow |
| Smart Contracts | Soroban (Rust) | Trustless escrow logic |
The source of truth is stellance/backend/prisma/schema.prisma. The models below mirror it exactly.
UserRole: CLIENT | FREELANCER | ADMIN
JobStatus: OPEN | IN_PROGRESS | COMPLETED | CANCELLED
ContractStatus: ACTIVE | COMPLETED | DISPUTED | CANCELLED
MilestoneStatus: PENDING | IN_REVIEW | APPROVED | PAID// User — clients, freelancers, and admins
User {
id: string // UUID primary key
email: string // unique
name: string
role: UserRole
password: string // argon2 hash — never returned in API responses
stellarPublicKey: string? // unique — the user's Stellar wallet address
tokenVersion: number // incremented on logout-all to invalidate all sessions
createdAt: DateTime
updatedAt: DateTime
}
// Job — posted by clients
Job {
id: string
title: string
description: string
budget: Decimal // 18 digits, 7 decimal places (Decimal(18,7))
category: string
status: JobStatus
clientId: string // FK → User
createdAt: DateTime
updatedAt: DateTime
}
// Contract — created when a client hires a freelancer for a job
Contract {
id: string
jobId: string // FK → Job (unique — one contract per job)
freelancerId: string // FK → User
clientId: string // FK → User
status: ContractStatus
escrowTxHash: string? // unique — Stellar tx hash that funded the escrow
createdAt: DateTime
updatedAt: DateTime
}
// Milestone — a deliverable phase within a contract
Milestone {
id: string
contractId: string // FK → Contract
title: string
amount: Decimal // portion of contract value for this milestone
status: MilestoneStatus
createdAt: DateTime
updatedAt: DateTime
}
// Payment — an on-chain fund release, one per milestone approval
Payment {
id: string
contractId: string // FK → Contract
milestoneId: string? // FK → Milestone (unique — one payment per milestone)
amount: Decimal
stellarTxHash: string // unique — the Stellar tx that transferred funds
createdAt: DateTime
}User ──────────────┬── jobs[] (as CLIENT)
├── contractsAsClient[] (as CLIENT)
├── contractsAsFreelancer[] (as FREELANCER)
└── refreshTokens[]
Job ───────────────── contract? (one-to-one, optional)
Contract ──────────┬── milestones[]
└── payments[]
Milestone ─────────── payment? (one-to-one, optional)
Payment ────────────── (belongs to Contract + optional Milestone)
Two fields link the off-chain database to the Stellar blockchain:
| Field | Model | Description |
|---|---|---|
stellarPublicKey |
User |
The user's Stellar account address |
escrowTxHash |
Contract |
Hash of the Soroban fund() transaction |
stellarTxHash |
Payment |
Hash of the Soroban release() or refund() transaction |
Any payment can be independently verified on stellar.expert using its stellarTxHash.
1. Client signs up → creates Stellar wallet (or connects existing)
2. Client posts a job (title, description, budget)
3. Freelancers browse jobs and apply
4. Client reviews applications and hires a freelancer
5. System creates a Contract
6. Client funds escrow (sends XLM/USDC to escrow account)
└─► Escrow smart contract locks funds
7. Contract status → "active"
1. Freelancer works on the job
2. Freelancer submits work for review
3. Client reviews and approves work
4. System calls escrow contract to release funds
└─► Stellar blockchain transfers XLM/USDC to freelancer's wallet
5. Payment record created (links to Stellar tx hash)
6. Contract status → "completed"
1. Either party opens a dispute
2. Contract status → "disputed"
3. Escrow funds are frozen
4. Platform admin reviews evidence
5. Admin decides:
a) Release to freelancer
b) Refund to client
c) Split payment
6. Escrow contract executes decision
All protected endpoints require a JWT token in the Authorization header:
Authorization: Bearer <jwt_token>
POST /auth/register - Create new user
POST /auth/login - Get JWT token + refresh token cookie
POST /auth/refresh - Rotate refresh token, get new access token
POST /auth/logout - Revoke current session
POST /auth/logout-all - Revoke all sessions
GET /users/me - Get current user profile ← implemented
PATCH /users/me - Update profile / set Stellar public key ← planned
GET /jobs - List all jobs (with filters)
GET /jobs/:id - Get single job
POST /jobs - Create job (client only)
PATCH /jobs/:id - Update job (owner only)
DELETE /jobs/:id - Delete job (owner only)
POST /jobs/:id/apply - Apply to job (freelancer only)
POST /contracts - Create contract (when hiring)
GET /contracts - List user's contracts
GET /contracts/:id - Get contract details
POST /contracts/:id/confirm-fund - Confirm on-chain escrow funding
PATCH /contracts/:id/milestones/:mid/submit - Submit work (freelancer)
PATCH /contracts/:id/milestones/:mid/approve - Approve & release funds (client)
POST /contracts/:id/dispute - Open dispute
GET /payments - List payments
GET /payments/:id - Get payment details
Responses are flat objects — there is no top-level success wrapper. The exact shape depends on the endpoint. Auth endpoints look like:
// POST /auth/register → 201
{
message: "Registered successfully",
access_token: "eyJhbGci...",
user: { id, email, name, role, stellarPublicKey, tokenVersion, createdAt, updatedAt }
}
// POST /auth/login → 200
{
message: "Logged in successfully",
access_token: "eyJhbGci...",
user: { id, email, name, role, stellarPublicKey }
}
// POST /auth/refresh → 200
{
access_token: "eyJhbGci..."
}
// POST /auth/logout → 200
{
message: "Logged out successfully"
}Errors use NestJS's standard format:
// Validation error
{
statusCode: 400,
message: ["email must be an email", "password must be longer than or equal to 8 characters"],
error: "Bad Request"
}
// Auth error
{
statusCode: 401,
message: "Invalid credentials",
error: "Unauthorized"
}See docs/api-reference.md for the full request/response reference.
- Fast: 3-5 second settlement
- Cheap: $0.0001 per transaction
- Built for payments: Native multi-currency support
- Soroban: Smart contracts for escrow logic
Each user has a Stellar wallet (public/private keypair):
- Public Key: Stored in database, used to receive payments
- Private Key: User controls (via Freighter browser extension)
The platform maintains an escrow account:
- Clients send funds to this account when hiring
- Smart contract controls when funds can be released
- Only released when both parties agree (or admin resolves dispute)
Client Wallet
│
│ (1) Send XLM/USDC to escrow
▼
Escrow Account (Smart Contract)
│
│ (2) Client approves work
▼
Freelancer Wallet
// Simplified contract structure
pub struct EscrowContract;
impl EscrowContract {
// Lock funds when contract is created
pub fn create_escrow(
contract_id: String,
client: Address,
freelancer: Address,
amount: i128,
) -> Result<(), Error>
// Release funds to freelancer
pub fn release_escrow(
contract_id: String,
caller: Address, // Must be client or admin
) -> Result<(), Error>
// Refund to client
pub fn refund_escrow(
contract_id: String,
caller: Address, // Must be both parties or admin
) -> Result<(), Error>
}// Example: Creating an escrow payment
import * as StellarSDK from '@stellar/stellar-sdk';
async fundEscrow(contractId: string, amount: number) {
const server = new StellarSDK.Horizon.Server('https://horizon-testnet.stellar.org');
// Build transaction
const transaction = new StellarSDK.TransactionBuilder(escrowAccount)
.addOperation(
StellarSDK.Operation.payment({
destination: freelancerPublicKey,
asset: StellarSDK.Asset.native(), // XLM
amount: amount.toString(),
})
)
.build();
// Sign and submit
transaction.sign(escrowKeypair);
const result = await server.submitTransaction(transaction);
return result.hash; // Store this in the database
}- Node.js 18+
- PostgreSQL
- Stellar testnet account (Create here)
# 1. Clone the repo
git clone https://github.com/stallance/stellances.git
cd stellances
# 2. Start PostgreSQL (requires Docker)
docker compose up -d
# 3. Install backend dependencies
cd stellance/backend
npm install
# 4. Set up backend environment
cp .env.example .env
# Edit .env — set JWT_SECRET and REFRESH_TOKEN_PEPPER to random strings
# DATABASE_URL default matches the docker-compose settings
# 5. Run database migrations
npx prisma migrate dev
# 6. Start the backend
npm run start:dev
# → http://localhost:3001/api (Swagger: http://localhost:3001/docs)
# 7. In a new terminal, install and start the frontend
cd stellance/frontend
npm install
cp .env.local.example .env.local
npm run dev
# → http://localhost:3000 (Stellar demo: http://localhost:3000/demo)Backend (stellance/backend/.env)
DATABASE_URL=postgresql://stellance:stellance_pass@localhost:5432/stellance
JWT_SECRET=your_random_secret_here
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_DAYS=30
REFRESH_TOKEN_PEPPER=your_random_pepper_here
FRONTEND_URL=http://localhost:3000Frontend (stellance/frontend/.env.local)
Copy the provided template:
cp stellance/frontend/.env.local.example stellance/frontend/.env.localDefault values:
NEXT_PUBLIC_API_URL=http://localhost:3001/api
NEXT_PUBLIC_STELLAR_NETWORK=testnet- Frontend? Look for issues tagged
frontend - Backend? Look for issues tagged
backend - Blockchain? Look for issues tagged
stellarorsmart contract - New to the project? Start with
good first issue
Before starting, make sure you understand:
- Dependencies: Does this issue depend on others being completed first?
- Data Models: Which entities does this touch? (See Data Models)
- User Flow: Which user journey is this part of? (See User Flows)
- API Contract: If building an endpoint, what should the request/response look like?
Let others know you're working on it:
I'd like to work on this! ETA: 3-4 days.
Quick question: Should the job listing support pagination or infinite scroll?
You pick Issue #5: Build Jobs Listing Page
- ✅ Read the issue description
- ✅ Check Data Models → understand
Jobentity - ✅ Check API Design →
GET /jobsendpoint - ✅ Check if backend issue #14 is done (it provides the API)
- If not done: either wait, or implement a mock API response
- ✅ Comment on the issue that you're starting
- ✅ Create a branch:
git checkout -b feature/jobs-listing-page - ✅ Build the feature
- ✅ Test locally
- ✅ Submit PR
- Code follows project structure and conventions
- No console.log() or commented-out code
- Environment variables are documented (if added new ones)
- Tests pass (if applicable)
- Linked to the issue:
Closes #5
## What does this PR do?
Implements the jobs listing page with search and filter functionality.
## Related Issue
Closes #5
## How to Test
1. Start the dev server
2. Navigate to `/dashboard/jobs`
3. Try searching for "React developer"
4. Filter by budget range
## Screenshots (if applicable)
[Add screenshot]
## Notes
- Used React Query for data fetching
- Added loading skeleton for better UXIf you're stuck or need clarification:
- Check the docs in the
/docsfolder - Comment on the issue you're working on
- Ask in Discussions (GitHub Discussions tab)
- Open a Draft PR with your questions in the description
Thank you for contributing to Stellance! 🚀
Your work helps freelancers around the world get paid fairly and instantly.