Skip to content

Commit

Permalink
feat: cli for trading SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
shoom3301 committed Sep 5, 2024
1 parent 92c7d14 commit 4049712
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 7 deletions.
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
"prepublishOnly": "npm test && npm run lint",
"graphql:codegen": "graphql-codegen --config graphql-codegen.yml",
"swagger:codegen": " openapi --input https://raw.githubusercontent.com/cowprotocol/services/v2.272.1/crates/orderbook/openapi.yml --output src/order-book/generated --exportServices false --exportCore false",
"typechain:codegen": "typechain --target ethers-v5 --out-dir ./src/composable/generated './abi/*.json'"
"typechain:codegen": "typechain --target ethers-v5 --out-dir ./src/composable/generated './abi/*.json'",
"trading-cli": "ts-node --project tsconfig.scripts.json ./src/trading/cli/index.ts",
"test-trading-cli-py": "python src/trading/example.py"
},
"dependencies": {
"@cowprotocol/app-data": "^2.1.0",
Expand All @@ -55,6 +57,7 @@
"@typechain/ethers-v5": "^11.0.0",
"@types/jest": "^29.4.0",
"@types/node": "^18.13.0",
"@types/prompts": "^2.4.9",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"babel-plugin-inline-import": "^3.0.0",
Expand All @@ -66,13 +69,16 @@
"ethers": "^5.7.2",
"jest": "^29.6.4",
"jest-fetch-mock": "^3.0.3",
"kleur": "^4.1.5",
"microbundle": "^0.15.1",
"openapi-typescript-codegen": "^0.23.0",
"prettier": "^2.5.1",
"prompts": "^2.4.2",
"ts-mockito": "^2.6.1",
"tsc-watch": "^6.0.0",
"typechain": "^8.2.0",
"typescript": "^4.9.5"
"typescript": "^4.9.5",
"yargs": "^17.7.2"
},
"jest": {
"automock": false,
Expand Down
60 changes: 60 additions & 0 deletions src/trading/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,63 @@ console.log('Order created, id: ', orderId)
#### Limit order

Same as for the swap order but without the `quoteRequest` parameter.

## CLI

### Example

```shell
yarn run trading-cli --\
--action=getQuote \
--chainId=11155111 \
--signer=574ed8a3c3baf4ed2c2fe5225a3bee6999b6a5ab735d297aca9dfc6c3f540000 \
--appCode="UtilsCliExample" \
--orderKind="sell" \
--sellToken="0x0625afb445c3b6b7b929342a04a22599fd5dbb59" \
--sellTokenDecimals=18 \
--buyToken="0xbe72e441bf55620febc26715db68d3494213d8cb" \
--buyTokenDecimals=6 \
--amount=12222000000000000000000 \
--env="prod" \
--partiallyFillable=false \
--slippageBps=0 \
--receiver="" \
--validFor=300 \
--partnerFeeBps=0 \
--partnerFeeRecipient=""
```

### The CLI can be called from any other programming language

```py
from subprocess import check_output
import json

args = [
'--action=getQuote',
'--chainId=11155111',
'--signer=574ed8a3c3baf4ed2c2fe5225a3bee6999b6a5ab735d297aca9dfc6c3f540000',
'--appCode="UtilsCliExample"',
'--orderKind="sell"',
'--sellToken="0x0625afb445c3b6b7b929342a04a22599fd5dbb59"',
'--sellTokenDecimals=18',
'--buyToken="0xbe72e441bf55620febc26715db68d3494213d8cb"',
'--buyTokenDecimals=6',
'--amount=12222000000000000000000',
'--env="prod"',
'--partiallyFillable=false',
'--slippageBps=0',
'--receiver=""',
'--validFor=300',
'--partnerFeeBps=0',
'--partnerFeeRecipient=""'
]

rawResult = check_output(['npx', 'cow-sdk-trading-cli'] + args)
result = json.loads(rawResult)

print('orderToSign:', result['orderToSign'])
print('amountsAndCosts:', result['amountsAndCosts'])
print('quoteResponse:', result['quoteResponse'])

```
20 changes: 20 additions & 0 deletions src/trading/cli/actions/getQuoteAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import prompts from 'prompts'
import { swapParametersSchema } from '../consts'
import { SwapParameters } from '../../types'
import { getQuote } from '../../getQuote'
import kleur from 'kleur'
import { printJSON } from '../utils'

export async function getQuoteAction(hasArgv: boolean) {
const params = (await prompts(swapParametersSchema)) as SwapParameters

const quote = await getQuote(params)

if (hasArgv) {
console.log(printJSON(quote))
return
}

console.log(kleur.green().bold('Quote: '))
console.log(kleur.white().underline(printJSON(quote)))
}
19 changes: 19 additions & 0 deletions src/trading/cli/actions/postLimitOrderAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import prompts from 'prompts'
import { limitParametersSchema } from '../consts'
import { LimitOrderParameters } from '../../types'
import kleur from 'kleur'
import { postLimitOrder } from '../../postLimitOrder'

export async function postLimitOrderAction(hasArgv: boolean) {
const params = (await prompts(limitParametersSchema)) as LimitOrderParameters

const orderId = await postLimitOrder(params)

if (hasArgv) {
console.log(orderId)
return
}

console.log(kleur.green().bold('Order id: '))
console.log(kleur.white().underline(orderId))
}
19 changes: 19 additions & 0 deletions src/trading/cli/actions/postSwapOrderAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import prompts from 'prompts'
import { swapParametersSchema } from '../consts'
import { SwapParameters } from '../../types'
import kleur from 'kleur'
import { postSwapOrder } from '../../postSwapOrder'

export async function postSwapOrderAction(hasArgv: boolean) {
const params = (await prompts(swapParametersSchema)) as SwapParameters

const orderId = await postSwapOrder(params)

if (hasArgv) {
console.log(orderId)
return
}

console.log(kleur.green().bold('Order id: '))
console.log(kleur.white().underline(orderId))
}
165 changes: 165 additions & 0 deletions src/trading/cli/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { PromptObject } from 'prompts'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from '../../common'
import { OrderKind } from '../../order-book'

export const traderParametersSchema: PromptObject[] = [
{
type: 'select',
name: 'chainId',
message: 'Network',
choices: ALL_SUPPORTED_CHAIN_IDS.map((key) => ({
title: key.toString(),
value: SupportedChainId[key],
})),
initial: SupportedChainId.MAINNET,
},
{
type: 'password',
name: 'signer',
message: 'Signer`s private key',
},
{
type: 'text',
name: 'appCode',
message: 'Your app code (for analytics purposes)',
},
]

export const tradeBaseParametersSchema: PromptObject[] = [
{
type: 'select',
name: 'orderKind',
choices: [
{ title: 'Sell', value: OrderKind.SELL },
{ title: 'Buy', value: OrderKind.BUY },
],
message: 'Order kind',
},
{
type: 'text',
name: 'sellToken',
message: 'Sell token address',
},
{
type: 'number',
name: 'sellTokenDecimals',
message: 'Sell token decimals',
},
{
type: 'text',
name: 'buyToken',
message: 'Buy token address',
},
{
type: 'number',
name: 'buyTokenDecimals',
message: 'Buy token decimals',
},
{
type: 'text',
name: 'amount',
message: 'Amount to trade (in units)',
},
]

export const tradeOptionalParametersSchema: PromptObject[] = [
{
type: 'select',
name: 'env',
choices: [
{ title: 'Prod', value: 'prod' },
{ title: 'Staging', value: 'staging' },
],
message: 'Environment',
},
{
type: 'toggle',
name: 'partiallyFillable',
message: 'Is order partially fillable?',
initial: false,
},
{
type: 'number',
name: 'slippageBps',
message: 'Slippage in BPS',
initial: 0,
},
{
type: 'text',
name: 'receiver',
message: 'Receiver address',
initial: '',
},
{
type: 'number',
name: 'validFor',
message: 'Order time to life (in seconds)',
initial: 300,
format: (val) => +val,
},
{
type: 'number',
name: 'partnerFeeBps',
message: 'Partner fee percent (in BPS)',
initial: 0,
},
{
type: 'text',
name: 'partnerFeeRecipient',
message: 'Partner fee recipient address',
initial: '',
},
]

export const limitSpecificParametersSchema: PromptObject[] = [
{
type: 'text',
name: 'sellAmount',
message: 'Sell amount (in units)',
},
{
type: 'text',
name: 'buyAmount',
message: 'Buy amount (in units)',
},
]

export const orderToSignParametersSchema: PromptObject[] = [
{
type: 'text',
name: 'from',
message: 'Account address',
},
{
type: 'text',
name: 'networkCostsAmount',
message: 'Network costs amount (in units) (from quote result)',
},
]

export const actionsSchema: PromptObject[] = [
{
type: 'select',
name: 'action',
choices: [
{ title: 'Get quote', value: 'getQuote' },
{ title: 'Post swap order', value: 'postSwapOrder' },
{ title: 'Post limit order', value: 'postLimitOrder' },
{ title: 'Get order to sign', value: 'getOrderToSign' },
],
message: 'Choose action to do:',
},
]

export const swapParametersSchema = [
...traderParametersSchema,
...tradeBaseParametersSchema,
...tradeOptionalParametersSchema,
]

export const limitParametersSchema = [
...traderParametersSchema,
...tradeBaseParametersSchema.filter((p) => p.name !== 'amount'),
...limitSpecificParametersSchema,
...tradeOptionalParametersSchema,
]
41 changes: 41 additions & 0 deletions src/trading/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import yargvParser from 'yargs-parser'
import prompts from 'prompts'
import { actionsSchema, swapParametersSchema } from './consts'
import { getQuoteAction } from './actions/getQuoteAction'
import { toggleLog } from '../consts'
import { postSwapOrderAction } from './actions/postSwapOrderAction'
import { postLimitOrderAction } from './actions/postLimitOrderAction'

const promptSchemas = [swapParametersSchema]

// IIFE
;(async () => {
const argv = yargvParser(process.argv.slice(2), { configuration: { 'parse-numbers': false } })
const argvKeys = Object.keys(argv)
const hasArgv = promptSchemas.some((schema) =>
schema.map((i) => i.name as string).every((param) => argvKeys.includes(param))
)

prompts.override(argv)

if (hasArgv) {
toggleLog(false)
}

const actionsResult = await prompts(actionsSchema)

switch (actionsResult.action) {
case 'getQuote':
await getQuoteAction(hasArgv)
break
case 'postSwapOrder':
await postSwapOrderAction(hasArgv)
break
case 'postLimitOrder':
await postLimitOrderAction(hasArgv)
break
default:
console.log('Unknown action: ', actionsResult.action)
break
}
})()
3 changes: 3 additions & 0 deletions src/trading/cli/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function printJSON(data: any): string {
return JSON.stringify(data, (_, value) => (typeof value === 'bigint' ? value.toString() : value), 4)
}
12 changes: 11 additions & 1 deletion src/trading/consts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { EcdsaSigningScheme, SigningScheme } from '../order-book'

export const log = (text: string) => console.log(`[COW TRADING SDK] ${text}`)
let isLogEnabled = true

export const toggleLog = (enabled: boolean) => {
isLogEnabled = enabled
}

export const log = (text: string) => {
if (!isLogEnabled) return

console.log(`[COW TRADING SDK] ${text}`)
}

export const DEFAULT_QUOTE_VALIDITY = 60 * 10 // 10 min

Expand Down
Loading

0 comments on commit 4049712

Please sign in to comment.