Skip to content

Commit

Permalink
add batch cancel to v4-client-js
Browse files Browse the repository at this point in the history
  • Loading branch information
aforaleka committed Aug 26, 2024
1 parent 56d8c2d commit 0bea762
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 2 deletions.
130 changes: 130 additions & 0 deletions v4-client-js/examples/batch_cancel_orders_example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import _ from 'lodash';

import { BECH32_PREFIX, Order_TimeInForce } from '../src';
import { CompositeClient, OrderBatchWithMarketId } from '../src/clients/composite-client';
import { Network, OrderSide } from '../src/clients/constants';
import LocalWallet from '../src/clients/modules/local-wallet';
import { SubaccountInfo } from '../src/clients/subaccount';
import { randomInt, sleep } from '../src/lib/utils';
import { DYDX_TEST_MNEMONIC, MAX_CLIENT_ID } from './constants';

type OrderInfo = {
marketId: string;
clientId: number;
side: OrderSide;
price: number;
size: number;
};

const generateShortTermOrdersInfo = (): OrderInfo[] => [
{
marketId: 'ETH-USD',
clientId: randomInt(MAX_CLIENT_ID),
side: OrderSide.SELL,
price: 4000,
size: 0.01,
},
{
marketId: 'ETH-USD',
clientId: randomInt(MAX_CLIENT_ID),
side: OrderSide.SELL,
price: 4200,
size: 0.02,
},
{
marketId: 'BTC-USD',
clientId: randomInt(MAX_CLIENT_ID),
side: OrderSide.BUY,
price: 40000,
size: 0.01,
},
];

const generateBatchCancelShortTermOrders = (ordersInfo: OrderInfo[]): OrderBatchWithMarketId[] => {
const ordersGroupedByMarketIds = _.groupBy(ordersInfo, (info) => info.marketId);
return Object.keys(ordersGroupedByMarketIds).map((marketId) => ({
marketId,
clientIds: ordersGroupedByMarketIds[marketId].map((info) => info.clientId),
}));
};

async function test(): Promise<void> {
try {
const wallet = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX);
console.log('**Wallet**', wallet);

const network = Network.testnet();
const client = await CompositeClient.connect(network);
console.log('**Client**', client);

const subaccount = new SubaccountInfo(wallet, 0);
const currentBlock = await client.validatorClient.get.latestBlockHeight();
const goodTilBlock = currentBlock + 10;

const shortTermOrdersInfo = generateShortTermOrdersInfo();
await placeShortTermOrders(client, subaccount, shortTermOrdersInfo, goodTilBlock);
await sleep(5000);
await batchCancelOrders(client, subaccount, shortTermOrdersInfo, goodTilBlock);
} catch (error) {
console.error('**Test Failed**', error.message);
}
}

const placeShortTermOrders = async (
client: CompositeClient,
subaccount: SubaccountInfo,
shortTermOrdersInfo: OrderInfo[],
goodTilBlock: number,
): Promise<void> => {
const orderPromises = shortTermOrdersInfo.map(async (order) => {
try {
const tx = await client.placeShortTermOrder(
subaccount,
order.marketId,
order.side,
order.price,
order.size,
order.clientId,
goodTilBlock,
Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
false,
);
console.log('**Short Term Order Tx**', tx.hash);
} catch (error) {
console.error(
`**Short Term Order Failed for Market ${order.marketId}, Client ID ${order.clientId}**`,
error.message,
);
}
});

// Wait for all order placements to complete
await Promise.all(orderPromises);
}

const batchCancelOrders = async (
client: CompositeClient,
subaccount: SubaccountInfo,
shortTermOrdersInfo: OrderInfo[],
goodTilBlock: number,
): Promise<void> => {
const shortTermOrdersPayload = generateBatchCancelShortTermOrders(shortTermOrdersInfo);
try {
const tx = await client.batchCancelShortTermOrdersWithMarketId(
subaccount,
shortTermOrdersPayload,
goodTilBlock + 10,
);
console.log('**Batch Cancel Short Term Orders Tx**', tx);
} catch (error) {
console.error('**Batch Cancel Short Term Orders Failed**', error.message);
}
}

test()
.then(() => {
console.log('**Batch Cancel Test Completed Successfully**');
})
.catch((error) => {
console.error('**Batch Cancel Test Execution Error**', error.message);
});
57 changes: 56 additions & 1 deletion v4-client-js/src/clients/composite-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { UserError } from './lib/errors';
import { generateRegistry } from './lib/registry';
import LocalWallet from './modules/local-wallet';
import { SubaccountInfo } from './subaccount';
import { BroadcastMode } from './types';
import { BroadcastMode, OrderBatch } from './types';
import { ValidatorClient } from './validator-client';

// Required for encoding and decoding queries that are of type Long.
Expand All @@ -58,6 +58,12 @@ export interface MarketInfo {
subticksPerTick: number;
}

export interface OrderBatchWithMarketId {
marketId: string;
clobPairId?: number;
clientIds: number[];
}

export class CompositeClient {
public readonly network: Network;
public gasDenom: SelectedGasDenom = SelectedGasDenom.USDC;
Expand Down Expand Up @@ -696,6 +702,55 @@ export class CompositeClient {
);
}

/**
* @description Batch cancel short term orders using marketId to clobPairId translation.
*
* @param subaccount The subaccount to cancel the order from
* @param shortTermOrders The list of short term order batches to cancel with marketId
* @param goodTilBlock The goodTilBlock of the order to cancel
* @returns The transaction hash.
*/
async batchCancelShortTermOrdersWithMarketId(
subaccount: SubaccountInfo,
shortTermOrders: OrderBatchWithMarketId[],
goodTilBlock: number,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
const orderBatches = await Promise.all(
shortTermOrders.map(async ({ marketId, clobPairId, clientIds }) => ({
clobPairId: (
clobPairId ??
(await this.indexerClient.markets.getPerpetualMarkets(marketId)).markets[marketId]
).clobPairId,
clientIds }))
);

return this.validatorClient.post.batchCancelShortTermOrders(
subaccount,
orderBatches,
goodTilBlock,
);
}

/**
* @description Batch cancel short term orders using clobPairId.
*
* @param subaccount The subaccount to cancel the order from
* @param shortTermOrders The list of short term order batches to cancel with clobPairId
* @param goodTilBlock The goodTilBlock of the order to cancel
* @returns The transaction hash.
*/
async batchCancelShortTermOrdersWithClobPairId(
subaccount: SubaccountInfo,
shortTermOrders: OrderBatch[],
goodTilBlock: number,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
return this.validatorClient.post.batchCancelShortTermOrders(
subaccount,
shortTermOrders,
goodTilBlock,
);
}

/**
* @description Transfer from a subaccount to another subaccount
*
Expand Down
3 changes: 2 additions & 1 deletion v4-client-js/src/clients/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const TYPE_URL_MSG_SUBMIT_PROPOSAL = '/cosmos.gov.v1.MsgSubmitProposal';
// x/clob
export const TYPE_URL_MSG_PLACE_ORDER = '/dydxprotocol.clob.MsgPlaceOrder';
export const TYPE_URL_MSG_CANCEL_ORDER = '/dydxprotocol.clob.MsgCancelOrder';
export const TYPE_URL_BATCH_CANCEL = '/dydxprotocol.clob.MsgBatchCancel';
export const TYPE_URL_MSG_CREATE_CLOB_PAIR = '/dydxprotocol.clob.MsgCreateClobPair';
export const TYPE_URL_MSG_UPDATE_CLOB_PAIR = '/dydxprotocol.clob.MsgUpdateClobPair';

Expand All @@ -101,7 +102,7 @@ export const TYPE_URL_MSG_UNDELEGATE = '/cosmos.staking.v1beta1.MsgUndelegate';
export const TYPE_URL_MSG_WITHDRAW_DELEGATOR_REWARD =
'/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward';

// ------------ Chain Constants ------------
// ------------ Chain Constants ------------
// The following are same across different networks / deployments.
export const GOV_MODULE_ADDRESS = 'dydx10d07y265gmmuvt4z0w9aw880jnsr700jnmapky';
export const DELAYMSG_MODULE_ADDRESS = 'dydx1mkkvp26dngu6n8rmalaxyp3gwkjuzztq5zx6tr';
Expand Down
3 changes: 3 additions & 0 deletions v4-client-js/src/clients/lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
MsgCancelOrder,
MsgCreateClobPair,
MsgUpdateClobPair,
MsgBatchCancel,
} from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/tx';
import { MsgDelayMessage } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/delaymsg/tx';
import { MsgCreatePerpetual } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/perpetuals/tx';
Expand All @@ -26,6 +27,7 @@ import {
TYPE_URL_MSG_CREATE_TRANSFER,
TYPE_URL_MSG_WITHDRAW_FROM_SUBACCOUNT,
TYPE_URL_MSG_DEPOSIT_TO_SUBACCOUNT,
TYPE_URL_BATCH_CANCEL,
} from '../constants';

export const registry: ReadonlyArray<[string, GeneratedType]> = [];
Expand All @@ -34,6 +36,7 @@ export function generateRegistry(): Registry {
// clob
[TYPE_URL_MSG_PLACE_ORDER, MsgPlaceOrder as GeneratedType],
[TYPE_URL_MSG_CANCEL_ORDER, MsgCancelOrder as GeneratedType],
[TYPE_URL_BATCH_CANCEL, MsgBatchCancel as GeneratedType],
[TYPE_URL_MSG_CREATE_CLOB_PAIR, MsgCreateClobPair as GeneratedType],
[TYPE_URL_MSG_UPDATE_CLOB_PAIR, MsgUpdateClobPair as GeneratedType],

Expand Down
26 changes: 26 additions & 0 deletions v4-client-js/src/clients/modules/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
} from '@dydxprotocol/v4-proto/src/codegen/cosmos/staking/v1beta1/tx';
import { ClobPair_Status } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/clob_pair';
import {
MsgBatchCancel,
MsgCreateClobPair,
MsgUpdateClobPair,
OrderBatch,
} from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/tx';
import { MsgDelayMessage } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/delaymsg/tx';
import { PerpetualMarketType } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/perpetuals/perpetual';
Expand Down Expand Up @@ -40,6 +42,7 @@ import {
TYPE_URL_MSG_DELEGATE,
TYPE_URL_MSG_UNDELEGATE,
TYPE_URL_MSG_WITHDRAW_DELEGATOR_REWARD,
TYPE_URL_BATCH_CANCEL,
} from '../constants';
import { DenomConfig } from '../types';
import {
Expand Down Expand Up @@ -149,6 +152,29 @@ export class Composer {
};
}

public composeMsgBatchCancelShortTermOrders(
address: string,
subaccountNumber: number,
shortTermCancels: OrderBatch[],
goodTilBlock: number,
): EncodeObject {
const subaccountId: SubaccountId = {
owner: address,
number: subaccountNumber,
};

const msg: MsgBatchCancel = {
subaccountId,
shortTermCancels,
goodTilBlock,
};

return {
typeUrl: TYPE_URL_BATCH_CANCEL,
value: msg,
};
}

public composeMsgCreateClobPair(
clobId: number,
perpetualId: number,
Expand Down
40 changes: 40 additions & 0 deletions v4-client-js/src/clients/modules/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
MsgPlaceOrder,
MsgCancelOrder,
Order_ConditionType,
OrderBatch,
} from './proto-includes';

// Required for encoding and decoding queries that are of type Long.
Expand Down Expand Up @@ -520,6 +521,45 @@ export class Post {
);
}

async batchCancelShortTermOrders(
subaccount: SubaccountInfo,
shortTermOrders: OrderBatch[],
goodTilBlock: number,
broadcastMode?: BroadcastMode,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
const msg = await this.batchCancelShortTermOrdersMsg(
subaccount.address,
subaccount.subaccountNumber,
shortTermOrders,
goodTilBlock,
);
return this.send(
subaccount.wallet,
() => Promise.resolve([msg]),
true,
undefined,
undefined,
broadcastMode,
);
}

async batchCancelShortTermOrdersMsg(
address: string,
subaccountNumber: number,
shortTermOrders: OrderBatch[],
goodTilBlock: number,
): Promise<EncodeObject> {
return new Promise((resolve) => {
const msg = this.composer.composeMsgBatchCancelShortTermOrders(
address,
subaccountNumber,
shortTermOrders,
goodTilBlock,
);
resolve(msg);
});
}

async transfer(
subaccount: SubaccountInfo,
recipientAddress: string,
Expand Down

0 comments on commit 0bea762

Please sign in to comment.