Escrow, one milestone at a time
One small contract per agreement holds the budget and pays only by its rules.
Three roles
Funder
Locks the budget and writes the plan.
Builder
Delivers each milestone and gets paid.
Reviewers
Sign off; k of n must agree (a multi-signature).
The life of a milestone
Money only moves on the two thick arrows.
Locked and in-review money is still in escrow.
Three ways to sign off
Funder approves
One signature. Best for small bounties.
k of n reviewers
The signature that reaches k also releases the share.
Automatic check
An oracle (a service reporting a public fact) confirms it. Not met? Run it again later.
A worked example
A 6,000 tUSDC grant, signed off by 2 of 3 mentors.
| Milestone | Share | Amount | Signed off by |
|---|---|---|---|
| Design spec and RFC2 of 3 reviewers | 15% | 900 tUSDC | 2 of 3 reviewers |
| Local cache and sync engine2 of 3 reviewers | 35% | 2,100 tUSDC | 2 of 3 reviewers |
| Conflict resolution UI2 of 3 reviewers | 30% | 1,800 tUSDC | 2 of 3 reviewers |
| Docs and releaseAutomatic check: release published | 20% | 1,200 tUSDC | Automatic check: release published |
Shares add up to exactly 100%; the last milestone takes any rounding leftover.
Deadlines, reminders and refunds
- Past its deadline, a milestone shows as overdue; the funder can send a reminder.
- Cancelling refunds everything unreleased, even a milestone in review.
For developers
The demo's data layer mirrors the contract one function per state change.
Show the contract interface
| Function | What it does | Demo equivalent |
|---|---|---|
| lockFunds() | Deploy the agreement and deposit the budget | lockAgreement() |
| submit() | Builder marks a milestone delivered | submitMilestone() |
| approve() | One signature; releases when the rule is met | approveMilestone() |
| requestChanges() | Send a milestone back, money stays locked | requestChanges() |
| checkAndRelease() | Ask the oracle; release if the condition is met | releaseByCheck() |
| cancel() | Refund every unreleased milestone to the funder | cancelAndRefund() |