Overview
Stellar and EVM chains both handle moving money. Neither handles conditional release of money —MilestoneEscrow is the contract that adds that condition: “release the funds when the milestone is delivered,” not before.
MilestoneEscrow is a tranche-based escrow for diaspora payments: someone abroad funds a deal, and payment is released to the recipient in agreed installments as milestones are completed — rather than as a single lump-sum transfer with no recourse.
Each MilestoneEscrow contract is a single deal between one payer and one payee. No admin, arbiter, or Ankara Chain wallet ever custodies funds outside the contract — every release either requires payer approval, arbiter resolution of an active dispute, or an elapsed timelock with no dispute raised.
The release flow
- Deploy — the integrating platform deploys a
MilestoneEscrowviaEscrowFactory.deployEscrow(), specifying the payer, payee, an optional arbiter, the stablecoin, a timelock duration, and the milestone amounts. - Fund — the payer approves the stablecoin and calls
fund(), depositing the full agreed total in one transaction. - Deliver — the payee calls
markDelivered(id)once a milestone’s work/goods are complete. This starts the timelock clock for that milestone. - Approve — the payer calls
approveMilestone(id), releasing that milestone’s funds to the payee immediately.
- Dispute — either party calls
raiseDispute(id)on a delivered milestone. The configured arbiter then callsresolveDispute(id, releaseToPayee)to release funds to the payee or refund the payer. Reverts withNotArbiterif no arbiter was configured for the deal. - Timelock — if the payer goes silent after delivery (no approval, no dispute), anyone can call
claimTimelockRelease(id)oncetimelockDurationseconds have elapsed sincemarkDelivered(), force-releasing funds to the payee. This protects the payee from a payer who simply stops responding.
Mutual cancellation
Bothpayer and payee can call voteCancel(). Once both have voted, _executeCancel() runs automatically: every milestone still in PENDING status is refunded to the payer. Milestones already DELIVERED, DISPUTED, RELEASED, or REFUNDED are untouched — they must go through their normal flow first.
Release is checks-effects-interactions
_release() flips the milestone’s status to RELEASED before the token transfer fires, consistent with the rest of the codebase’s pattern of ordering state changes before external calls rather than using a reentrancy guard.
Currency whitelisting
MilestoneEscrow itself doesn’t restrict which ERC-20 token it accepts — that’s enforced one layer up, by EscrowFactory:
deployEscrow() reverts with StablecoinNotAccepted if the requested token hasn’t been whitelisted by the factory owner. This keeps the deal-level contract simple while giving the platform operator central control over which currencies can be used.
Optional KYC gating
Like the ERC-20/ERC-721/ERC-1155 bases,MilestoneEscrow supports an optional IIdentityVerifier (address(0) disables gating entirely). When set, fund() checks the payer and _release() checks the payee — so both sides of a KYC-gated deal must be verified before money moves.
EscrowFactory
DeploysMilestoneEscrow instances via ERC1967Proxy, following the same proxy-registration pattern as TokenFactory, NFTFactory, and MultiTokenFactory — except there’s only one implementation slot (not a per-template enum + mapping), since there’s only one escrow contract type:
totalDeployedEscrows(), getDeployerEscrows(address). MilestoneEscrow is registered as the 4th FactoryType (ESCROW) in AnkaraFactoryRegistry, which unifies lookups (getAllDeployedByAddress(), isAnkaraToken()) across all four factories.
SDK Integration
TokenFactory.deployEscrow() deploys a new deal. The EscrowManager class (packages/sdk/src/core/EscrowManager.ts) wraps the full lifecycle of an already-deployed escrow:
EscrowManager also exposes reads (getMilestone, getAllMilestones, remainingBalance, isFunded, etc.) and the dispute/timelock/cancel/admin methods described above. See the CLI’s 8 escrow-* commands (starting with ankara deploy-escrow) for a fully interactive walkthrough of the same lifecycle.