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:
- Set
signed
tofalse
usingsetPreSignature
- 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 sameorderUid
that was pre-signedsigned
-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:
// ...
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
:
// ...
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.
- Accept the connection request in Rabby
- Press the "Run" button again
- Observe the
orderUid
,safeTxHash
, andsenderSignature
in the output panel - Browse to your
Safe
wallet and confirm the transaction - 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'.
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 }
}