Manage Orders

This guide shows you how to:

  • Create an order.

  • Place an order.

  • Cancel an order.

  • Query orders by subaccount.

If you inspect the underlying types for these transactions, you'll notice that a "nonce" field is required. This is a unique integer in ascending order. Our off-chain engine has a nonce query to return the latest nonce for a given subaccount. All this is abstracted away within the SDK, so you do not need to manually use this query.

Import the functions

import { Wallet } from 'ethers';
import { getVertexClient, prettyPrintJson } from "./common";
import { OrderActionParams } from "@vertex-protocol/client";
import { nowInSeconds, toFixedPoint } from "@vertex-protocol/utils";
import { getExpirationTimestamp } from "@vertex-protocol/contracts";

Scaffold Your Subaccount

To place orders, we need a subaccount with funds. We need to perform the deposit funds step as before.

const vertexClient = await getVertexClient();

const address = await (
    vertexClient.context.signerOrProvider as Wallet
  ).getAddress();
const subaccountName = "default";
const depositAmount = 10 ** 6;

let tx = await vertexClient.spot._mintMockERC20({
  productId: 0,
  amount: depositAmount,
});
await tx.wait();

tx = await vertexClient.spot.approveAllowance({
  productId: 0,
  amount: depositAmount,
});
await tx.wait();

tx = await vertexClient.spot.deposit({
  productId: 0,
  amount: depositAmount,
  subaccountName: "default",
});

await tx.wait();
await new Promise((resolve) => setTimeout(resolve, 10000));

Create an order

An order requires a number of parameters, encapsulated by the OrderActionParams['order'] type.

In the example below:

  • The order expiration time is given by calling the nowInSeconds function from the utils package and adding 60 seconds. This means the order will expire 60 seconds from now.

    • Notice the usage of getExpirationTimestamp, order types, such as IOC (immediate or cancel) are encoded in the expiration field, the getExpirationTimestamp function will return the correct expiration timestamp for the given order type.

  • The price field is set at 20000 - a low value (at the time of writing) to prevent execution. This enables us to cancel the order later on without it being instantly filled. Please adjust this price accordingly.

  • The amount field is set at 10**16 - this is the amount to buy/sell. A positive value is to buy, negative is to sell.

    • Amount is normalized to 18 decimal places, which is what toFixedPoint does by default.

    • NOTE: Min size for BTC is 10**16 and for ETH is 10**17. Orders below these sizes will fail to be placed.

const orderParams: OrderActionParams["order"] = {
  subaccountName: "default",
  expiration: getExpirationTimestamp("default", nowInSeconds() + 60).toString(),
  price: 20000,
  // setting amount to 10**16
  amount: toFixedPoint(0.01, 18).toString(),
};

Place the order

Use the order parameters to place the order with the placeOrder function.

const placeOrderResult = await vertexClient.market.placeOrder({
  order: orderParams,
  productId: 1,
});

prettyPrintJson("Place Order Result", placeOrderResult);

Create an order tx

Alternatively, you can create an order tx and interface with lower level methods to place the order. The order parameters are encapsulated by OrderParams.

import { OrderParams, getOrderNonce } from "@vertex-protocol/contracts";

const orderNonce = getOrderNonce();

const orderParamsWithNonce: OrderParams = {
  subaccountName,
  subaccountOwner: address,
  expiration: getExpirationTimestamp("default", nowInSeconds() + 60).toString(),
  price: 20000,
  amount: toFixedPoint(0.01).toString(),
  nonce: orderNonce,
};

Order digest

You can optionally generate the order digest, which can then be used to further manage the order e.g: cancelling the order.

import { getOrderDigest } from "@vertex-protocol/contracts";
import { getChainIdFromSigner } from '@vertex-protocol/contracts';

const productId = 1;

// Get chain ID
const chainId = getChainIdFromSigner(vertexClient.context.signerOrProvider as Wallet)

// Get Orderbook address
const orderbookAddress =
  await vertexClient.context.contracts.clearinghouse.getOrderbook(productId);

const orderDigest =getOrderDigest({
  chainId,
  order: orderParamsWithNonce,
  orderbookAddress,
});

Execute order tx

Now, you can place the order by signing and executing the generated order tx.

import {
  getExpirationTimestamp,
  getOrderDigest,
  getVertexEIP712Values,
} from "@vertex-protocol/contracts";
import { hexlify } from "ethers/lib/utils";

// orderParamsWithNonce from previous step

const order = getVertexEIP712Values("place_order", orderParamsWithNonce);

const signature = await vertexClient.context.engineClient.sign(
  "place_order",
  orderbookAddress,
  chainId,
  orderParamsWithNonce
);

const placeOrderWithNonceResult =
  await vertexClient.context.engineClient.execute("place_order", {
    product_id: 1,
    order: {
      ...order,
      sender: hexlify(order.sender),
    },
    signature,
    spot_leverage: null,
  });

Query orders on the subaccount

Now we can query the subaccount for open orders with the getOpenSubaccountOrders function.

const address = await (
    vertexClient.context.signerOrProvider as Wallet
  ).getAddress();
const openOrders = await vertexClient.market.getOpenSubaccountOrders({
  subaccountOwner: address,
  subaccountName: "default",
  productId: 1,
});

prettyPrintJson("Subaccount Open Orders", openOrders);

Cancel order

Cancel the order using the digest of the placed order. You can cancel multiple orders at once.

const cancelOrderResult = await vertexClient.market.cancelOrder({
  digests: [placeOrderResult.digest],
  productIds: [1],
  subaccountName: "default",
});

prettyPrintJson("Cancel Order Result", cancelOrderResult);

Query Orders to Verify Cancellation

Run query orders on the subaccount again to make sure the cancellation was successful.

Clean up

Finally, clean up by withdrawing the same amount.

await vertexClient.spot.withdraw({
  productId: 0,
  amount: depositAmount - 10 ** 5,
  subaccountName: "default",
});

Full example

import { Wallet } from 'ethers';
import { getVertexClient, prettyPrintJson } from "./common";
import { OrderActionParams } from "@vertex-protocol/client";
import { nowInSeconds } from "@vertex-protocol/utils";
import { getExpirationTimestamp } from "@vertex-protocol/contracts";

async function main() {
  const vertexClient = await getVertexClient();

  const address = await (
    vertexClient.context.signerOrProvider as Wallet
  ).getAddress();
  const subaccountName = "default";
  const depositAmount = 10 ** 6;

  // SETUP - skipping logging here as it's in depositWithdraw
  let tx = await vertexClient.spot._mintMockERC20({
    productId: 0,
    amount: depositAmount,
  });
  await tx.wait();
  tx = await vertexClient.spot.approveAllowance({
    productId: 0,
    amount: depositAmount,
  });
  await tx.wait();
  tx = await vertexClient.spot.deposit({
    productId: 0,
    amount: depositAmount,
    subaccountName,
  });
  await tx.wait();
  await new Promise((resolve) => setTimeout(resolve, 10000));

  // Create the order
  const orderParams: OrderActionParams["order"] = {
    subaccountName,
    // `nowInSeconds` is exposed by the `@vertex-protocol/utils` package
    // This gives 60s before the order expires
    // Currently, IOC is also supported as an expiration type
    expiration: getExpirationTimestamp("default", nowInSeconds() + 60).toString(),
    // Limit price
    // Set this to a low amount so that the order does not fill immediately, this might need adjustment based on the product & current price
    price: 20000,
    // Positive amount for buys, negative for sells
    amount: 1,
  };

  // Place the order
  const placeOrderResult = await vertexClient.market.placeOrder({
    order: orderParams,
    productId: 1,
  });
  prettyPrintJson("Place Order Result", placeOrderResult);

  // Now query orders for this subaccount
  const openOrders = await vertexClient.market.getOpenSubaccountOrders({
    subaccountOwner: address,
    subaccountName,
    productId: 1,
  });
  prettyPrintJson("Subaccount Open Orders", openOrders);

  // Now cancel the order by its digest, you can cancel multiple at once
  const cancelOrderResult = await vertexClient.market.cancelOrder({
    digests: [placeOrderResult.digest],
    productIds: [1],
    subaccountName,
  });
  prettyPrintJson("Cancel Order Result", cancelOrderResult);

  // Now query orders after cancellation
  const openOrdersAfterCancel =
    await vertexClient.market.getOpenSubaccountOrders({
      subaccountOwner: address,
      subaccountName,
      productId: 1,
    });
  prettyPrintJson("Subaccount Open Orders After Cancel", openOrdersAfterCancel);

  // Cleanup by withdrawing
  await vertexClient.spot.withdraw({
    productId: 0,
    amount: depositAmount - 10 ** 5,
    subaccountName,
  });
}

main();

Last updated