A decentralized freelancer marketplace built on Stellar (Soroban) that provides secure, trustless escrow services for freelance work agreements.
- Overview
- Features
- How It Works
- Architecture
- Smart Contract Details
- Tech Stack
- Getting Started
- Usage Guide
- Deployment
- Security
- Project Structure
- Contributing
- License
SecureFlow is a blockchain-powered freelancer marketplace that enables clients and freelancers to collaborate without requiring trust between parties. Built on the Stellar network using Soroban smart contracts, SecureFlow ensures secure milestone-based payments, transparent dispute resolution, and on-chain reputation β all enforced by code, not intermediaries.
- Trustless Escrow β Funds are locked in smart contracts until work is verified and approved
- Fast & Low-Cost β Leverages Stellar's 3β5 second finality and near-zero fees
- Global Access β Works with native XLM and any whitelisted token
- Fair Disputes β Per-milestone multi-sig arbiter voting with configurable quorum
- On-Chain Reputation β Build trust through verifiable, tamper-proof reputation scores
- Gasless Applications β Freelancers can apply to jobs without holding XLM for fees
| Feature | Description |
|---|---|
| Smart contract escrow | Funds locked until milestones approved |
| Milestone-based payments | Each milestone paid independently upon approval |
| Open job marketplace | Anyone can apply; client selects the best candidate |
| Direct contracts | Create with a known freelancer β no application needed |
| Tiered cancellation | Cancel an unstarted job with a tiered penalty (0β30%) based on cancellation history and application count |
| Dynamic fund management | Add or withdraw funds from specific milestones before work starts |
| Deadline extension | Depositor can extend the project deadline |
| Emergency refund | Automatic refund path after deadline expiration |
| Feature | Description |
|---|---|
| Add / remove milestones | Depositor can add or remove milestones before work starts |
| Milestone submission | Freelancer submits work with an updated description |
| Resubmission | Freelancer can resubmit a rejected milestone |
| Approve / reject | Client approves (pays out) or rejects (with reason) each milestone |
| Dispute milestone | Either party can dispute; routes to arbiters |
| Milestone negotiation | Freelancer proposes an amount/description change; client approves or rejects |
| On-chain evidence | Either party can attach IPFS CIDs as evidence before an arbiter rules |
| Feature | Description |
|---|---|
| Per-milestone multi-sig | Arbiters cast votes per disputed milestone |
| Configurable quorum | required_confirmations set at escrow creation |
| Split payouts | Arbiter specifies exact freelancer and client amounts (must sum to milestone amount) |
| Vote tracking | get_dispute_vote_count and has_dispute_voted prevent double-voting |
| Overdue disputes | Either party can raise a dispute after the deadline |
| Arbiter award / refund | Arbiters can award the freelancer a partial amount or approve a full client refund |
| Feature | Description |
|---|---|
| On-chain reputation | Score increases with approved milestones |
| Client β freelancer rating | 1β5 stars + written review after project completion |
| Freelancer β client rating | Freelancers can rate clients too |
| Average ratings | Per-address average rating and count |
| Badge tiers | Beginner β Intermediate β Advanced β Expert based on completed projects |
| Feature | Description |
|---|---|
| Emergency pause | Owner can pause all write operations instantly |
| Job creation pause | Pause new job creation without affecting active escrows |
| Token whitelisting | Only approved tokens accepted |
| Token blacklisting | Ban a previously whitelisted token |
| Arbiter management | Authorize or revoke arbiters |
| Platform fee control | Configurable fee in basis points (max 10%) |
| Fee withdrawal | Fee collector withdraws accumulated fees per token |
| Delete escrow | Owner can delete terminal escrows with zero remaining funds |
| Stuck fund recovery | Owner withdraws excess balance above all escrowed amounts |
| Feature | Description |
|---|---|
| Gasless job applications | Backend fee-bump wraps user's signed XDR β applicants need zero XLM |
| Paginated applications | get_applications_page(offset, limit) for scalable UIs |
| Application count | get_application_count for pagination headers |
| User escrow index | Per-address list of escrow IDs for fast dashboards |
| WASM size | 59 KB (well under the 64 KB Soroban limit) |
Create Job β Apply / Accept β Start Work β Submit Milestones β Approve / Dispute β Payment
The client deposits funds into the escrow contract and defines:
- Project title, description, and deadline
- Milestones (amount + description per milestone); amounts must sum to
total_amount - platform_fee - Payment token (native XLM or whitelisted token)
- Optional: a specific beneficiary (direct contract) or leave open for applications
- Arbiters list and required confirmation count for dispute resolution
Funds are transferred from the client to the contract at creation time.
Freelancers browse open jobs, submit a cover letter and proposed timeline. Applications are stored on-chain. The client reviews and calls accept_freelancer. Gasless application support means applicants do not need XLM for fees.
The assigned freelancer calls start_work. The escrow status moves from Pending β InProgress and the fee portion is earmarked at this point.
For each milestone the freelancer submits via submit_milestone (or resubmit_milestone after rejection). The milestone status becomes Submitted.
Approve β payment released immediately to freelancer, milestone marked Approved.
Reject β milestone reverts to allow resubmission; rejection reason stored on-chain.
Dispute β milestone marked Disputed. Either party can attach IPFS evidence via submit_evidence. Authorized arbiters then call resolve_dispute.
Each arbiter calls resolve_dispute(escrow_id, milestone_index, arbiter, freelancer_amount, client_amount, reason). Votes are tracked idempotently. When the vote count reaches required_confirmations, the contract:
- Transfers
freelancer_amountto the beneficiary - Transfers
client_amountto the depositor - Marks the milestone
Resolved - Resets the vote count to 0
- Updates escrow status to
ReleasedorInProgressbased on remaining balance
A depositor can cancel an unstarted (no freelancer assigned) job. The penalty scales with repeat cancellations and the number of applicants who invested time:
| Cancellations | Base penalty |
|---|---|
| 0 β 2 | 0% |
| 3 β 5 | 5% |
| 6 β 10 | 10% |
| 11+ | 15% |
An additional 0β15% may apply based on application count. Total penalty is capped at 30% and the penalty decays over ~30 days (518,400 ledgers) of inactivity.
If scope or budget changes after work starts, the freelancer can call propose_milestone_change with a new amount and description. The client then calls approve_milestone_proposal (applies the change) or reject_milestone_proposal (reverts).
contracts/secureflow/src/
βββ lib.rs Public contract interface β all entrypoints
βββ storage_types.rs Enums, structs, DataKey variants, error codes
βββ escrow_core.rs Storage helpers, token transfers, whitelist checks
βββ escrow_management.rs create_escrow, add/remove milestones, cancel, fund mgmt
βββ work_lifecycle.rs start_work, submit/approve/reject/dispute milestone,
β milestone negotiation, per-milestone resolve_dispute
βββ refund_system.rs refund_escrow, emergency refund, deadline extension,
β overdue disputes, arbiter award/refund
βββ marketplace.rs apply_to_job, accept_freelancer, paginated apps
βββ ratings.rs submit_rating, get_average_rating, badges, client ratings
βββ evidence.rs submit_evidence, get_evidence
βββ admin.rs owner controls, pause, blacklist, fee withdrawal, delete_escrow
Pending β InProgress β Released
β Refunded
β Disputed
β Expired
β Cancelled
NotStarted β Submitted β Approved
β Rejected (freelancer resubmits)
β Disputed β Resolved
ProposalPending (negotiation in progress)
React 19 + TypeScript
βββ Web3Context wallet connection, high-level contract calls
βββ ContractService simulate-readonly / sendOwnerTransaction helpers
βββ Stellar SDK Transaction building, auth entry signing, RPC
βββ Soroban RPC Testnet / mainnet
| Key | Value |
|---|---|
| Contract ID | CBWMVACS6BVU55SQOSU2YLE6PL4J6COXJAWZZLTVFRE4E7UYOW4DX5KQ |
| WASM hash | 972dcf84f8a673d0063202ebc725cf16f1147ba9ff2c83de6541d1e1b4defb98 |
| Network | Test SDF Network ; September 2015 |
| Platform fee | 250 bp (2.5%) |
| Owner / fee-collector | GBL5ZXODI2UVOTTLNJGCJ2N52MO4XEUQB6TMXOEZIAVPGLBPWOJ6HDEE |
Note: Stellar testnet resets periodically wipe all contracts. After a reset, rebuild the WASM (
stellar contract build), upload it, deploy, and callinitializeonce. UpdateVITE_SECUREFLOW_CONTRACT_IDin.envto the new contract ID.
SecureFlow uses an embedded fee model. The total_amount deposited at creation already includes the platform fee:
platform_fee = total_amount Γ platform_fee_bp / 10_000
net_to_milestones = total_amount - platform_fee
sum(milestone.amounts) == net_to_milestones
The fee is earmarked (not transferred) at start_work and collected by the fee collector via withdraw_fees.
// Lifecycle
create_escrow(depositor, beneficiary?, arbiters, required_confirmations,
milestones: Vec<(i128, String)>, token?, total_amount, duration,
project_title, project_description) β u32 // returns escrow_id
start_work(escrow_id, beneficiary)
submit_milestone(escrow_id, milestone_index, description, beneficiary)
resubmit_milestone(escrow_id, milestone_index, description, beneficiary)
approve_milestone(escrow_id, milestone_index, depositor)
reject_milestone(escrow_id, milestone_index, reason, depositor)
dispute_milestone(escrow_id, milestone_index, reason, disputer)
// Cancellation & funds
cancel_job(escrow_id, depositor)
add_job_funds(escrow_id, depositor, additional_amount, milestone_index)
withdraw_job_funds(escrow_id, depositor, withdraw_amount, milestone_index)
// Milestone negotiation
propose_milestone_change(escrow_id, milestone_index, proposed_amount, proposed_description, freelancer)
approve_milestone_proposal(escrow_id, milestone_index, depositor)
reject_milestone_proposal(escrow_id, milestone_index, depositor)
// Dispute resolution
resolve_dispute(escrow_id, milestone_index, arbiter, freelancer_amount, client_amount, reason)
submit_evidence(escrow_id, milestone_index, submitter, cid)
// Marketplace
apply_to_job(escrow_id, cover_letter, proposed_timeline, freelancer)
accept_freelancer(escrow_id, freelancer, depositor)
get_applications_page(escrow_id, offset, limit)
get_application_count(escrow_id)
// Refunds
refund_escrow(escrow_id, depositor)
emergency_refund_after_deadline(escrow_id, depositor)
extend_deadline(escrow_id, extra_seconds, depositor)
raise_overdue_dispute(escrow_id, requester, reason)
arbiter_approve_refund(escrow_id, arbiter)
arbiter_award_freelancer(escrow_id, arbiter, freelancer_amount)
// Ratings
submit_rating(escrow_id, rating, review, client)
submit_client_rating(escrow_id, rating, review, freelancer)
get_average_rating(freelancer) β (total, count)
get_badge(freelancer) β Badge
get_average_client_rating(client) β (total, count)
// Admin
initialize(owner, fee_collector, platform_fee_bp, default_whitelisted_tokens)
pause_contract() / unpause_contract()
pause_job_creation() / unpause_job_creation()
blacklist_token(token) / unblacklist_token(token)
whitelist_token(token)
authorize_arbiter(arbiter) / remove_arbiter(arbiter)
set_platform_fee_bp(fee_bp)
set_fee_collector(fee_collector)
set_owner(new_owner)
get_withdrawable_fees(token?) β i128
withdraw_fees(token?, caller)
withdraw_stuck_funds(token, to, amount)
delete_escrow(escrow_id)| Code | Name | Meaning |
|---|---|---|
| 1 | AlreadyInitialized |
initialize called twice |
| 100 | EscrowNotFound |
Invalid escrow ID |
| 200 | Unauthorized |
Caller not authorized |
| 300 | InvalidAmount |
Amount β€ 0 or exceeds bounds |
| 400 | InvalidStatus |
Operation not valid for current status |
| 500 | MilestoneNotFound |
Invalid milestone index |
| 600 | TokenNotWhitelisted |
Token not allowed |
| 700 | InsufficientFunds |
Contract balance too low |
| 800 | FeeTooHigh |
Fee exceeds 10% |
| 900 | AlreadyApplied |
Freelancer already applied |
| 1000 | NotApplied |
Freelancer hasn't applied |
| 1100 | FreelancerAlreadyAssigned |
Cannot reassign once started |
| 1200 | DeadlineNotPassed |
Too early for emergency refund |
| 1300 | ContractIsPaused |
Emergency pause active |
| 1400 | AlreadyBlacklisted |
Token already blacklisted |
| 1500 | NothingToRefund |
Fee balance is zero |
| 1600 | InsufficientWithdrawable |
Stuck-fund withdrawal exceeds excess |
| 1700 | NotInitialized |
Contract not yet initialized |
| 1800 | AlreadyVoted |
Arbiter already voted on this dispute |
| 1900 | InvalidVoteSplit |
freelancer_amount + client_amount β milestone.amount |
| 2000 | NoPendingProposal (prev. 2400) |
No proposal pending to approve/reject |
| 2100 | FundsStillLocked |
Escrow has remaining balance, cannot delete |
| 2200 | EscrowNotTerminal |
Escrow not in a terminal state |
| 2300 | CannotCancelAssignedJob |
Freelancer already assigned |
- Language: Rust (no_std)
- SDK: soroban-sdk 23.0.2
- Target: wasm32v1-none
- Toolchain: rust-toolchain.toml (stable channel pinned)
- Framework: React 19
- Language: TypeScript
- Build: Vite
- UI: Radix UI + Tailwind CSS
- State: Zustand
- Routing: React Router
- Forms: React Hook Form + Zod
- SDK: @stellar/stellar-sdk
- Wallets: @creit.tech/stellar-wallets-kit (Freighter, xBull, Lobstr, etc.)
- Generated clients:
src/contracts/generated/(auto-generated from contract ABI)
- Runtime: Cloudflare Workers (Hono framework)
- Purpose: Wraps user-signed XDRs in Stellar fee-bump transactions so applicants pay zero gas
- Rust stable toolchain
wasm32v1-nonetarget:rustup target add wasm32v1-none- Node.js v22+
- Stellar CLI v25+
- Scaffold Stellar plugin
git clone https://github.com/yourusername/secureflow.git
cd secureflow
npm installcp .env.example .envEdit .env:
VITE_STELLAR_NETWORK=testnet
VITE_SECUREFLOW_CONTRACT_ID=CBWMVACS6BVU55SQOSU2YLE6PL4J6COXJAWZZLTVFRE4E7UYOW4DX5KQ
# API backend (local dev: http://localhost:8787)
VITE_API_URL=http://localhost:8787
# Shared secret β must match API_SECRET in backend/.env
VITE_API_SECRET=<your_shared_secret>
# Optional: USDC token contract address for the token dropdown
VITE_USDC_TOKEN_CONTRACT=# Start frontend dev server
npm run dev
# β http://localhost:5173
# Build for production
npm run build
# Run contract tests
cargo test --manifest-path contracts/secureflow/Cargo.tomlAfter any contract change and re-deploy, regenerate the TypeScript bindings:
stellar scaffold build --build-clientsstellar contract build
# Output: target/wasm32v1-none/release/secureflow.wasm (~59 KB)# Upload WASM
stellar contract upload \
--wasm target/wasm32v1-none/release/secureflow.wasm \
--source me \
--network testnet
# Deploy contract
stellar contract deploy \
--wasm-hash <WASM_HASH> \
--source me \
--network testnet
# Initialize (replace CONTRACT_ID with output of deploy)
stellar contract invoke \
--id <CONTRACT_ID> \
--source me \
--network testnet \
-- initialize \
--owner me \
--fee-collector me \
--platform-fee-bp 250Update VITE_SECUREFLOW_CONTRACT_ID in .env and environments.toml with the new contract ID.
- No secrets in git β
.envandbackend/.envare gitignored. Verified viagit ls-files. - Stellar identity (
me.toml) β stored at~/.config/stellar/identity/, outside the repo, matched by.gitignore: **/identity/*.toml. - On-chain auth β all write operations call
address.require_auth(). No off-chain bypass possible. - Pause guard β
require_not_pausedwraps every state-changing entrypoint. Owner can halt everything instantly. - Fee protection β fee is earmarked at
start_work, not at creation, preventing early withdrawal griefing. - Re-dispute fix β
DisputeVoteCountis reset to 0 after each resolution, preventing stale vote counts from triggering a second execution.
| Risk | Status | Mitigation |
|---|---|---|
ADMIN_SECRET_KEY in backend/.env |
Not in git | This key controls the gasless fee-bump wallet AND owns the contract. Rotate immediately if compromised. |
VITE_API_SECRET bundled in frontend JS |
By design | Provides light authorization for the gasless API. Not a blockchain private key. Rotate via backend re-deploy. |
VITE_SECRET_KEY in .env |
Not in git, not used in frontend code | Stale variable β safe to remove. |
| Soroban testnet resets | Periodic | Redeploy WASM, call initialize, update .env and environments.toml. |
| Contract instance TTL | Managed | All writes call extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT). |
- Create a Job β set title, description, deadline, milestones, and deposit funds.
- Review Applications β freelancers apply; you see cover letters, timelines, badges, and ratings.
- Accept a Freelancer β call
accept_freelancer; contract is nowPendingawaitingstart_work. - Review Milestones β approve (payment released), reject (request revisions), or dispute (arbiter vote).
- Negotiate β accept or reject a freelancer's milestone change proposal.
- Browse Jobs β filter open jobs by budget, token, deadline.
- Apply β cover letter + proposed timeline (gasless β no XLM required).
- Start Work β call
start_workonce selected. - Submit Milestones β submit each milestone with a description when done.
- Dispute β if a rejection is unfair, dispute and attach IPFS evidence.
- Propose Changes β if scope changes, propose a milestone renegotiation.
- Authorized by owner β must be added via
authorize_arbiter. - Review disputes β examine on-chain evidence (IPFS CIDs).
- Vote β call
resolve_disputewith a precisefreelancer_amount+client_amountsplit. - Quorum executes β when
required_confirmationsvotes are reached, payout executes automatically.
- Admin Panel β requires owner wallet address.
- Pause β
pause_contracthalts all write ops;pause_job_creationhalts only new jobs. - Fees β set
platform_fee_bp, updatefee_collector, callwithdraw_feesper token. - Tokens β whitelist or blacklist tokens; blacklisted tokens block new escrow creation.
- Arbiters β authorize or revoke arbiter wallets.
- Cleanup β call
delete_escrowon terminal, zero-balance escrows to reclaim storage.
secureflow/
βββ contracts/
β βββ secureflow/
β βββ Cargo.toml
β βββ src/
β βββ lib.rs Contract entrypoints
β βββ storage_types.rs Enums, structs, DataKey, error codes
β βββ escrow_core.rs Storage helpers, token transfers
β βββ escrow_management.rs create_escrow, cancel_job, fund management
β βββ work_lifecycle.rs Milestone workflow, negotiation, resolve_dispute
β βββ refund_system.rs Refunds, deadline, overdue disputes
β βββ marketplace.rs Applications, accept freelancer
β βββ ratings.rs Ratings, badges, reputation
β βββ evidence.rs IPFS evidence storage
β βββ admin.rs Pause, blacklist, fee withdrawal, delete_escrow
βββ src/
β βββ components/
β β βββ admin/ Admin panel components
β β βββ approvals/ Milestone approval UI
β β βββ create/ Job creation wizard
β β βββ dashboard/ Stats, escrow cards
β β βββ freelancer/ Freelancer dashboard
β β βββ jobs/ Job marketplace
β β βββ notification-center.tsx On-chain event notifications
β β βββ ui/ Radix UI wrappers
β βββ contexts/
β β βββ web3-context.tsx Wallet + contract call context
β β βββ notification-context.tsx Notification state
β βββ contracts/
β β βββ generated/ Auto-generated TS contract clients
β βββ lib/
β β βββ web3/
β β β βββ contract-service.ts Full contract method bindings
β β β βββ stellar-config.ts Network config, contract IDs
β β β βββ wallet-signer.ts Transaction signing helpers
β β βββ api.ts Backend API client (gasless)
β βββ pages/
β β βββ AdminPage.tsx
β β βββ ApprovalsPage.tsx
β β βββ CreatePage.tsx
β β βββ DashboardPage.tsx
β β βββ FreelancerPage.tsx
β β βββ JobsPage.tsx
β β βββ HomePage.tsx
β βββ store/
β βββ wallet.store.ts Zustand wallet state
βββ backend/ Gasless API (Cloudflare Workers)
β βββ src/
β βββ .env.example
βββ environments.toml Stellar scaffold env config
βββ Cargo.toml Workspace manifest
βββ package.json
βββ README.md
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Make your changes with tests
- Run
cargo testandnode node_modules/typescript/bin/tsc --noEmit - Commit and open a Pull Request
See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
MIT β see LICENSE.
- Built with Scaffold Stellar
- Powered by Stellar and Soroban
- UI components from Radix UI
Built on Stellar Β· Soroban Docs Β· Stellar Expert (testnet)