diff --git a/content/tutorial/01-cow-protocol/01-orders/01-sign-order/README.md b/content/tutorial/01-cow-protocol/01-orders/01-sign-order/README.md deleted file mode 100644 index b68e60ee..00000000 --- a/content/tutorial/01-cow-protocol/01-orders/01-sign-order/README.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Sign order ---- - -An example of signing a limit order via EOA wallet diff --git a/content/tutorial/01-cow-protocol/01-orders/01-sign-order/app-a/src/lib/run.ts b/content/tutorial/01-cow-protocol/01-orders/01-sign-order/app-a/src/lib/run.ts deleted file mode 100644 index b9058941..00000000 --- a/content/tutorial/01-cow-protocol/01-orders/01-sign-order/app-a/src/lib/run.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Web3Provider } from '@ethersproject/providers' -import { OrderSigningUtils, UnsignedOrder, OrderKind } from '@cowprotocol/cow-sdk' - -export async function run(provider: Web3Provider): Promise { - 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) -} diff --git a/content/tutorial/01-cow-protocol/01-orders/04-get-quote/README.md b/content/tutorial/01-cow-protocol/01-orders/04-get-quote/README.md deleted file mode 100644 index 3aaa048e..00000000 --- a/content/tutorial/01-cow-protocol/01-orders/04-get-quote/README.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Get quote ---- - -Something about getting a quote. \ No newline at end of file diff --git a/content/tutorial/01-cow-protocol/01-orders/04-get-quote/app-a/src/lib/run.ts b/content/tutorial/01-cow-protocol/01-orders/04-get-quote/app-a/src/lib/run.ts deleted file mode 100644 index b696a7e6..00000000 --- a/content/tutorial/01-cow-protocol/01-orders/04-get-quote/app-a/src/lib/run.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Web3Provider } from '@ethersproject/providers' -import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell } from '@cowprotocol/cow-sdk' - -export async function run(provider: Web3Provider): Promise { - const chainId = +(await provider.send('eth_chainId', [])); - if (chainId !== 100) { - throw new Error('Invalid chainId') - } - - const accounts = await provider.listAccounts(); - const account = accounts[0]; - - const signer = provider.getSigner(); - const ownerAddress = await signer.getAddress(); - - const sellToken = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; // wxDAI - const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c'; // COW - const sellAmount = '1000000000000000000'; // 1 wxDAI - - const quoteRequest: OrderQuoteRequest = { - sellToken, - buyToken, - from: ownerAddress, - receiver: ownerAddress, - sellAmountBeforeFee: sellAmount, - kind: OrderQuoteSideKindSell.SELL, - } - - const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }) - const { quote } = await orderBookApi.getQuote(quoteRequest) - - return quote -} diff --git a/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/README.md b/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/README.md deleted file mode 100644 index 8a214884..00000000 --- a/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/README.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Apply to auction ---- - -TODO: Apply to auction diff --git a/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/app-a/src/lib/code.ts b/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/app-a/src/lib/code.ts deleted file mode 100644 index e9009bf3..00000000 --- a/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/app-a/src/lib/code.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function run() { - console.log('Apply to auction A') -} \ No newline at end of file diff --git a/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/app-b/src/lib/code.ts b/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/app-b/src/lib/code.ts deleted file mode 100644 index cc62858a..00000000 --- a/content/tutorial/01-cow-protocol/02-batch-auction/01-apply-to-auction/app-b/src/lib/code.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function run() { - console.log('Apply to auction B') -} \ No newline at end of file diff --git a/content/tutorial/01-cow-protocol/tsconfig.json b/content/tutorial/01-cow-protocol/tsconfig.json deleted file mode 100644 index 373cc520..00000000 --- a/content/tutorial/01-cow-protocol/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "*": [ "../common/node_modules/*" ] - }, - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true - } -} diff --git a/content/tutorial/01-orders/01-basic-orders/00-getting-started/README.md b/content/tutorial/01-orders/01-basic-orders/00-getting-started/README.md new file mode 100644 index 00000000..e4bbca7f --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/00-getting-started/README.md @@ -0,0 +1,64 @@ +--- +title: Getting started +--- + +Tutorials are designed to be followed in order, as each tutorial builds on the previous one. The tutorial system is built with Web Containers, which means that you can run the code in your browser without having to install anything on your computer. + +## Prerequisites + +- [Rabby](https://rabby.io/) or [Metamask](https://metamask.io) installed in your browser +- A web browser with WASM support (Chrome, Firefox, Edge, Safari) + +> If you are using Brave, you will need to disable shields for the webcontainers to work. + +## Code snippets + +All code snippets are in TypeScript, and are executed in a sandboxed environment. You can edit the code and run it again. + +Let's look at the current code snippet: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers' + +export async function run(provider: Web3Provider): Promise { + // TODO: Implement +} +``` + +### Web3 Provider + +The tutorials allows for the use of a browser's Web3 provider, such as Rabby or Metamask. The `provider` argument is a [Web3Provider](https://docs.ethers.io/v5/api/providers/types/#providers-Web3Provider) from the [ethers.js](https://docs.ethers.io/v5/) library. + +This is automatically injected into the code snippet, and you can use it to interact with the blockchain via your browser's wallet. + +### `run` function + +Tutorials are very simple plain TypeScript, and the `run` function is the entry point for the code snippet. This function is called when you click the "Run" button. + +## Running the code + +Let's finish the code snippet by adding some debugging and returning a value: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers' + +export async function run(provider: Web3Provider): Promise { + console.log('Hello world!'); + return { + message: "Hello world!" + }; +} +``` + +Once you click the "Run" button, you should see the following output: + +```json +/// file: output.json +{ + "message": "Hello world!" +} +``` + +> You will see that the console output is not visible. To see this, you must open the browser's developer tools (F12) and select the "Console" tab. \ No newline at end of file diff --git a/content/tutorial/01-orders/01-basic-orders/00-getting-started/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/00-getting-started/app-a/src/lib/run.ts new file mode 100644 index 00000000..1d90053d --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/00-getting-started/app-a/src/lib/run.ts @@ -0,0 +1,5 @@ +import type { Web3Provider } from '@ethersproject/providers' + +export async function run(provider: Web3Provider): Promise { + // TODO: Implement +} \ No newline at end of file diff --git a/content/tutorial/01-orders/01-basic-orders/00-getting-started/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/00-getting-started/app-b/src/lib/run.ts new file mode 100644 index 00000000..01d69583 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/00-getting-started/app-b/src/lib/run.ts @@ -0,0 +1,8 @@ +import type { Web3Provider } from '@ethersproject/providers' + +export async function run(provider: Web3Provider): Promise { + console.log('Hello world!'); + return { + message: "Hello world!" + }; +} \ No newline at end of file diff --git a/content/tutorial/01-orders/01-basic-orders/00-getting-started/meta.json b/content/tutorial/01-orders/01-basic-orders/00-getting-started/meta.json new file mode 100644 index 00000000..3ea6a463 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/00-getting-started/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Getting started", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-orders/01-basic-orders/01-approve/README.md b/content/tutorial/01-orders/01-basic-orders/01-approve/README.md new file mode 100644 index 00000000..80276885 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/01-approve/README.md @@ -0,0 +1,134 @@ +--- +title: Approving sell token +--- + +For an order to be tradeable on CoW Protocol, the owner needs to approve the [GPv2VaultRelayer](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core/vault-relayer) to spend the token they want to trade. + +> A list of the core deployed contracts can be found [here](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core). + +## Contract (token) interaction + +For interacting with contracts, the tutorials use [ethers.js](https://docs.ethers.io/v5/). + +To interact with a contract, we need to know: + +- the contract address +- the ABI + +Additionally, if we want to **make a transaction**, we must have a _signer_ (e.g. a wallet). + +### Contract address + +The contract to be interacted with is the token we want to trade. In this tutorial, we use the [wxDAI](https://gnosisscan.io/token/0xe91d153e0b41518a2ce8dd3d7944fa863463a97d) token, and assign it's address `0xe91d153e0b41518a2ce8dd3d7944fa863463a97d` to a `const`. + +At the same time, we assign the address of the [GPv2VaultRelayer](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core/vault-relayer) to a `const`: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; + +export async function run(provider: Web3Provider): Promise { + const relayerAddress = '0xC92E8bdf79f0507f65a392b0ab4667716BFE0110'; + const tokenAddress = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; + + // ... +} +``` + +### `approve` ABI + +You may either set a limited approval (at least the amount of tokens you want to trade) or an unlimited approval. To do this, we use the [`approve`](https://eips.ethereum.org/EIPS/eip-20#approve) function of the [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token standard. + +We can set this function's ABI as a `const`: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; + +export async function run(provider: Web3Provider): Promise { + // ... + + const approveAbi = [ + { + inputs: [ + { name: '_spender', type: 'address' }, + { name: '_value', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + ]; + + // ... +} +``` + +### Signer + +To make a transaction, we need a signer. In this tutorial, we use an injected Web3Provider, such as [Rabby](https://rabby.io). We can get the signer from the provider: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; + +export async function run(provider: Web3Provider): Promise { + // ... + + const signer = provider.getSigner(); + + // ... +} +``` + +### Connect to the contract + +To interact with the contract, we need to connect to it. We can do this by using the `Contract` class from ethers.js: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { Contract } from '@ethersproject/contracts';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + + const wxDai = new Contract(tokenAddress, approveAbi, signer); + + // ... +} +``` + +### Execute the `approve` + +Now we have everything we need to execute the `approve` function. We will now give the `relayerAddress` unlimited approval to spend the `tokenAddress` token. + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { Contract, ethers } from '@ethersproject/contracts';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + + const wxDai = new Contract(tokenAddress, approveAbi, signer); + + const tx = await wxDai.approve(relayerAddress, ethers.constants.MaxUint256); + console.log('tx', tx); + const receipt = await tx.wait(); + + return receipt; +} +``` + +## Run the code + +To run the snippet, we simply press the "Run" button in the bottom right panel (the web container). + +When running the script, we will be asked to connect a wallet. We can use Rabby for this. + +1. Accept the connection request in Rabby +2. Click "Run" +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 diff --git a/content/tutorial/01-orders/01-basic-orders/01-approve/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/01-approve/app-a/src/lib/run.ts new file mode 100644 index 00000000..2e28bcbf --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/01-approve/app-a/src/lib/run.ts @@ -0,0 +1,5 @@ +import type { Web3Provider } from '@ethersproject/providers' + +export async function run(provider: Web3Provider): Promise { + // TODO: Implement +} diff --git a/content/tutorial/01-orders/01-basic-orders/01-approve/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/01-approve/app-b/src/lib/run.ts new file mode 100644 index 00000000..f2424ec5 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/01-approve/app-b/src/lib/run.ts @@ -0,0 +1,30 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { Contract, ethers } from "ethers"; + +export async function run(provider: Web3Provider): Promise { + const relayerAddress = '0xC92E8bdf79f0507f65a392b0ab4667716BFE0110'; + const tokenAddress = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; + + // Compacted ABI for the approve function + const approveAbi = [ + { + inputs: [ + { name: '_spender', type: 'address' }, + { name: '_value', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, + ]; + + const signer = provider.getSigner(); + const wxDai = new Contract(tokenAddress, approveAbi, signer); + + const tx = await wxDai.approve(relayerAddress, ethers.constants.MaxUint256); + console.log('tx', tx); + const receipt = await tx.wait(); + + return receipt; +} diff --git a/content/tutorial/01-orders/01-basic-orders/01-approve/meta.json b/content/tutorial/01-orders/01-basic-orders/01-approve/meta.json new file mode 100644 index 00000000..ebe50b5f --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/01-approve/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Approve sell token", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-orders/01-basic-orders/02-quote/README.md b/content/tutorial/01-orders/01-basic-orders/02-quote/README.md new file mode 100644 index 00000000..71e6aa5d --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/02-quote/README.md @@ -0,0 +1,160 @@ +--- +title: Quoting +--- + +OK, so you're wanting to trade some tokens on CoW Protocol. Great! But before we do that, let's get a quote first so we know what we're getting into. We get quotes using the [Order book API](https://beta.docs.cow.fi/cow-protocol/tutorials/arbitrate/orderbook). + +## API + +The CoW Protocol API is [documented in swagger](https://beta.docs.cow.fi/cow-protocol/reference/apis/orderbook). Using API endpoints can be a bit tricky, so we've exposed all the settings that you need (including things like rate limiters so you don't get blocked) in a simple to use library: `@cowprotocol/cow-sdk`. + +To install it, run: `npm install @cowprotocol/cow-sdk` + +## Quote + +As an example, let's get a quote for trading 1 `wxDAI` for `COW` on [Gnosis chain](https://gnosis.io/). This is a simple swap (market order). + +### Which environment? + +CoW Protocol supports multiple environments (e.g. mainnet, gnosis chain, sepolia, etc). We need to know which environment we want to trade on. For this tutorial, we'll use the Gnosis chain. Let's get the `chainId` for the connected wallet and compare it to the `SupportedChainId` enum from the `@cowprotocol/cow-sdk` library: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { SupportedChainId } from '@cowprotocol/cow-sdk';+++ + +export async function run(provider: Web3 Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } +} +``` + +### Instantiate the SDK + +Next, we need to instantiate the `OrderBookApi` from the `@cowprotocol/cow-sdk` library. We can do this by passing the `chainId` to the constructor: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + // ... +} +``` + +We now have an instantiated `OrderBookApi` that we can use to interact with the Order Book API, and make sure that we don't bust any rate limits. + +### Order parameters + +Now that we have an instantiated `OrderBookApi`, we can get a quote. To do this, we need to know: + +- the token we want to sell (the `sellToken`), in this case [`wxDAI`](https://gnosisscan.io/token/0xe91d153e0b41518a2ce8dd3d7944fa863463a97d) +- the token we want to buy (the `buyToken`), in this case [`COW`](https://gnosisscan.io/token/0x177127622c4A00F3d409B75571e12cB3c8973d3c) +- the amount of tokens we want to sell (the `sellAmount`), in this case `1 wxDAI` in atomic units (i.e. wei), which is `1000000000000000000` + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + const signer = provider.getSigner(); + const ownerAddress = await signer.getAddress(); + + const sellToken = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; // wxDAI + const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c'; // COW + const sellAmount = '1000000000000000000'; // 1 wxDAI + // ... +} +``` + +### Order request object + +Now that we have all the parameters, we can create an `OrderQuoteRequest` object. Fortunately for us, all the typings are already defined in the `@cowprotocol/cow-sdk` library, so we can just use those: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { SupportedChainId, OrderBookApi, OrderQuoteRequest, OrderQuoteSideKindSell } from '@cowprotocol/cow-sdk';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + const quoteRequest: OrderQuoteRequest = { + sellToken, + buyToken, + from: ownerAddress, + receiver: ownerAddress, + sellAmountBeforeFee: sellAmount, + +++kind: OrderQuoteSideKindSell.SELL,+++ + }; + // ... +} +``` + +We can see that there are some special types, that are designed to reduce the potential for human error. These types are automatically generated from the swagger documentation, and compliance with them will ensure that your code is compatible with the API. Examples above include `OrderQuoteRequest` and `OrderQuoteSideKindSell`. + +### Get the quote + +Now that we have the `OrderQuoteRequest`, we can get the quote: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi, OrderQuoteRequest, OrderQuoteSideKindSell } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + const { quote } = await orderBookApi.getQuote(quoteRequest); + + return quote; +} +``` + +## 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. + +1. Accept the connection request in Rabby +2. Press the "Run" button again +3. Observe the `OrderQuoteResponse` object returned to the output panel + +An example quote should look like: + +```json +/// file: output.json +{ + "sellToken": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", + "buyToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", + "receiver": "0x29104bb91ADA737a89393c78335e48fF4708727E", + "sellAmount": "998118187948506302", + "buyAmount": "4000045686115459544", + "validTo": 1704267037, + "appData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "feeAmount": "1881812051493698", + "kind": "sell", + "partiallyFillable": false, + "sellTokenBalance": "erc20", + "buyTokenBalance": "erc20", + "signingScheme": "eip712" +} +``` + +In the above case, we can see that: + +- the `sellToken` is `0xe91d153e0b41518a2ce8dd3d7944fa863463a97d` (which is `wxDAI`) +- the `buyToken` is `0x177127622c4a00f3d409b75571e12cb3c8973d3c` (which is `COW`) +- the `sellAmount` is `998118187948506302` atomic units of `wxDAI` (which is `0.998118187948506302` `wxDAI`) +- the `buyAmount` is `4000045686115459544` atomic units of `COW` (which is `4.000045686115459544` `COW`) +- the `validTo` is `1704267037` (which is Jan 03 2024 07:30:37 GMT+0000) +- the `feeAmount` is `1881812051493698` atomic units of `wxDAI` (which is `0.001881812051493698` `wxDAI`) +- the `kind` is `sell` + +The above `OrderQuoteResponse` object actually maps to the [`GPv2Order.Data`](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core/settlement#gpv2orderdata-struct) struct for the smart contract, so this is what we will sign in the [next tutorial](/tutorial/sign) for our swap. \ No newline at end of file diff --git a/content/tutorial/01-orders/01-basic-orders/02-quote/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/02-quote/app-a/src/lib/run.ts new file mode 100644 index 00000000..2e28bcbf --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/02-quote/app-a/src/lib/run.ts @@ -0,0 +1,5 @@ +import type { Web3Provider } from '@ethersproject/providers' + +export async function run(provider: Web3Provider): Promise { + // TODO: Implement +} diff --git a/content/tutorial/01-orders/01-basic-orders/02-quote/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/02-quote/app-b/src/lib/run.ts new file mode 100644 index 00000000..29aed4c0 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/02-quote/app-b/src/lib/run.ts @@ -0,0 +1,31 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell } from '@cowprotocol/cow-sdk' + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + + const signer = provider.getSigner(); + const ownerAddress = await signer.getAddress(); + + const sellToken = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; // wxDAI + const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c'; // COW + const sellAmount = '1000000000000000000'; // 1 wxDAI + + const quoteRequest: OrderQuoteRequest = { + sellToken, + buyToken, + from: ownerAddress, + receiver: ownerAddress, + sellAmountBeforeFee: sellAmount, + kind: OrderQuoteSideKindSell.SELL, + }; + + const { quote } = await orderBookApi.getQuote(quoteRequest); + + return quote +} diff --git a/content/tutorial/01-cow-protocol/meta.json b/content/tutorial/01-orders/01-basic-orders/02-quote/meta.json similarity index 77% rename from content/tutorial/01-cow-protocol/meta.json rename to content/tutorial/01-orders/01-basic-orders/02-quote/meta.json index bc688850..0d97e43c 100644 --- a/content/tutorial/01-cow-protocol/meta.json +++ b/content/tutorial/01-orders/01-basic-orders/02-quote/meta.json @@ -1,5 +1,5 @@ { - "title": "CoW Protocol", + "title": "Get quote", "scope": { "prefix": "/src/lib/", "name": "src" diff --git a/content/tutorial/01-orders/01-basic-orders/03-sign/README.md b/content/tutorial/01-orders/01-basic-orders/03-sign/README.md new file mode 100644 index 00000000..7822b098 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/03-sign/README.md @@ -0,0 +1,72 @@ +--- +title: Signing +--- + +Here we will build on the previous tutorial and sign the quote we got from the [quote tutorial](/tutorial/quote), so that we can place an order on CoW Protocol. + +## Intents and signatures + +CoW Protocol uses [intents](https://beta.docs.cow.fi/cow-protocol/reference/core/intents) to represent orders. An intent is a [signed message](https://beta.docs.cow.fi/cow-protocol/reference/core/signing-schemes). Most intents are signed using the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signing scheme, and this is what we will use in this tutorial. + +## Signing an order + +In the previous tutorial, we received an `OrderQuoteResponse` object. This object contains all the data that we need to sign an order. + +### Types and utilities + +For signing, we will use the `UnsignedOrder` type from the `SDK`, along with the `OrderSigningUtils` utility. + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell, OrderSigningUtils, UnsignedOrder } from '@cowprotocol/cow-sdk';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + + const order: UnsignedOrder = { + ...quote, + receiver: ownerAddress, // required due type mismatch + } +} +``` + +> The `OrderQuoteResponse` type contains a `receiver` field, however the type of this field is not compatible with the `UnsignedOrder` type. This is why we need to override the `receiver` field with the `ownerAddress` (which is the address of the wallet we are using). + +### Sign the order + +Now that we have the `UnsignedOrder`, we can sign it: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell, OrderSigningUtils, UnsignedOrder } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + + return OrderSigningUtils.signOrder(order, chainId, signer); +} +``` + +## 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. + +1. Accept the connection request in Rabby +2. Press the "Run" button again +3. Observe the `SigningResult` object returned to the output panel + +An example signing result should look like: + +```json +/// file: output.json +{ + "signature": "0x98ac143acad82e3908489ac8ca3f908cb49b0a861f15a51fc0a79bdea6dcca0212403f419ed8b022881a7cecf7358a69c2cfa9596e877fc67bea5be6d9981cf51b", + "signingScheme": "eip712" +} +``` + +In the above case, we can see the `signature` returned is the `ECDSA` signature of the `EIP-712` hash of the `UnsignedOrder`. Now this can be used to place an order on CoW Protocol. \ No newline at end of file diff --git a/content/tutorial/01-orders/01-basic-orders/03-sign/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/03-sign/app-a/src/lib/run.ts new file mode 100644 index 00000000..29aed4c0 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/03-sign/app-a/src/lib/run.ts @@ -0,0 +1,31 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell } from '@cowprotocol/cow-sdk' + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + + const signer = provider.getSigner(); + const ownerAddress = await signer.getAddress(); + + const sellToken = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; // wxDAI + const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c'; // COW + const sellAmount = '1000000000000000000'; // 1 wxDAI + + const quoteRequest: OrderQuoteRequest = { + sellToken, + buyToken, + from: ownerAddress, + receiver: ownerAddress, + sellAmountBeforeFee: sellAmount, + kind: OrderQuoteSideKindSell.SELL, + }; + + const { quote } = await orderBookApi.getQuote(quoteRequest); + + return quote +} diff --git a/content/tutorial/01-orders/01-basic-orders/03-sign/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/03-sign/app-b/src/lib/run.ts new file mode 100644 index 00000000..0ccb2d2a --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/03-sign/app-b/src/lib/run.ts @@ -0,0 +1,36 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell, OrderSigningUtils, UnsignedOrder } from '@cowprotocol/cow-sdk' + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + + const signer = provider.getSigner(); + const ownerAddress = await signer.getAddress(); + + const sellToken = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; // wxDAI + const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c'; // COW + const sellAmount = '1000000000000000000'; // 1 wxDAI + + const quoteRequest: OrderQuoteRequest = { + sellToken, + buyToken, + from: ownerAddress, + receiver: ownerAddress, + sellAmountBeforeFee: sellAmount, + kind: OrderQuoteSideKindSell.SELL, + }; + + const { quote } = await orderBookApi.getQuote(quoteRequest); + + const order: UnsignedOrder = { + ...quote, + receiver: ownerAddress, + } + + return OrderSigningUtils.signOrder(order, chainId, signer) +} diff --git a/content/tutorial/01-cow-protocol/02-batch-auction/meta.json b/content/tutorial/01-orders/01-basic-orders/03-sign/meta.json similarity index 76% rename from content/tutorial/01-cow-protocol/02-batch-auction/meta.json rename to content/tutorial/01-orders/01-basic-orders/03-sign/meta.json index 96a58072..88f090eb 100644 --- a/content/tutorial/01-cow-protocol/02-batch-auction/meta.json +++ b/content/tutorial/01-orders/01-basic-orders/03-sign/meta.json @@ -1,5 +1,5 @@ { - "title": "Batch auction", + "title": "Sign order", "scope": { "prefix": "/src/lib/", "name": "src" diff --git a/content/tutorial/01-orders/01-basic-orders/04-submit/README.md b/content/tutorial/01-orders/01-basic-orders/04-submit/README.md new file mode 100644 index 00000000..b07df182 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/04-submit/README.md @@ -0,0 +1,61 @@ +--- +title: Submitting +--- + +Further building on the previous tutorial, we will now submit the order we signed in the [sign order tutorial](/tutorial/sign) to CoW Protocol. + +## Submitting an order + +Submitting orders to the API may very well result in an error. For this reason, we should **ALWAYS** handle errors. To do this, we will use the `try/catch` syntax. + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell, OrderSigningUtils, UnsignedOrder, SigningScheme } from '@cowprotocol/cow-sdk';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + + try { + const orderId = await orderBookApi.sendOrder({ + ...quote, + ...orderSigningResult, + signingScheme: orderSigningResult.signingScheme as unknown as SigningScheme + }) + + return { orderId } + } catch (e) { + return e + } +} +``` + +> Currently the `OrderSigningResult` returns an enum which is not compatible with the `SigningScheme` type. This is why we need to cast it to `unknown` and then to `SigningScheme`. + +## 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. + +1. Accept the connection request in Rabby +2. Press the "Run" button again +3. Observe the `orderId` returned to the output panel + +An example `orderId` should look like: + +```json +/// file: output.json +{ + "orderId": "0xae842840f65743bc84190a68da1e4adf1771b242fa903b6c2e87bc5050e07c1329104bb91ada737a89393c78335e48ff4708727e65952d5e" +} +``` + +> The [`orderId`](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core/settlement#orderuid) is the unique identifier for the order we have just submitted. We can use this `orderId` (also known as `orderUid`) to check the status of the order on [CoW Explorer](https://beta.docs.cow.fi/cow-protocol/tutorials/cow-explorer/order). Keep this handy, as we will practice some more with this `orderId` in the next tutorial! + +### Errors + +A couple of errors may easily result when running this code: + +- **`InsufficientBalance`**: The wallet you have signed with does not have enough balance for the `sellToken`. A reminder in this example, the `sellToken` is `wxDai` on Gnosis chain. +- **`InsufficientAllowance`**: In this case, the wallet has enough balance, however you have missed out a step in the [approve tutorial](/tutorial/approve) and have not approved the `relayerAddress` to spend the `sellToken` on your behalf. diff --git a/content/tutorial/01-orders/01-basic-orders/04-submit/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/04-submit/app-a/src/lib/run.ts new file mode 100644 index 00000000..0ccb2d2a --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/04-submit/app-a/src/lib/run.ts @@ -0,0 +1,36 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell, OrderSigningUtils, UnsignedOrder } from '@cowprotocol/cow-sdk' + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + + const signer = provider.getSigner(); + const ownerAddress = await signer.getAddress(); + + const sellToken = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; // wxDAI + const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c'; // COW + const sellAmount = '1000000000000000000'; // 1 wxDAI + + const quoteRequest: OrderQuoteRequest = { + sellToken, + buyToken, + from: ownerAddress, + receiver: ownerAddress, + sellAmountBeforeFee: sellAmount, + kind: OrderQuoteSideKindSell.SELL, + }; + + const { quote } = await orderBookApi.getQuote(quoteRequest); + + const order: UnsignedOrder = { + ...quote, + receiver: ownerAddress, + } + + return OrderSigningUtils.signOrder(order, chainId, signer) +} diff --git a/content/tutorial/01-orders/01-basic-orders/04-submit/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/04-submit/app-b/src/lib/run.ts new file mode 100644 index 00000000..19e1aa2d --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/04-submit/app-b/src/lib/run.ts @@ -0,0 +1,50 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { OrderBookApi, SupportedChainId, OrderQuoteRequest, OrderQuoteSideKindSell, OrderSigningUtils, UnsignedOrder, SigningScheme } from '@cowprotocol/cow-sdk' + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + + const signer = provider.getSigner(); + const ownerAddress = await signer.getAddress(); + + const sellToken = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; // wxDAI + const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c'; // COW + const sellAmount = '1000000000000000000'; // 1 wxDAI + + const quoteRequest: OrderQuoteRequest = { + sellToken, + buyToken, + from: ownerAddress, + receiver: ownerAddress, + sellAmountBeforeFee: sellAmount, + kind: OrderQuoteSideKindSell.SELL, + }; + + const { quote } = await orderBookApi.getQuote(quoteRequest); + + const order: UnsignedOrder = { + ...quote, + receiver: ownerAddress, + } + + const orderSigningResult = await OrderSigningUtils.signOrder(order, chainId, signer) + + try { + const orderId = await orderBookApi.sendOrder({ + ...quote, + ...orderSigningResult, + signingScheme: orderSigningResult.signingScheme as unknown as SigningScheme + }) + + return { + orderId, + } + } catch (e) { + return e + } +} diff --git a/content/tutorial/01-orders/01-basic-orders/04-submit/meta.json b/content/tutorial/01-orders/01-basic-orders/04-submit/meta.json new file mode 100644 index 00000000..c663d7a9 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/04-submit/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Submit order", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/README.md b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/README.md new file mode 100644 index 00000000..9205f60b --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/README.md @@ -0,0 +1,113 @@ +--- +title: Cancelling off-chain +--- + +In the last tutorial, we submitted an order to CoW Protocol. In this tutorial, we will attempt to cancel the order we just submitted. + +## Off-chain cancellation + +One of the many advantages of CoW Protocol and the use of intents is that we can cancel orders for free, off-chain. This means that we do not need to pay any gas fees to cancel an order. + +To cancel an order, we need to know the `orderUid` of the order we want to cancel. This is the unique identifier you received in the last tutorial. + +### Instantiate the SDK + +We will start from the basic setup from the [quote tutorial](/tutorial/quote) after we have instantiated the `OrderBookApi` and configured the `signer`. + +### Cancellation parameters + +Now that we have an instantiated `OrderBookApi`, we can cancel an order. To do this, we need to know: + +- the `orderUid` of the order we want to cancel, for example `0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1` + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + + const orderUid = `0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1` + + // ... +} +``` + +### Signing the cancellation + +Just like we did in the [sign order tutorial](/tutorial/sign), we need to sign the cancellation. To do this, we will use the `OrderSigningUtils` utility. + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; ++++import { SupportedChainId, OrderBookApi, OrderSigningUtils } from '@cowprotocol/cow-sdk';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + + const orderCancellationsSigningResult = await OrderSigningUtils.signOrderCancellations({ + [orderUid], + chainId, + signer + }); + + // ... +} +``` + +> The cancellation API endpoint for a single `orderUid` is marked deprecated. This is why we are using the plural version of the API endpoint (and hey, it's more efficient too!). + +### Cancelling the order + +Now that we have the signature, we can attempt to cancel the order: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi, OrderSigningUtils } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + + try { + const cancellationsResult = await orderBookApi.sendSignedOrderCancellations({ + ...orderCancellationsSigningResult, + orderUids: [orderUid], + }) + + return { cancellationsResult } + } catch (e) { + return e + } +} +``` + +Just as we did in the [submit order tutorial](/tutorial/submit), we are using a `try/catch` block to handle errors. + +## 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. + +1. Accept the connection request in Rabby +2. Press the "Run" button again +3. Observe the cancellation result returned to the output panel + +A successful cancellation should look like: + +```json +/// file: output.json +{ + "cancellationsResult": "Cancelled" +} +``` + +> The API simply returns `200` and `"Cancelled"` if the cancellation is successful. + +### Errors + +A couple of errors may easily result when running this code: + +- **`OrderFullyExecuted`**: The order you are trying to cancel has already been fully executed. This means that the order has been fully filled and the funds have been transferred to the `receiver`. diff --git a/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/app-a/src/lib/run.ts new file mode 100644 index 00000000..5eebdd18 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/app-a/src/lib/run.ts @@ -0,0 +1,13 @@ +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + const signer = provider.getSigner(); + +} diff --git a/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/app-b/src/lib/run.ts new file mode 100644 index 00000000..3dc9ace4 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/app-b/src/lib/run.ts @@ -0,0 +1,32 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { SupportedChainId, OrderBookApi, OrderSigningUtils } from '@cowprotocol/cow-sdk' + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + const signer = provider.getSigner(); + + const orderUid = '0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1'; + + const orderCancellationsSigningResult = await OrderSigningUtils.signOrderCancellations( + [orderUid], + chainId, + signer + ); + + try { + const cancellationsResult = await orderBookApi.sendSignedOrderCancellations({ + ...orderCancellationsSigningResult, + orderUids: [orderUid], + }) + + return { cancellationsResult } + } catch (e) { + return e + } + +} diff --git a/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/meta.json b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/meta.json new file mode 100644 index 00000000..19606a05 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/05-cancel-off-chain/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Cancel order off-chain", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/README.md b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/README.md new file mode 100644 index 00000000..aa7ecd8a --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/README.md @@ -0,0 +1,136 @@ +--- +title: Cancelling on-chain +--- + +The preferred way to cancel an order is off-chain, notably because it is free. However, this places trust in the API to cancel the order. If you want to enforce the cancellation of an order, you can do so on-chain. This will cost gas, but will ensure that the order is cancelled. + +To cancel, we will send an `invalidateOrder` transaction to the [`GPv2Settlement`](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core/settlement) contract. + +> A list of the core deployed contracts can be found [here](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core). + +## Contract (`GPv2Settlement`) interaction + +For interacting with contracts, the tutorials use [ethers.js](https://docs.ethers.io/v5/). + +To interact with a contract, we need to know: + +- the contract address +- the ABI + +Additionally, if we want to **make a transaction**, we must have a _signer_ (e.g. a wallet). + +### Contract address and `orderUid` + +The contract to be interacted with is the [`GPv2Settlement`](https://beta.docs.cow.fi/cow-protocol/reference/contracts/core/settlement) contract. We assign it's address `0x9008D19f58AAbD9eD0D60971565AA8510560ab41` to a `const`. + +At the same time, we assign the `orderUid` of the order we want to cancel to a `const`. + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + const settlementAddress = '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'; + const orderUid = '0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1'; + // ... +} +``` + +### `invalidateOrder` ABI + +The contract's `invalidateOrder` function is used to cancel an order. We can retrieve the ABI for this function from the contract's verified code on [Gnosisscan](https://gnosisscan.io/address/0x9008D19f58AAbD9eD0D60971565AA8510560ab41#code). We can set this function's ABI as a `const`: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + + const invalidateOrderAbi = [ + { + "inputs": [ + { "internalType": "bytes", "name": "orderUid", "type": "bytes" } + ], + "name": "invalidateOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]; + + // ... +} +``` + +### Signer + +To make a transaction, we need a signer. In this tutorial, we use an injected Web3Provider, such as [Rabby](https://rabby.io). We can get the signer from the provider: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + + const signer = provider.getSigner(); + + // ... +} +``` + +### Connect to the contract + +To interact with the contract, we need to connect to it. We can do this by using the `Contract` class from ethers.js: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; ++++import { Contract } from '@ethersproject/contracts';+++ + +export async function run(provider: Web3Provider): Promise { + // ... + + const settlement = new Contract(settlementAddress, invalidateOrderAbi, signer); + + // ... +} +``` + +### Execute the `invalidateOrder` + +Now we have everything we need to execute the `invalidateOrder` function. In order to do this, we pass the `orderUid` to the function: + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; +import { Contract } from '@ethersproject/contracts'; + +export async function run(provider: Web3Provider): Promise { + // ... + + const tx = await settlement.invalidateOrder(orderUid); + console.log('tx', tx); + const receipt = await tx.wait(); + + return receipt; +} +``` + +## 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. + +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 diff --git a/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/app-a/src/lib/run.ts new file mode 100644 index 00000000..b8a26d1f --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/app-a/src/lib/run.ts @@ -0,0 +1,12 @@ +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const signer = provider.getSigner(); + +} diff --git a/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/app-b/src/lib/run.ts new file mode 100644 index 00000000..be9d35a5 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/app-b/src/lib/run.ts @@ -0,0 +1,35 @@ +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId } from '@cowprotocol/cow-sdk'; +import { Contract } from 'ethers'; + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const settlementAddress = '0x9008D19f58AAbD9eD0D60971565AA8510560ab41'; + const orderUid = '0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1'; + + // Compacted ABI for the `invalidateOrder` function + const invalidateOrderAbi = [ + { + "inputs": [ + { "internalType": "bytes", "name": "orderUid", "type": "bytes" } + ], + "name": "invalidateOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] + + const signer = provider.getSigner(); + const settlement = new Contract(settlementAddress, invalidateOrderAbi, signer); + + const tx = await settlement.invalidateOrder(orderUid); + console.log('tx', tx); + const receipt = await tx.wait(); + + return receipt; +} diff --git a/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/meta.json b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/meta.json new file mode 100644 index 00000000..a24a595c --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/06-cancel-on-chain/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Cancel order on-chain", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-orders/01-basic-orders/07-view/README.md b/content/tutorial/01-orders/01-basic-orders/07-view/README.md new file mode 100644 index 00000000..2c113a65 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/07-view/README.md @@ -0,0 +1,76 @@ +--- +title: Viewing status +--- + +Now, you've submitted an order to CoW Protocol. In this tutorial, we will attempt to view the status of the order we just submitted. + +## Viewing the status + +With CoW Protocol, the only way to view the status of an order that has not yet been settled is to query the `OrderBookApi`. This is because the order is not yet on-chain, and therefore cannot be queried from the blockchain. + +## Instantiate the SDK + +We will start from the basic setup from the [quote tutorial](/tutorial/quote) after we have instantiated the `OrderBookApi`. + +### Query parameters + +Now that we have an instantiated `OrderBookApi`, we can query the status of an order. To do this, we need to know: + +- the `orderUid` of the order we want to query, for example `0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1` + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + const orderUid = `0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1`; + // ... +} +``` + +### Querying the order + +In this tutorial, we will do two types of queries: + +- `getOrder` to get general information about the order +- `getTrades` that will show us the trades that have been executed against the order + +```typescript +/// file: run.ts +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + // ... + try { + const order = await orderBookApi.getOrder(orderUid); + const trades = await orderBookApi.getTrades({ orderUid }); + + return { + order, + trades + } + } catch (e) { + return e; + } + // ... +} +``` + +## Run the code + +To run the code, we can press the "Run" button in the bottom right panel (the web container). + +A successful query should look something like: + +```json +/// file: output.json +{ + "order": { ... }, + "trades": [ ... ] +} +``` + +> The `order` and `trades` objects are quite large, so we have omitted them from this example. \ No newline at end of file diff --git a/content/tutorial/01-orders/01-basic-orders/07-view/app-a/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/07-view/app-a/src/lib/run.ts new file mode 100644 index 00000000..913e5d33 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/07-view/app-a/src/lib/run.ts @@ -0,0 +1,12 @@ +import type { Web3Provider } from '@ethersproject/providers'; +import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk'; + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + +} diff --git a/content/tutorial/01-orders/01-basic-orders/07-view/app-b/src/lib/run.ts b/content/tutorial/01-orders/01-basic-orders/07-view/app-b/src/lib/run.ts new file mode 100644 index 00000000..944baf8e --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/07-view/app-b/src/lib/run.ts @@ -0,0 +1,26 @@ +import type { Web3Provider } from '@ethersproject/providers' +import { SupportedChainId, OrderBookApi } from '@cowprotocol/cow-sdk' + +export async function run(provider: Web3Provider): Promise { + const chainId = +(await provider.send('eth_chainId', [])); + if (chainId !== SupportedChainId.GNOSIS_CHAIN) { + throw new Error(`Please connect to the Gnosis chain. ChainId: ${chainId}`); + } + + const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.GNOSIS_CHAIN }); + + const orderUid = '0x8464affce2df48b60f6976e51414dbc079e9c30ef64f4c1f78c7abe2c7f96a0c29104bb91ada737a89393c78335e48ff4708727e659523a1'; + + try { + const order = await orderBookApi.getOrder(orderUid); + const trades = await orderBookApi.getTrades({ orderUid }); + + return { + order, + trades + } + } catch (e) { + return e; + } + +} diff --git a/content/tutorial/01-orders/01-basic-orders/07-view/meta.json b/content/tutorial/01-orders/01-basic-orders/07-view/meta.json new file mode 100644 index 00000000..fe9a6264 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/07-view/meta.json @@ -0,0 +1,8 @@ +{ + "title": "View status", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-orders/01-basic-orders/meta.json b/content/tutorial/01-orders/01-basic-orders/meta.json new file mode 100644 index 00000000..d9205b91 --- /dev/null +++ b/content/tutorial/01-orders/01-basic-orders/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Basic orders", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-cow-protocol/01-orders/02-app-data/README.md b/content/tutorial/01-orders/02-app-data/02-app-data/README.md similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/02-app-data/README.md rename to content/tutorial/01-orders/02-app-data/02-app-data/README.md diff --git a/content/tutorial/01-cow-protocol/01-orders/02-app-data/app-a/src/lib/run.ts b/content/tutorial/01-orders/02-app-data/02-app-data/app-a/src/lib/run.ts similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/02-app-data/app-a/src/lib/run.ts rename to content/tutorial/01-orders/02-app-data/02-app-data/app-a/src/lib/run.ts diff --git a/content/tutorial/01-orders/02-app-data/02-app-data/app-a/tsconfig.json b/content/tutorial/01-orders/02-app-data/02-app-data/app-a/tsconfig.json new file mode 100644 index 00000000..ebe331bc --- /dev/null +++ b/content/tutorial/01-orders/02-app-data/02-app-data/app-a/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../common/tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "../../../../common/*", + "../../../../common/node_modules/*" + ] + } + }, + "include": [ + "**/*" // Include all files in app-a + ] +} diff --git a/content/tutorial/01-orders/02-app-data/meta.json b/content/tutorial/01-orders/02-app-data/meta.json new file mode 100644 index 00000000..18d140f6 --- /dev/null +++ b/content/tutorial/01-orders/02-app-data/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Application Data (appData)", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/README.md b/content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/README.md similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/README.md rename to content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/README.md diff --git a/content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/GPv2Settlement.json b/content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/GPv2Settlement.json similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/GPv2Settlement.json rename to content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/GPv2Settlement.json diff --git a/content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/const.ts b/content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/const.ts similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/const.ts rename to content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/const.ts diff --git a/content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/getSafeSdkAndKit.ts b/content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/getSafeSdkAndKit.ts similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/getSafeSdkAndKit.ts rename to content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/getSafeSdkAndKit.ts diff --git a/content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/run.ts b/content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/run.ts similarity index 99% rename from content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/run.ts rename to content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/run.ts index 905053cf..44616ebf 100644 --- a/content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-a/src/lib/run.ts +++ b/content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-a/src/lib/run.ts @@ -38,6 +38,4 @@ export async function run(provider: Web3Provider): Promise { partiallyFillable: true, signingScheme: SigningScheme.PRESIGN, } - - return } diff --git a/content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-b/src/lib/run.ts b/content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-b/src/lib/run.ts similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/03-pre-signed-order/app-b/src/lib/run.ts rename to content/tutorial/01-orders/03-advanced-orders/03-pre-signed-order/app-b/src/lib/run.ts diff --git a/content/tutorial/01-orders/03-advanced-orders/meta.json b/content/tutorial/01-orders/03-advanced-orders/meta.json new file mode 100644 index 00000000..bc77606b --- /dev/null +++ b/content/tutorial/01-orders/03-advanced-orders/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Advanced orders", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/01-orders/03-advanced-orders/tsconfig.json b/content/tutorial/01-orders/03-advanced-orders/tsconfig.json new file mode 100644 index 00000000..81738a50 --- /dev/null +++ b/content/tutorial/01-orders/03-advanced-orders/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "../common/node_modules/*" + ] + } + } +} \ No newline at end of file diff --git a/content/tutorial/01-cow-protocol/01-orders/meta.json b/content/tutorial/01-orders/meta.json similarity index 100% rename from content/tutorial/01-cow-protocol/01-orders/meta.json rename to content/tutorial/01-orders/meta.json diff --git a/content/tutorial/01-orders/tsconfig.json b/content/tutorial/01-orders/tsconfig.json new file mode 100644 index 00000000..81738a50 --- /dev/null +++ b/content/tutorial/01-orders/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "../common/node_modules/*" + ] + } + } +} \ No newline at end of file diff --git a/content/tutorial/03-analyze/meta.json b/content/tutorial/03-analyze/meta.json new file mode 100644 index 00000000..b40a042f --- /dev/null +++ b/content/tutorial/03-analyze/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Analyze auctions", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/03-analyze/tsconfig.json b/content/tutorial/03-analyze/tsconfig.json new file mode 100644 index 00000000..81738a50 --- /dev/null +++ b/content/tutorial/03-analyze/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "../common/node_modules/*" + ] + } + } +} \ No newline at end of file diff --git a/content/tutorial/04-solvers/meta.json b/content/tutorial/04-solvers/meta.json new file mode 100644 index 00000000..997118b8 --- /dev/null +++ b/content/tutorial/04-solvers/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Run a solver", + "scope": { + "prefix": "/src/lib/", + "name": "src" + }, + "focus": "/src/lib/run.ts" +} diff --git a/content/tutorial/04-solvers/tsconfig.json b/content/tutorial/04-solvers/tsconfig.json new file mode 100644 index 00000000..81738a50 --- /dev/null +++ b/content/tutorial/04-solvers/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "../common/node_modules/*" + ] + } + } +} \ No newline at end of file diff --git a/src/lib/server/markdown.js b/src/lib/server/markdown.js index ebaec563..18b29c54 100644 --- a/src/lib/server/markdown.js +++ b/src/lib/server/markdown.js @@ -12,9 +12,11 @@ const languages = { html: 'markup', svelte: 'svelte', js: 'javascript', + json: 'javascript', css: 'css', diff: 'diff', ts: 'typescript', + typescript: 'typescript', '': '' }; diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js index 0865d372..6b172358 100644 --- a/src/routes/+page.server.js +++ b/src/routes/+page.server.js @@ -1,5 +1,5 @@ import { redirect } from '@sveltejs/kit'; export function load() { - throw redirect(307, '/tutorial/sign-order'); + throw redirect(307, '/tutorial/getting-started'); } diff --git a/src/routes/tutorial/+page.js b/src/routes/tutorial/+page.js index 0865d372..6b172358 100644 --- a/src/routes/tutorial/+page.js +++ b/src/routes/tutorial/+page.js @@ -1,5 +1,5 @@ import { redirect } from '@sveltejs/kit'; export function load() { - throw redirect(307, '/tutorial/sign-order'); + throw redirect(307, '/tutorial/getting-started'); } diff --git a/src/routes/tutorial/[slug]/+page.server.js b/src/routes/tutorial/[slug]/+page.server.js index 2708be3c..f339456d 100644 --- a/src/routes/tutorial/[slug]/+page.server.js +++ b/src/routes/tutorial/[slug]/+page.server.js @@ -8,7 +8,7 @@ export function entries() { export async function load({ params }) { if (params.slug === 'local-transitions') { - throw redirect(307, '/tutorial/sign-order'); + throw redirect(307, '/tutorial/getting-started'); } const exercise = await get_exercise(params.slug); diff --git a/tests/env_file.spec.ts b/tests/env_file.spec.ts index 6b49ad71..47a5def4 100644 --- a/tests/env_file.spec.ts +++ b/tests/env_file.spec.ts @@ -9,7 +9,7 @@ test('.env file: no timeout error occurs when switching a tutorials without a .e }) => { await page.bringToFront(); - await page.goto('/tutorial/sign-order'); + await page.goto('/tutorial/getting-started'); const iframe_locator = page.frameLocator(iframe_selector); @@ -39,7 +39,7 @@ test('.env file: environment variables are available when switching a tutorial w }) => { await page.bringToFront(); - await page.goto('/tutorial/sign-order'); + await page.goto('/tutorial/getting-started'); const iframe_locator = page.frameLocator(iframe_selector);