Skip to content

Commit

Permalink
feat(examples): creating an order via smart-contract wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
shoom3301 committed Dec 8, 2023
1 parent 7ef442e commit 9ded2b1
Show file tree
Hide file tree
Showing 11 changed files with 2,301 additions and 50 deletions.
14 changes: 13 additions & 1 deletion examples/cra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@cowprotocol/cow-sdk": "^2.0.0-alpha.6",
"@cowprotocol/cow-sdk": "^4.0.3",
"@safe-global/api-kit": "^1.3.0",
"@safe-global/protocol-kit": "^1.2.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"ethers": "^5.7.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
Expand All @@ -31,6 +34,15 @@
]
},
"devDependencies": {
"buffer": "npm:[email protected]",
"assert": "npm:[email protected]",
"http": "npm:stream-http@^3.2.0",
"https": "npm:https-browserify@^1.0.0",
"zlib": "npm:browserify-zlib@^0.2.0",
"crypto": "npm:[email protected]",
"stream": "npm:[email protected]",
"url": "npm:[email protected]",
"util": "npm:[email protected]",
"@types/jest": "^29.5.0",
"@types/node": "^18.15.10",
"@types/react": "^18.0.30",
Expand Down
2 changes: 2 additions & 0 deletions examples/cra/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FC, useEffect, useState } from 'react'
import { useWeb3Info } from './hooks/useWeb3Info'
import { ChainIdContext } from './context'
import { QuickStartPage } from './pages/quickStart'
import { SmartContractWallet } from './pages/smartContractWallet'

const EXAMPLES: ExampleProps[] = [
{ title: 'Quick start', Component: QuickStartPage },
Expand All @@ -20,6 +21,7 @@ const EXAMPLES: ExampleProps[] = [
{ title: 'Sign and send order', Component: SignAndSendOrderPage },
{ title: 'Sign order cancellation', Component: SignOrderCancellationPage },
{ title: 'Send order cancellation', Component: SendOrderCancellationPage },
{ title: 'Smart contract wallet', Component: SmartContractWallet },
]

interface ExampleProps {
Expand Down
3 changes: 3 additions & 0 deletions examples/cra/src/hooks/useWeb3Info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { Web3Provider } from '@ethersproject/providers'

const provider = new Web3Provider(window.ethereum)

// Connect to injected wallet
;(window.ethereum as {enable(): void})?.enable()

export interface Web3Info {
provider: Web3Provider
chainId: number
Expand Down
3 changes: 3 additions & 0 deletions examples/cra/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
import { ExternalProvider } from '@ethersproject/providers'
import { Buffer } from 'buffer'

window.Buffer = Buffer

declare global {
interface Window {
Expand Down
4 changes: 2 additions & 2 deletions examples/cra/src/pages/getQuote/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FormEvent, useCallback, useEffect, useState } from 'react'
import '../../pageStyles.css'
import { OrderQuoteRequest, OrderQuoteResponse, OrderBookApi, OrderQuoteSide } from '@cowprotocol/cow-sdk'
import { OrderQuoteRequest, OrderQuoteResponse, OrderBookApi, OrderQuoteSideKindSell } from '@cowprotocol/cow-sdk'
import { useWeb3Info } from '../../hooks/useWeb3Info'
import { JsonContent } from '../../components/jsonContent'
import { useCurrentChainId } from '../../hooks/useCurrentChainId'
Expand Down Expand Up @@ -43,7 +43,7 @@ export function GetQuotePage() {
from: account,
receiver: account,
sellAmountBeforeFee: (0.4 * 10 ** 18).toString(),
kind: OrderQuoteSide.kind.SELL,
kind: OrderQuoteSideKindSell.SELL,
}

return (
Expand Down
16 changes: 8 additions & 8 deletions examples/cra/src/pages/quickStart/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FormEvent, useCallback, useEffect, useState } from 'react'
import '../../pageStyles.css'
import { OrderBookApi, OrderQuoteSide, OrderSigningUtils, OrderQuoteRequest, SigningScheme } from '@cowprotocol/cow-sdk'
import { OrderBookApi, OrderQuoteSideKindSell, OrderSigningUtils, OrderQuoteRequest, SigningScheme } from '@cowprotocol/cow-sdk'
import { useWeb3Info } from '../../hooks/useWeb3Info'
import { useCurrentChainId } from '../../hooks/useCurrentChainId'
import { ResultContent } from '../../components/resultContent'
Expand Down Expand Up @@ -29,7 +29,7 @@ export function QuickStartPage() {
from: account,
receiver: account,
sellAmountBeforeFee: (0.4 * 10 ** 18).toString(), // 0.4 WETH
kind: OrderQuoteSide.kind.SELL,
kind: OrderQuoteSideKindSell.SELL,
}

// Get quote
Expand All @@ -39,28 +39,28 @@ export function QuickStartPage() {
const orderSigningResult = await OrderSigningUtils.signOrder({ ...quote, receiver: account }, chainId, signer)

// Send order to the order-book
const orderId = await orderBookApi.sendOrder({
const orderUid = await orderBookApi.sendOrder({
...quote,
signature: orderSigningResult.signature,
signingScheme: orderSigningResult.signingScheme as string as SigningScheme,
})

// Get order data
const order = await orderBookApi.getOrder(orderId)
const order = await orderBookApi.getOrder(orderUid)

// Get order trades
const trades = await orderBookApi.getTrades({ orderId })
const trades = await orderBookApi.getTrades({ orderUid })

// Sign order cancellation
const orderCancellationSigningResult = await OrderSigningUtils.signOrderCancellations([orderId], chainId, signer)
const orderCancellationSigningResult = await OrderSigningUtils.signOrderCancellations([orderUid], chainId, signer)

// Send order cancellation
const cancellationResult = await orderBookApi.sendSignedOrderCancellations({
...orderCancellationSigningResult,
orderUids: [orderId],
orderUids: [orderUid],
})

setOutput({ orderId, order, trades, orderCancellationSigningResult, cancellationResult })
setOutput({ orderUid, order, trades, orderCancellationSigningResult, cancellationResult })
},
[chainId, provider, account]
)
Expand Down
20 changes: 20 additions & 0 deletions examples/cra/src/pages/smartContractWallet/GPv2Settlement.json
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"
}
]
12 changes: 12 additions & 0 deletions examples/cra/src/pages/smartContractWallet/const.ts
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',
}
134 changes: 134 additions & 0 deletions examples/cra/src/pages/smartContractWallet/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {FormEvent, useCallback, useEffect, useMemo, useState} from 'react'

import { Contract } from '@ethersproject/contracts'
import { OrderBookApi, OrderCreation, OrderKind, SigningScheme, UnsignedOrder } from '@cowprotocol/cow-sdk'

import { JsonContent } from '../../components/jsonContent'
import { ResultContent } from '../../components/resultContent'
import { useWeb3Info } from '../../hooks/useWeb3Info'
import { useCurrentChainId } from '../../hooks/useCurrentChainId'

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

const appData = '{"appCode":"CoW Swap-SafeApp","environment":"local","metadata":{"orderClass":{"orderClass":"limit"},"quote":{"slippageBips":"0"}},"version":"0.11.0"}'
const appDataHash = '0x6bb009e9730f09d18011327b6a1e4b9df70a3eb4d49e7cb622f79caadac5751a'


const orderBookApi = new OrderBookApi()
const settlementContract = new Contract(SETTLEMENT_CONTRACT_ADDRESS, SETTLEMENT_CONTRACT_ABI)

export function SmartContractWallet() {
const {provider, account} = useWeb3Info()
const chainId = useCurrentChainId()

const [safeAddress, setSafeAddress] = useState<string | null>(null)
const [input, setInput] = useState<UnsignedOrder | null>(null)
const [output, setOutput] = useState<any>('')

const {safeSdk, safeApiKit} = useSafeSdkAndKit(safeAddress, chainId, provider)

const defaultOrder: UnsignedOrder | null = useMemo(() => {
return safeAddress ? {
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,
} : null
}, [safeAddress])

useEffect(() => {
orderBookApi.context.chainId = chainId
}, [chainId])


const signOrder = useCallback(
async (event: FormEvent) => {
event.preventDefault()

if (!safeAddress) {
alert('Please, specify Safe address')
return
}

if (!input || !safeSdk || !safeApiKit) return

setOutput('Loading...')

try {
const orderCreation: OrderCreation = {
...input,
from: safeAddress,
signingScheme: SigningScheme.PRESIGN,
signature: safeAddress,
appData,
appDataHash,
}

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

const presignCallData = settlementContract.interface.encodeFunctionData('setPreSignature', [
orderId,
true,
])

const presignRawTx = {
to: settlementContract.address,
data: presignCallData,
value: '0',
}

// Sending 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 || ''

await safeApiKit.proposeTransaction({
safeAddress,
safeTransactionData: signedSafeTx.data,
safeTxHash,
senderAddress: account,
senderSignature,
})

setOutput({ orderId, safeTxHash, senderSignature })
} catch (e: any) {
setOutput(e.toString())
}
},
[chainId, input, provider, setOutput, safeSdk, safeApiKit]
)

return (
<div>
<div className="form">
<div>
<h1>Safe address:</h1>
<input type="text"
style={{width: '600px'}}
value={safeAddress || ''}
onChange={e => setSafeAddress(e.target.value)}/>

<h1>Order:</h1>
<JsonContent defaultValue={defaultOrder} onChange={setInput}/>
</div>

<div>
<button onClick={signOrder}>Sign and send order</button>
</div>
</div>

<ResultContent data={output}/>
</div>
)
}
39 changes: 39 additions & 0 deletions examples/cra/src/pages/smartContractWallet/useSafeSdkAndKit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {useEffect, useState} from 'react'
import Safe, {EthersAdapter} from '@safe-global/protocol-kit'
import {ethers} from 'ethers'
import SafeApiKit from '@safe-global/api-kit'
import {SAFE_TRANSACTION_SERVICE_URL} from './const'
import {SupportedChainId} from '@cowprotocol/cow-sdk'
import {Web3Provider} from '@ethersproject/providers'

export function useSafeSdkAndKit(safeAddress: string | null, chainId: SupportedChainId, provider: Web3Provider) {
const [safeSdk, setSafeSdk] = useState<Safe | null>(null)
const [safeApiKit, setSafeApiKit] = useState<SafeApiKit | null>(null)

useEffect(() => {
const txServiceUrl = SAFE_TRANSACTION_SERVICE_URL[chainId]

if (!safeAddress) return

if (!txServiceUrl) {
console.error('Unsupported chainId', chainId)
return
}

const ethAdapter = new EthersAdapter({
ethers,
signerOrProvider: provider.getSigner(0),
})

const safeApiKit = new SafeApiKit({
txServiceUrl,
ethAdapter
})

setSafeApiKit(safeApiKit)

Safe.create({ethAdapter, safeAddress}).then(setSafeSdk).catch(console.error)
}, [chainId, provider, safeAddress])

return { safeSdk, safeApiKit }
}
Loading

0 comments on commit 9ded2b1

Please sign in to comment.