Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tutorial webcontainer refactoring and sdk examples #4

Merged
merged 9 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf

[*.md]
max_line_length = off
trim_trailing_whitespace = false

[{*.ts,*.tsx}]
ij_typescript_force_quote_style = true
ij_typescript_use_double_quotes = false
ij_typescript_use_semicolon_after_statement = false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
.idea
/node_modules
/build
/.svelte-kit
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# learn.svelte.dev
# CoW Tutorial

A soup-to-nuts interactive tutorial on how to build apps with Svelte.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Sign order
---

An example of signing a limit order via EOA wallet
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Web3Provider } from '@ethersproject/providers'
import { OrderSigningUtils, UnsignedOrder, OrderKind } from '@cowprotocol/cow-sdk'

export async function run(provider: Web3Provider): Promise<unknown> {
const accounts = await provider.listAccounts()
const account = accounts[0]

const chainId = +(await provider.send('eth_chainId', []))

const order: UnsignedOrder = {
sellToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
buyToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
receiver: account,
sellAmount: '2',
buyAmount: '1',
validTo: Math.round((Date.now() + 200_000) / 1000),
appData: '0x',
feeAmount: '0',
kind: OrderKind.SELL,
partiallyFillable: false,
}

const signer = provider.getSigner()

return OrderSigningUtils.signOrder(order, chainId, signer)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: App data
---

App data generation
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Web3Provider } from '@ethersproject/providers'
import { MetadataApi } from '@cowprotocol/app-data'

export async function run(provider: Web3Provider): Promise<unknown> {
const metadataApi = new MetadataApi()

const appCode = '<YOUR_APP_CODE>'
const environment = 'prod'
const referrer = { address: `0x360Ba61Bc799edfa01e306f1eCCb2F6e0C3C8c8e` }

const quote = { slippageBips: '0' } // Slippage percent, it's 0 to 100
const orderClass = { orderClass: 'limit' } // "market" | "limit" | "liquidity"

const appDataDoc = await metadataApi.generateAppDataDoc({
appCode,
environment,
metadata: {
referrer,
quote,
orderClass
},
})

const { cid, appDataHex, appDataContent } = await metadataApi.appDataToCid(appDataDoc)

return { appDataDoc, cid, appDataHex, appDataContent }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
title: Pre-signed 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

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.

In this example we will create a pre-signed order using Safe, but you can use any smart contract wallet.

## Required dependencies

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

## Define the order parameters

First of all, we should define the order parameters.
The description of each parameter can be found in the [Order structure docs.](https://TODO)

```
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+++
}
```

> `signingScheme` is the only difference between a regular order and a pre-signed order

## Order meta data

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/).

```
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)
```

## Post the order to the order-book

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.

```
const orderCreation: OrderCreation = {
...defaultOrder,
from: safeAddress,
signature: safeAddress,
appData: appDataContent,
appDataHash: appDataHex,
}

// Send order to CoW Protocol order-book
const +++orderId+++ = await orderBookApi.sendOrder(orderCreation)
```

## Create the transaction to the Settlement contract

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.

```
// Create the pre-signature transaction
const presignCallData = settlementContract.interface.encodeFunctionData(+++'setPreSignature'+++, [
orderId,
true,
])
const presignRawTx = {
to: settlementContract.address,
data: presignCallData,
value: '0',
}

// 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,
})
```

## Sign and execute transaction

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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"inputs": [
{
"internalType": "bytes",
"name": "orderUid",
"type": "bytes"
},
{
"internalType": "bool",
"name": "signed",
"type": "bool"
}
],
"name": "setPreSignature",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import GPv2SettlementAbi from './GPv2Settlement.json'
import {SupportedChainId} from '@cowprotocol/cow-sdk'

export const SETTLEMENT_CONTRACT_ADDRESS = '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'

export const SETTLEMENT_CONTRACT_ABI = GPv2SettlementAbi

export const SAFE_TRANSACTION_SERVICE_URL: Record<SupportedChainId, string> = {
[SupportedChainId.MAINNET]: 'https://safe-transaction-mainnet.safe.global',
[SupportedChainId.GNOSIS_CHAIN]: 'https://safe-transaction-gnosis-chain.safe.global',
[SupportedChainId.GOERLI]: 'https://safe-transaction-goerli.safe.global',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Safe, { EthersAdapter } from '@safe-global/protocol-kit'
import { ethers } from 'ethers'
import SafeApiKit from '@safe-global/api-kit'
import type { Web3Provider } from '@ethersproject/providers'
import {SAFE_TRANSACTION_SERVICE_URL} from './const'

interface SafeSdkAndKit {
safeApiKit: SafeApiKit,
safeSdk: Safe
}

export async function getSafeSdkAndKit(chainId: number, provider: Web3Provider, safeAddress: string): Promise<SafeSdkAndKit> {
const ethAdapter = new EthersAdapter({
ethers,
signerOrProvider: provider.getSigner(0),
})
const safeApiKit = new SafeApiKit({
txServiceUrl: SAFE_TRANSACTION_SERVICE_URL[chainId],
ethAdapter
})

return Safe.create({ethAdapter, safeAddress}).then(safeSdk => {
return {
safeApiKit,
safeSdk
}
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Web3Provider } from '@ethersproject/providers'
import { Contract } from '@ethersproject/contracts'

import { OrderBookApi, UnsignedOrder, OrderKind, SigningScheme } from '@cowprotocol/cow-sdk'
import { getSafeSdkAndKit } from './getSafeSdkAndKit'

import { SETTLEMENT_CONTRACT_ABI, SETTLEMENT_CONTRACT_ADDRESS } from './const'

export async function run(provider: Web3Provider): Promise<unknown> {
const safeAddress = '<PUT_YOUR_SAFE_ADDRESS>'

// Get chainId and account from the current provider
const accounts = await provider.listAccounts()
const account = accounts[0]
const chainId = +(await provider.send('eth_chainId', []))

// Create the CoW Protocol OrderBookApi instance
const orderBookApi = new OrderBookApi({ chainId })

// Create the CoW Protocol Settlement contract instance
const settlementContract = new Contract(SETTLEMENT_CONTRACT_ADDRESS, SETTLEMENT_CONTRACT_ABI)

// Create the Safe SDK and Safe API Kit instances
const {safeApiKit, safeSdk} = await getSafeSdkAndKit(chainId, provider, safeAddress)

// Create the order
// Pay attention to the `signingScheme` field that is set to `SigningScheme.PRESIGN`
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,
}
}
Loading
Loading