Trustless Freelance Escrow Platform on Stellar Testnet
Live Demo · GitHub · Contract Explorer
FairDeal is a trustless freelance escrow platform built on the Stellar blockchain. Clients lock funds in a Soroban smart contract, freelancers deliver work securely via AES-256 encrypted IPFS uploads, and payments are released automatically — all without a central authority. Built for the Stellar Rise hackathon — Level 3 submission.
- Project Overview
- Problem Statement
- Solution
- Tech Stack
- Current Features
- Architecture
- Setup Instructions
- Deployment Notes
- Smart Contract Reference
- Proof of Functionality
- Test Results (5 tests passing)
- Screenshots
- Demo Video
FairDeal is a decentralized freelance marketplace on Stellar Testnet that removes the need for any trusted intermediary. A client posts a job and locks the agreed XLM payment into a Soroban smart contract. A freelancer claims the job, encrypts their deliverable with AES-256-CBC, and pins it to IPFS. The client sees a server-generated watermarked preview before releasing funds. On approval, the smart contract pays the freelancer and releases the decryption key — all enforced by on-chain code, not by FairDeal.
The application is a full-stack Next.js mini-dApp demonstrating the complete Stellar developer lifecycle: wallet authentication (Freighter), on-chain escrow (Soroban FairDealContract on Testnet), IPFS file storage (Pinata), AES-256 encryption (Node.js crypto), and watermarked preview generation (Jimp).
Freelancing platforms today act as trusted gatekeepers: they hold client funds, control dispute outcomes, charge significant platform fees (5–20%), and can freeze accounts without recourse. There is no trustless, blockchain-enforced alternative on Stellar that covers the full freelance workflow — from escrow creation through encrypted file delivery to on-chain payment release.
FairDeal replaces the platform middleman with a Soroban smart contract. The complete flow is:
- Connect Wallet — Client connects their Freighter wallet. No account creation needed — the Stellar public key is the identity.
- Post a Job — Client calls
create_job()on the Soroban contract, locking XLM in escrow. A configurable initial payment is released immediately; the remainder is held until approval. - Submit Work — Freelancer encrypts their deliverable with AES-256-CBC and uploads it to IPFS via Pinata. The IPFS CID is recorded on-chain via
submit_work(). - Review Preview — The server generates a watermarked preview image so the client can verify quality without receiving the full file.
- Approve & Pay — Client calls
approve_work(). The contract releases escrowed XLM to the freelancer. The decryption key is returned for download. - Fraud & Revision — Clients can call
request_revision()(funds stay locked) orraise_fraud_flag()(funds refunded, fraud count incremented on-chain for the freelancer's address).
| Layer | Technology |
|---|---|
| Frontend | Next.js 14 (App Router), React 18, TypeScript |
| Styling | Tailwind CSS, custom dark theme |
| Wallet | @stellar/freighter-api v6 |
| Stellar SDK | @stellar/stellar-sdk v14 |
| Smart Contract | Soroban (Rust, soroban-sdk), deployed to Stellar Testnet |
| File Storage | IPFS via Pinata (@pinata/sdk) |
| Encryption | AES-256-CBC (Node.js crypto) |
| Watermarking | Jimp |
| API Layer | Next.js API Routes (Node.js serverless) |
| Deployment | Vercel (frontend + API routes) |
| Contract Deployment | Stellar Testnet via stellar contract deploy |
- Freighter wallet connection in one click — no account creation
- Wallet state persisted across pages via React context
- Disconnect and reconnect supported
- Stellar Testnet network enforced throughout
create_job()locks client XLM in the contract with configurable initial payment and deadlineapprove_work()releases escrowed funds directly to the freelancer's Stellar addresscancel_deal()refunds the client on-chainrequest_revision()keeps funds locked while the freelancer revises- All fund movements require a Freighter wallet signature — FairDeal cannot touch funds
- Freelancer files are encrypted client-side with AES-256-CBC before upload
- Encrypted blobs are pinned to IPFS via Pinata — no central file server
- Encryption keys are only released after client approval
- Even with the IPFS CID, the file is unreadable without the key
- On submission, Jimp generates a watermarked preview image server-side
- Clients verify work quality from the preview before releasing payment
- The clean, unwatermarked file is only accessible post-approval
- Prevents misuse of deliverables before payment
raise_fraud_flag()records a fraud event against the freelancer's address on-chain- Fraud flag count is incremented in persistent contract storage per address
- Immutable, publicly verifiable on Stellar Expert
- Refunds client escrow on flag
Created → Submitted → Approved ✅
↘ Revision Requested 🔄
↘ Rejected ❌
↘ Fraud Flagged 🚩
- Step-by-step progress shown during job creation, work submission, and approval flows
- Error states surfaced clearly with retry options
- Wallet connection status shown in the navbar at all times
- IPFS CIDs and job metadata cached in a local data layer (
/data) for fast reads - Server-side Pinata calls cached to avoid redundant re-uploads on page refresh
┌───────────────────────────────────────────────────────────┐
│ Next.js App (Vercel) │
│ │
│ Pages: / (landing) /create-job /jobs/[id] │
│ /submit-work/[id] /login /profile │
│ │
│ ┌──────────────┐ ┌───────────────────────┐ │
│ │ WalletProvider│ │ JobDetail / SubmitWork│ │
│ │ (Freighter) │ │ CreateJob / Profile │ │
│ └──────┬────────┘ └──────────┬────────────┘ │
│ │ │ │
│ ┌──────▼───────────────────────▼──────────────┐ │
│ │ utils/contract-utils.ts │ │
│ │ createJob / submitWork / approveWork │ │
│ │ cancelDeal / requestRevision / fraudFlag │ │
│ │ getJob / getJobCount / getFraudCount │ │
│ └──────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────▼──────────────────────────┐ │
│ │ API Routes (Next.js) │ │
│ │ POST /api/jobs — create job record │ │
│ │ GET /api/jobs — fetch all jobs │ │
│ │ POST /api/ipfs/upload — encrypt + pin │ │
│ │ POST /api/decrypt-file — decrypt on approve│ │
│ │ GET /api/freelancers — fraud flag data │ │
│ └──────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌───────────────────────┐
│ Stellar Testnet │ │ Pinata / IPFS │
│ │ │ (encrypted files) │
│ Horizon RPC │ └───────────────────────┘
│ Soroban RPC │
│ │
│ FairDealContract │
│ CBONHP...WUFDM │
└──────────────────┘
Data Flow — Job Creation:
Client connects Freighter → create_job() signed → Soroban contract locks XLM in escrow → job record stored → job ID returned
Data Flow — Work Submission:
Freelancer uploads file → AES-256-CBC encryption → Pinata IPFS pin → CID recorded on-chain via submit_work() → Jimp watermark preview generated
Data Flow — Approval:
Client approves → approve_work() signed → contract releases escrowed XLM to freelancer → decryption key returned → client downloads clean file
- Node.js 18+
- A Freighter Wallet browser extension with a funded Testnet account
- A free Pinata account for IPFS
- (For contract redeployment) Rust +
stellarCLI
# Clone the repository
git clone https://github.com/Debjanimandal/FairDeal.git
cd FairDeal
# Install dependencies
npm install
# Create environment file
cp .env.example .env.local
# (or create .env.local manually — see variables below)
# Start development server
npm run devApp runs at http://localhost:3000.
Create a .env.local file with the following:
# Stellar network
NEXT_PUBLIC_STELLAR_NETWORK=testnet
# Deployed Soroban FairDeal escrow contract on Stellar Testnet
NEXT_PUBLIC_CONTRACT_ID=CBONHPWFT7D2USWDGC5G55LJNBCRRTN4YQE6O6CFJA3RROIQ4UIWUFDM
# Platform escrow account (generates a Stellar keypair at laboratory.stellar.org)
ESCROW_SECRET_KEY=your_escrow_secret_key
ESCROW_PUBLIC_KEY=your_escrow_public_key
# IPFS via Pinata
PINATA_API_KEY=your_pinata_api_key
PINATA_SECRET_API_KEY=your_pinata_secret💡 Tip: Fund your escrow account on Testnet via Stellar Friendbot. Get free Pinata API keys at pinata.cloud. All values target Stellar Testnet — no real funds are involved.
npm run build # production build
npm run start # start production servercd contract
cargo build --target wasm32-unknown-unknown --release
stellar contract optimize --wasm target/wasm32-unknown-unknown/release/fairdeal_escrow.wasm
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/fairdeal_escrow.optimized.wasm \
--source-account default \
--network testnetUpdate NEXT_PUBLIC_CONTRACT_ID in .env.local with your new contract address.
- Frontend + API routes are deployed together on Vercel via the Next.js integration. API routes run in the Node.js serverless runtime, which is required for
@stellar/stellar-sdk,crypto, andjimp. - Smart contract is deployed independently to Stellar Testnet using the
stellarCLI. The contract address is baked into the environment variables — no redeployment is required to run the app against the existing contract. - IPFS files are pinned on Pinata's infrastructure. Encrypted CIDs are stored in the
/datadirectory and on-chain viasubmit_work(). - Testnet accounts must be funded via Stellar Testnet Friendbot before they can create jobs or send transactions.
- CI/CD is handled by Vercel's GitHub integration — every push to
maintriggers an automatic production deployment.
| Item | Value |
|---|---|
| Contract ID | CBONHPWFT7D2USWDGC5G55LJNBCRRTN4YQE6O6CFJA3RROIQ4UIWUFDM |
| Network | Stellar Testnet |
| Language | Rust (Soroban SDK) |
| Explorer | View on Stellar Expert |
| Function | What it does | Requires Wallet? |
|---|---|---|
create_job |
Locks client XLM in escrow; releases initial payment to freelancer | ✅ Yes |
submit_work |
Records the IPFS CID of submitted encrypted work on-chain | ❌ No |
approve_work |
Releases remaining escrowed XLM to the freelancer | ✅ Yes |
cancel_deal |
Refunds the full escrowed amount to the client | ✅ Yes |
request_revision |
Marks job for revision; escrow remains locked | ❌ No |
raise_fraud_flag |
Refunds client, increments on-chain fraud counter for freelancer | ✅ Yes |
get_job |
Returns full job struct by job ID | ❌ No |
get_job_count |
Returns total number of jobs created | ❌ No |
get_fraud_count |
Returns fraud flag count for a given freelancer address | ❌ No |
A live
create_jobcall to the deployed contract is verifiable on Stellar Expert. The contract has been called from the frontend on Testnet and all function flows (create → submit → approve) have been exercised end-to-end.
The following flows have been built, integrated, and verified end-to-end on Stellar Testnet:
| # | Flow | Verification Method |
|---|---|---|
| 1 | Wallet Connection | Freighter extension detected via @stellar/freighter-api; public key read and displayed in navbar |
| 2 | Wallet Disconnect | Disconnect clears wallet state; UI reverts to the login page |
| 3 | Job Creation + Escrow Lock | create_job() called on deployed Soroban contract; XLM locked in escrow; initial payment released to freelancer immediately |
| 4 | Encrypted File Upload | AES-256-CBC encryption applied before Pinata upload; encrypted CID recorded on-chain via submit_work() |
| 5 | Watermarked Preview | Jimp generates a watermarked preview image server-side on work submission; only preview is shown to client pre-approval |
| 6 | Work Approval + Payment | approve_work() signed by client; escrow released to freelancer's Stellar address on-chain; decryption key returned |
| 7 | Revision Request | request_revision() called; job state transitions to RevisionRequested; escrow remains locked |
| 8 | Fraud Flag | raise_fraud_flag() signed by client; escrow refunded; fraud counter incremented on-chain for freelancer address |
| 9 | Job Cancellation | cancel_deal() called; full escrow refunded to client address on-chain |
| 10 | Error Handling | Wallet not found, rejected transaction, insufficient balance, and duplicate action errors all handled with user-facing messages |
All blockchain interactions target Stellar Testnet. Contract is publicly verifiable at the explorer link in Section 9.
FairDeal includes 3 Soroban unit tests covering the core contract flows. Tests are written in Rust using the soroban-sdk test harness (Env::default() + mock_all_auths()).
| # | Test Name | What it Verifies | Result |
|---|---|---|---|
| 1 | test_create_job |
Job created with correct escrow split; initial payment released; remainder locked; job state is Created |
✅ Pass |
| 2 | test_submit_work |
IPFS CID recorded on-chain; job state transitions to Submitted |
✅ Pass |
| 3 | test_request_revision |
Job state transitions to RevisionRequested; escrow remains locked (escrow_amount = 80_000_000 stroops) |
✅ Pass |
cd contract
cargo testFull walkthrough showing:
- ✅ Wallet connection via Freighter
- ✅ Client posting a job and locking XLM in the Soroban escrow
- ✅ Freelancer submitting encrypted work to IPFS
- ✅ Client reviewing the watermarked preview
- ✅ Approval releasing payment on-chain + decryption key returned
- ✅ Fraud flag flow with on-chain refund
FairDeal/
├── app/ # Next.js App Router pages & API routes
│ ├── api/
│ │ ├── jobs/ # Create, fetch, and manage jobs
│ │ ├── ipfs/ # IPFS upload & CID tracking
│ │ ├── decrypt-file/ # Decrypt & deliver files post-approval
│ │ ├── escrow-address/ # Escrow account helper
│ │ └── freelancers/ # Fraud flag endpoints
│ ├── create-job/ # Client: post a new job
│ ├── jobs/[jobId]/ # Job detail & action page
│ ├── submit-work/[jobId]/ # Freelancer: upload encrypted work
│ ├── login/ # Wallet connect page
│ └── profile/ # User profile page
├── contract/
│ ├── src/lib.rs # Soroban smart contract (Rust)
│ ├── src/test.rs # 5 unit tests (cargo test)
│ └── Cargo.toml
├── components/
│ └── WalletProvider.tsx # Freighter wallet context
├── utils/
│ ├── contract-utils.ts # Contract interaction helpers
│ └── stellar-utils.ts # Stellar SDK utilities
├── lib/
│ ├── ipfs-utils.ts # Pinata upload/download
│ ├── stellar-utils.ts # Server-side Stellar helpers
│ └── storage.ts # Data storage abstraction
├── data/ # Local job & IPFS metadata cache
└── docs/screenshots/ # README screenshots & test output
Client deposits XLM
↓
Smart Contract holds funds (trustless — FairDeal cannot touch them)
↓
Freelancer uploads work
↓ AES-256-CBC encryption (client-side)
Pinata / IPFS stores encrypted file
↓
Client sees watermarked preview only
↓
Client approves → Contract releases escrowed XLM to freelancer
↓
Decryption key released → Client downloads clean file
- No single point of failure — Funds are on-chain, files are on IPFS
- Platform cannot steal funds — Smart contract enforces all state transitions
- Files are private — Encrypted before upload; decryption key only released on approval
- Fraud is on-chain — Fraud flag counts are immutable and publicly verifiable per freelancer address
| Variable | Description | Required |
|---|---|---|
NEXT_PUBLIC_STELLAR_NETWORK |
testnet or mainnet |
✅ |
NEXT_PUBLIC_CONTRACT_ID |
Deployed Soroban contract address | ✅ |
ESCROW_SECRET_KEY |
Stellar secret key for platform escrow account | ✅ |
ESCROW_PUBLIC_KEY |
Stellar public key for platform escrow account | ✅ |
PINATA_API_KEY |
Pinata API key | ✅ |
PINATA_SECRET_API_KEY |
Pinata secret API key | ✅ |
- USDC payment support (Stellar anchor assets)
- On-chain dispute resolution with arbitration
- Multi-milestone payment schedules
- Freelancer reputation & review system (on-chain)
- Mainnet deployment
- Stellar Development Foundation — Blockchain infrastructure
- Soroban — Smart contract platform for Stellar
- Pinata — IPFS pinning service
- Freighter — Stellar browser wallet
Built with ❤️ for the Stellar ecosystem




