facebook

Building a Blockchain-Based Freelance Payment System

Calender

1st August 2025

Clock

5 min read

Author
Ankit Mishra

Sr. Associate Consultant L2 - Development

Freelance work is increasingly global, but traditional platforms still face issues like delayed payments, high fees, and trust concerns. A blockchain-based freelance system, built using blockchain development services, can eliminate middlemen, ensure fair payments through smart contracts, and offer transparent dispute resolution.

 

What We're Building

A decentralized freelance payment system on Ethereum (or any EVM-compatible chain) featuring:

  • Escrow-based payments
  • Client and freelancer ratings
  • Dispute resolution via third-party voters
  • Full transparency with on-chain history

Contract Architecture

Freelance Escrow - Handles escrow, job creation, approval, payment withdrawal, disputes, and ratings.
 

Freelance Escrow Contract

This contract manages the core features of the system, such as creating freelance jobs, approving work, withdrawing payments, rating both parties, and handling disputes. Here's the contract code:

 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract FreelanceEscrow {

    struct Job {
        address client;
        address freelancer;
        uint256 amount;
        bool isApproved;
        bool isWithdrawn;
        bool disputeOpened;
        address[] voters;
        uint8 votesForFreelancer;
        uint8 freelancerRating;
        uint8 clientRating;
        bool freelancerRated;
        bool clientRated;
        mapping(address => bool) hasVoted;
    }

    Job[] public jobs;

    event JobCreated(uint256 indexed jobId, address client, address freelancer, uint256 amount);
    event JobApproved(uint256 indexed jobId);
    event PaymentWithdrawn(uint256 indexed jobId);
    event FreelancerRated(uint256 indexed jobId, uint8 rating);
    event ClientRated(uint256 indexed jobId, uint8 rating);
    event DisputeOpened(uint256 indexed jobId);
    event DisputeVoted(uint256 indexed jobId, bool supportFreelancer);

    // Public method for creating freelance jobs
    function createFreelanceJob(address _freelancer) external payable {
        require(msg.value > 0, 'Escrow must have funds');

        uint256 jobId = jobs.length;
        Job storage newJob = jobs.push();
        newJob.client = msg.sender;
        newJob.freelancer = _freelancer;
        newJob.amount = msg.value;
        newJob.isApproved = false;
        newJob.isWithdrawn = false;
        newJob.disputeOpened = false;
        newJob.voters = new address[](0) ;
        newJob.votesForFreelancer = 0;
        newJob.freelancerRating = 0;
        newJob.clientRating = 0;
        newJob.freelancerRated = false;
        newJob.clientRated = false;

        emit JobCreated(jobId, msg.sender, _freelancer, msg.value);
    }

    // Approve work function (public)
    function approveWork(uint256 jobId) external {
        Job storage job = jobs[jobId];
        require(msg.sender == job.client, 'Only client can approve');
        require(!job.isApproved, 'Already approved');

        job.isApproved = true;
        emit JobApproved(jobId);
    }

    // Withdraw payment function (public)
    function withdrawPayment(uint256 jobId) external {
        Job storage job = jobs[jobId];
        require(msg.sender == job.freelancer, 'Only freelancer');
        require(!job.isWithdrawn, 'Already withdrawn');
        require(
            job.isApproved || (job.disputeOpened && job.votesForFreelancer > job.voters.length / 2),
            'Not eligible for withdrawal'
        );

        job.isWithdrawn = true;
        payable(job.freelancer).transfer(job.amount);
        emit PaymentWithdrawn(jobId);
    }

    // Rate freelancer (public)
    function rateFreelancer(uint256 jobId, uint8 _rating) external {
        Job storage job = jobs[jobId];
        require(msg.sender == job.client, 'Only client');
        require(!job.freelancerRated, 'Already rated');
        require(_rating <= 5, 'Rating out of range');

        job.freelancerRating = _rating;
        job.freelancerRated = true;
        emit FreelancerRated(jobId, _rating);
    }

    // Rate client (public)
    function rateClient(uint256 jobId, uint8 _rating) external {
        Job storage job = jobs[jobId];
        require(msg.sender == job.freelancer, 'Only freelancer');
        require(!job.clientRated, 'Already rated');
        require(_rating <= 5, 'Rating out of range');

        job.clientRating = _rating;
        job.clientRated = true;
        emit ClientRated(jobId, _rating);
    }

    // Open dispute (public)
    function openDispute(uint256 jobId, address[] calldata _voters) external {
        Job storage job = jobs[jobId];
        require(msg.sender == job.client || msg.sender == job.freelancer, 'Unauthorized');
        require(!job.disputeOpened, 'Already open');
        require(_voters.length > 0, 'Voters required');

        job.disputeOpened = true;
        job.voters = _voters;
        emit DisputeOpened(jobId);
    }

    // Vote in dispute (public)
    function voteDispute(uint256 jobId, bool supportFreelancer) external {
        Job storage job = jobs[jobId];
        require(job.disputeOpened, 'No dispute active');
        require(!job.hasVoted[msg.sender], 'Already voted');

        bool validVoter = false;
        for (uint i = 0; i < job.voters.length; i++) {
            if (job.voters[i] == msg.sender) {
                validVoter = true;
                break;
            }
        }
        require(validVoter, 'Not an assigned voter');

        job.hasVoted[msg.sender] = true;
        if (supportFreelancer) {
            job.votesForFreelancer++;
        }
        emit DisputeVoted(jobId, supportFreelancer);
    }

    // Get job details
    function getJob(uint256 jobId) external view returns (
        address client,
        address freelancer,
        uint256 amount,
        bool isApproved,
        bool isWithdrawn,
        bool disputeOpened,
        uint8 votesForFreelancer,
        uint8 freelancerRating,
        uint8 clientRating,
        bool freelancerRated,
        bool clientRated
    ) {
        Job storage job = jobs[jobId];
        return (
            job.client,
            job.freelancer,
            job.amount,
            job.isApproved,
            job.isWithdrawn,
            job.disputeOpened,
            job.votesForFreelancer,
            job.freelancerRating,
            job.clientRating,
            job.freelancerRated,
            job.clientRated
        );
    }
}

 

Key Features

  1. Escrow System: Clients deposit funds into an escrow that's automated through smart contracts, allowing freelancers to withdraw only after approval or a resolved dispute.
  2. On-chain Ratings: Both the freelancer and client rate each other (1-5 stars), leaving immutable feedback for transparency.
  3. Dispute Voting: Third-party voters (assigned during the dispute) vote on the dispute's outcome. If the freelancer wins the majority vote, they can withdraw the funds even if the client hasn't approved the work.
  4. Job Tracking: All jobs created are indexed and can be referenced in the factory contract for easy tracking.
     

    Example Usage Flow

  • Job Creation: The client calls createFreelanceJob(freelancer) and deposits ETH.
  • Freelancer Completes Work: The freelancer finishes the task.
  • Client Approves Work: The client approves the work by calling approveWork().
  • Freelancer Withdraws Payment: If the work is approved or after a successful dispute vote, the freelancer calls withdrawPayment().
  • Ratings: After the transaction, both parties rate each other using rateFreelancer() and rateClient().
  • Dispute Process: If the client doesn't approve the work, a dispute can be opened, and voters vote using voteDispute(). If the freelancer wins, they can withdraw the payment.
     

Security Considerations

  • Reentrancy Protection: Use of nonReentrant modifiers to prevent reentrancy attacks during payment withdrawals.
  • Identity Systems: Incorporate ENS or Decentralized IDs (DIDs) to enhance identity verification and trust.
  • DAO Governance: Consider replacing the static voter selection system with DAO governance for more decentralized dispute resolution.
     

Conclusion 

This blockchain-based freelancer payment system empowers both freelancers and clients to interact directly, ensuring trustless, fair, and transparent transactions. With no platform fees, instant payouts, decentralized dispute resolution, and an on-chain reputation system, this solution is poised to redefine global freelance work, promoting fairness and reducing reliance on centralized platforms.

Ready to build your own decentralized freelance platform?
Get in touch with our blockchain experts to develop a secure, smart contract-powered payment system tailored to your needs. Contact us today.

Table of Content

Author Ankit Mishra

Ankit Mishra is an experienced backend developer with 2.5 years of industry expertise. He is well-versed in using Nodejs, Express, Sequelize, PHP, Laraval, Apache, HTML, CSS, JavaScript, jQuery, and various databases, and stays up-to-date with the latest technologies. Ankit has worked on various projects, including Oodles Blockchain internal site, Duel Deal, and Distincts, and is always eager to explore new technologies and think critically. His creative and analytical abilities make him a valuable asset to any organization he works with.

Sr. Associate Consultant L2 - Development

bg bg

What's Trending in Tech

bg

Our Offices

India

INDIA

DG-18-009, Tower B,
Emaar Digital Greens, Sector 61,
Gurugram, Haryana
122011.
Unit- 117-120, First Floor,
Welldone Tech Park,
Sector 48, Sohna road,
Gurugram, Haryana
122018.
USA

USA

30N, Gloud St STR E, Sheridan, Wyoming (USA) - 82801
Singapore

SINGAPORE

10 Anson Road, #13-09, International Plaza Singapore 079903.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.