Skip to main content

Escrow Flow (pt 1 - Token Trading)

Here's the process for Alice to buy an ERC-721 token from Bob with her ERC-20 token:

  1. Alice deposits her ERC-20 token into escrow via the ERC20EscrowObligation contract, demanding the ERC-721 token she wants
  2. Bob sends the ERC-721 token to Alice via the ERC721PaymentObligation contract, receiving an EAS attestation that he did so
  3. Bob uses the attestation to claim Alice's escrow

Depositing escrow

First, make your demand with ERC721PaymentObligation.ObligationData.

// Encode the demand for the ERC-721 token Alice wants
bytes memory demand = abi.encode(
ERC721PaymentObligation.ObligationData({
token: 0x1234..., // ERC-721 token address
tokenId: 42, // Token ID Alice wants
payee: alice // Alice's address
})
);

Then, approve the ERC20EscrowObligation contract to spend the token you're paying with, and deposit it into escrow using ERC20EscrowObligation.doObligation.

// Approve the escrow contract to spend Alice's ERC-20 tokens
IERC20(erc20Token).approve(address(erc20EscrowObligation), 1000 * 10**18);

// Deposit into escrow with the demand
bytes32 escrowUid = erc20EscrowObligation.doObligation(
ERC20EscrowObligation.ObligationData({
token: erc20Token,
amount: 1000 * 10**18,
arbiter: address(erc721PaymentObligation),
demand: demand
}),
block.timestamp + 86400 // 24 hour expiration
);

If you want to buy a different kind of token, use the ObligationData struct from the corresponding PaymentObligation contract instead (e.g. ERC20PaymentObligation for buying an ERC-20 token, or TokenBundlePaymentObligation for a bundle of many token types).

// Example: Demanding ERC-20 payment instead
bytes memory erc20Demand = abi.encode(
ERC20PaymentObligation.ObligationData({
token: 0x5678...,
amount: 500 * 10**18,
payee: alice
})
);

// Example: Demanding a token bundle
bytes memory bundleDemand = abi.encode(
TokenBundlePaymentObligation.ObligationData({
erc20Tokens: [erc20_1, erc20_2],
erc20Amounts: [100, 200],
erc721Tokens: [erc721_1],
erc721TokenIds: [1],
erc1155Tokens: [erc1155_1],
erc1155TokenIds: [10],
erc1155Amounts: [5],
payee: alice
})
);

If you want to pay with a different kind of token, use the corresponding EscrowObligation contract instead when calling doObligation. Remember to approve the EscrowObligation contract to spend the token you're paying with (via setApprovalForAll for ERC-1155), and to approve all tokens in the bundle if you're using TokenBundleEscrowObligation.

// Example: Escrowing an ERC-721 token
IERC721(erc721Token).approve(address(erc721EscrowObligation), tokenId);
bytes32 escrowUid = erc721EscrowObligation.doObligation(
ERC721EscrowObligation.ObligationData({
token: erc721Token,
tokenId: tokenId,
arbiter: address(erc20PaymentObligation),
demand: erc20Demand
}),
expirationTime
);

// Example: Escrowing ERC-1155 tokens
IERC1155(erc1155Token).setApprovalForAll(address(erc1155EscrowObligation), true);
bytes32 escrowUid = erc1155EscrowObligation.doObligation(
ERC1155EscrowObligation.ObligationData({
token: erc1155Token,
tokenId: tokenId,
amount: 10,
arbiter: address(erc20PaymentObligation),
demand: erc20Demand
}),
expirationTime
);

Fulfilling and claiming escrow

You can fulfill demands for ERC-721 payments with ERC721PaymentObligation.doObligation. Remember to approve the contract to use your token first. Calling doObligation will return an attestation UID that you can use to claim the escrow.

// Bob approves the payment contract to transfer his ERC-721
IERC721(erc721Token).approve(address(erc721PaymentObligation), tokenId);

// Bob fulfills Alice's demand
bytes32 paymentUid = erc721PaymentObligation.doObligation(
ERC721PaymentObligation.ObligationData({
token: erc721Token,
tokenId: tokenId,
payee: alice
})
);

// Bob claims Alice's escrow using both attestation UIDs
erc20EscrowObligation.collectEscrow(escrowUid, paymentUid);

Use the corresponding PaymentObligation contract to fulfill demands for other tokens.

// Example: Fulfilling ERC-20 payment demand
IERC20(erc20Token).approve(address(erc20PaymentObligation), amount);
bytes32 paymentUid = erc20PaymentObligation.doObligation(
ERC20PaymentObligation.ObligationData({
token: erc20Token,
amount: amount,
payee: alice
})
);

// Example: Fulfilling token bundle payment demand
// Approve all tokens in the bundle first
for (uint i = 0; i < erc20Tokens.length; i++) {
IERC20(erc20Tokens[i]).approve(address(bundlePaymentObligation), erc20Amounts[i]);
}
for (uint i = 0; i < erc721Tokens.length; i++) {
IERC721(erc721Tokens[i]).approve(address(bundlePaymentObligation), erc721TokenIds[i]);
}
// ... then fulfill
bytes32 paymentUid = bundlePaymentObligation.doObligation(bundleData);

Reclaiming expired escrow

You can reclaim your escrow if nobody fulfills it before it expires.

// Alice reclaims her expired escrow
erc20EscrowObligation.reclaimExpired(escrowUid);

Utility contracts and SDK functions

There are utility contracts that provide a convenient interface for doing token trades atomically, and SDKs in TypeScript, Rust, and Python that wrap these. The SDKs additionally have functions that generate ERC-20 permits to enable easy approval and escrow/payment in one transaction.

The functions to escrow one type of token, demanding any other type (buyYforX), and to fulfill any type of escrow demanding that token type (payXforY), are available in [TokenType]BarterUtils.sol, or the corresponding module of each SDK (e.g. client.erc20, client.tokenBundle...). Available token types are native tokens (ETH), ERC-20, ERC-721, ERC-1155, and bundles of all of these together.

// Alice: Create escrow offering ERC-20 for ERC-721 using barter utils
IERC20(usdcToken).approve(address(erc20BarterUtils), 1000e6);
bytes32 escrowUid = erc20BarterUtils.buyErc721WithErc20(
usdcToken, // bid token
1000e6, // bid amount
erc721Token, // ask token
42, // ask token ID
expiration
);

// Bob: Fulfill escrow offering ERC-721 for ERC-20
IERC721(erc721Token).approve(address(erc721BarterUtils), 42);
bytes32 paymentUid = erc721BarterUtils.payErc721ForErc20(escrowUid);

// Or use permit for gasless approval (ERC-20 only)
bytes32 escrowUid = erc20BarterUtils.permitAndBuyErc721WithErc20(
usdcToken, 1000e6, erc721Token, 42, expiration,
deadline, v, r, s // permit signature
);