Skip to main content

In the previous tutorials we have create a pre-signed order and showed how to view it. Let's say for some reason we want to cancel the order (like instead of buying 1 xDAI worth of COW, you now want to buy 200 xDAI worth of COW)! In this tutorial we will show how to cancel an order.

There are actually two ways to cancel a pre-signed order:

  1. Set signed to false using setPreSignature
  2. Invalidate the order using invalidateOrder, as seen in cancelling on-chain

We will use the first method in this tutorial (as it results in a cheaper transaction fee).

Contract GPv2Settlement interaction

In order to revoke the signature of a pre-signed order, we need to call the setPreSignature with:

  • orderUid - the same orderUid that was pre-signed
  • signed - false

We will start at the same point as the previous tutorial, where we have created a pre-signed order and signed the transaction with a Safe.

Define the orderUid

Instead of using the API to generate the orderUid when sending an order, we will simply use the orderUid that was previously generated and define that as a constant:

run.ts
// ...

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

Replace the orderUid with the one that was generated in the previous tutorial

Set signed to false

Now that we have the orderUid, we can call setPreSignature with the orderUid and signed set to false:

run.ts
// ...

export async function run(provider: Web3Provider): Promise<unknown> {
  // ...
  const presignCallData = settlementContract.interface.encodeFunctionData('setPreSignature', [
	orderUid,
	false,
  ])
  // ...
}

This should be all the changes required in order to revoke the pre-signed order with your Safe.

As the orderUid is now statically defined, a lot of code can be removed from the previous tutorial. You can check the optimum solution by clicking 'Solve'.

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 orderUid, safeTxHash, and senderSignature in the output panel
  4. Browse to your Safe wallet and confirm the transaction
  5. On successful confirmation of the transaction, the order's signature should be revoked.

When checking the orderUid status in CoW Explorer, the order should now be marked as 'Signing' instead of 'Open'. The means that the order is no longer valid and can't be filled. Once the expiry time has passed, the order will be marked as 'Expired'.

Next: MEV Blocker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import type { Web3Provider } from '@ethersproject/providers'
import { BigNumber, Contract, ethers } from 'ethers'
import {
  SupportedChainId,
  OrderBookApi,
  SigningScheme,
  OrderQuoteRequest,
  OrderQuoteSideKindSell,
  OrderCreation,
  COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS
} from '@cowprotocol/cow-sdk'
import { MetadataApi, latest } from '@cowprotocol/app-data'
import { MetaTransactionData } from '@safe-global/safe-core-sdk-types'
import Safe, { EthersAdapter } from '@safe-global/protocol-kit'
import SafeApiKit from '@safe-global/api-kit'
 
export async function run(provider: Web3Provider): Promise<unknown> {
  const chainId = +(await provider.send('eth_chainId', []));
  if (chainId !== SupportedChainId.GNOSIS_CHAIN) {
      await provider.send('wallet_switchEthereumChain', [{ chainId: SupportedChainId.GNOSIS_CHAIN }]);
  }
  const orderBookApi = new OrderBookApi({ chainId })
  const metadataApi = new MetadataApi()
 
  const appCode = 'Decentralized CoW'
  const environment = 'production'
  const referrer = { address: `0xcA771eda0c70aA7d053aB1B25004559B918FE662` }
  const quoteAppDoc: latest.Quote = { slippageBips: '50' }
  const orderClass: latest.OrderClass = { orderClass: 'limit' }
 
  const appDataDoc = await metadataApi.generateAppDataDoc({
    appCode,
    environment,
    metadata: {
      referrer,
      quote: quoteAppDoc,
      orderClass
    },
  })
 
  const { appDataHex, appDataContent } = await metadataApi.appDataToCid(appDataDoc)
 
  const signer = provider.getSigner();
  const ownerAddress = await signer.getAddress();
 
  const abi = [
    {
      "inputs": [
        { "internalType": "bytes", "name": "orderUid", "type": "bytes" },
        { "internalType": "bool", "name": "signed", "type": "bool" }
      ],
      "name": "setPreSignature",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
 
  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.SEPOLIA]: 'https://safe-transaction-sepolia.safe.global',
  }
  
  const getSafeSdkAndKit = async (safeAddress: string) => {
    const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer })
    const txServiceUrl = SAFE_TRANSACTION_SERVICE_URL[chainId]
    const safeApiKit = new SafeApiKit({ txServiceUrl, ethAdapter })
  
    const safeSdk = await Safe.create({ethAdapter, safeAddress});
 
    return { safeApiKit, safeSdk }
  }
 
  const proposeSafeTx = async (params: MetaTransactionData) => {
    const safeTx = await safeSdk.createTransaction({safeTransactionData: params})
    const signedSafeTx = await safeSdk.signTransaction(safeTx)
    const safeTxHash = await safeSdk.getTransactionHash(signedSafeTx)
    const senderSignature = signedSafeTx.signatures.get(ownerAddress.toLowerCase())?.data || ''
  
    // Send the pre-signed transaction to the Safe
    await safeApiKit.proposeTransaction({
      safeAddress,
      safeTransactionData: signedSafeTx.data,
      safeTxHash,
      senderAddress: ownerAddress,
      senderSignature,
    })
 
    return { safeTxHash, senderSignature }
  }
 
  const safeAddress = '0x075E706842751c28aAFCc326c8E7a26777fe3Cc2'
  const settlementContract = new Contract(COW_PROTOCOL_SETTLEMENT_CONTRACT_ADDRESS[chainId], abi)
 
  const { safeApiKit, safeSdk } = await getSafeSdkAndKit(safeAddress)
 
  const sellAmount = '1000000000000000000';
  const sellToken = '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d';
  const buyToken = '0x177127622c4A00F3d409B75571e12cB3c8973d3c';
 
  const quoteRequest: OrderQuoteRequest = {
    sellToken,
    buyToken,
    receiver: safeAddress,
    sellAmountBeforeFee: sellAmount,
    kind: OrderQuoteSideKindSell.SELL,
    appData: appDataContent,
    appDataHash: appDataHex,
    from: ownerAddress,
  }
 
  const { quote } = await orderBookApi.getQuote(quoteRequest);
 
  const order: OrderCreation = {
    ...quote,
    sellAmount,
    buyAmount: BigNumber.from(quote.buyAmount).mul(9950).div(10000).toString(),
    feeAmount: '0',
    appData: appDataContent,
    appDataHash: appDataHex,
    partiallyFillable: true,
    from: safeAddress,
    signature: '0x',
    signingScheme: SigningScheme.PRESIGN,
  }
 
  const orderUid = await orderBookApi.sendOrder(order)
 
  const presignCallData = settlementContract.interface.encodeFunctionData('setPreSignature', [
    orderUid,
    true,
  ])
 
  const presignRawTx: MetaTransactionData = {
    to: settlementContract.address,
    value: '0',
    data: presignCallData,
  }
 
  const { safeTxHash, senderSignature } = await proposeSafeTx(presignRawTx)
 
  return { orderUid, safeTxHash, senderSignature }
}
 
initialising