Escrow Contract Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Escrow Contract Development
Medium
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Developing Escrow Contracts

An escrow contract looks simple: deposit money, meet the condition, withdraw money. In practice, it's one of the most audited contract types because any logic error or rights flaw leads to direct fund loss. Not to incorrect balance display — to ETH or token loss.

Two Main Failure Points

Insufficiently Strict Release Conditions

The most common bug in escrow contracts is incomplete condition checking before release(). Example: NFT marketplace with escrow for P2P deals. Buyer deposits ETH, seller should transfer NFT. Contract checks ownerOf(tokenId) == address(this) before release — that the NFT is on the contract. But doesn't verify it's exactly the NFT claimed at deposit time.

Attack: seller deposits a cheap NFT from the same collection (or a different contract with matching tokenId), contract sees the NFT on its address and releases the ETH. Loss for the buyer — the difference between the claimed and actual NFT.

Correct implementation stores not just a deposit flag, but a complete deal snapshot: NFT contract address, tokenId, expected seller, amount, deadline.

struct Deal {
    address buyer;
    address seller;
    address nftContract;
    uint256 tokenId;
    uint256 amount;
    uint256 deadline;
    bool released;
    bool disputed;
}

Arbitration and Dispute Mechanism Problem

Simple two-party escrow (buyer and seller agree on release) has an obvious problem: if parties disagree, funds freeze forever. Either a third arbitrator is needed or a timeout with refund logic.

An arbitrator is a separate centralization and risk point. If arbitrator is an EOA, it's a single point of failure (key loss). If arbitrator is a contract, governance is needed. If arbitrator is a multisig (Gnosis Safe), this is an acceptable compromise for most cases.

Important: an arbitrator shouldn't be able to forcefully withdraw funds to an arbitrary address. Arbitrator rights must be limited: either approve release to buyer or approve return to seller. Nothing more.

How We Build Escrow Contracts

Base Structure

Three deal states: PENDING (funds deposited, condition not met), COMPLETED (release executed), CANCELLED (refund). State transitions strictly through functions with checks. No direct state access from outside.

Checks-effects-interactions everywhere without exception. This is especially important for ETH escrow: update state (marked as released) before sending ETH, not after.

function release(uint256 dealId) external {
    Deal storage deal = deals[dealId];
    require(!deal.released, "Already released");
    require(msg.sender == deal.buyer || msg.sender == arbiter, "Unauthorized");
    
    deal.released = true; // Effects first
    
    // Interactions last
    (bool success, ) = deal.seller.call{value: deal.amount}("");
    require(success, "Transfer failed");
    
    emit Released(dealId, deal.seller, deal.amount);
}

Working with ERC-20 Tokens

ETH escrow is simpler: ETH can't be recalled after deposit via approve(). With ERC-20 — different story. If the contract holds tokens via transferFrom() at deposit time — it physically owns them, and approve revocation after deposit changes nothing. This is the correct pattern.

Wrong pattern: contract only records allowance and does transferFrom() at release time. Between deposit and release, the buyer can revoke approve(), and release() will revert. Seller fulfilled the condition but didn't receive funds.

For fee-on-transfer tokens (USDT on some chains) count actually received amount: balanceBefore - balanceAfter, don't trust the amount parameter in transferFrom().

Timeouts and Deadlines

Every deal should have a deadline. Without it: seller doesn't fulfill the condition, buyer can't claim money, funds freeze. After deadline expires — automatic refund to buyer without seller agreement needed.

Check deadline via block.timestamp. Known pitfall: block.timestamp can shift ±15 seconds on Ethereum. For deadlines in days this is negligible, for deadlines in seconds — it's not.

Reentrancy in Escrow

ETH escrow is especially vulnerable to reentrancy via receive(). Use ReentrancyGuard from OpenZeppelin on release() and refund() functions. Alternative — pull pattern: don't send ETH directly, instead record in mapping withdrawable[seller] += amount, seller calls withdraw() themselves. This completely eliminates reentrancy in release().

Approach Reentrancy Risk UX
Push (direct send) Yes, needs ReentrancyGuard Automatic
Pull (withdrawable mapping) Absent Requires separate transaction
Pull + permit Absent Gasless via signature

Upgradeability and Multi-Purpose Escrow

For marketplaces with high transaction volume, a factory pattern makes sense: one EscrowFactory that deploys minimal proxies (EIP-1167) per deal. This isolates funds from different deals in separate contracts and simplifies audit.

Upgradeability via Transparent Proxy or UUPS adds risk: an administrator could change release() logic after deposit. For honest escrow, upgradeability should be limited or excluded. If upgradeability is needed for bugfixes — use timelock (minimum 48 hours) and multisig.

Timeline

Basic ETH/ERC-20 escrow with arbitrator and deadline: 2-3 business days including tests. NFT escrow with dispute mechanism and factory pattern: 4-6 business days. Cost is calculated individually after clarifying requirements.