Escrow protocol for DeFi coordination - showing it through NFT capital, OTC swaps and freelance work
CoordiFi is a flexible escrow protocol we have built to solve coordination problems in DeFi. The core is a SupremeFactory contract using EIP-1167 minimal proxies for gas efficient deployments (90% cheaper than normal). To show what it can do we made three applications - NFT capital partnerships, OTC token trading, and milestone based freelance payments.
NFT Escrow enables secure coordination between whitelist holders who lack capital and investors willing to fund the mints. A SmartMintWallet acts as the whitelisted address, preventing rug pulls by ensuring minted NFTs go directly to the escrow custody rather than the WL holder. After which the Profits are automatically split on chain according to pre agreed terms, eliminating back channel scams and informal trust throughout the process.
OTC Escrow facilitates safe P2P token swaps using a maker-taker model with optional Uniswap V3 price validation. Before the settlement, the contract queries on chain pool prices and warns the user if it is out of the tolerance limit. This model mitigates frontrunning, sandwich attacks, and manipulated pricing that plague traditional DEX swaps.
Freelance Escrow introduces milestone based payments with multi worker support and dispute resolution. Uniquely, it integrates Yellow Network's state channels for gasless operations clients and freelancers can approve milestones, submit work, and communicate off-chain, then batch-settle everything in a single transaction, achieving ~90% gas savings. Deliverables are stored permanently via Pinata/IPFS, and structured revision flows plus platform-mediated dispute resolution ensure trustless coordination from kickoff to the payout.
We built this with Foundry for contracts, React + Vite for frontend, and the typical web3 stack i.e, wagmi, viem, ethers. The interesting part is the architecture though. We have a SupremeFactory contract that serves as the main entry point, deploying all escrow instances using EIP-1167 minimal proxies (contracts/src/SupremeFactory.sol lines 4, 116, 176, 226 — imports Clones.sol and calls Clones.clone()). This is roughly 90% cheaper than deploying full contracts each time, which was important since every NFT partnership, OTC trade, or freelance project needs its own escrow instance.
The three applications share the same factory but use different templates (SupremeFactory.sol lines 26–28 defines the 3 template addresses, constructor lines 92–94 deploys them). NFTEscrow uses a SmartMintWallet system where the wallet gets whitelisted instead of the user's EOA, so capital providers can't get rugged (contracts/src/SmartMintWallet.sol lines 36–37: OnlyEscrow modifier). OTCEscrow hooks into Uniswap V3 pools to validate prices before settlement using slot0() queries (contracts/src/templates/OTCEscrow.sol function _validatePrice() lines 222–238).
FreelanceEscrow is the interesting one - it has milestone dependencies (contracts/src/templates/FreelanceEscrow.sol line 32: milestoneDependencies mapping, lines 410–412: _areDependenciesCompleted() check) and multi-worker support (line 26: workerMilestones mapping).
Yellow Network was probably the most challenging integration. The idea was to use their state channels for gasless milestone operations - workers submit work, clients approve, request revisions, all of that happens off-chain through their ClearNode, then you batch settle everything in one on-chain transaction at the end. We integrated their @erc7824/nitrolite SDK (frontend/package.json line 13: "@erc7824/nitrolite": "^0.5.3") for the WebSocket connection and message signing.
The way it works: you connect via WebSocket to their ClearNode (frontend/src/lib/yellowSDK.ts line 88: wss://clearnet-sandbox.yellow.com/ws), complete an auth flow where you sign a challenge with EIP-712, get back a JWT token, then you can create app sessions using createAppSessionMessage (frontend/src/hooks/useYellowSession.ts line 661). State updates use createSubmitAppStateMessage (same file, lines 844–850 SubmitAppStateRequestParamsV04). The session tracks allocations between client and worker plus versioned state updates for each milestone action.
The hacky part and this is worth mentioning :Yellow's resize_channel function, which is supposed to fund payment channels, kept failing with a "channel not found" error. There's essentially a race condition where after you call create_channel and immediately try to resize_channel to fund it, their backend hasn't indexed the channel yet. We tried adding delays but it was still unreliable.
So our workaround was to create channels for YellowScan visibility. (they show up even at 0 balance) but skip the problematic funding step entirely ( frontend/src/hooks/useYellowSession.ts lines 595–622: comment at line 597 explains: "resize_channel was failing with 'channel not found' so we skip funding"). The actual funds go directly to our FreelanceEscrow smart contract from the user's wallet. Yellow Network just handles the state coordination who submitted what, who approved which milestone, revision history, etc. Then at settlement time, we call settleWithYellowProof() on-chain ( contracts/src/templates/FreelanceEscrow.sol lines 545–661) which pays workers and refunds clients based on what the client specifies.
We also hit a quorum issue: initially we had 3 participants (client, worker, platform with weights 40/40/50 and quorum 80) but Platform was a placeholder address and ClearNode doesn't sign as Platform, so workers couldn't reach quorum. So basically fixed it by switching to 2-party (client/worker, weights 50/50, quorum 50) where either party can update state alone ( frontend/src/lib/yellowSDK.ts lines 115–119: FREELANCE_QUORUM config).
Other integrations went smoother: Pinata for IPFS ( frontend/src/lib/pinata.ts uploadToIPFS() function) where freelancers upload deliverables and we store the CID on-chain (FreelanceEscrow.sol lines 407, 415–418: ipfsHash in Deliverable struct), Supabase for off-chain stuff like project metadata and communication threads ( frontend/src/lib/supabase.ts 7 table types defined lines 20–130). Uniswap V3 for OTC price validation was actually clean - convert sqrtPriceX96 from slot0 to real price (OTCEscrow.sol lines 242–248: _convertSqrtPriceToPrice()) and warn for trades beyond tolerance (line 238: deviation <= toleranceBPS).
So the main thing here is this hybrid approach where Yellow handles gasless coordination but actual money flows through the escrow contract. Not the purest state channel implementation, but it works and still gets significant gas savings on milestone operations.

