UNPKG

@sangaman/xud

Version:
350 lines 15.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) t[p[i]] = s[p[i]]; return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const v1_1 = __importDefault(require("uuid/v1")); const events_1 = require("events"); const OrderBookRepository_1 = __importDefault(require("./OrderBookRepository")); const MatchingEngine_1 = __importDefault(require("./MatchingEngine")); const MatchesProcessor_1 = __importDefault(require("./MatchesProcessor")); const errors_1 = __importDefault(require("./errors")); const types_1 = require("../types"); const utils_1 = require("../utils/utils"); /** A class representing an orderbook containing all orders for all active trading pairs. */ class OrderBook extends events_1.EventEmitter { constructor(logger, models, pool, lndClient, raidenClient) { super(); this.logger = logger; this.pool = pool; this.lndClient = lndClient; this.raidenClient = raidenClient; /** An array of supported pair instances for the orderbook. */ this.pairs = []; /** An array of supported pair ids for the orderbook. */ this.pairIds = []; /** A map between active trading pair ids and matching engines. */ this.matchingEngines = new Map(); /** A map between active trading pair ids and local buy and sell orders. */ this.ownOrders = new Map(); /** A map between active trading pair ids and peer buy and sell orders. */ this.peerOrders = new Map(); /** A map between an order's local id and its global id. */ this.localIdMap = new Map(); this.swapHandler = (order) => { if (order.quantity === 0) { // full order execution if (types_1.orders.isPeerOrder(order)) { this.removeOrder(this.peerOrders, order.id, order.pairId); } else { this.removeOwnOrder(order.pairId, order.id); } } else { // TODO: partial order execution, update existing order } }; this.init = () => __awaiter(this, void 0, void 0, function* () { this.pairs = yield this.repository.getPairs(); this.pairs.forEach((pair) => { this.pairIds.push(pair.id); this.matchingEngines.set(pair.id, new MatchingEngine_1.default(this.logger, pair.id)); this.ownOrders.set(pair.id, this.initOrders()); this.peerOrders.set(pair.id, this.initOrders()); }); }); this.initOrders = () => { return { buyOrders: new Map(), sellOrders: new Map(), }; }; /** * Get lists of buy and sell orders of peers. */ this.getPeerOrders = (pairId, maxResults) => { return this.getOrders(pairId, maxResults, this.peerOrders); }; /* * Get lists of this node's own buy and sell orders. */ this.getOwnOrders = (pairId, maxResults) => { return this.getOrders(pairId, maxResults, this.ownOrders); }; this.getOrders = (pairId, maxResults, ordersMap) => { const orders = ordersMap.get(pairId); if (!orders) { throw errors_1.default.INVALID_PAIR_ID(pairId); } if (maxResults > 0) { return { buyOrders: Array.from(orders.buyOrders.values()).slice(0, maxResults), sellOrders: Array.from(orders.sellOrders.values()).slice(0, maxResults), }; } else { return { buyOrders: Array.from(orders.buyOrders.values()), sellOrders: Array.from(orders.sellOrders.values()), }; } }; this.addLimitOrder = (order) => { return this.addOwnOrder(order); }; this.addMarketOrder = (order) => { const price = order.quantity > 0 ? Number.MAX_VALUE : 0; return this.addOwnOrder(Object.assign({}, order, { price }), true); }; this.removeOwnOrderByLocalId = (pairId, localId) => { const id = this.localIdMap.get(localId); if (id === undefined) { return { removed: false, globalId: id }; } else { this.localIdMap.delete(localId); return { removed: this.removeOwnOrder(pairId, id), globalId: id, }; } }; this.removeOwnOrder = (pairId, orderId) => { const matchingEngine = this.matchingEngines.get(pairId); if (!matchingEngine) { this.logger.warn(`Invalid pairId: ${pairId}`); return false; } if (matchingEngine.removeOwnOrder(orderId)) { this.logger.debug(`order removed: ${JSON.stringify(orderId)}`); return this.removeOrder(this.ownOrders, orderId, pairId); } else { return false; } }; this.removePeerOrder = (orderId, pairId, quantityToDecrease) => { const matchingEngine = this.matchingEngines.get(pairId); const ordersMap = this.peerOrders.get(pairId); if (!matchingEngine || !ordersMap) { this.logger.warn(`Invalid pairId: ${pairId}`); return false; } const order = matchingEngine.removePeerOrder(orderId, quantityToDecrease); if (order) { let result; if (!quantityToDecrease || quantityToDecrease === 0) { result = this.removeOrder(this.peerOrders, orderId, pairId); } else { result = this.updateOrderQuantity(order, quantityToDecrease); } if (result) { this.emit('peerOrder.invalidation', { orderId, pairId, quantity: quantityToDecrease }); return true; } } this.logger.warn(`Invalid orderId: ${orderId}`); return false; }; this.addOwnOrder = (order, discardRemaining = false) => { if (this.localIdMap.has(order.localId)) { throw errors_1.default.DUPLICATE_ORDER(order.localId); } const matchingEngine = this.matchingEngines.get(order.pairId); if (!matchingEngine) { throw errors_1.default.INVALID_PAIR_ID(order.pairId); } const stampedOrder = Object.assign({}, order, { id: v1_1.default(), createdAt: utils_1.ms() }); const matchingResult = matchingEngine.matchOrAddOwnOrder(stampedOrder, discardRemaining); const { matches, remainingOrder } = matchingResult; if (matches.length > 0) { matches.forEach(({ maker, taker }) => { this.handleMatch({ maker, taker }); this.updateOrderQuantity(maker, maker.quantity); }); } if (remainingOrder && !discardRemaining) { this.broadcastOrder(remainingOrder); this.addOrder(this.ownOrders, remainingOrder); this.logger.debug(`order added: ${JSON.stringify(remainingOrder)}`); } return matchingResult; }; /** * Add peer order * @returns false if it's a duplicated order or with an invalid pair id, otherwise true */ this.addPeerOrder = (order) => { const matchingEngine = this.matchingEngines.get(order.pairId); if (!matchingEngine) { this.logger.debug(`incoming peer order invalid pairId: ${order.pairId}`); // TODO: penalize peer return false; } const stampedOrder = Object.assign({}, order, { createdAt: utils_1.ms() }); if (!this.addOrder(this.peerOrders, stampedOrder)) { this.logger.debug(`incoming peer order is duplicated: ${order.id}`); // TODO: penalize peer return false; } matchingEngine.addPeerOrder(stampedOrder); this.logger.debug(`order added: ${JSON.stringify(stampedOrder)}`); this.emit('peerOrder.incoming', stampedOrder); return true; }; this.removePeerOrders = (peer) => __awaiter(this, void 0, void 0, function* () { this.matchingEngines.forEach((matchingEngine) => { const orders = matchingEngine.removePeerOrders(peer.nodePubKey); orders.forEach((order) => { this.removeOrder(this.peerOrders, order.id, order.pairId); this.emit('peerOrder.invalidation', { orderId: order.id, pairId: order.pairId, }); }); }); }); this.updateOrderQuantity = (order, quantityToDecrease) => { const isOwnOrder = types_1.orders.isOwnOrder(order); const orderMap = this.getOrderMap(isOwnOrder ? this.ownOrders : this.peerOrders, order); const orderToUpdate = orderMap.get(order.id); if (!orderToUpdate) { return false; } orderToUpdate.quantity -= quantityToDecrease; if (orderToUpdate.quantity === 0) { if (isOwnOrder) { const { localId } = order; this.localIdMap.delete(localId); } orderMap.delete(order.id); } return true; }; /** * Add an order to an order map * @returns false if an order with the same id already exist, otherwise true */ this.addOrder = (ordersMap, order) => { if (this.isOwnOrdersMap(ordersMap)) { const { localId } = order; this.localIdMap.set(localId, order.id); } const orderMap = this.getOrderMap(ordersMap, order); if (orderMap.has(order.id)) { return false; } else { orderMap.set(order.id, order); return true; } }; this.removeOrder = (ordersMap, orderId, pairId) => { const orders = ordersMap.get(pairId); if (!orders) { throw errors_1.default.INVALID_PAIR_ID(pairId); } if (orders.buyOrders.has(orderId)) { orders.buyOrders.delete(orderId); return true; } else if (orders.sellOrders.has(orderId)) { orders.sellOrders.delete(orderId); return true; } return false; }; this.getOrderMap = (ordersMap, order) => { const orders = ordersMap.get(order.pairId); if (!orders) { throw errors_1.default.INVALID_PAIR_ID(order.pairId); } if (order.quantity > 0) { return orders.buyOrders; } else { return orders.sellOrders; } }; this.isOwnOrdersMap = (ordersMap) => { return ordersMap === this.ownOrders; }; /** * Send all local orders to a given peer in an [[OrdersPacket]. * @param reqId the request id of a [[GetOrdersPacket]] packet that this method is responding to */ this.sendOrders = (peer, reqId) => __awaiter(this, void 0, void 0, function* () { // TODO: just send supported pairs const outgoingOrders = []; this.pairIds.forEach((pairId) => { const orders = this.getOwnOrders(pairId, 0); orders['buyOrders'].forEach(order => outgoingOrders.push(this.createOutgoingOrder(order))); orders['sellOrders'].forEach(order => outgoingOrders.push(this.createOutgoingOrder(order))); }); peer.sendOrders(outgoingOrders, reqId); }); /** * Create an outgoing order and broadcast it to all peers. */ this.broadcastOrder = (order) => { if (this.pool) { const outgoingOrder = this.createOutgoingOrder(order); if (outgoingOrder) { this.pool.broadcastOrder(outgoingOrder); } } }; this.createOutgoingOrder = (order) => { // TODO: Remove functionality of attaching invoices to orders per new swap approach. const invoice = 'dummyInvoice'; // temporarily testing invoices while lnd is not available const _a = Object.assign({}, order, { invoice }), { createdAt, localId } = _a, outgoingOrder = __rest(_a, ["createdAt", "localId"]); return outgoingOrder; }; this.handleMatch = (match) => { this.logger.debug(`order match: ${JSON.stringify(match)}`); if (this.pool) { const { maker } = match; if (types_1.orders.isOwnOrder(maker)) { this.pool.broadcastOrderInvalidation({ orderId: maker.id, pairId: maker.pairId, quantity: maker.quantity, }); } } this.matchesProcessor.add(match); }; this.matchesProcessor = new MatchesProcessor_1.default(logger, pool, raidenClient); this.repository = new OrderBookRepository_1.default(logger, models); if (pool) { pool.on('packet.order', this.addPeerOrder); pool.on('packet.orderInvalidation', order => this.removePeerOrder(order.orderId, order.pairId, order.quantity)); pool.on('packet.getOrders', this.sendOrders); pool.on('peer.close', this.removePeerOrders); } if (raidenClient) { raidenClient.on('swap', this.swapHandler); } } } exports.default = OrderBook; //# sourceMappingURL=OrderBook.js.map