From b4fcfe45a10c2a6af8edf4fcca528e56afd6c7b9 Mon Sep 17 00:00:00 2001 From: Nathaniel Parke Date: Thu, 12 Nov 2020 11:43:38 +0800 Subject: [PATCH] Finish adding routes --- src/exchange/api.ts | 86 +++++++++++++++-- src/routes.ts | 227 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 281 insertions(+), 32 deletions(-) diff --git a/src/exchange/api.ts b/src/exchange/api.ts index d45ed28..3908069 100644 --- a/src/exchange/api.ts +++ b/src/exchange/api.ts @@ -12,7 +12,8 @@ import { import { Coin, Dir, - Exchange, Fill, + Exchange, + Fill, L2OrderBook, MarketInfo, Order, @@ -743,6 +744,27 @@ export class SerumApi { ); } + async cancelByClientId( + orderId: string, + coin: Coin, + priceCurrency: Coin + ): Promise { + const { transaction, signers } = await this.makeCancelByClientIdTransaction( + orderId, + coin, + priceCurrency + ); + const txid = await this.sendTransaction( + transaction, + signers, + DEFAULT_TIMEOUT, + () => {} + ); + logger.debug( + `finished sending cancel transaction:\n${orderId}\ntxid ${txid}` + ); + } + async makeCancelByClientIdTransaction( orderId: string, coin: Coin, @@ -768,7 +790,8 @@ export class SerumApi { // Assume we sent with lowest sort open orders account account = accountsForMarket.sort(this.compareOpenOrdersAccounts)[0]; logger.debug( - `Did not find order (${orderId}) in open order accounts. Using ${account.publicKey.toBase58()} as account.` + `Did not find order (${orderId}) in open order accounts. + Using ${account.publicKey.toBase58()} as account.` ); } logger.info( @@ -792,6 +815,59 @@ export class SerumApi { }; } + async cancelByStandardOrderId( + orderId: string, + coin: Coin, + priceCurrency: Coin + ): Promise { + const { + transaction, + signers, + } = await this.makeCancelByStandardIdTransaction( + orderId, + coin, + priceCurrency + ); + const txid = await this._connection.sendTransaction(transaction, signers, { + skipPreflight: true, + }); + await this.awaitTransactionSignatureConfirmation(txid); + } + + async makeCancelByStandardIdTransaction( + orderId: string, + coin: Coin, + priceCurrency: Coin + ): Promise<{ transaction: Transaction; signers: Account[] }> { + const market = new Pair(coin, priceCurrency); + let order = this.getOrderFromOwnOrdersCache(orderId, market); + if (!order) { + this.getOwnOrders(coin, priceCurrency); + order = this.getOrderFromOwnOrdersCache(orderId, market); + if (!order) { + throw Error("Could not find order for cancellation."); + } + } + logger.info( + `Cancelling ${orderId} ${coin} ${priceCurrency} using orderId ${order.info.orderId}` + ); + + const serumMarket = await this.getMarketFromAddress( + this.getMarketAddress(coin, priceCurrency) + ); + const transaction = await serumMarket.makeCancelOrderTransaction( + this._connection, + this._publicKey, + order.info.toSerumOrder() + ); + transaction.add(serumMarket.makeMatchOrdersTransaction(5)); + const signers = [new Account(this._privateKey)]; + return { + transaction, + signers, + }; + } + getOrderFromOwnOrdersCache( orderId: string, market: Pair @@ -910,11 +986,7 @@ export class SerumApi { ).then((fills) => fills.reduce((acc, curr) => [...acc, ...curr])); } - parseRawFills( - rawFills: RawTrade[], - coin: Coin, - priceCurrency: Coin - ): Fill[] { + parseRawFills(rawFills: RawTrade[], coin: Coin, priceCurrency: Coin): Fill[] { const time = getUnixTs(); const parseFill = (rawFill): Fill => { return { diff --git a/src/routes.ts b/src/routes.ts index fef5b79..d37471c 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,7 +1,8 @@ import express from "express"; -import { SerumApi } from "./exchange/api"; import expressAsyncHandler from "express-async-handler"; -import { logger } from "./utils"; +import { DirUtil, logger } from "./utils"; +import { SerumApi } from "./exchange"; +import { Coin, OrderType } from "./exchange/types"; const router = express.Router(); let api: SerumApi; @@ -32,35 +33,211 @@ router.get( }) ); -router.get("/orderbook/:coin-:quote", expressAsyncHandler(async (req, res, next) => { - logger.info(`Received request to ${req.params.exchange} api getOrderbook`); - api - .getWsOrderBook(req.params.coin, req.params.quote) - .then((orderBook) => res.send({ status: "ok", data: orderBook })) - .catch((err) => next(err)); -})); +router.get( + "/orderbook/:coin-:quote", + expressAsyncHandler(async (req, res, next) => { + logger.info("Received request to api getOrderbook"); + api + .getWsOrderBook(req.params.coin, req.params.quote) + .then((orderBook) => res.send({ status: "ok", data: orderBook })) + .catch((err) => next(err)); + }) +); -router.get("/trades/:coin-:quote", expressAsyncHandler(async (req, res, next) => { - logger.info(`Received request to ${req.params.exchange} api trades`); - api - .getTrades(req.params.coin, req.params.quote) - .then((trades) => res.send({ status: "ok", data: trades })) - .catch((err) => { - logger.info(err); - next(err); - }); -})); +router.get( + "/trades/:coin-:quote", + expressAsyncHandler(async (req, res, next) => { + logger.info("Received request to api getTrades"); + api + .getTrades(req.params.coin, req.params.quote) + .then((trades) => res.send({ status: "ok", data: trades })) + .catch((err) => { + logger.info(err); + next(err); + }); + }) +); -router.get("/place_order", expressAsyncHandler(async (req, res, next) => {})); +router.post( + "/place_order", + expressAsyncHandler(async (req, res, next) => { + logger.info(`Order parameters ${JSON.stringify(req.body)}`); + api + .placeOrder( + DirUtil.parse(req.body.side), + req.body.coin, + req.body.priceCurrency, + req.body.quantity, + req.body.price, + OrderType[req.body.orderType], + { + clientId: req.body.clientId, + orderEdge: req.body.orderEdge, + } + ) + .then((id) => res.send({ status: "ok", data: { id: id } })) + .catch((err) => { + logger.log("error", `${req.params.exchange} make_order error: ${err}`); + try { + const body = { + status: "error", + data: { + errorType: err.name, + errorMessage: err.message || JSON.stringify(err), + stack: (err.stack && err.stack.toString()) || JSON.stringify(err), + }, + }; + res.send(body); + } catch (e) { + try { + const body = { + status: "error", + data: { errorMessage: err, name: undefined, stack: undefined }, + }; + res.send(body); + } catch (f) { + next(err); + } + } + }); + }) +); -router.get("/cancel/:orderId", expressAsyncHandler(async (req, res, next) => {})); +router.post( + "/cancel", + expressAsyncHandler(async (req, res, next) => { + const coin = req.body.coin; + const priceCurrency = req.body.priceCurrency; + const orderId = req.body.orderId; + const clientOrderId = req.body.clientOrderId; -router.get("/own_orders", expressAsyncHandler(async (req, res, next) => {})); + if (!coin) { + const body = { + status: "error", + data: { + errorMessage: "Coin parameter missing from cancel request", + }, + }; + res.send(body); + } else if (!priceCurrency) { + const body = { + status: "error", + data: { + errorMessage: "Price currency parameter missing from cancel request", + }, + }; + res.send(body); + } else if (!orderId && !clientOrderId) { + const body = { + status: "error", + data: { + errorMessage: + "Order id and client order id missing from cancel request", + }, + }; + res.send(body); + } -router.get("/fills", expressAsyncHandler(async (req, res, next) => {})); + let cancelFn: ( + orderId: string, + coin: Coin, + priceCurrency: Coin + ) => Promise; + if (clientOrderId) { + cancelFn = api.cancelByClientId; + } else { + cancelFn = api.cancelByStandardOrderId; + } + cancelFn(req.params.orderId, req.params.coin, req.params.priceCurrency) + .then((result) => res.send({ status: "ok", data: {} })) + .catch((err) => { + logger.error( + `${req.params.coin}/${req.params.priceCurrency} cancel + ${req.params.orderId} received error ${JSON.stringify(err)}` + ); + try { + const body = { + status: "error", + data: { + errorType: err.name, + errorMessage: err.message || JSON.stringify(err), + stack: (err.stack && err.stack.toString()) || JSON.stringify(err), + }, + }; + res.send(body); + } catch (e) { + try { + const body = { + status: "error", + data: { errorMessage: err, name: undefined, stack: undefined }, + }; + res.send(body); + } catch (f) { + next(err); + } + } + }); + }) +); -router.get("/balances", expressAsyncHandler(async (req, res, next) => {})); +router.get( + "/own_orders/:coin-:quote", + expressAsyncHandler(async (req, res, next) => { + api + .getOwnOrders(req.params.coin, req.params.priceCurrency) + .then((orders) => res.send({ status: "ok", data: { orders } })) + .catch((err) => { + logger.log( + "error", + `Call to own_orders encountered error ${err.name}: \n ${err.stack}` + ); + res.send({ + status: "error", + data: { + errorType: err.name, + errorMessage: err.message, + stack: err.stack.toString(), + }, + }); + }); + }) +); -router.get("/settle", expressAsyncHandler(async (req, res, next) => {})); +router.get( + "/fills/:coin-:quote", + expressAsyncHandler(async (req, res, next) => { + if ("coin" in req.query && "priceCurrency" in req.query) { + api + .getFills(req.params.coin, req.params.priceCurrency) + .then((fills) => res.send({ status: "ok", data: fills })) + .catch((err) => next(err)); + } else { + api + .getFills() + .then((fills) => res.send({ status: "ok", data: fills })) + .catch((err) => next(err)); + } + }) +); + +router.get( + "/balances", + expressAsyncHandler(async (req, res, next) => { + api + .getBalances() + .then((balances) => res.send({ status: "ok", data: balances })) + .catch((err) => next(err)); + }) +); + +router.post( + "/settle", + expressAsyncHandler(async (req, res, next) => { + api + .settleFunds(req.body.coin, req.body.priceCurrency) + .then((_) => res.send({ status: "ok", data: {} })) + .catch((err) => next(err)); + }) +); export { router as default };