Skip to content

Commit

Permalink
feat: eth flow (#25)
Browse files Browse the repository at this point in the history
# Description

Contains advanced order tutorials for Eth-Flow

**NOTE:** Only Eth-flow tutorials are within scope for this PR.

# Changes

- [x] Add Eth-flow tutorials
- [x] Add advanced-tutorials wide helper (`gpv2order.ts`)
- [x] Scaffolding for `ERC-1271` (i.e. Safe)
  • Loading branch information
mfw78 authored Jan 9, 2024
1 parent cd4b34f commit c1ab2c7
Show file tree
Hide file tree
Showing 34 changed files with 2,380 additions and 278 deletions.
Original file line number Diff line number Diff line change
@@ -1,131 +1,204 @@
---
title: Eth-Flow
title: Creating order
---

There are two types of wallets:
- EOA (externally owned account) wallets, which are controlled by a private key
- Smart contract wallets, which are controlled by a smart contract
[Eth-flow](https://beta.docs.cow.fi/cow-protocol/reference/contracts/periphery/eth-flow) allows users to create orders selling `ETH` without wrapping it to `WETH` first.

Since smart contract wallets are controlled by a smart contract, they can't sign transactions using `EIP-712`.
Anyway, CoW Protocol supports smart contract wallets by allowing them to create pre-signed orders.
The key of pre-signed orders is a transaction to Settlement contract that proves the ownership of an order.
To create an `Eth-flow` order we will need to interact with the `Eth-Flow` contract.

In this example we will create a pre-signed order using Safe, but you can use any smart contract wallet.
## Contract (`CoWSwapEthFlow`) interaction

## Required dependencies
For interacting with contracts, the tutorials use [ethers.js](https://docs.ethers.io/v5/).

For pre-signed orders, we need to use:
- `OrderBookApi` to send an order to the CoW Protocol order-book
- `MetadataApi` to generate order meta data
- `Safe` to create and sign the transaction to the Settlement contract
- `SafeApiKit` to propose the transaction to Safe owners
To interact with a contract, we need to know:

## Define the order parameters
- the contract address
- the ABI

First of all, we should define the order parameters.
The description of each parameter can be found in the [Order structure docs.](https://TODO)
Additionally, as we want to **make a transaction**, we must have a _signer_ (e.g. a wallet).

```
const defaultOrder: UnsignedOrder = {
receiver: safeAddress,
buyAmount: '650942340000000000000',
buyToken: '0x91056D4A53E1faa1A84306D4deAEc71085394bC8',
sellAmount: '100000000000000000',
sellToken: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
validTo: Math.round((Date.now() + 900_000) / 1000),
appData: '0x',
feeAmount: '0',
kind: OrderKind.SELL,
partiallyFillable: true,
+++signingScheme: SigningScheme.PRESIGN+++
### Contract address

`EthFlow` is a periphery contract, and it's deployed on each supported network. As `EthFlow` orders are natively indexed by the [autopilot](https://beta.docs.cow.fi/cow-protocol/tutorials/arbitrate/autopilot), there also exists a `production` and `staging` version of the contract on each network.

For this tutorial, we will use the [`production` version of the contract](https://gnosisscan.io/address/0x40A50cf069e992AA4536211B23F286eF88752187) on Gnosis chain. Let's assign it's address to a constant:

```typescript
/// file: run.ts
import type { Web3Provider } from '@ethersproject/providers';
import { SupportedChainId } from '@cowprotocol/cow-sdk';
import { MetadataApi, latest } from '@cowprotocol/app-data';

export async function run(provider: Web3Provider): Promise<unknown> {
// ...
const ethFlowAddress = '0x40A50cf069e992AA4536211B23F286eF88752187';
// ...
}
```

> `signingScheme` is the only difference between a regular order and a pre-signed order
### Eth-flow ABI

## Order meta data
We can retrieve the ABI for the `Eth-flow` contract from the contract's verified code on [Gnosisscan](https://gnosisscan.io/address/0x40A50cf069e992AA4536211B23F286eF88752187#code). As we're going to be using other functions from the `Eth-flow` contract, we will just copy the whole ABI to an `ethFlow.abi.json` file.

For analytics purposes, we can add some metadata to the order.
The metadata is a JSON document that is stored in IPFS and referenced by the order.
The metadata is optional, but it's recommended to add it.
After order creation, the metadata will be displayed in [the Explorer](https://explorer.cow.fi/).
To do so:

```
const appCode = '<YOUR_APP_CODE>'
const environment = 'prod'
// Slippage percent, it's 0 to 100
const quote = { slippageBips: '50' }
// "market" | "limit" | "liquidity"
const orderClass = { orderClass: 'limit' }
// Generate the app-data document
const appDataDoc = await metadataApi.generateAppDataDoc({
appCode,
environment,
metadata: {
quote,
orderClass
},
})
const +++{ appDataHex, appDataContent }+++ = await metadataApi.appDataToCid(appDataDoc)
```
1. Copy the ABI from [Gnosisscan](https://gnosisscan.io/address/0x40A50cf069e992AA4536211B23F286eF88752187#code)
2. Create a new file `ethFlow.abi.json` in the `src` folder
3. Paste the ABI into the file

## Post the order to the order-book
Now that we have the ABI, we can import it into our `run.ts` file:

Having the order and the metadata, we can post the order to the order-book.
`orderId` is the ID of the order in the order-book, and it's a key for the Settlement contract transaction.
```typescript
/// file: run.ts
import type { Web3Provider } from '@ethersproject/providers';
import { SupportedChainId } from '@cowprotocol/cow-sdk';
import { MetadataApi, latest } from '@cowprotocol/app-data';
+++import abi from './ethFlow.abi.json';+++

export async function run(provider: Web3Provider): Promise<unknown> {
// ...
}
```
const orderCreation: OrderCreation = {
...defaultOrder,
from: safeAddress,
signature: safeAddress,
appData: appDataContent,
appDataHash: appDataHex,

### Connect to the contract

Now that we have the contract address and the ABI, we can connect to the contract:

```typescript
/// file: run.ts
import type { Web3Provider } from '@ethersproject/providers';
+++import { Contract } from 'ethers';+++
import { SupportedChainId } from '@cowprotocol/cow-sdk';
import { MetadataApi, latest } from '@cowprotocol/app-data';
import abi from './ethFlow.abi.json';

export async function run(provider: Web3Provider): Promise<unknown> {
// ...
const ethFlowContract = new ethers.Contract(ethFlowAddress, abi, signer);
// ...
}
```

// Send order to CoW Protocol order-book
const +++orderId+++ = await orderBookApi.sendOrder(orderCreation)
> Ensure that you connect using the `signer` as we will be making a transaction.
### Get a quote

It's recommended to get a quote before creating an order (though it's not required). We've done this in a [previous tutorial](/tutorial/quote-order), and we will use this as a starting point.

In this tutorial we are aiming to swap 1 `xDAI` for `COW` on Gnosis Chain. When getting a quote for Eth Flow orders, the `sellToken` should **always** be the wrapped native token (e.g. `WETH` on Ethereum, `WXDAI` on Gnosis Chain, etc.). Here we can get the wrapped native token address using the `wrappedNativeToken` function on the `EthFlow` contract.

```typescript
/// file: run.ts
import type { Web3Provider } from '@ethersproject/providers';
+++import {+++
+++ SupportedChainId,+++
+++ OrderBookApi,+++
+++ UnsignedOrder,+++
+++ SigningScheme,+++
+++ OrderQuoteRequest,+++
+++ OrderQuoteSideKindSell,+++
+++} from '@cowprotocol/cow-sdk';+++
import { MetadataApi, latest } from '@cowprotocol/app-data';
import abi from './ethFlow.abi.json';

export async function run(provider: Web3Provider): Promise<unknown> {
// ...
+++const orderBookApi = new OrderBookApi({ chainId: SupportedChainID.GNOSIS_CHAIN });+++
const metadataApi = new MetadataApi();
// ...

+++const sellToken = await ethFlowContract.wrappedNativeToken();+++
const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c';
const sellAmount = '1000000000000000000';

const quoteRequest: OrderQuoteRequest = {
sellToken,
buyToken,
sellAmountBeforeFee: sellAmount,
kind: OrderQuoteSideKindSell.SELL,
receiver: ownerAddress,
+++from: ownerAddress,+++
appData: appDataContent,
appDataHash: appDataHex,
+++signingScheme: SigningScheme.EIP1271,+++
+++onchainOrder: true,+++
}

+++const { quote, id: quoteId } = await orderBookApi.getQuote(quoteRequest);+++
}
```

## Create the transaction to the Settlement contract
In addition to the `OrderQuoteRequest` fields we've used in the previous tutorial, we've added:

- `from`: the address of the order's owner (this is required for `EIP1271` orders)
- `signingScheme`: as Eth-flow orders are signed using the `EIP1271` scheme, we need to specify such a scheme
- `onchainOrder`: the order is going to be created on-chain, so we need to specify this

In the previous step, we created an order in the order-book, but it's not valid yet.
To make it valid, we need to create a transaction to the Settlement contract that proves the ownership of the order.
### `EthFlowOrder.Data` struct

Now that we have a quote, we can go about creating an `EthFlow` order. Unfortunately no `EthFlowOrder` type exists in the SDK, so we will need to create one ourselves. Simply we create a type that extends the `UnsignedOrder` type (removing the fields that are not required for [`EthFlow` orders](https://beta.docs.cow.fi/cow-protocol/reference/contracts/periphery/eth-flow#ethfloworderdata)) and add a `quoteId` field:

```typescript
/// file: run.ts
// ...

+++type EthFlowOrder = Omit<UnsignedOrder, 'sellToken' | 'sellTokenBalance' | 'buyTokenBalance' | 'kind' | 'signingScheme'> & {+++
+++ quoteId: number;+++
+++}+++

export async function run(provider: Web3Provider): Promise<unknown> {
// ...
}
```
// Create the pre-signature transaction
const presignCallData = settlementContract.interface.encodeFunctionData(+++'setPreSignature'+++, [
orderId,
true,
])
const presignRawTx = {
to: settlementContract.address,
data: presignCallData,
value: '0',

Now that we have the `EthFlowOrder` type, and we have a quote, we can create an order:

```typescript
/// file: run.ts
import type { Web3Provider } from '@ethersproject/providers';
+++import { BigNumber, Contract } from 'ethers';+++
// ...
export async function run(provider: Web3Provider): Promise<unknown> {
// ...
const order: EthFlowOrder = {
...quote,
+++buyAmount: BigNumber.from(quote.buyAmount).mul(9950).div(10000).toString(),+++
receiver: ownerAddress,
appData: appDataHex,
quoteId,
}
}
```

> The `buyAmount` has a slippage of 0.5% applied to it. This is to ensure that it is filled if trading very low amounts of `xDAI` for this tutorial. The slippage amount is arbitrary and can be changed.
> The `quoteId` is the `id` of the quote we received from the `OrderBookApi`, however this is **not required** for creating an `EthFlow` order and may be populated with any value.
// Send pre-signature transaction to settlement contract
// In this example we are using the Safe SDK, but you can use any other smart-contract wallet
const safeTx = await safeSdk.createTransaction({safeTransactionData: presignRawTx})
const signedSafeTx = await safeSdk.signTransaction(safeTx)
const safeTxHash = await safeSdk.getTransactionHash(signedSafeTx)
const senderSignature = signedSafeTx.signatures.get(account.toLowerCase())?.data || ''
// Send the pre-signed transaction to the Safe
await +++safeApiKit.proposeTransaction+++({
safeAddress,
safeTransactionData: signedSafeTx.data,
safeTxHash,
senderAddress: account,
senderSignature,
})
### Execute the `createOrder`

Now we have everything for creating an order, we can execute the `createOrder`. Before we do however, unlike the previous tutorials, we will need to send some `ETH` for this transaction. We can do this by adding a `value` field to the `createOrder` call. The value should be **`sellAmount`** that was originally defined for the quote request.

```typescript
/// file: run.ts
// ...
export async function run(provider: Web3Provider): Promise<unknown> {
// ...

+++const tx = await ethFlowContract.createOrder(order, { value: sellAmount });+++
console.log('Transaction Hash:', tx.hash);
const receipt = await tx.wait();

return receipt;
}
```

## Sign and execute transaction
## Run the code

To run the code, we can press the "Run" button in the bottom right panel (the web container).

When running the script, we may be asked to connect a wallet. We can use Rabby for this.

After the transaction is proposed to the Safe, the Safe owners should sign and execute it.
After the transaction is executed, the order will be valid and can be filled.
1. Accept the connection request in Rabby
2. Press the "Run" button again
3. Observe the `tx` object in the browser's console
4. On successful confirmation of the transaction, the `receipt` object will be returned to the output panel

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit c1ab2c7

Please sign in to comment.