Skip to content

Commit dccbc42

Browse files
committed
feat(trading): function to generate eth-flow transaction
1 parent e1ac3b9 commit dccbc42

File tree

6 files changed

+133
-82
lines changed

6 files changed

+133
-82
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Signer } from 'ethers'
2+
import { LimitTradeParameters, TransactionParams } from './types'
3+
import { calculateUniqueOrderId, EthFlowOrderExistsCallback } from './calculateUniqueOrderId'
4+
import { getOrderToSign } from './getOrderToSign'
5+
import { type EthFlow, EthFlow__factory } from '../common/generated'
6+
import {
7+
BARN_ETH_FLOW_ADDRESSES,
8+
CowEnv,
9+
ETH_FLOW_ADDRESSES,
10+
SupportedChainId,
11+
WRAPPED_NATIVE_CURRENCIES,
12+
} from '../common'
13+
import { GAS_LIMIT_DEFAULT } from './consts'
14+
import type { EthFlowOrder } from '../common/generated/EthFlow'
15+
16+
export async function getEthFlowTransaction(
17+
signer: Signer,
18+
appDataKeccak256: string,
19+
_params: LimitTradeParameters,
20+
networkCostsAmount = '0',
21+
checkEthFlowOrderExists?: EthFlowOrderExistsCallback
22+
): Promise<{ orderId: string; transaction: TransactionParams }> {
23+
const chainId = (await signer.getChainId()) as SupportedChainId
24+
const from = await signer.getAddress()
25+
26+
const params = {
27+
..._params,
28+
sellToken: WRAPPED_NATIVE_CURRENCIES[chainId],
29+
}
30+
const { quoteId } = params
31+
32+
const contract = getEthFlowContract(chainId, signer, params.env)
33+
const orderToSign = getOrderToSign({ from, networkCostsAmount }, params, appDataKeccak256)
34+
const orderId = await calculateUniqueOrderId(chainId, orderToSign, checkEthFlowOrderExists, params.env)
35+
36+
const ethOrderParams: EthFlowOrder.DataStruct = {
37+
...orderToSign,
38+
quoteId,
39+
appData: appDataKeccak256,
40+
validTo: orderToSign.validTo.toString(),
41+
}
42+
43+
const estimatedGas = await contract.estimateGas
44+
.createOrder(ethOrderParams, { value: orderToSign.sellAmount })
45+
.then((res) => BigInt(res.toHexString()))
46+
.catch((error) => {
47+
console.error(error)
48+
49+
return GAS_LIMIT_DEFAULT
50+
})
51+
52+
const callData = contract.interface.encodeFunctionData('createOrder', [ethOrderParams])
53+
54+
return {
55+
orderId,
56+
transaction: {
57+
callData,
58+
gasLimit: calculateGasMargin(estimatedGas).toString(),
59+
to: contract.address,
60+
value: orderToSign.sellAmount,
61+
},
62+
}
63+
}
64+
65+
const ethFlowContractCache: Partial<Record<SupportedChainId, EthFlow | undefined>> = {}
66+
67+
function getEthFlowContract(chainId: SupportedChainId, signer: Signer, env?: CowEnv): EthFlow {
68+
const cache = ethFlowContractCache[chainId]
69+
70+
if (cache) return cache
71+
72+
const contract = EthFlow__factory.connect(
73+
(env === 'staging' ? BARN_ETH_FLOW_ADDRESSES : ETH_FLOW_ADDRESSES)[chainId],
74+
signer
75+
)
76+
77+
ethFlowContractCache[chainId] = contract
78+
79+
return contract
80+
}
81+
82+
/**
83+
* Returns the gas value plus a margin for unexpected or variable gas costs (20%)
84+
* @param value the gas value to pad
85+
*/
86+
function calculateGasMargin(value: bigint): bigint {
87+
return value + (value * BigInt(20)) / BigInt(100)
88+
}

src/trading/getQuote.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,14 @@ export async function getQuote(
111111
const orderTypedData = await getOrderTypedData(chainId, orderToSign)
112112

113113
return {
114-
result: { amountsAndCosts, quoteResponse, appDataInfo, orderToSign, tradeParameters, orderTypedData },
114+
result: {
115+
amountsAndCosts,
116+
quoteResponse,
117+
appDataInfo,
118+
orderToSign,
119+
tradeParameters,
120+
orderTypedData,
121+
},
115122
orderBookApi,
116123
}
117124
}

src/trading/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export { postLimitOrder } from './postLimitOrder'
1010
export { postCoWProtocolTrade } from './postCoWProtocolTrade'
1111
export { getOrderToSign } from './getOrderToSign'
1212
export { postOnChainTrade } from './postOnChainTrade'
13+
export { getEthFlowTransaction } from './getEthFlowTransaction'
1314

1415
/**
1516
* Helpers

src/trading/postOnChainTrade.test.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ jest.mock('../common/generated', () => {
2525

2626
import { VoidSigner } from '@ethersproject/abstract-signer'
2727
import { AppDataInfo, LimitOrderParameters } from './types'
28-
import { SupportedChainId } from '../common'
28+
import { SupportedChainId, WRAPPED_NATIVE_CURRENCIES } from '../common'
2929
import { OrderBookApi, OrderKind } from '../order-book'
3030
import { postOnChainTrade } from './postOnChainTrade'
3131

3232
const defaultOrderParams: LimitOrderParameters = {
3333
chainId: SupportedChainId.GNOSIS_CHAIN,
3434
signer: '1bb337bafb276f779c3035874b8914e4b851bb989dbb34e776397076576f3804',
3535
appCode: '0x007',
36-
sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
36+
sellToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
3737
sellTokenDecimals: 18,
3838
buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
3939
buyTokenDecimals: 18,
@@ -47,8 +47,12 @@ const defaultOrderParams: LimitOrderParameters = {
4747

4848
const account = '0x21c3de23d98caddc406e3d31b25e807addf33333'
4949
const signer = new VoidSigner(account)
50+
51+
const sendTransactionMock = jest.fn().mockResolvedValue({ txHash: '0xccdd11', orderId: '0xabc22' })
5052
signer.getChainId = jest.fn().mockResolvedValue(SupportedChainId.GNOSIS_CHAIN)
53+
signer.sendTransaction = sendTransactionMock
5154

55+
const callData = '0x123456'
5256
const currentTimestamp = 1487076708000
5357

5458
const uploadAppDataMock = jest.fn()
@@ -65,6 +69,9 @@ const ethFlowContractMock = {
6569
createOrder: jest.fn(),
6670
},
6771
createOrder: jest.fn(),
72+
interface: {
73+
encodeFunctionData: jest.fn().mockReturnValue(callData),
74+
},
6875
}
6976

7077
describe('postOnChainTrade', () => {
@@ -76,7 +83,6 @@ describe('postOnChainTrade', () => {
7683
ethFlowContractFactoryMock.mockReturnValue(ethFlowContractMock)
7784
uploadAppDataMock.mockResolvedValue(undefined)
7885
ethFlowContractMock.estimateGas.createOrder.mockResolvedValue({ toHexString: () => '0x1' })
79-
ethFlowContractMock.createOrder.mockResolvedValue({ hash: '0x000cc' })
8086

8187
Date.now = jest.fn(() => currentTimestamp)
8288
})
@@ -85,7 +91,8 @@ describe('postOnChainTrade', () => {
8591
uploadAppDataMock.mockReset()
8692
ethFlowContractFactoryMock.mockReset()
8793
ethFlowContractMock.estimateGas.createOrder.mockReset()
88-
ethFlowContractMock.createOrder.mockReset()
94+
ethFlowContractMock.interface.encodeFunctionData.mockReset()
95+
sendTransactionMock.mockReset()
8996
})
9097

9198
it('Should call checkEthFlowOrderExists if it is set', async () => {
@@ -107,19 +114,19 @@ describe('postOnChainTrade', () => {
107114

108115
await postOnChainTrade(orderBookApiMock, signer, appDataMock, defaultOrderParams)
109116

110-
const call = ethFlowContractMock.createOrder.mock.calls[0][1]
117+
const call = sendTransactionMock.mock.calls[0][0]
111118

112-
expect(call.gasLimit).toBe(BigInt(180000)) // 150000 by default + 20%
119+
expect(call.gasLimit).toBe(BigInt(180000).toString()) // 150000 by default + 20%
113120
})
114121

115122
it('Should create an on-chain transaction with all specified parameters', async () => {
116123
await postOnChainTrade(orderBookApiMock, signer, appDataMock, defaultOrderParams)
117124

118-
expect(ethFlowContractMock.createOrder).toHaveBeenCalledTimes(1)
119-
expect(ethFlowContractMock.createOrder).toHaveBeenCalledWith(
125+
expect(ethFlowContractMock.interface.encodeFunctionData).toHaveBeenCalledTimes(1)
126+
expect(ethFlowContractMock.interface.encodeFunctionData).toHaveBeenCalledWith('createOrder', [
120127
{
121128
appData: appDataMock.appDataKeccak256,
122-
sellToken: defaultOrderParams.sellToken,
129+
sellToken: WRAPPED_NATIVE_CURRENCIES[defaultOrderParams.chainId],
123130
sellAmount: defaultOrderParams.sellAmount,
124131
sellTokenBalance: 'erc20',
125132
buyAmount: '1990000000000000000', // defaultOrderParams.buyAmount - slippage
@@ -132,7 +139,6 @@ describe('postOnChainTrade', () => {
132139
receiver: account,
133140
validTo: defaultOrderParams.validTo!.toString(),
134141
},
135-
{ value: defaultOrderParams.sellAmount, gasLimit: BigInt(1) }
136-
)
142+
])
137143
})
138144
})

src/trading/postOnChainTrade.ts

Lines changed: 12 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
import { Signer } from 'ethers'
22
import { AppDataInfo, LimitTradeParameters } from './types'
3-
import { calculateUniqueOrderId, EthFlowOrderExistsCallback } from './calculateUniqueOrderId'
4-
import { getOrderToSign } from './getOrderToSign'
5-
import { type EthFlow, EthFlow__factory } from '../common/generated'
6-
import {
7-
BARN_ETH_FLOW_ADDRESSES,
8-
CowEnv,
9-
ETH_FLOW_ADDRESSES,
10-
SupportedChainId,
11-
WRAPPED_NATIVE_CURRENCIES,
12-
} from '../common'
13-
import { GAS_LIMIT_DEFAULT, log } from './consts'
14-
import type { EthFlowOrder } from '../common/generated/EthFlow'
3+
import { EthFlowOrderExistsCallback } from './calculateUniqueOrderId'
4+
5+
import { log } from './consts'
156
import { OrderBookApi } from '../order-book'
7+
import { getEthFlowTransaction } from './getEthFlowTransaction'
168

179
export async function postOnChainTrade(
1810
orderBookApi: OrderBookApi,
@@ -22,72 +14,22 @@ export async function postOnChainTrade(
2214
networkCostsAmount = '0',
2315
checkEthFlowOrderExists?: EthFlowOrderExistsCallback
2416
): Promise<{ txHash: string; orderId: string }> {
25-
const chainId = orderBookApi.context.chainId
26-
27-
const params = {
28-
..._params,
29-
sellToken: WRAPPED_NATIVE_CURRENCIES[chainId],
30-
}
31-
const { quoteId } = params
3217
const { appDataKeccak256, fullAppData } = appData
3318

34-
const from = await signer.getAddress()
35-
36-
const contract = getEthFlowContract(chainId, signer, params.env)
37-
const orderToSign = getOrderToSign({ from, networkCostsAmount }, params, appDataKeccak256)
38-
const orderId = await calculateUniqueOrderId(chainId, orderToSign, checkEthFlowOrderExists, params.env)
39-
40-
const ethOrderParams: EthFlowOrder.DataStruct = {
41-
...orderToSign,
42-
quoteId,
43-
appData: appDataKeccak256,
44-
validTo: orderToSign.validTo.toString(),
45-
}
19+
const { orderId, transaction } = await getEthFlowTransaction(
20+
signer,
21+
appDataKeccak256,
22+
_params,
23+
networkCostsAmount,
24+
checkEthFlowOrderExists
25+
)
4626

4727
log('Uploading app-data')
4828
await orderBookApi.uploadAppData(appDataKeccak256, fullAppData)
4929

50-
log('Estimating on-chain order gas')
51-
const estimatedGas = await contract.estimateGas
52-
.createOrder(ethOrderParams, { value: orderToSign.sellAmount })
53-
.then((res) => BigInt(res.toHexString()))
54-
.catch((error) => {
55-
console.error(error)
56-
57-
return GAS_LIMIT_DEFAULT
58-
})
59-
6030
log('Sending on-chain order transaction')
61-
const txReceipt = await contract.createOrder(ethOrderParams, {
62-
value: orderToSign.sellAmount,
63-
gasLimit: calculateGasMargin(estimatedGas),
64-
})
31+
const txReceipt = await signer.sendTransaction(transaction)
6532

6633
log(`On-chain order transaction sent, txHash: ${txReceipt.hash}, order: ${orderId}`)
6734
return { txHash: txReceipt.hash, orderId }
6835
}
69-
70-
const ethFlowContractCache: Partial<Record<SupportedChainId, EthFlow | undefined>> = {}
71-
72-
function getEthFlowContract(chainId: SupportedChainId, signer: Signer, env?: CowEnv): EthFlow {
73-
const cache = ethFlowContractCache[chainId]
74-
75-
if (cache) return cache
76-
77-
const contract = EthFlow__factory.connect(
78-
(env === 'staging' ? BARN_ETH_FLOW_ADDRESSES : ETH_FLOW_ADDRESSES)[chainId],
79-
signer
80-
)
81-
82-
ethFlowContractCache[chainId] = contract
83-
84-
return contract
85-
}
86-
87-
/**
88-
* Returns the gas value plus a margin for unexpected or variable gas costs (20%)
89-
* @param value the gas value to pad
90-
*/
91-
function calculateGasMargin(value: bigint): bigint {
92-
return value + (value * BigInt(20)) / BigInt(100)
93-
}

src/trading/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,10 @@ export interface AppDataInfo {
111111
appDataKeccak256: string
112112
env?: CowEnv
113113
}
114+
115+
export interface TransactionParams {
116+
callData: string
117+
gasLimit: string
118+
to: string
119+
value: string
120+
}

0 commit comments

Comments
 (0)