Skip to content

Commit

Permalink
feat: market order
Browse files Browse the repository at this point in the history
  • Loading branch information
graykode committed Apr 5, 2024
1 parent 36793f8 commit 41ea389
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 0 deletions.
98 changes: 98 additions & 0 deletions src/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,101 @@ export const limitOrder = async (
)
}
}

/**
* Executes a market order on the specified chain for trading tokens.
*
* @param {CHAIN_IDS} chainId The chain ID.
* @param {`0x${string}`} userAddress The Ethereum address of the user placing the order.
* @param {`0x${string}`} inputToken The address of the token to be used as input.
* @param {`0x${string}`} outputToken The address of the token to be received as output.
* @param {string} amount The amount of input tokens for the order.
* @param {Object} [options] Optional parameters for the market order.
* @param {PermitSignature} [options.signature] The permit signature for token approval.
* @param {string} [options.rpcUrl] The RPC URL to use for executing the order.
* @returns {Promise<void>} Promise resolving once the market order is executed.
*/

export const marketOrder = async (
chainId: CHAIN_IDS,
userAddress: `0x${string}`,
inputToken: `0x${string}`,
outputToken: `0x${string}`,
amount: string,
options?: {
signature?: PermitSignature
rpcUrl?: string
},
): Promise<Transaction> => {
const { signature, rpcUrl } = options || {
signature: undefined,
rpcUrl: undefined,
}
const market = await fetchMarket(chainId, [inputToken, outputToken], rpcUrl)
const isBid = isAddressEqual(market.quote.address, inputToken)
if ((isBid && !market.bidBookOpen) || (!isBid && !market.askBookOpen)) {
throw new Error(`
import { openMarket } from '@clober-dex/v2-sdk'
const transaction = await openMarket(
${chainId},
'${inputToken}',
'${outputToken}',
)
`)
}

const tokensToSettle = [inputToken, outputToken].filter(
(address) => !isAddressEqual(address, zeroAddress),
)
const quoteAmount = parseUnits(
amount,
isBid ? market.quote.decimals : market.base.decimals,
)
const { result } = await getExpectedOutput(
chainId,
inputToken,
outputToken,
amount,
rpcUrl
? {
rpcUrl,
}
: {},
)
const isETH = isAddressEqual(inputToken, zeroAddress)
const permitParamsList =
signature && !isETH
? [
{
token: inputToken,
permitAmount: quoteAmount,
signature,
},
]
: []

return buildTransaction(
chainId,
{
chain: CHAIN_MAP[chainId],
account: userAddress,
address: CONTRACT_ADDRESSES[chainId]!.Controller,
abi: CONTROLLER_ABI,
functionName: 'take',
args: [
result.map(({ bookId, takenAmount }) => ({
id: bookId,
limitPrice: 0n,
quoteAmount: takenAmount,
hookData: zeroHash,
})),
tokensToSettle,
permitParamsList,
getDeadlineTimestampInSeconds(),
],
value: isETH ? quoteAmount : 0n,
},
options?.rpcUrl,
)
}
146 changes: 146 additions & 0 deletions test/market-order.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { afterEach, expect, test } from 'vitest'
import { marketOrder, signERC20Permit } from '@clober-dex/v2-sdk'
import { mnemonicToAccount } from 'viem/accounts'

import { cloberTestChain } from './utils/test-chain'
import { createProxyClients } from './utils/utils'
import { FORK_URL, TEST_MNEMONIC } from './utils/constants'
import { fetchTokenBalance } from './utils/currency'
import { fetchBlockNumer } from './utils/chain'
import { fetchAskDepth, fetchBidDepth } from './utils/depth'

const clients = createProxyClients([11, 12])
const account = mnemonicToAccount(TEST_MNEMONIC)

afterEach(async () => {
const blockNumber = await fetchBlockNumer()
await Promise.all(
clients.map(({ testClient }) => {
return testClient.reset({
jsonRpcUrl: FORK_URL,
blockNumber,
})
}),
)
})

test('market order in not open market', async () => {
const { publicClient } = clients[0]
expect(
await marketOrder(
cloberTestChain.id,
'0x447ad4a108b5540c220f9f7e83723ac87c0f8fd8',
'0x447ad4a108b5540c220f9f7e83723ac87c0f8fd8',
'0x0000000000000000000000000000000000000000',
'10',
{ rpcUrl: publicClient.transport.url! },
).catch((e) => e.message),
).toEqual(`
import { openMarket } from '@clober-dex/v2-sdk'
const transaction = await openMarket(
421614,
'0x447ad4a108b5540c220f9f7e83723ac87c0f8fd8',
'0x0000000000000000000000000000000000000000',
)
`)
})

test('market bid', async () => {
const { walletClient, publicClient } = clients[0]
const signature = await signERC20Permit(
cloberTestChain.id,
account,
'0x00bfd44e79fb7f6dd5887a9426c8ef85a0cd23e0',
'1000000',
{ rpcUrl: publicClient.transport.url! },
)
const transaction = await marketOrder(
cloberTestChain.id,
account.address,
'0x00bfd44e79fb7f6dd5887a9426c8ef85a0cd23e0',
'0x0000000000000000000000000000000000000000',
'1000000',
{ signature, rpcUrl: publicClient.transport.url! },
)

const [beforeUSDCBalance, beforeETHBalance, beforeAskDepth] =
await Promise.all([
fetchTokenBalance(
cloberTestChain.id,
'0x00bfd44e79fb7f6dd5887a9426c8ef85a0cd23e0',
account.address,
publicClient.transport.url!,
),
publicClient.getBalance({
address: account.address,
}),
fetchAskDepth(publicClient.transport.url!),
])

await walletClient.sendTransaction({ ...transaction!, account })

const [afterUSDCBalance, afterETHBalance, afterAskDepth] = await Promise.all([
fetchTokenBalance(
cloberTestChain.id,
'0x00bfd44e79fb7f6dd5887a9426c8ef85a0cd23e0',
account.address,
publicClient.transport.url!,
),
publicClient.getBalance({
address: account.address,
}),
fetchAskDepth(publicClient.transport.url!),
])

expect(Number(beforeUSDCBalance)).toBeGreaterThan(Number(afterUSDCBalance))
expect(Number(afterETHBalance)).toBeGreaterThan(Number(beforeETHBalance))
expect(beforeAskDepth.length).toBeGreaterThan(afterAskDepth.length)
expect(afterAskDepth.length).toBe(0)
})

test('market ask', async () => {
const { walletClient, publicClient } = clients[1]
const transaction = await marketOrder(
cloberTestChain.id,
account.address,
'0x0000000000000000000000000000000000000000',
'0x00bfd44e79fb7f6dd5887a9426c8ef85a0cd23e0',
'10',
{ rpcUrl: publicClient.transport.url! },
)

const [beforeUSDCBalance, beforeETHBalance, beforeBidDepth] =
await Promise.all([
fetchTokenBalance(
cloberTestChain.id,
'0x00bfd44e79fb7f6dd5887a9426c8ef85a0cd23e0',
account.address,
publicClient.transport.url!,
),
publicClient.getBalance({
address: account.address,
}),
fetchBidDepth(publicClient.transport.url!),
])

await walletClient.sendTransaction({ ...transaction!, account })

const [afterUSDCBalance, afterETHBalance, afterBidDepth] = await Promise.all([
fetchTokenBalance(
cloberTestChain.id,
'0x00bfd44e79fb7f6dd5887a9426c8ef85a0cd23e0',
account.address,
publicClient.transport.url!,
),
publicClient.getBalance({
address: account.address,
}),
fetchBidDepth(publicClient.transport.url!),
])

expect(Number(afterUSDCBalance)).toBeGreaterThan(Number(beforeUSDCBalance))
expect(Number(beforeETHBalance)).toBeGreaterThan(Number(afterETHBalance))
expect(beforeBidDepth.length).toBeGreaterThan(afterBidDepth.length)
expect(afterBidDepth.length).toBe(0)
})

0 comments on commit 41ea389

Please sign in to comment.