HomeKnowledge BaseTutorial: Creating Smart Orders with CoW Protocol

Tutorial: Creating Smart Orders with CoW Protocol

10 min read

CoW Protocol’s native order types are limited to partially-fillable limit buy and limit sell orders. While this expression language covers most retail use cases, it misses some more-powerful advanced order types such as:

  • Good after time orders
  • Stop loss orders
  • Automated rebalancing portfolio orders

… to just name a few.

Adding new native order types to CoW Protocol would involve changing and redeploying the smart contract logic of the core protocol, and is therefore something we want to avoid for the time being.

While the above-mentioned order semantics could be communicated to the protocol/solvers without a smart contract change (e.g. by using additional fields in the app_data), they couldn’t be enforced by the settlement logic and would therefore be subject to “off-chain consensus” (misbehavior would result in slashing by the DAO). This limits the amount of value that can be exchanged using these order types and puts more risk and trust on the solvers.

Smart orders are a clever way of achieving the above-mentioned semantics in a completely trust-less way without requiring a protocol change. Smart orders are based on ERC1271’s contract signature scheme and allow smart contracts to decide at settlement time if an order that is placed in their name is satisfying their trade preference or not.

While regular orders are signed by their owner ahead of time and statically verified using ecrecover, smart contract orders implement interactive signature verification by asking the author at settlement time if they are okay with the proposed trade. This verification allows the author to decide just-in-time if they are happy with the proposed trade, so that their approval can be conditioned on any on-chain information (e.g. block time, oracle prices, current balance, etc.).

In essence, this allows developers to create smart autonomous agents that trade according to predefined rules and strategies, entirely on-chain.

For example, an automated rebalancing portfolio order (a smart contract that aims to have equal $ value of all its tokens) could perform the following check:

def checkSignature(order): 
    buyBalanceAfterTrade = order.buyToken.balanceOf(self) + order.buyAmount
    sellBalanceAfterTrade = order.sellToken.balanceOf(self) + order.sellAmount
	
	buyBalanceValue = oracle.value(order.buyToken, buyBalanceAfterTrade)
	sellBalanceValue = self.oracle.value(order.sellToken, sellBalanceAfterTrade)

	# Check we only trade portfolio tokens
	assert(order.sellToken in portfolio and order.buyToken in portfolio)
	# Check we don't over-balance
	assert(sellBalanceValue >= buyBalanceValue)
	return  True

Note that the concrete CoW Protocol order that is verified by the smart order contract conforms to the native CoW Protocol order type (i.e. it is a partially-fillable limit order).

However, by incorporating additional information into the interactive verification step (e.g. check the oracle price and only accept if ETH trades below X), the smart order can add additional conditions to the trade.

In fact, the “tricky” part is finding the correct native CoW Protocol order parameter for which the smart contract’s signature verification passes. For this, order placement can happen either:

  • By solvers that are aware of smart orders in the form of just-in-time liquidity (orders that are not part of the official auction set and therefore don’t contribute to the object value)
  • Permissionlessly via the public CoW Protocol API (since ERC1271 doesn’t require a private key signature, anyone can place orders on behalf of a smart contract)

The former option is better suited for very complex order types where the correct order parameters may depend on the outcome of the optimization problem itself. Solvers are incentivised to include these orders since they might allow them to generate more surplus for public user orders.

However, aside from reaching out to solvers directly, there isn’t a great way to inform solvers about this type of custom liquidity today. Therefore, for the purpose of this tutorial, we will focus on the simpler type of order placement: smart orders that are included via CoW Protocol’s public API and available to all solvers by default.

How do you build a smart order?

We developed an experimental framework for creating smart orders, which proposes a standard interface for conditional smart orders. Orders that implement this interface can be automatically picked up by our supporting infrastructure to place orders without the need for individual operational maintenance: https://github.com/cowprotocol/conditional-smart-orders

The core idea is that the smart order contract not only verifies if it is happy to trade a given order. It also proposes (via a separate view method) a concrete CoW Protocol order that it would be willing to accept.

Upon instantiation, the smart order triggers an event. This event is indexed and adds this contract to a registry that an external watchtower is constantly monitoring by querying the implemented view method. Upon seeing an order candidate, the watchtower places this order into the CoW Protocol API.

From a developer perspective, all you need to do is implement a single method (getTradableOrder), which provides logic for what order parameters your contract would accept given the current state of the chain. The default implementation of the signature verification logic would be as simple as checking that the given order is indeed the one returned by getTradableOrder (less strict checks are of course also possible in case it’s hard to reliably reproduce the exact order params in between calls).

As soon as getTradableOrder returns an order, our watchtower will forward it to the CoW Protocol API, a solver will pick it up and, via the settlement logic, ask your contract to verify it as part of the settlement transaction.

We implemented the registry indexing and watchtower logic using two Tenderly Web3 Actions:

  1. One to index order creation events and add contracts to the watch registry
  2. Another one to periodically check contracts in the registry if they are willing to trade at the current block

There are other ways to run this off-chain infrastructure, but we found Tenderly’s framework the most convenient without the need for our own cloud setup.

The repository also comes with a few suggested order types (use at your own risk). We would highly appreciate contributions to this repository (you can also apply for Grants for specific order types).

How do you deploy a smart order with your Safe?

While the conditional-order framework makes it quite easy to implement custom verification logic, there is usually some amount of custody overhead logic involved that needs to be implemented in such a contract (e.g. how do I get my funds back when I want to exit the strategy). This logic can be complex, expensive to audit and prone to errors. Thankfully, there is a proven smart account abstraction standard out there (Safe) that allows for a very flexible custody solution and that is already securing many billions of dollars in funds.

In this tutorial we will therefore use a Safe as our main “smart agent” contract and delegate to the conditional smart order logic only for signature verification.

Safe supports ERC1271 signatures out of the box, but also allows the owner to set a custom ERC1271 implementation (e.g. the one your smart order implements). Safes implement ERC1271 via a so-called “fallback handler”, a component that is called whenever the Safe is invoked on a method it doesn’t implement directly (e.g. isValidSignature). Therefore, by setting our smart order implementation as the Safe’s custom fallback handler we replace the default ERC1271 implementation with our own and make our agent trade according to our custom rules. Note that a Safe with such a fallback handler will only be able to ERC1271 sign off on CoW Protocol orders (no other 3rd party dApp related signatures will be valid). Normal Safe transactions with owner signatures will continue to work as usual.

So let’s get started! Most smart orders in our conditional smart order framework come with a factory that allows instantiating new instances directly from the Safe UI. The plan of attack is therefore to:

  1. Create a new Safe
  2. Approve the tokens in the CoW Swap app
  3. Create a new smart order instance (targeted at this Safe specifically)
  4. Set the smart order’s address from the previous step as the Safe’s fallback handler
  5. Fund it with the token(s) to be traded. Wait and see the Safe trade automatically

Step-by-step guide

1. Create a new Safe

Head over to https://app.safe.global/welcome and follow the prompts for “Create new Safe”. Feel free to leave the owner structure simple, e.g. 1/1 signer.

1-create-new-safe.webp

2. Approve the tokens in the CoW Swap App

Inside your Safe, head over to Apps → CoW Swap. At the top-left of the app, click on Account → Tokens and select the token that needs to be enabled. Swap for any other token and approve the token(s) you intend to trade with this contract.

In this example we are going to trade Wrapped XDAI:

2-approve-tokens-in-cow-swap-app.webp

3. Create a new smart order instance

For this example we will deploy a TradeAboveThreshold order, which will automatically sell whenever the holds more than X of some token.

The factory for this contract on Gnosis Chain is located at 0xd20a99e3c6c103108d74e241908e00ef4db447fb (if you can’t find the factory, you can deploy a new factory using foundry). To create a new instance, head to your Safe and create a New TransactionContract Interaction and load the contract ABI from the factory address.

We want to call create. Fill out the required parameters (with the target being your current Safe). In this example we are selling wxDAI for USDC whenever the contract holds $1 or more:

3-create-a-new-smart-order-instance.webp

Add transaction, create batch, and execute. After the transaction has been executed, open the transaction hash in a block explorer to find the address of the smart order instance that the factory has created (e.g. from looking at the internal txs).

3-1-create-a-new-smart-order-instance.webp

Here we created https://gnosisscan.io/address/0xdf724fdbaef2419f1af895f494bd8969ec433d01.

4. Set Custom Fallback handler

Head back to the Safe to create a New TransactionContract Interaction, this time on the Safe itself (enter the address of the Safe).

When prompted choose Use Implementation ABI.

4-set-custom-fallback-handler.webp

The contract method selector we want to invoke is setFallbackHandler and the address should be the contract that has been created in the previous step:

4-1-set-custom-fallback-handler.webp

After adding and executing this transaction, you will have set the smart order as your Safe’s ERC1271 implementation.

5. Fund the Safe

Now, when the Safe receives 1 wxDAI, it should automatically place an order to sell it for USDC. Let’s try:

5-fund-the-safe.webp

Shortly after the transfer is mined, if we check https://explorer.cow.fi/ for our Safe address, we should see a pending order trying to sell the wxDAI for USDC

5-1-fund-the-safe.webp

And, after a short amount of time the order should be filled.

You can repeat this process as often as you want by just sending some more funds to this Safe. In order to withdraw the proceeds you can use the standard Safe functionality.

Conclusion

In this tutorial we described and hinted at the power that smart orders can have in the context of smart contracts becoming autonomous trading agents, talked about how a simple “conditional order” type can be created with a few lines of solidity and walked through a step-by-step example of how we can deploy a smart order using Safe as a custody solution.

We hope this article inspired you to try out smart orders, or even build your own on top of CoW Protocol. Please get in touch with suggestions and ideas. We are looking forward to seeing what type of orders can be built with this paradigm.

Give ERC1271 orders a try today and let us know what you think by reaching out on Twitter or Discord!