Skip to content

Commit

Permalink
[TS] [CLOB-895] v0.38.2: Add cancel order examples (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonfung-dydx authored Sep 29, 2023
1 parent bfdd391 commit 6aca7bd
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 14 deletions.
4 changes: 2 additions & 2 deletions v4-client-js/examples/composite_example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import LocalWallet from '../src/clients/modules/local-wallet';
import { Subaccount } from '../src/clients/subaccount';
import { randomInt } from '../src/lib/utils';
import { DYDX_TEST_MNEMONIC } from './constants';
import { DYDX_TEST_MNEMONIC, MAX_CLIENT_ID } from './constants';
import ordersParams from './human_readable_orders.json';

async function sleep(ms: number): Promise<void> {
Expand Down Expand Up @@ -37,7 +37,7 @@ async function test(): Promise<void> {
side,
price,
0.01,
randomInt(100_000_000),
randomInt(MAX_CLIENT_ID),
timeInForce,
timeInForceSeconds,
OrderExecution.DEFAULT,
Expand Down
2 changes: 2 additions & 0 deletions v4-client-js/examples/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const PERPETUAL_PAIR_BTC_USD: number = 0;
const quantums: Long = new Long(1_000_000_000);
const subticks: Long = new Long(1_000_000_000);

export const MAX_CLIENT_ID = 2 ** 32 - 1;

// PlaceOrder variables
export const defaultOrder: IPlaceOrder = {
clientId: 0,
Expand Down
82 changes: 82 additions & 0 deletions v4-client-js/examples/long_term_order_cancel_example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { BECH32_PREFIX, OrderFlags } from '../src';
import { CompositeClient } from '../src/clients/composite-client';
import {
Network, OrderExecution, OrderSide, OrderTimeInForce, OrderType,
} from '../src/clients/constants';
import LocalWallet from '../src/clients/modules/local-wallet';
import { Subaccount } from '../src/clients/subaccount';
import { randomInt } from '../src/lib/utils';
import { DYDX_TEST_MNEMONIC, MAX_CLIENT_ID } from './constants';

async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function test(): Promise<void> {
const wallet = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX);
console.log(wallet);
const network = Network.staging();
const client = await CompositeClient.connect(network);
console.log('**Client**');
console.log(client);
const subaccount = new Subaccount(wallet, 0);

/*
Note this example places a stateful order.
Programmatic traders should generally not use stateful orders for following reasons:
- Stateful orders received out of order by validators will fail sequence number validation
and be dropped.
- Stateful orders have worse time priority since they are only matched after they are included
on the block.
- Stateful order rate limits are more restrictive than Short-Term orders, specifically max 2 per
block / 20 per 100 blocks.
- Stateful orders can only be canceled after they’ve been included in a block.
*/
const longTermOrderClientId = randomInt(MAX_CLIENT_ID);
try {
// place a long term order
const tx = await client.placeOrder(
subaccount,
'ETH-USD',
OrderType.LIMIT,
OrderSide.SELL,
40000,
0.01,
longTermOrderClientId,
OrderTimeInForce.GTT,
60,
OrderExecution.DEFAULT,
false,
false,
);
console.log('**Long Term Order Tx**');
console.log(tx.hash);
} catch (error) {
console.log('**Long Term Order Failed**');
console.log(error.message);
}

await sleep(5000); // wait for placeOrder to complete

try {
// cancel the long term order
const tx = await client.cancelOrder(
subaccount,
longTermOrderClientId,
OrderFlags.LONG_TERM,
'ETH-USD',
0,
120,
);
console.log('**Cancel Long Term Order Tx**');
console.log(tx);
} catch (error) {
console.log('**Cancel Long Term Order Failed**');
console.log(error.message);
}
}

test().then(() => {
}).catch((error) => {
console.log(error.message);
});
69 changes: 69 additions & 0 deletions v4-client-js/examples/short_term_order_cancel_example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { BECH32_PREFIX, OrderFlags, Order_TimeInForce } from '../src';
import { CompositeClient } from '../src/clients/composite-client';
import {
Network, OrderSide,
} from '../src/clients/constants';
import LocalWallet from '../src/clients/modules/local-wallet';
import { Subaccount } from '../src/clients/subaccount';
import { randomInt, sleep } from '../src/lib/utils';
import { DYDX_TEST_MNEMONIC, MAX_CLIENT_ID } from './constants';

async function test(): Promise<void> {
const wallet = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX);
console.log(wallet);
const network = Network.staging();
const client = await CompositeClient.connect(network);
console.log('**Client**');
console.log(client);
const subaccount = new Subaccount(wallet, 0);

const currentBlock = await client.validatorClient.get.latestBlockHeight();
const nextValidBlockHeight = currentBlock + 1;
// Note, you can change this to any number between `next_valid_block_height`
// to `next_valid_block_height + SHORT_BLOCK_WINDOW`
const goodTilBlock = nextValidBlockHeight + 10;
const shortTermOrderClientId = randomInt(MAX_CLIENT_ID);
try {
// place a short term order
const tx = await client.placeShortTermOrder(
subaccount,
'ETH-USD',
OrderSide.SELL,
40000,
0.01,
shortTermOrderClientId,
goodTilBlock,
Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
false,
);
console.log('**Short Term Order Tx**');
console.log(tx.hash);
} catch (error) {
console.log('**Short Term Order Failed**');
console.log(error.message);
}

await sleep(5000); // wait for placeOrder to complete

try {
// cancel the short term order
const tx = await client.cancelOrder(
subaccount,
shortTermOrderClientId,
OrderFlags.SHORT_TERM,
'ETH-USD',
goodTilBlock + 10,
0,
);
console.log('**Cancel Short Term Order Tx**');
console.log(tx);
} catch (error) {
console.log('**Cancel Short Term Order Failed**');
console.log(error.message);
}
}

test().then(() => {
}).catch((error) => {
console.log(error.message);
});
60 changes: 60 additions & 0 deletions v4-client-js/examples/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { BECH32_PREFIX } from '../src';
import { CompositeClient } from '../src/clients/composite-client';
import {
Network, OrderExecution, OrderSide, OrderTimeInForce, OrderType,
} from '../src/clients/constants';
import LocalWallet from '../src/clients/modules/local-wallet';
import { Subaccount } from '../src/clients/subaccount';
import { randomInt } from '../src/lib/utils';
import { DYDX_TEST_MNEMONIC, MAX_CLIENT_ID } from './constants';
import ordersParams from './human_readable_orders.json';

async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function test(): Promise<void> {
const wallet = await LocalWallet.fromMnemonic(DYDX_TEST_MNEMONIC, BECH32_PREFIX);
console.log(wallet);
const network = Network.staging();
const client = await CompositeClient.connect(network);
console.log('**Client**');
console.log(client);
const subaccount = new Subaccount(wallet, 0);
for (const orderParams of ordersParams) {
try {
const type = OrderType[orderParams.type as keyof typeof OrderType];
const side = OrderSide[orderParams.side as keyof typeof OrderSide];
const timeInForceString = orderParams.timeInForce ?? 'GTT';
const timeInForce = OrderTimeInForce[timeInForceString as keyof typeof OrderTimeInForce];
const price = orderParams.price ?? 1350;
const timeInForceSeconds = (timeInForce === OrderTimeInForce.GTT) ? 60 : 0;
const postOnly = orderParams.postOnly ?? false;
const tx = await client.placeOrder(
subaccount,
'ETH-USD',
type,
side,
price,
0.01,
randomInt(MAX_CLIENT_ID),
timeInForce,
timeInForceSeconds,
OrderExecution.DEFAULT,
postOnly,
false,
);
console.log('**Order Tx**');
console.log(tx);
} catch (error) {
console.log(error.message);
}

await sleep(5000); // wait for placeOrder to complete
}
}

test().then(() => {
}).catch((error) => {
console.log(error.message);
});
4 changes: 2 additions & 2 deletions v4-client-js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion v4-client-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dydxprotocol/v4-client-js",
"version": "0.38.1",
"version": "0.38.2",
"description": "General client library for the new dYdX system (v4 decentralized)",
"main": "build/src/index.js",
"scripts": {
Expand Down
47 changes: 39 additions & 8 deletions v4-client-js/src/clients/composite-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Order_ConditionType, Order_TimeInForce } from '@dydxprotocol/v4-proto/s
import Long from 'long';
import protobuf from 'protobufjs';

import { isStatefulOrder, verifyOrderFlags } from '../lib/validation';
import { OrderFlags } from '../types';
import {
DYDX_DENOM,
Expand Down Expand Up @@ -160,13 +161,13 @@ export class CompositeClient {
}

/**
* @description Calculate the goodTilBlock value for a SHORT_TERM order
* @description Validate the goodTilBlock value for a SHORT_TERM order
*
* @param goodTilBlock Number of blocks from the current block height the order will
* be valid for.
*
* @throws UnexpectedClientError if a malformed response is returned with no GRPC error
* at any point.
* @throws UserError if the goodTilBlock value is not valid given latest block height and
* SHORT_BLOCK_WINDOW.
*/
private async validateGoodTilBlock(goodTilBlock: number): Promise<void> {
const height = await this.validatorClient.get.latestBlockHeight();
Expand Down Expand Up @@ -238,8 +239,8 @@ export class CompositeClient {
price,
size,
clientId,
timeInForce,
goodTilBlock,
timeInForce,
reduceOnly,
);
msg.then((it) => resolve([it])).catch((err) => {
Expand Down Expand Up @@ -497,7 +498,7 @@ export class CompositeClient {
* @param subaccount The subaccount to cancel the order from
* @param clientId The client id of the order to cancel
* @param orderFlags The order flags of the order to cancel
* @param clobPairId The clob pair id of the order to cancel
* @param marketId The market to cancel the order on
* @param goodTilBlock The goodTilBlock of the order to cancel
* @param goodTilBlockTime The goodTilBlockTime of the order to cancel
*
Expand All @@ -509,10 +510,40 @@ export class CompositeClient {
subaccount: Subaccount,
clientId: number,
orderFlags: OrderFlags,
clobPairId: number,
goodTilBlock?: number,
goodTilBlockTime?: number,
marketId: string,
goodTilBlock: number,
goodTilTimeInSeconds: number,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {

const marketsResponse = await this.indexerClient.markets.getPerpetualMarkets(marketId);
const market = marketsResponse.markets[marketId];
const clobPairId = market.clobPairId;

if (!verifyOrderFlags(orderFlags)) {
throw new Error(`Invalid order flags: ${orderFlags}`);
}

let goodTilBlockTime;
if (isStatefulOrder(orderFlags)) {
if (goodTilTimeInSeconds === 0) {
throw new Error('goodTilTimeInSeconds must be set for LONG_TERM or CONDITIONAL order');
}
if (goodTilBlock !== 0) {
throw new Error(
'goodTilBlock should be zero since LONG_TERM or CONDITIONAL orders ' +
'use goodTilTimeInSeconds instead of goodTilBlock.',
);
}
goodTilBlockTime = this.calculateGoodTilBlockTime(goodTilTimeInSeconds);
} else {
if (goodTilBlock === 0) {
throw new Error('goodTilBlock must be non-zero for SHORT_TERM orders');
}
if (goodTilTimeInSeconds !== 0) {
throw new Error('goodTilTimeInSeconds should be zero since SHORT_TERM orders use goodTilBlock instead of goodTilTimeInSeconds.');
}
}

return this.validatorClient.post.cancelOrder(
subaccount,
clientId,
Expand Down
9 changes: 9 additions & 0 deletions v4-client-js/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,12 @@ export function clientIdFromString(
// We must coerce this into a 32-bit unsigned integer.
return hash + (2 ** 31);
}

/**
* Pauses the execution of the program for a specified time.
* @param ms - The number of milliseconds to pause the program.
* @returns A promise that resolves after the specified number of milliseconds.
*/
export async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
7 changes: 6 additions & 1 deletion v4-client-js/src/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ function verifyNumberIsUint32(num: number): boolean {
return num >= 0 && num <= MAX_UINT_32;
}

function isStatefulOrder(orderFlags: OrderFlags): boolean {
export function verifyOrderFlags(orderFlags: OrderFlags): boolean {
return orderFlags === OrderFlags.SHORT_TERM ||
orderFlags === OrderFlags.LONG_TERM || orderFlags === OrderFlags.CONDITIONAL;
}

export function isStatefulOrder(orderFlags: OrderFlags): boolean {
return orderFlags === OrderFlags.LONG_TERM || orderFlags === OrderFlags.CONDITIONAL;
}

Expand Down

0 comments on commit 6aca7bd

Please sign in to comment.