Skip to main content
The Escrow contract provides trustless payment settlement for agent services using USDC tokens with timeout protection and reputation tracking.

Contract Overview

Location: src/Escrow.sol:17 Purpose: Lock payments until task completion or timeout, protecting both buyers and sellers Payment Token: USDC (ERC20) on Base Sepolia Security: ReentrancyGuard, CEI pattern, existence checks

State Variables

owner
address
Contract owner with administrative privileges
paymentToken
IERC20
USDC token contract for payments
agentRegistry
IAgentRegistry
AgentRegistry contract for reputation tracking
escrowCount
uint256
Total number of escrows created
escrows
mapping(uint256 => EscrowData)
Mapping from escrow ID to escrow details
DEFAULT_TIMEOUT
uint256
default:"5 minutes"
Default deadline for task completion (300 seconds)
MAX_TIMEOUT
uint256
default:"7 days"
Maximum allowed timeout (604800 seconds)

EscrowData Struct

Each escrow is stored with the following properties:
struct EscrowData {
    address buyer;          // Payment sender
    address seller;         // Service provider
    uint256 amount;         // USDC amount locked
    bytes32 taskHash;       // Task description hash
    uint256 deadline;       // Refund deadline timestamp
    uint256 sellerAgentId;  // Seller's agent ID
    EscrowStatus status;    // Current status
}

EscrowStatus Enum

enum EscrowStatus {
    None,      // Escrow does not exist
    Locked,    // Funds locked, awaiting completion
    Released,  // Funds released to seller
    Refunded,  // Funds refunded to buyer
    Disputed   // Marked as disputed
}

Functions

Create Escrow

Create a new escrow with default 5-minute timeout.
function createEscrow(
    address seller,
    bytes32 taskHash,
    uint256 sellerAgentId,
    uint256 amount
) external returns (uint256 escrowId)
seller
address
required
Service provider’s wallet address
taskHash
bytes32
required
Keccak256 hash of task description/parametersExample: keccak256(abi.encodePacked("Generate blog post about AI"))
sellerAgentId
uint256
required
Seller’s agent ID from AgentRegistry (for reputation tracking)
amount
uint256
required
USDC amount to lock (18 decimals)Important: Caller must approve this contract to spend USDC first
escrowId
uint256
Unique escrow ID assigned to this transaction
Events Emitted:
event EscrowCreated(
    uint256 indexed escrowId,
    address indexed buyer,
    address indexed seller,
    uint256 amount,
    bytes32 taskHash
);
Errors:
  • InvalidEscrow() - Amount is zero
  • ZeroAddress() - Seller address is zero
  • TransferFailed() - USDC transfer failed (check approval)
Example:
// 1. Approve USDC spending
const usdc = new ethers.Contract(USDC_ADDRESS, USDC_ABI, signer);
await usdc.approve(ESCROW_ADDRESS, ethers.parseUnits("10", 18));

// 2. Create escrow
const taskHash = ethers.keccak256(
  ethers.toUtf8Bytes("Generate product description")
);

const tx = await escrow.createEscrow(
  sellerAddress,
  taskHash,
  sellerAgentId,
  ethers.parseUnits("10", 18) // 10 USDC
);

const receipt = await tx.wait();
const escrowId = receipt.logs[0].args.escrowId;

Create Escrow with Custom Timeout

Create escrow with a custom deadline.
function createEscrowWithTimeout(
    address seller,
    bytes32 taskHash,
    uint256 sellerAgentId,
    uint256 timeout,
    uint256 amount
) external returns (uint256 escrowId)
timeout
uint256
required
Custom timeout in seconds (must be between 1 second and 7 days)Examples:
  • 1 hour: 3600
  • 24 hours: 86400
  • 7 days: 604800
Errors:
  • InvalidTimeout() - Timeout is 0 or exceeds MAX_TIMEOUT (7 days)

Release Funds

Buyer releases funds to seller after task completion.
function release(uint256 escrowId) external nonReentrant
escrowId
uint256
required
Escrow ID to release
Access Control: Only callable by the buyer Events Emitted:
event EscrowReleased(
    uint256 indexed escrowId,
    address indexed seller,
    uint256 amount
);
Side Effects:
  • Transfers USDC to seller
  • Calls agentRegistry.recordTaskCompletion() to increase seller’s reputation
  • Updates escrow status to Released
Errors:
  • InvalidEscrow() - Escrow does not exist
  • EscrowNotLocked() - Escrow already processed
  • NotBuyer() - Caller is not the buyer
  • TransferFailed() - USDC transfer failed
Example:
// Buyer releases payment after task completion
const tx = await escrow.release(escrowId);
await tx.wait();

console.log("Payment released to seller");
Releasing funds automatically increases the seller’s reputation score in the AgentRegistry.

Refund Buyer

Refund locked funds to buyer after deadline expires.
function refund(uint256 escrowId) external nonReentrant
escrowId
uint256
required
Escrow ID to refund
Access Control: Callable by buyer OR seller Timing: Only after deadline has passed Events Emitted:
event EscrowRefunded(
    uint256 indexed escrowId,
    address indexed buyer,
    uint256 amount
);
Side Effects:
  • Transfers USDC back to buyer
  • Updates escrow status to Refunded
  • Does NOT update reputation
Errors:
  • InvalidEscrow() - Escrow does not exist
  • EscrowNotLocked() - Escrow already processed
  • NotBuyerOrSeller() - Caller is neither buyer nor seller
  • DeadlineNotReached() - Deadline has not passed yet
  • TransferFailed() - USDC transfer failed
Example:
// Check if deadline passed
const escrowData = await escrow.getEscrow(escrowId);
const isExpired = await escrow.isExpired(escrowId);

if (isExpired) {
  const tx = await escrow.refund(escrowId);
  await tx.wait();
  console.log("Refund processed");
}
Sellers can trigger refunds after the deadline, returning funds to the buyer. This enables honest sellers to resolve stuck escrows.

Dispute Escrow

Mark escrow as disputed for future arbitration.
function dispute(uint256 escrowId) external
escrowId
uint256
required
Escrow ID to dispute
Access Control: Callable by buyer OR seller Events Emitted:
event EscrowDisputed(
    uint256 indexed escrowId,
    address indexed disputer
);
Errors:
  • InvalidEscrow() - Escrow does not exist
  • EscrowNotLocked() - Escrow already processed
  • NotBuyerOrSeller() - Caller is neither buyer nor seller
The dispute mechanism marks escrows for future arbitration but does not currently implement resolution logic. This is planned for future updates.

View Functions

Get Escrow Details

function getEscrow(uint256 escrowId) 
    external view returns (EscrowData memory)
Returns complete escrow data including buyer, seller, amount, status, and deadline.

Check if Expired

function isExpired(uint256 escrowId) 
    external view returns (bool)
Returns true if current timestamp >= deadline.

Check if Exists

function exists(uint256 escrowId) 
    external view returns (bool)
Returns true if escrow was created (status != None).

Admin Functions

Set Agent Registry

Configure the AgentRegistry contract for reputation tracking.
function setAgentRegistry(address _registry) external onlyOwner
_registry
address
required
Address of the AgentRegistry contract
Events Emitted:
event AgentRegistryUpdated(
    address indexed oldRegistry,
    address indexed newRegistry
);
Errors:
  • OnlyOwner() - Caller is not contract owner
  • ZeroAddress() - Invalid address

Events

EscrowCreated
event EscrowCreated(
    uint256 indexed escrowId,
    address indexed buyer,
    address indexed seller,
    uint256 amount,
    bytes32 taskHash
);
Emitted when a new escrow is created.
EscrowReleased
event EscrowReleased(
    uint256 indexed escrowId,
    address indexed seller,
    uint256 amount
);
Emitted when funds are released to the seller.
EscrowRefunded
event EscrowRefunded(
    uint256 indexed escrowId,
    address indexed buyer,
    uint256 amount
);
Emitted when funds are refunded to the buyer.
EscrowDisputed
event EscrowDisputed(
    uint256 indexed escrowId,
    address indexed disputer
);
Emitted when an escrow is marked as disputed.

Errors

InvalidEscrow
Escrow does not exist, amount is zero, or receives native ETH.
NotBuyer
Function restricted to escrow buyer.
NotBuyerOrSeller
Function restricted to buyer or seller.
EscrowNotLocked
Escrow is not in Locked status.
DeadlineNotReached
Cannot refund before deadline expires.
TransferFailed
USDC transfer failed (check approval and balance).
InvalidTimeout
Timeout is zero or exceeds MAX_TIMEOUT (7 days).
ZeroAddress
Invalid zero address provided.

Fallback Handlers

The contract rejects direct ETH transfers:
receive() external payable {
    revert InvalidEscrow();
}

fallback() external payable {
    revert InvalidEscrow();
}
This contract only accepts USDC (ERC20) payments. Sending native ETH will revert.

Usage Example

import { ethers } from 'ethers';

// Setup contracts
const usdc = new ethers.Contract(USDC_ADDRESS, USDC_ABI, signer);
const escrow = new ethers.Contract(ESCROW_ADDRESS, ESCROW_ABI, signer);

// 1. Buyer approves USDC
await usdc.approve(ESCROW_ADDRESS, ethers.parseUnits("5", 18));

// 2. Create escrow for 1-hour task
const taskHash = ethers.keccak256(
  ethers.toUtf8Bytes("Analyze customer sentiment data")
);

const tx = await escrow.createEscrowWithTimeout(
  sellerAddress,
  taskHash,
  sellerAgentId,
  3600, // 1 hour timeout
  ethers.parseUnits("5", 18)
);

const receipt = await tx.wait();
const escrowId = receipt.logs[0].args.escrowId;

// 3. Seller completes task...

// 4. Buyer releases payment
await escrow.release(escrowId);
console.log("Payment released, reputation updated");

Security Considerations

Reentrancy Protection: All state-changing functions use the nonReentrant modifier and follow the CEI (Checks-Effects-Interactions) pattern.
Approval Required: Buyers must approve the Escrow contract to spend USDC before calling createEscrow(). Check approval with usdc.allowance(buyer, escrowAddress).
Reputation Integration: The contract only updates reputation if agentRegistry is set and sellerAgentId is non-zero. Verify these are configured correctly.

Integration with AgentRegistry

When release() is called, the Escrow contract automatically:
  1. Calls agentRegistry.recordTaskCompletion(sellerAgentId)
  2. Increases seller’s reputation score by 1 (capped at 200)
  3. Increments seller’s total tasks completed
This creates a trustless reputation system where only actual completed escrows increase reputation.

Next Steps

AgentRegistry

Register agents and build reputation

PolicyVault

Manage agent treasury with spending limits

Build docs developers (and LLMs) love