Escrow
The Escrow contract facilitates secure token holding with cross-chain settlement capabilities, enabling trustless multi-party transactions with automatic refund mechanisms.
Overview
The escrow system allows depositors to lock tokens that can be released to recipients upon successful cross-chain settlement verification. If settlement doesn't occur within the specified timeframe, funds are automatically refundable to both parties according to pre-defined splits.
Core Concepts
Escrow Structure
Each escrow contains:
struct Escrow {
bytes12 salt; // Unique identifier component
address depositor; // Party depositing funds
address recipient; // Party receiving funds on settlement
address token; // Token address (0x0 for native)
uint256 escrowAmount; // Total amount escrowed
uint256 refundAmount; // Amount refundable to depositor
uint256 refundTimestamp; // When refunds become available
address settler; // Settlement verification contract
address sender; // Expected cross-chain message sender
bytes32 settlementId; // Unique settlement identifier
uint256 senderChainId; // Origin chain of settlement
}
Escrow States
enum EscrowStatus {
NULL, // Escrow doesn't exist
CREATED, // Funds deposited, awaiting settlement
REFUND_DEPOSIT, // Depositor refunded
REFUND_RECIPIENT, // Recipient refunded
FINALIZED // Settlement complete or fully refunded
}
Workflow
1. Creating an Escrow
Depositors create escrows by transferring tokens to the contract:
// Single escrow
Escrow memory escrowData = Escrow({
salt: bytes12(keccak256("unique-id")),
depositor: msg.sender,
recipient: recipientAddress,
token: tokenAddress, // or address(0) for ETH
escrowAmount: 1000e18,
refundAmount: 100e18, // 10% refund to depositor if not settled
refundTimestamp: block.timestamp + 7 days,
settler: settlerContract,
sender: orchestratorAddress,
settlementId: keccak256("settlement-123"),
senderChainId: 137 // Polygon
});
// Create escrow (include msg.value for native token)
escrow.escrow([escrowData]);
2. Settlement Process
Settlement occurs when the designated settler contract confirms the cross-chain message:
// Settlement is triggered when conditions are met
bytes32[] memory escrowIds = new bytes32[](1);
escrowIds[0] = keccak256(abi.encode(escrowData));
escrow.settle(escrowIds);
// The escrow contract verifies with the settler
// settler.read(settlementId, sender, senderChainId) must return true
Upon successful settlement:
- Full
escrowAmount
transfers to the recipient - Escrow status becomes
FINALIZED
EscrowSettled
event is emitted
3. Refund Mechanism
After refundTimestamp
passes, refunds become available:
// Refund both parties in one transaction
escrow.refund(escrowIds);
// Or refund individually (useful if one party is unresponsive)
escrow.refundDepositor(escrowIds);
escrow.refundRecipient(escrowIds);
Refund distribution:
- Depositor receives:
refundAmount
- Recipient receives:
escrowAmount - refundAmount
Integration with Settlers
The escrow system is settler-agnostic, working with any contract implementing the ISettler
interface:
interface ISettler {
function read(
bytes32 settlementId,
address attester,
uint256 chainId
) external view returns (bool isSettled);
}
This allows integration with various settlement mechanisms:
- Signature-based verification (SimpleSettler)
- Cross-chain messaging protocols (LayerZeroSettler)
- Oracle-based confirmation
- Custom verification logic
Security Features
Atomic Operations
- All escrow operations are atomic - either fully complete or revert
- Multiple escrows can be created/settled/refunded in single transaction
Protected Refunds
- Refunds only available after
refundTimestamp
- Partial refund amounts protect both parties
- Individual refund functions prevent griefing
Status Validation
- Every operation validates current escrow status
- Prevents double-spending and reentrancy
- Clear state transitions with events
Gas Optimization
Batch Operations
Process multiple escrows efficiently:
// Create multiple escrows
Escrow[] memory escrows = new Escrow[](3);
// ... populate escrows
escrow.escrow(escrows);
// Settle multiple
bytes32[] memory ids = new bytes32[](3);
// ... populate ids
escrow.settle(ids);
Storage Efficiency
- Escrow data hashed for unique ID generation
- Minimal storage slots per escrow
- Status tracked separately for gas-efficient updates
Events
event EscrowCreated(bytes32 escrowId);
event EscrowRefundedDepositor(bytes32 escrowId);
event EscrowRefundedRecipient(bytes32 escrowId);
event EscrowSettled(bytes32 escrowId);
Example Use Cases
Cross-Chain Token Sale
// Seller creates escrow for tokens on Chain A
// Buyer sends payment on Chain B
// LayerZeroSettler confirms payment
// Tokens release to buyer automatically
Trustless OTC Trade
// Party A escrows Token X
// Party B escrows Token Y on different chain
// Both settlements verified atomically
// Automatic refund if either side fails
Conditional Payments
// Escrow funds with custom settler logic
// Release based on oracle data, time locks, or multi-sig
// Partial refunds for milestone-based releases
Best Practices
Setting Refund Amounts
- Consider transaction costs when setting
refundAmount
- Use partial refunds to incentivize settlement
- Set reasonable
refundTimestamp
based on expected settlement time
Settler Selection
- Use SimpleSettler for trusted, centralized operations
- Use LayerZeroSettler for decentralized cross-chain verification
- Implement custom settlers for specific business logic
Error Handling
Always handle these potential errors:
InvalidStatus()
- Operation not allowed in current stateInvalidEscrow()
- Invalid parameters (e.g., refund > escrow)RefundInvalid()
- Refund attempted before deadlineSettlementInvalid()
- Settler rejected the settlement
Contract Addresses
Escrow contracts are deployed deterministically across all supported chains. Check the Address Book for current deployments.