Skip to main content


As CoW Protocol only supports ERC-20 tokens, this means that if a user wants to sell ETH on CoW Protocol, they need to:

  1. Convert their ETH into WETH
  2. Approve the vault relayer for spending their WETH
  3. Create their order

This process is time-consuming and potentially costly for the user and is why we developed Eth-flow, a contract that smooths the user experience when selling native tokens on CoW Protocol.


An intermediary smart contract is used to wrap ETH into WETH and create an intent on behalf of the user. This contract then expresses the users' intent to trade on CoW Protocol using an ERC-1271-signed intent.

The user interacts with Eth-flow to deposit ETH via createOrder. The Eth-flow contract will then create an intent on behalf of the user that will be placed into the Order Book by the Protocol and settled by solvers - the same way as if the user had wrapped their ETH to WETH and created the intent themselves.

The proceeds will go to the user and not to the exchange because we specify the user as the receiver in the Eth-flow contract intent.

User intent / Contract intent

Every ETH sell intent from a user ("user intent") is transformed into a WETH sell intent in the Eth-flow contract ("contract intent").

This intent is implicitly created when the user deposits ETH, by emitting an on-chain event that is indexed by the off-chain components of the protocol.

The user's intent is a subset of the contract intent as some parameters are implicit (such as the sell token being ETH and the receiver being the user). The following table describes the parameters of the user intent.


The user intent described below is not a valid intent for the settlement contract. It represents the data used by the Eth-flow contract for bookkeeping. The contract intent (GPv2Order.Data) is that which is settled by the Protocol.

buyTokenanysame as user
receiver!= address(0)same as userMust NOT be the zero address as this has the meaning of self in CoW Protocol
sellAmountanysame as user
buyAmountanysame as user
validToanytype(uint32).maxRequired to be fixed at the maximum point in the future as filledAmount in GPv2Settlement contract is relied upon which can be cleared by freeFilledAmountStorage
appDataanysame as user
feeAmountanysame as user
kindsellsellLimited to sell intents only as dust from buy intents left in the Eth-flow contract would not be economical for a user to withdraw
partiallyFillableanysame as user
sellTokenBalanceerc20erc20Only erc20 implemented
buyTokenBalanceerc20erc20Only erc20 implemented

Contract order signing

The contract order uses ERC-1271 signatures.

The signature is empty, since all information needed to verify the order can be found on-chain.

Signature verification in a settlement works as follows:

  1. The intent digest is computed as part of the settlement process and is the message that is assumed to be signed by the Eth-flow contract with ERC-1271. The digest is used to retrieve the owner and the validTo from the intent mapping.
  2. The intent validTo is checked against the current timestamp.
  3. The intent must be valid (should be set and not invalidated).

If all verification steps succeed, Eth-flow affirmatively signs the digest with ERC-1271.

Guarantees / Invariants

  1. A user can have multiple open intents

Data Types and Storage


This struct contains the parts of a user intent that need to be stored on chain.

struct OnchainData {
address owner;
uint32 validTo;

For asserting the validity of the intent, the Eth-flow contract applies some assumptions to the owner:

  • owner = address(0) ⇒ unset
  • owner = address(0xffffffffffffffffffffffffffffffffffffffff) ⇒ invalidated

Modifying the validTo field does not change the contract intent digest.


This struct collects all parameters needed to describe a single user trade intent.

struct Data {
IERC20 buyToken;
address receiver;
uint256 sellAmount;
uint256 buyAmount;
bytes32 appData;
uint256 feeAmount;
uint32 validTo;
bool partiallyFillable;
int64 quoteId;
...Same as GPv2Order.Data
quoteIdquoteId returned by Order book API when requesting a quote for this order

Users should provide a valid quoteId when placing an order. This is not enforced by the Eth-flow contract, however quotes may be used as a basis to determine whether the automated refunding service should refund an order that has expired or not. In all cases, the user is able to manually refund the portion of their order that has not been matched.


The Eth-flow contract stores users' intents as a mapping:

mapping(bytes32 => EthFlowOrder.OnchainData) public orders;

The key of the mapping is the intent digest. It is derived by computing the contract intent digest (i.e. GPv2Order.Data struct digest) from the user intent parameters.


There is a possibility of a collision in the digest. There could be two different Eth-flow intents that end up having the same digest. In this case, only one of the two intents can be created and the contract would revert if trying to create the second one.



All interactions with the Eth-flow contract require an Ethereum transaction by the user and incur a gas cost in addition to the protocol fee.

For users


For when a user wants to sell ETH:

function createOrder(EthFlowOrder.Data order) payable;

Eth-flow performs some checks when the user creates an intent. Failing any of these checks means that the transaction reverts:

  1. The amount of ETH sent along with the transaction must be exactly what is needed to cover the sell amount plus the fees.
  2. The order must be valid at the time the transaction is mined.

The intent parameters are used to compute the intent digest according to the intent mapping. On successful execution of the transaction a new order is added to storage:

intent digest -> (msg.sender, validTo)


For when a user wants to invalidate an intent and return the ETH to the intent creator:

function invalidateOrder(EthFlowOrder.Data order)

order is the same intent struct used to create the intent.

Intents can be invalidated in two ways:

  1. The user who created the intent calls this function. Every valid intent can be invalidated at any time by its creator.
  2. After the intent has expired, any address can trigger its invalidation. This is done to allow CoW Protocol to provide a service to automatically refund unmatched orders to the users.

An intent's validity and owner are recovered from the intent mapping.

Each intent can be invalidated at most once and returns all funds that have not yet been used for trading. After invalidation, the intent is marked as invalid by setting the intent mapping for the intent digest to invalidated.


Do you need to manually recover funds from an Eth-flow intent? There's a tool for that. Simply enter the TX hash of the Eth-flow intent creation transaction and the tool will generate a transaction that will invalidate the intent and return the funds to the user.


The Eth-flow contract has events that are indexed by the Protocol. These events are:

  • OrderPlacement


There are two components in services that are used in the off-chain infrastructure for Eth-flow:

  • Protocol - adds the intent to the order book
  • Refunder - automatically refunds unmatched intents