[ DOCUMENTATION_MENU ]
[ BUILD ]

Escrow contract

PaiEscrowV2, function by function, with the ABI an agent needs to call it directly.

PaiEscrowV2 lives on Robinhood Chain mainnet (chain id 4663) at 0x6FdD0A840f29bA1F4b67b2328343D949dc4764A0, verified on Sourcify. It calls the Groth16 DeliveryVerifier at 0x8E3D0C497CCbB0cc44663A2D5Df00Fd131C29f06 and the Poseidon library at 0xd4De205c325cAFBc11a1FebdB061bF00cA48Cc4b. Source: contracts/src/PaiEscrowV2.sol.

Functions

Function Who What it does
open(seller, token, amount, pub, terms, deadline, proof) buyer verifies the delivery proof, then locks amount of ETH (token = 0x0, send it as value) or an ERC-20 the buyer approved
claim(id, key) anyone if Poseidon(key) == keyHash and the deadline has not passed, pays the seller minus the fee and stores the key
release(id) buyer pays the seller without a key
refund(id) anyone after the deadline, returns everything to the buyer
decline(id) seller refunds the buyer at once
verifyDelivery(pub, proof) view checks a proof without opening an order
getOrder(id) view the full order, key included once claimed

pub is [keyHash, cHash, dHash]. proof is the Groth16 proof as eight integers in the order the verifier expects: a[0], a[1], b[0][1], b[0][0], b[1][1], b[1][0], c[0], c[1] (the b coordinates swapped, as snarkjs exportSolidityCallData prints them).

ABI

struct Order { address buyer; uint40 openedAt; uint40 deadline; uint16 feeBps; address seller; uint8 status; uint40 closedAt; address token; uint256 amount; uint256 keyHash; uint256 cHash; uint256 dHash; bytes32 terms; uint256 key; }
function open(address seller, address token, uint256 amount, uint256[3] pub, bytes32 terms, uint40 deadline, uint256[8] proof) payable returns (uint256 id)
function claim(uint256 id, uint256 key)
function release(uint256 id)
function refund(uint256 id)
function decline(uint256 id)
function verifyDelivery(uint256[3] pub, uint256[8] proof) view returns (bool)
function getOrder(uint256 id) view returns (Order)
event Opened(uint256 indexed id, address indexed buyer, address indexed seller, address token, uint256 amount, uint256 keyHash, uint256 cHash, uint256 dHash, bytes32 terms, uint40 deadline, uint16 feeBps)
event Claimed(uint256 indexed id, address indexed seller, uint256 key, uint256 payout, uint256 fee)

Status values: 1 open, 2 claimed, 3 released, 4 refunded, 5 declined.

Gas

Measured on mainnet: open about 500 000 gas (the proof check is most of it), claim about 105 000.

Fees

The protocol fee is 0.50 %, snapshotted into each order when it is opened and capped at 2 % in the contract. It is taken from the seller's payout on claim and release, never on a refund.

V1

The first escrow, a hash-lock without proofs (keccak256(key)), stays on-chain at 0x0b5aacaB93ee0Ce06da9aF3F151b2671F69b4805. The app still reads its orders at /app/order/v1-<id>. New orders go to V2.