← back

CASE STUDY

Cosmic Handshake

~1% fee instead of the usual 10–20%. 61-test suite, contract verified on-chain.

Trustless freelance escrow on Monad. A client escrows MON across up to five described milestones, each released, refunded, disputed, or claimed independently — the contract is the trusted third party, so nobody can freeze the money or take a cut beyond what's written in the code.

  • Solidity
  • Foundry
  • Monad
  • Next.js
  • wagmi/viem

The problem

Freelance marketplaces exist because neither side trusts the other to move money first. Today that trust is rented from a platform — Upwork, Fiverr — which holds the funds in a black box and charges 10–20% for the service. The platform can freeze a payout, change its terms, or simply decide against you, and the fee isn't priced against the cost of escrow; it's priced against having nowhere else to go.

Cosmic Handshake replaces the intermediary with a contract. It can't abscond with the money, can't freeze a release, and can't take a cut beyond what's written into its code: roughly 1%, skimmed on-chain at settlement. Built on Monad testnet.

Milestones as a state machine

A client escrows MON for a named freelancer, split across up to five milestones, each with its own on-chain description and its own independent release. Every milestone carries one of six states:

  • Terra — escrowed, awaiting delivery
  • Luna — freelancer marked delivered, awaiting approval
  • Solis — approved and released (99% freelancer, 1% platform)
  • Void — cancelled before delivery, refunded to the client
  • Eclipse — deadline passed, claimed by the freelancer regardless of status
  • Nova — disputed and frozen until the arbiter rules

The hard part of escrow isn't the happy path — it's what happens when someone stops replying, and there are two ways to get that wrong.

Escrow that only releases on client approval lets a silent client strand the money forever; the freelancer has delivered and has no recourse. Eclipse fixes that: past the job's shared deadline, funds become claimable by the freelancer whether or not the client ever showed up. But a pure timeout is just as broken in the other direction — it lets a freelancer deliver nothing and wait out the clock. Nova is the counterweight. Either party can freeze a delivered-but-unapproved milestone for a designated arbiter, who awards a full payout or a full refund and takes no fee in either direction, so arbitration is never financially motivated toward one outcome.

The deadline is per-job rather than per-milestone, which is a deliberate simplification — one clock is much easier to reason about when funds are at stake, and milestone-level deadlines are on the deferred list rather than the shipped one.

Testing, because it holds real money

Contract bugs aren't patchable after deployment, so the test suite is as much the deliverable as the contract. EscrowV3 carries 61 unit tests; EscrowV2 keeps its own 58, plus fuzz and stateful-invariant suites that still run against it:

  • Fuzz — 5 properties at 256 runs each: the fee split always reconstructs the exact milestone amount for any value up to 1M MON, and arbitrary 1–5 milestone splits always sum-check.
  • Invariant — a handler drives random action sequences across random actors, holding one property: contract balance always equals the sum of unsettled milestones.
  • Reentrancy — a malicious freelancer contract attempts to re-enter on payout.

Boundary coverage is explicit where the arithmetic gets uncomfortable: fee rounding on 1-wei milestones, the five-milestone cap, zero and invalid inputs, and multi-job isolation. Every custom error has a test for the condition that triggers it.

The deployed EscrowV3 is verified on Monad Testnet (chain 10143) with a Sourcify exact match, at 0xdc32…2818. Solidity and Foundry, with no external contract dependencies.

The client

Next.js App Router with wagmi/viem and RainbowKit. Every job lives at a shareable /job/<id> URL that renders according to whichever wallet is connected: the client sees Approve and Cancel, the freelancer sees Mark Delivered, Claim, and Dispute, the arbiter sees Resolve, and anyone else gets a read-only view. One URL, four audiences, no account system — the wallet is the session. /dashboard lists every job a wallet is party to with per-milestone progress, and the arbiter gets an inbox of what needs ruling on.

Two AI assistants run through server-side API routes rather than the browser: one helps scope a job into sensible milestones at creation time, and one summarises a dispute for the arbiter. Both are advisory — neither can move funds, because nothing off-chain should be able to.

What I'd change

The contract went through three versions: V1 handled single lump-sum jobs, V2 added milestones, V3 gave them on-chain descriptions. Earlier versions are still deployed and verified but no longer wired to the frontend.

The most limiting deferral is the single arbiter, currently set to the deployer address. That's a demo-grade compromise that quietly reintroduces the exact problem the project exists to solve: a trusted third party who can decide where the money goes. Multi-arbiter voting is what makes the trustless claim actually hold end to end, and until it exists the honest description is "trustless settlement with a trusted referee."

Also deferred: ERC20 support (MON only today), reputation scores, and milestone-level deadlines.