Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS] [CLOB-895] short term place orders for TS #39

Merged
merged 5 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions v4-client-js/examples/composite_example.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Order_TimeInForce } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/order';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the correct Order_TIF we want to be using as a a parameter? Note that I took out everything related to OrderExecution too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a translation layer in example to translate orderExecution string to Order_TimeInForce


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of modifying composite_examples.ts, can you copy it to a separate composite_short_term_examples.ts with the new code? We still need this composite_examples.ts to test regular placeOrder for FE.

import { BECH32_PREFIX } from '../src';
import { CompositeClient } from '../src/clients/composite-client';
import {
Network, OrderExecution, OrderSide, OrderTimeInForce, OrderType,
Network, OrderSide,
} from '../src/clients/constants';
import LocalWallet from '../src/clients/modules/local-wallet';
import { Subaccount } from '../src/clients/subaccount';
Expand All @@ -23,25 +25,24 @@ async function test(): Promise<void> {
const subaccount = new Subaccount(wallet, 0);
for (const orderParams of ordersParams) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ordersParams come from human_readable_orders.json, which is designed to include all combinations for our FE. For testing short_term orders, let's create a new short_term_orders.json file for all the test cases. I think you can eliminate a bunch of them and make the test quicker.

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(

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 + 3;

const tx = await client.placeShortTermOrder(
subaccount,
'ETH-USD',
type,
side,
price,
0.01,
randomInt(100_000_000),
timeInForce,
timeInForceSeconds,
OrderExecution.DEFAULT,
postOnly,
Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
goodTilBlock,
false,
);
console.log('**Order Tx**');
Expand Down
152 changes: 151 additions & 1 deletion v4-client-js/src/clients/composite-client.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { EncodeObject } from '@cosmjs/proto-signing';
import { GasPrice, IndexedTx, StdFee } from '@cosmjs/stargate';
import { BroadcastTxAsyncResponse, BroadcastTxSyncResponse } from '@cosmjs/tendermint-rpc/build/tendermint37';
import { Order_ConditionType, Order_TimeInForce } from '@dydxprotocol/v4-proto/src/codegen/dydxprotocol/clob/order';
import Long from 'long';
import protobuf from 'protobufjs';

import { OrderFlags } from '../types';
import {
DYDX_DENOM,
GAS_PRICE,
Network, OrderExecution, OrderSide, OrderTimeInForce, OrderType,
Network, OrderExecution, OrderSide, OrderTimeInForce, OrderType, SHORT_BLOCK_WINDOW,
} from './constants';
import {
calculateQuantums,
Expand All @@ -21,6 +22,7 @@ import {
calculateConditionalOrderTriggerSubticks,
} from './helpers/chain-helpers';
import { IndexerClient } from './indexer-client';
import { UserError } from './lib/errors';
import LocalWallet from './modules/local-wallet';
import { Subaccount } from './subaccount';
import { ValidatorClient } from './validator-client';
Expand Down Expand Up @@ -157,6 +159,27 @@ export class CompositeClient {
return height + 3;
}

/**
* @description Calculate 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.
*/
private async validateGoodTilBlock(goodTilBlock: number): Promise<void> {
const height = await this.validatorClient.get.latestBlockHeight();
const nextValidBlockHeight = height + 1;
const lowerBound = nextValidBlockHeight;
const upperBound = nextValidBlockHeight + SHORT_BLOCK_WINDOW;
if (goodTilBlock < lowerBound || goodTilBlock > upperBound) {
throw new UserError(`Invalid Short-Term order GoodTilBlock.
Should be greater-than-or-equal-to ${lowerBound} and less-than-or-equal-to ${upperBound}.
Provided good til block: ${goodTilBlock}`);
}
}

/**
* @description Calculate the goodTilBlockTime value for a LONG_TERM order
* the calling function is responsible for creating the messages.
Expand All @@ -175,6 +198,60 @@ export class CompositeClient {
return Math.round(future.getTime() / 1000);
}

/**
* @description Place a short term order with human readable input.
*
* Use human readable form of input, including price and size
* The quantum and subticks are calculated and submitted
*
* @param subaccount The subaccount to place the order under
* @param marketId The market to place the order on
* @param side The side of the order to place
* @param price The price of the order to place
* @param size The size of the order to place
* @param clientId The client id of the order to place
* @param timeInForce The time in force of the order to place
* @param goodTilBlock The goodTilBlock of the order to place
* @param reduceOnly The reduceOnly of the order to place
*
*
* @throws UnexpectedClientError if a malformed response is returned with no GRPC error
* at any point.
* @returns The transaction hash.
*/
async placeShortTermOrder(
subaccount: Subaccount,
marketId: string,
side: OrderSide,
price: number,
size: number,
clientId: number,
goodTilBlock: number,
timeInForce: Order_TimeInForce,
reduceOnly: boolean,
): Promise<BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx> {
const msgs: Promise<EncodeObject[]> = new Promise((resolve) => {
const msg = this.placeShortTermOrderMessage(
subaccount,
marketId,
side,
price,
size,
clientId,
timeInForce,
goodTilBlock,
reduceOnly,
);
msg.then((it) => resolve([it])).catch((err) => {
console.log(err);
});
});
return this.send(
subaccount.wallet,
() => msgs,
true);
}

/**
* @description Place an order with human readable input.
*
Expand Down Expand Up @@ -341,6 +418,79 @@ export class CompositeClient {
);
}

/**
* @description Calculate and create the short term place order message
*
* Use human readable form of input, including price and size
* The quantum and subticks are calculated and submitted
*
* @param subaccount The subaccount to place the order under
* @param marketId The market to place the order on
* @param side The side of the order to place
* @param price The price of the order to place
* @param size The size of the order to place
* @param clientId The client id of the order to place
* @param timeInForce The time in force of the order to place
* @param goodTilBlock The goodTilBlock of the order to place
* @param reduceOnly The reduceOnly of the order to place
*
*
* @throws UnexpectedClientError if a malformed response is returned with no GRPC error
* at any point.
* @returns The message to be passed into the protocol
*/
private async placeShortTermOrderMessage(
subaccount: Subaccount,
marketId: string,
side: OrderSide,
price: number,
size: number,
clientId: number,
goodTilBlock: number,
timeInForce: Order_TimeInForce,
reduceOnly: boolean,
): Promise<EncodeObject> {
await this.validateGoodTilBlock(goodTilBlock);

const marketsResponse = await this.indexerClient.markets.getPerpetualMarkets(marketId);
const market = marketsResponse.markets[marketId];
const clobPairId = market.clobPairId;
const atomicResolution = market.atomicResolution;
const stepBaseQuantums = market.stepBaseQuantums;
const quantumConversionExponent = market.quantumConversionExponent;
const subticksPerTick = market.subticksPerTick;
const orderSide = calculateSide(side);
const quantums = calculateQuantums(
size,
atomicResolution,
stepBaseQuantums,
);
const subticks = calculateSubticks(
price,
atomicResolution,
quantumConversionExponent,
subticksPerTick,
);
const orderFlags = OrderFlags.SHORT_TERM;
return this.validatorClient.post.composer.composeMsgPlaceOrder(
subaccount.address,
subaccount.subaccountNumber,
clientId,
clobPairId,
orderFlags,
goodTilBlock,
0, // Short term orders use goodTilBlock.
orderSide,
quantums,
subticks,
timeInForce,
reduceOnly,
0, // Client metadata is 0 for short term orders.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: client metadata can be non-zero for short-term and stateful orders

Order_ConditionType.CONDITION_TYPE_UNSPECIFIED, // Short term orders cannot be conditional.
Long.fromInt(0), // Short term orders cannot be conditional.
);
}

/**
* @description Cancel an order with human readable input.
*
Expand Down
2 changes: 2 additions & 0 deletions v4-client-js/src/clients/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export const DEFAULT_API_TIMEOUT: number = 3_000;

export const MAX_MEMO_CHARACTERS: number = 256;

export const SHORT_BLOCK_WINDOW: number = 20;

// Querying
export const PAGE_REQUEST: PageRequest = {
key: new Uint8Array(),
Expand Down