A Decentralized Escrow Platform for the Future of Freelance Work
Trustless milestone payments secured by Stellar Soroban smart contracts
- Problem Statement
- Why Stellar?
- Live Deployment
- Contract Addresses & Transactions
- Architecture
- Smart Contracts
- Production Hardening (Level 4)
- Tech Stack
- Project Structure
- Testing
- CI/CD Pipeline
- Local Development
- Roadmap
- Author
The $1.5 trillion freelance economy is structurally broken and systematically favours centralized intermediaries over the workers who power it.
| Issue | Impact |
|---|---|
| Platform Fees | Upwork & Fiverr charge 10β20% of every payment, directly extracted from freelancer earnings |
| Payment Risk | 71% of freelancers report non-payment or severely delayed payments after delivering work |
| Settlement Delays | Cross-border payments via legacy banking rails take 5β14 business days and carry steep wire fees |
| Opaque Disputes | Centralized dispute resolution is slow, biased, and non-auditable, leaving freelancers vulnerable |
StellarGig eliminates the intermediary layer entirely by replacing it with programmable, auditable Soroban smart contracts. Clients fund an on-chain escrow vault before work begins; funds are automatically released to the freelancer upon milestone approval β no platform fees, no payment delays, no trust required.
StellarGig is not a generic blockchain application. It is a protocol that specifically requires Stellar's unique network architecture:
| Stellar Property | StellarGig Benefit |
|---|---|
| ~5 second finality | Freelancers receive instant payouts instead of waiting days |
| Sub-cent fees ($0.00001) | Enables micro-milestones (e.g. $5 for a quick sketch) β economically unviable on Ethereum |
| Soroban Inter-Contract Calls | Our Job Contract securely commands the Escrow Vault Contract atomically on-chain |
| SEP Anchor Integrations | Future fiat on/off-ramp support for non-crypto-native users in emerging markets |
| Fee Bump Transactions | Path to gasless onboarding β sponsor transaction fees for new freelancers |
| Resource | Link |
|---|---|
| π Live dApp | stellar-gig.netlify.app |
| π¬ Demo Video | Google Drive β Walkthrough Recording |
| π Pitch Deck / PPT | Google Slides Link |
| π» GitHub Repo | CodeBugMalik/StellarGig |
| π User Feedback Form | StellarGig Feedback β Google Forms |
| π Onboarded Users & Wallet Interactions | Responses Spreadsheet β Google Sheets |
| File | Bug | Fix |
|---|---|---|
lib/constants.ts |
HORIZON_URL was hardcoded with no env override |
Made it env-aware with NEXT_PUBLIC_HORIZON_URL fallback |
components/dashboard/EarningsChart.tsx |
Y-axis used Ξ (Ethereum symbol) instead of XLM |
Changed ticker format to plain numeric (XLM shown in tooltip) |
app/jobs/[id]/page.tsx |
Cancel button guard checked !job.freelancer β always truthy because contract sets freelancer=client as placeholder |
Changed guard to job.freelancer === job.client |
app/jobs/[id]/page.tsx |
setInterval poll for transaction status never cleaned up on unmount |
Added useRef + useEffect cleanup; stored interval in pollRef |
components/wallet/WalletButton.tsx |
Wallet dropdown stayed open when clicking outside | Added useRef + mousedown listener to close on outside click |
lib/contracts/job-client.ts |
parseJobStatus / parseMilestoneStatus silently defaulted on unknown values |
Added console.warn before fallback to surface contract mismatches |
.env.example |
Missing NEXT_PUBLIC_HORIZON_URL env variable documentation |
Added the variable to .env.example |
.github/workflows/ci.yml |
CI previously failed due to uncommitted Cargo.lock resolving breaking ed25519-dalek 3.0 / edition2024 dependencies on cargo build |
Committed Cargo.lock for contracts, enabled --locked builds, and restored stable toolchain |
-
Network Status Banner (
components/layout/NetworkStatusBanner.tsx)- Displays a visible "You are on Stellar Testnet" warning banner at the top of every page
- Automatically hidden on mainnet β zero config required
- Integrated into root layout (
app/layout.tsx)
-
Transaction History Component (
components/dashboard/TransactionHistory.tsx)- Shows a scrollable list of recent on-chain transactions with explorer links
- Compact card design matching the existing dashboard aesthetic
- Empty state guidance for new users
-
Reusable Poll Cleanup Pattern
- Replaced inline
setIntervalinapp/create/page.tsxandapp/jobs/[id]/page.tsxwith ref-tracked intervals - Ensures all polling stops when component unmounts β eliminates memory leaks
- Replaced inline
-
Contract Status Logging
- Added developer-visible
console.warnfor unrecognized contract enum values - Helps catch contract-frontend version mismatches during development
- Added developer-visible
| Test File | What It Covers |
|---|---|
__tests__/components/EscrowStatusBar.test.tsx |
Zero state, partial release, full release, non-numeric input handling |
__tests__/lib/contract-parsers.test.ts |
All job/milestone status mappings, unknown status fallback, stroops round-trip, large values |
__tests__/components/NetworkStatusBanner.test.tsx |
Testnet banner renders with correct label and warning text |
All contracts are deployed and cross-initialized on the Stellar Testnet using the pranjal developer identity.
| Contract | Address |
|---|---|
| Escrow Vault Contract | CDQTRHKGXJDNUQMJ2MNQVSGB5SOEESSQGU65EX63G5Q5ZSS5ZTP5UCG7 |
| Job Manager Contract | CB5ZWJ5F3ZBKQ2FOKYGEHW6RFX7F6ACKY52CKYVBESTFEHZUSSC4WTQX |
| Action | Transaction Hash |
|---|---|
| Escrow Contract β Upload & Deploy | 1c24ec3f...3207bf |
| Job Contract β Upload & Deploy | ebc4756e...0446a |
| Escrow Contract β Initialize (cross-link to Job) | 2bd6535b...2923e |
| Job Contract β Initialize (cross-link to Escrow) | 1feb4620...be6e8 |
StellarGig is composed of two Soroban smart contracts that communicate via Inter-Contract Calls (ICC), and a Next.js frontend that builds and submits signed Stellar transactions.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Next.js Frontend β
β β
β Landing β Browse Jobs β Job Detail β Create Job β Dashboard β
β StellarWalletsKit β
β (Freighter / xBull / Albedo) β
ββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ¬ββββββββββββββββββββ
β TypeScript Contract Clients β
ββββββββββΌββββββββββ βββββββββββΌβββββββββ
β Job Contract βββICCβββ β Escrow Contract β
β β β β
β create_job() β β fund_job() β
β accept_job() β β release_ β
β submit_ β β milestone() β
β milestone() β β refund() β
β approve_ β β get_escrow() β
β milestone() β β β
β dispute_ β β β
β milestone() β β β
β resolve_ β β β
β dispute() β β β
β cancel_job() β β β
β mark_funded() β β β
ββββββββββββββββββββ ββββββββββββββββββββ
Stellar Testnet
The ICC design is the architectural centrepiece of StellarGig. All escrow state changes are triggered atomically by the Job Contract β there is no way for the frontend to manipulate escrow funds independently.
Step 1: Client calls create_job() β Job created with status: Open
Step 2: Client calls fund_job() β Escrow locks XLM
Escrow ICCs β Job mark_funded()
Job status: Funded (atomic)
Step 3: Freelancer calls accept_job() β Job status: InProgress
Step 4: Freelancer calls submit_ β Job status: UnderReview
milestone()
Step 5a: Client calls approve_milestone() β Job ICCs β Escrow release_milestone()
Freelancer receives XLM instantly
Step 5b: Client calls dispute_milestone() β Job status: Disputed
Client calls resolve_dispute() β Approve: payment released via ICC
Reject: milestone reset to Pending
Step 6: Client calls cancel_job() β Job ICCs β Escrow refund()
Client receives full refund
Manages the full lifecycle of every freelance engagement on-chain.
| Function | Access | Description |
|---|---|---|
initialize() |
Admin (once) | Set the cross-linked Escrow Contract address |
create_job() |
Client | Post a new job with title, description, and milestones (1β10 max) |
accept_job() |
Freelancer | Accept a funded job (self-dealing guard enforced) |
submit_milestone() |
Freelancer | Submit a milestone for client review |
approve_milestone() |
Client | Approve milestone β ICC triggers escrow release |
dispute_milestone() |
Client | Raise a dispute on a submitted milestone |
resolve_dispute() |
Client | Approve (release payment) or reject (reset to Pending) |
cancel_job() |
Client | Cancel open/funded job β ICC triggers full escrow refund |
mark_funded() |
Escrow Contract only | Atomically marks job as Funded (auth-restricted ICC endpoint) |
Security Hardening applied in Level 4:
- Initialization guard β prevents double-initialization after deployment
- Input validation β non-empty title/description, positive milestone amounts, max 10 milestones
- Self-dealing guard β client cannot accept their own job as freelancer
- Caller authorization on
mark_fundedβ only the registered Escrow Contract can call this - Typed
#[contracterror]enum β replaces all rawpanic!strings - Instance TTL extension β
extend_ttl(4096, 50000)on every state-changing function
Holds XLM in a secure vault and releases it only on instruction from the Job Contract.
| Function | Access | Description |
|---|---|---|
initialize() |
Admin (once) | Set the cross-linked Job Contract address |
fund_job() |
Client | Lock XLM for a job β ICC calls mark_funded() on Job Contract |
release_milestone() |
Job Contract only | Transfer milestone amount to freelancer wallet |
refund() |
Job Contract only | Return remaining locked XLM to client |
get_escrow() |
Public (read) | Query current escrow state for a job |
get_total_escrowed() |
Public (read) | Query total XLM currently held in the vault |
The following security audits and production improvements were implemented and tested in Level 4:
| Fix | Contract | Description |
|---|---|---|
| Initialization Guard | Both | Prevents re-initialization after first deploy |
mark_funded Auth |
Job | Only the registered Escrow contract can transition job to Funded |
| Atomic Funding Flow | Escrow | fund_job() atomically ICCs mark_funded() β eliminates sync bugs |
| Milestone Bounds Check | Job | Validates 1β10 milestones, non-zero amounts, non-empty strings |
| Self-Dealing Guard | Job | Client cannot accept their own job as the freelancer |
resolve_dispute() |
Job | Client can approve (release payment) or reject (reset milestone) |
| Caller Restriction | Escrow | release_milestone() and refund() restricted to the Job Contract |
| Instance TTL Extension | Both | extend_ttl(4096, 50000) prevents ledger data expiry |
| Typed Error Enum | Both | #[contracterror] enum replaces all raw panic! strings |
| Fix | File | Description |
|---|---|---|
| Memory Leak Fix | app/create/page.tsx |
Polling interval cleared on component unmount via useRef |
| N+1 Query Fix | hooks/useJobs.ts |
Jobs fetched in parallel with Promise.all instead of sequential awaits |
| Session Key Validation | hooks/useWallet.ts |
Public key regex check (/^G[A-Z2-7]{55}$/) before loading from session |
| Global Error Boundary | components/ui/ErrorBoundary.tsx |
React class ErrorBoundary wrapping the full app layout |
| Loading Skeletons | components/escrow/EscrowStatus.tsx |
Skeleton loaders prevent UI flashing on data fetch |
| Confirmation Modal | components/ui/ConfirmModal.tsx |
Transaction warning dialog with action name and XLM amount before signing |
| scvVec Serialization Fix | lib/contracts/job-client.ts |
Fixed Bad union switch: 1 by using scvVec directly over nativeToScVal for milestone args |
| Tool | Purpose | Configuration |
|---|---|---|
| PostHog | Product analytics β user flows and feature engagement | lib/analytics.ts β tracks wallet_connected, job_created, job_action_executed |
| Sentry | Error monitoring and crash reporting | sentry.client.config.ts / sentry.server.config.ts / sentry.edge.config.ts |
| Lighthouse CI | Automated performance and accessibility audits in CI | lighthouserc.json β runs in ci.yml on every push |
| Suite | Tests | Status |
|---|---|---|
| Frontend (Vitest) | 14 tests | β All Passing |
| Escrow Contract (Rust) | 3 tests | β All Passing |
| Job Contract (Rust) | 10 tests | β All Passing |
| Total | 27 tests | β 27/27 Passing |
npm run test| Test File | Coverage |
|---|---|
Badge.test.tsx |
Status badge rendering across all 7 states |
MilestoneTracker.test.tsx |
Milestone progress display and completion count |
EmptyState.test.tsx |
Empty state component rendering with optional actions |
stellar.test.ts |
Address formatting, explorer link generation, XLM/stroop conversion |
# Escrow Contract (3 tests)
cd contracts/escrow-contract && cargo test
# Job Contract (10 tests)
cd contracts/job-contract && cargo testEscrow Contract tests: test_fund_job, test_release_milestone, test_refund
Job Contract tests: test_create_job, test_create_job_empty_title (panic), test_double_initialization (panic), test_cancel_open_job, test_accept_job_and_submit_milestone, test_accept_job_self_dealing (panic), test_dispute_milestone, test_resolve_dispute_approve, test_resolve_dispute_reject, test_approve_milestone
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Frontend Framework | Next.js (App Router) | 14.2 | SSR, file-based routing, production builds |
| Language | TypeScript | 5.x | Full type safety across frontend and contract clients |
| Styling | Tailwind CSS | 3.4 | Utility-first CSS with dark mode |
| Animations | Framer Motion | 11.x | Micro-interactions and page transitions |
| Smart Contracts | Soroban (Rust) | stable | On-chain job and escrow logic |
| Blockchain SDK | @stellar/stellar-sdk | 12.3 | Transaction building, XDR encoding, RPC calls |
| Wallet Integration | StellarWalletsKit | 1.9 | Freighter, xBull, and Albedo multi-wallet support |
| Frontend Testing | Vitest + Testing Library | 2.1 | Unit and component tests |
| Contract Testing | soroban-sdk testutils | β | Rust contract simulation and mock ICC |
| Error Monitoring | Sentry | 10.x | Crash reporting and error tracking |
| Analytics | PostHog | 1.x | Product analytics and event capture |
| Performance Auditing | Lighthouse CI | 0.13 | Automated web performance checks in CI |
| CI/CD | GitHub Actions | β | Automated lint, test, build, and deploy pipeline |
| Hosting | Netlify | β | Frontend production deployment |
StellarGig/
βββ .github/
β βββ workflows/
β βββ ci.yml # Lint + test + build + Lighthouse CI on push
β βββ deploy-contract.yml # Manual contract build and testnet deployment
βββ app/ # Next.js App Router pages
β βββ page.tsx # Landing page β hero, value props, CTA
β βββ jobs/
β β βββ page.tsx # Browse all open jobs
β β βββ [id]/page.tsx # Job detail β actions, milestones, escrow status
β βββ create/page.tsx # Multi-milestone job creation form
β βββ dashboard/page.tsx # Wallet activity feed and job history
β βββ layout.tsx # Root layout with ErrorBoundary and Navbar
βββ components/
β βββ layout/
β β βββ Navbar.tsx # Glassmorphism nav with wallet connect + PostHog init
β β βββ Footer.tsx
β βββ wallet/
β β βββ WalletButton.tsx # Multi-wallet connection trigger
β βββ jobs/
β β βββ JobCard.tsx # Job listing card with status badge
β β βββ MilestoneTracker.tsx # Visual milestone progress indicator
β βββ escrow/
β β βββ EscrowStatus.tsx # Escrow vault state with loading skeletons
β βββ dashboard/
β β βββ ActivityFeed.tsx # On-chain event stream
β βββ ui/
β βββ Button.tsx
β βββ Badge.tsx
β βββ ConfirmModal.tsx # Transaction confirmation dialog
β βββ ErrorBoundary.tsx # React class error boundary
β βββ Skeleton.tsx
βββ contracts/
β βββ job-contract/
β β βββ src/
β β βββ lib.rs # Full job lifecycle contract
β β βββ test.rs # 10 unit tests with MockEscrowContract
β βββ escrow-contract/
β βββ src/
β βββ lib.rs # Escrow vault contract
β βββ test.rs # 3 unit tests with MockJobContract
βββ hooks/
β βββ useJobs.ts # Parallel job fetching with Promise.all
β βββ useWallet.ts # Wallet session management with key validation
βββ lib/
β βββ stellar.ts # StellarHelper β wallet, transactions, polling, events
β βββ analytics.ts # PostHog + Sentry event tracking wrapper
β βββ constants.ts # Contract IDs, RPC URL, network passphrase
β βββ types.ts # Shared TypeScript interfaces
β βββ contracts/
β βββ job-client.ts # TypeScript wrapper for Job Contract calls
β βββ escrow-client.ts # TypeScript wrapper for Escrow Contract calls
βββ __tests__/ # Frontend test suite (Vitest)
βββ sentry.client.config.ts # Sentry client-side initialization
βββ sentry.server.config.ts # Sentry server-side initialization
βββ sentry.edge.config.ts # Sentry edge runtime initialization
βββ lighthouserc.json # Lighthouse CI configuration
βββ next.config.js # Next.js + Sentry webpack wrapper
Triggered automatically on every push and pull request to main.
Push to main
β
βββ Frontend Job
β βββ npm ci
β βββ npm run lint
β βββ npm run test β 14 Vitest tests
β βββ npm run build β Next.js production build
β βββ Lighthouse CI β Performance + accessibility audit
β
βββ Contract Job
βββ cargo build --target wasm32-unknown-unknown --release (escrow)
βββ cargo build --target wasm32-unknown-unknown --release (job)
βββ cargo test (escrow) β 3 Rust tests
βββ cargo test (job) β 10 Rust tests
Manually triggered via GitHub Actions workflow dispatch. Builds both WASM binaries and deploys to the Stellar Testnet.
- Node.js 20+
- Rust (stable toolchain)
- Stellar CLI β
cargo install stellar-cli --locked - Freighter Wallet browser extension
# Clone the repository
git clone https://github.com/CodeBugMalik/StellarGig.git
cd StellarGig
# Install frontend dependencies
npm install
# Configure environment variables
cp .env.example .env.localEdit .env.local with your contract IDs:
NEXT_PUBLIC_JOB_CONTRACT_ID=CB5ZWJ5F3ZBKQ2FOKYGEHW6RFX7F6ACKY52CKYVBESTFEHZUSSC4WTQX
NEXT_PUBLIC_ESCROW_CONTRACT_ID=CDQTRHKGXJDNUQMJ2MNQVSGB5SOEESSQGU65EX63G5Q5ZSS5ZTP5UCG7
NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org# Start development server
npm run dev
# β http://localhost:3000# Build WASM binaries
cd contracts/escrow-contract && cargo build --target wasm32-unknown-unknown --release
cd ../job-contract && cargo build --target wasm32-unknown-unknown --release
# Deploy to Stellar Testnet
stellar contract deploy \
--wasm contracts/escrow-contract/target/wasm32-unknown-unknown/release/escrow_contract.wasm \
--source pranjal --network testnet
stellar contract deploy \
--wasm contracts/job-contract/target/wasm32-unknown-unknown/release/job_contract.wasm \
--source pranjal --network testnet
# Cross-initialize both contracts
stellar contract invoke --id <ESCROW_ID> --source pranjal --network testnet \
-- initialize --job_contract <JOB_ID>
stellar contract invoke --id <JOB_ID> --source pranjal --network testnet \
-- initialize --escrow_contract <ESCROW_ID>- Dual Soroban smart contracts with Inter-Contract Communication
- Next.js 14 frontend with multi-wallet support
- Milestone-based escrow lifecycle
- Real-time contract event streaming
- Testnet deployment with on-chain transactions
- Contract security hardening (13 targeted fixes across both contracts)
- Frontend production quality (memory leak, N+1 query, session validation, error boundary)
- Transaction Confirmation Modal before every state-changing action
- Loading skeleton UI to eliminate flashing screens
- PostHog product analytics + Sentry error monitoring integration
- Lighthouse CI integrated into GitHub Actions pipeline
- 10+ real users onboarded on Stellar Testnet with wallet interaction proof
- Fresh testnet deployment with hardened contracts, cross-initialized
- Third-party security audit of both Soroban contracts
- Mainnet deployment of hardened contracts
- Fee Sponsorship via Stellar Fee Bump Transactions β gasless onboarding for non-crypto-native users
- Multisig escrow for high-value enterprise contracts
- Public launch on Twitter and Product Hunt
- Contribution: publish open-source technical guide on Soroban Inter-Contract Communication patterns
Pranjal Malik β @CodeBugMalik



