A client wants a piece of work done by a freelancer they have never met. Neither trusts the other with the money first. Today, a platform stands in the middle: it holds the payment, takes a cut, and decides disputes — and it can freeze accounts, change its fees, side with whoever it prefers, or disappear with the balance. Build the version where the middle is a contract.
Why the chain is load-bearing
The funds for a job are locked in the contract at the moment the job is accepted. From then on they can move in exactly three ways: to the freelancer when the client confirms delivery, back to the client when the job is cancelled under the rules, or split by an arbiter in a dispute. The deployer of the contract cannot touch them. The platform — if there is one — cannot touch them. A court order cannot make the contract do anything it was not written to do. Both parties can read the rules before they commit a single wei.
What is still trusted: the arbiter, in disputes only. Your roles table should make that boundary precise — what an arbiter can and cannot do with money that is not in dispute.
Actors
| Role | Does |
|---|---|
| Client | Posts a job with a locked budget, confirms delivery, opens disputes |
| Freelancer | Accepts a job, submits delivery, opens disputes |
| Arbiter | Resolves disputes by splitting the locked amount |
| Anyone | Triggers timeouts once a deadline has passed |
| Deployer | Deploys, and then — ideally — has no further power. If it keeps any, say which and why |
Core features
The features that make this subject this subject. A delivery without them is a different, smaller project.
- Post a job. The client creates a job with a description (or a hash of one), a budget, and a deadline, and locks the budget in the contract in the same transaction. Native ETH is enough; an ERC-20 option is an extension.
- Accept. A freelancer accepts an open job. From acceptance, the funds are committed.
- Deliver and release. The freelancer marks the job delivered; the client releases the funds. The freelancer receives the amount, minus a fee if your design has one.
- Cancel. Before acceptance, the client can cancel and recover the budget. After acceptance, cancellation follows rules you define and document.
- Dispute. Either party can open a dispute after delivery. The arbiter resolves it by choosing how the locked amount is split. The arbiter’s identity is fixed per job at creation, or chosen from a registry — either way, both parties know who it is before committing.
- Timeouts. A client who never responds to a delivery should not hold the freelancer’s money forever: after a delay, anyone can trigger the release. A freelancer who never delivers should not hold the client’s money forever: after the deadline, anyone can trigger a refund. Design the delays so that neither party benefits from silence.
- A job’s history is readable from the contract’s events: created, accepted, delivered, released, disputed, resolved, cancelled.
Extensions
- ERC-20 payments, with the approval flow handled properly in the frontend.
- Milestones: a job split into several payments, each released separately.
- A fee that goes to a treasury address, and a way to change the fee that is not “the deployer decides” — a timelock, a cap, or a vote.
- An arbiter registry with staking: arbiters put up a bond, and a party who wins an appeal gets part of it. Now the arbiter is trusted less.
- Reputation: a per-address record of completed jobs and dispute outcomes, computed from events by an indexer.
- Pull payments: instead of sending funds on release, credit a balance the recipient withdraws. Explain why that is safer.
Traps
- Sending ETH with
transfer. It forwards a fixed gas stipend and breaks with smart-contract recipients. Usecall, guard against reentrancy, and know why. - Reentrancy on release. The classic. If you send funds before updating the job’s state, a malicious freelancer contract can release twice. Checks-effects-interactions, and a test that proves it.
- Timeouts that reward silence. If the auto-release delay is shorter than a reasonable review period, clients get robbed by the clock. If the refund deadline is too short, freelancers get robbed. Choose, document, and test both edges.
- An arbiter who can touch undisputed funds. If your
resolvefunction works on any job, your arbiter is the platform you were trying to remove. - Off-chain job descriptions with on-chain nothing. If the contract holds only the money and the description lives on your server, a dispute has nothing to refer to. Store at least a content hash.
What the demo should show
Two browser profiles — client and freelancer — on Base Sepolia. The client posts a job and the locked balance appears on the explorer. The freelancer accepts and delivers. Then one of the two paths, live: the happy release, with the freelancer’s balance changing; or a dispute, with a third profile as arbiter splitting the funds. Then a failure: the client tries to release a job that has not been delivered, and the transaction reverts with a readable reason in the UI. Finally, the timeout path, which you will have pre-staged with a job whose deadline has already passed, because the jury will not wait a day.