Coby's Dutch CoWs

Dutch auctions for CoW Protocol on Base

Coby's Dutch CoWs integration guide

Dutch CoWs allows smart contracts to place Dutch Auction orders directly to CoW Protocol. A contract, whether via a hook or a user interaction, can place an order filled by a verified solver without the developer needing to set up any off-chain infrastructure.

Integrate

Do these three calls in one transaction, from the contract that holds the sell token. receiver must be that contract and startBlock must be block.number. The interface is src/IDutchCoW.sol.

1Predict the escrow

predictEscrow(params) returns the address the escrow will have.

2Approve it

Approve that address for sellAmount of the sell token.

3Place the order

placeOrder(params) deploys the escrow and registers the auction.

AuctionParams memory params = AuctionParams({
    sellToken: sellToken,
    buyToken: buyToken,
    sellAmount: sellAmount,
    maxAmount: maxAmount,
    receiver: address(this),
    startBlock: block.number,
    validFor: validFor,
    slippageBps: slippageBps
});

address escrow = auctioneer.predictEscrow(params);

Option A: standard approve. Use this when the token's approve returns a bool, which covers most ERC-20s.

IERC20(sellToken).approve(escrow, sellAmount);
auctioneer.placeOrder(params);

Some tokens, such as USDT, return nothing from approve. Option A reverts on those. Option B accepts both kinds of return, so use it when you have not tested the token.

Option B: low-level approve. Accepts a bool return or no return.

(bool ok, bytes memory data) = sellToken.call(abi.encodeWithSelector(0x095ea7b3, escrow, sellAmount));
require(ok && (data.length == 0 || abi.decode(data, (bool))), "approve");
auctioneer.placeOrder(params);
Allowance: at least sellAmount and less than 2 * sellAmount. The escrow pulls the whole allowance. Approve a little more than sellAmount only when the token takes a transfer fee.
Parameters: slippageBps allows 51 to 9998, which is 0.51% to 99.98%. validFor must be at least 25 blocks. maxAmount * slippageBps / 10_000 must be at least validFor, or the price step rounds to zero and the call reverts.
Fee: Dutch CoWs takes a 0.1% fee on volume. It is a CoW partner fee set in the order's app data (partnerFee, volumeBps: 10, recipient 0x785fAA8D709D7CaAD63d5FbDB750D4a22600d30d). Your order's app data is the handler's APP_DATA constant, so it carries the fee with every fill.

How it works

Your contract calls the auctioneer. The auctioneer deploys a small escrow, pulls the sell token into it, and registers the order with ComposableCoW. The CoW watch tower posts the order to the orderbook. A solver fills it by paying the buy token to your contract and taking the sell token from the escrow.

The price stays at maxAmount for the first 5 blocks, then falls. slippageBps is the total drop in basis points of maxAmount. validFor is how many Base blocks that drop takes. Base blocks are 200ms. The floor is maxAmount * (10_000 - slippageBps) / 10_000.

If nobody fills it, anyone can call releaseFunds on the escrow once block.number is greater than startBlock + validFor. The sell tokens go back to your contract. When the balance is at least 1000 raw units, the caller keeps balance / 1000. The full behavior is in the reference.

Base Deployment Addresses

ContractAddress
Auctioneer0xE105d00b8E13Cb7f33Ea22cA2c2320f40A0E2c63
Handler0xB417180c2FD563bE2fB7833E95908B4D243bB62b
Escrow implementation0x2607A4712ed8C0BE50A92f71f32f0cBE0C366de9
ComposableCoW0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74
Settlement0x9008D19f58AAbD9eD0D60971565AA8510560ab41
Vault relayer0xC92E8bdf79f0507f65a392b0ab4667716BFE0110