UNPKG

@devasher/kuru-sdk

Version:

Ethers v6 SDK to interact with Kuru (forked from @kuru-labs/kuru-sdk)

650 lines 38.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OrderBook = void 0; // ============ External Imports ============ const ethers_1 = require("ethers"); const utils_1 = require("../utils"); // ============ Config Imports ============ const OrderBook_json_1 = __importDefault(require("../../abi/OrderBook.json")); class OrderBook { /** * @dev Retrieves the Level 2 order book data from the order book contract. * @param providerOrSigner - The ethers.js provider to interact with the blockchain. * @param orderbookAddress - The address of the order book contract. * @param marketParams - The market parameters including price and size precision. * @returns A promise that resolves to the order book data. */ static async getL2OrderBook(providerOrSigner, orderbookAddress, marketParams, l2Book, contractVaultParams) { const orderbook = new ethers_1.ethers.Contract(orderbookAddress, OrderBook_json_1.default.abi, providerOrSigner); let data = l2Book; if (!data) { data = await orderbook.getL2Book({ from: ethers_1.ZeroAddress }); } let offset = 66; // Start reading after the block number const blockNumber = parseInt(data.slice(2, 66), 16); // The block number is stored in the first 64 bytes after '0x' let manualBids = []; let manualAsks = []; // Decode bids while (offset < data.length) { const price = BigInt('0x' + data.slice(offset, offset + 64)); offset += 64; // Skip over padding if (price === BigInt(0)) { break; // Stop reading if price is zero } const size = BigInt('0x' + data.slice(offset, offset + 64)); offset += 64; // Skip over padding manualBids.push([ parseFloat((0, ethers_1.formatUnits)(price, (0, utils_1.log10BigNumber)(marketParams.pricePrecision))), parseFloat((0, ethers_1.formatUnits)(size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))), ]); } // Decode asks while (offset < data.length) { const price = BigInt('0x' + data.slice(offset, offset + 64)); offset += 64; // Skip over padding if (price === BigInt(0)) { break; // Stop reading if price is zero } const size = BigInt('0x' + data.slice(offset, offset + 64)); offset += 64; // Skip over padding manualAsks.push([ parseFloat((0, ethers_1.formatUnits)(price, (0, utils_1.log10BigNumber)(marketParams.pricePrecision))), parseFloat((0, ethers_1.formatUnits)(size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))), ]); } // Get AMM Prices const { ammPrices, vaultParams } = await getAmmPrices(providerOrSigner, orderbookAddress, marketParams, blockNumber, contractVaultParams); // Combine manual orders and AMM prices const combinedBids = combinePrices(manualBids, ammPrices.bids); const combinedAsks = combinePrices(manualAsks, ammPrices.asks); // Sort bids and asks combinedBids.sort((a, b) => b[0] - a[0]); combinedAsks.sort((a, b) => b[0] - a[0]); return { bids: combinedBids, asks: combinedAsks, blockNumber, manualOrders: { bids: manualBids, asks: manualAsks, }, vaultParams, }; } static reconcileOrderCreated(existingOrderBook, marketParams, orderEvent) { // Create deep copies to prevent mutations const newOrderBook = { ...existingOrderBook, manualOrders: { bids: [...existingOrderBook.manualOrders.bids], asks: [...existingOrderBook.manualOrders.asks], }, }; // Convert size and price to floating-point numbers const orderSize = parseFloat((0, ethers_1.formatUnits)(orderEvent.size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))); const orderPrice = parseFloat((0, ethers_1.formatUnits)(orderEvent.price, (0, utils_1.log10BigNumber)(marketParams.pricePrecision))); // Determine which side of the book to update const sideToUpdate = orderEvent.isBuy ? newOrderBook.manualOrders.bids : newOrderBook.manualOrders.asks; // Find if there's an existing order at this price const existingOrderIndex = sideToUpdate.findIndex(([price, _]) => price === orderPrice); if (existingOrderIndex !== -1) { // If an order at this price exists, update its size sideToUpdate[existingOrderIndex][1] += orderSize; } else { // If no order at this price exists, add a new order sideToUpdate.push([orderPrice, orderSize]); // Re-sort the manual orders if (orderEvent.isBuy) { newOrderBook.manualOrders.bids.sort((a, b) => b[0] - a[0]); // Descending } else { newOrderBook.manualOrders.asks.sort((a, b) => a[0] - b[0]); // Ascending } } // Recombine manual orders with AMM prices const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); const combinedBids = combinePrices(newOrderBook.manualOrders.bids, ammPrices.bids); const combinedAsks = combinePrices(newOrderBook.manualOrders.asks, ammPrices.asks); // Sort combined orders combinedBids.sort((a, b) => b[0] - a[0]); combinedAsks.sort((a, b) => b[0] - a[0]); // Update bids and asks newOrderBook.bids = combinedBids; newOrderBook.asks = combinedAsks; // Update the block number newOrderBook.blockNumber = Number(orderEvent.blockNumber.toString()); return newOrderBook; } static reconcileCanceledOrders(existingOrderBook, marketParams, canceledOrderEvent) { // Create deep copies to prevent mutations const newOrderBook = { ...existingOrderBook, manualOrders: { bids: [...existingOrderBook.manualOrders.bids], asks: [...existingOrderBook.manualOrders.asks], }, }; for (const canceledOrder of canceledOrderEvent.canceledOrdersData) { // Convert size and price to floating-point numbers const orderSize = parseFloat((0, ethers_1.formatUnits)(canceledOrder.size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))); const orderPrice = parseFloat((0, ethers_1.formatUnits)(canceledOrder.price, (0, utils_1.log10BigNumber)(marketParams.pricePrecision))); // Determine which side of the book to update const sideToUpdate = canceledOrder.isbuy ? newOrderBook.manualOrders.bids : newOrderBook.manualOrders.asks; // Find the existing order at this price const existingOrderIndex = sideToUpdate.findIndex(([price, _]) => price === orderPrice); if (existingOrderIndex !== -1) { // If an order at this price exists, reduce its size sideToUpdate[existingOrderIndex][1] -= orderSize; // If the size becomes zero or negative, remove the order if (sideToUpdate[existingOrderIndex][1] <= 0) { sideToUpdate.splice(existingOrderIndex, 1); } } // If the order doesn't exist in our book, we don't need to do anything } // Recombine manual orders with AMM prices const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); const combinedBids = combinePrices(newOrderBook.manualOrders.bids, ammPrices.bids); const combinedAsks = combinePrices(newOrderBook.manualOrders.asks, ammPrices.asks); // Sort combined orders combinedBids.sort((a, b) => b[0] - a[0]); combinedAsks.sort((a, b) => b[0] - a[0]); // Update bids and asks newOrderBook.bids = combinedBids; newOrderBook.asks = combinedAsks; // Update the block number newOrderBook.blockNumber = parseInt(canceledOrderEvent.canceledOrdersData[0].blocknumber, 16); return newOrderBook; } static reconcileTradeEvent(existingOrderBook, marketParams, tradeEvent) { // Create deep copies to prevent mutations const newOrderBook = { ...existingOrderBook, vaultParams: { ...existingOrderBook.vaultParams }, manualOrders: { bids: [...existingOrderBook.manualOrders.bids], asks: [...existingOrderBook.manualOrders.asks], }, }; const tradePrice = parseFloat((0, ethers_1.formatUnits)(tradeEvent.price, 18)); if (tradeEvent.orderId === 0) { // Trade involves AMM order const spreadConstant = BigInt(newOrderBook.vaultParams.spread.toString()) / BigInt(10); const updatedSizeBN = BigInt(tradeEvent.updatedSize); if (tradeEvent.isBuy) { // Trader is buying, AMM is selling (ask side) if (updatedSizeBN === BigInt(0)) { // Move to next price level and create new orders newOrderBook.vaultParams.vaultBestAsk = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestAsk, BigInt(1000) + spreadConstant, BigInt(1000)); // Update bid price based on new ask price newOrderBook.vaultParams.vaultBestBid = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestAsk, BigInt(1000), BigInt(1000) + spreadConstant); // Update order sizes newOrderBook.vaultParams.vaultAskOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultAskOrderSize, BigInt(2000), BigInt(2000) + spreadConstant); newOrderBook.vaultParams.vaultBidOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultAskOrderSize, BigInt(2000) + spreadConstant, BigInt(2000)); // Reset partially filled sizes for new price level newOrderBook.vaultParams.askPartiallyFilledSize = BigInt(0); newOrderBook.vaultParams.bidPartiallyFilledSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.bidPartiallyFilledSize, BigInt(1000), BigInt(1000) + spreadConstant); } else { // Update partially filled size for current price level newOrderBook.vaultParams.askPartiallyFilledSize = BigInt(newOrderBook.vaultParams.vaultAskOrderSize.toString()) - updatedSizeBN; } } else { // Trader is selling, AMM is buying (bid side) if (updatedSizeBN === BigInt(0)) { // Move to next price level and create new orders newOrderBook.vaultParams.vaultBestBid = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestBid, BigInt(1000), BigInt(1000) + spreadConstant); // Update ask price based on new bid price newOrderBook.vaultParams.vaultBestAsk = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestBid, BigInt(1000) + spreadConstant, BigInt(1000)); // Update order sizes newOrderBook.vaultParams.vaultBidOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBidOrderSize, BigInt(2000) + spreadConstant, BigInt(2000)); newOrderBook.vaultParams.vaultAskOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBidOrderSize, BigInt(2000), BigInt(2000) + spreadConstant); // Reset partially filled sizes for new price level newOrderBook.vaultParams.bidPartiallyFilledSize = BigInt(0); } else { // Update partially filled size for current price level newOrderBook.vaultParams.bidPartiallyFilledSize = BigInt(newOrderBook.vaultParams.vaultBidOrderSize.toString()) - updatedSizeBN; } } // After updating the vault parameters, recalculate the AMM prices const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); // Recombine manual orders with updated AMM prices const combinedBids = combinePrices(newOrderBook.manualOrders.bids, ammPrices.bids); const combinedAsks = combinePrices(newOrderBook.manualOrders.asks, ammPrices.asks); // Sort combined orders combinedBids.sort((a, b) => b[0] - a[0]); combinedAsks.sort((a, b) => b[0] - a[0]); // Update bids and asks newOrderBook.bids = combinedBids; newOrderBook.asks = combinedAsks; } else { // Trade involves manual order // Convert filled size to float for manual orders const filledSize = parseFloat((0, ethers_1.formatUnits)(tradeEvent.filledSize, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))); // Determine which side of the book to update const sideToUpdate = tradeEvent.isBuy ? newOrderBook.manualOrders.asks : newOrderBook.manualOrders.bids; // Find the existing order at this price const existingOrderIndex = sideToUpdate.findIndex(([price, _]) => price === tradePrice); if (existingOrderIndex !== -1) { // If an order at this price exists, reduce its size sideToUpdate[existingOrderIndex][1] -= filledSize; // If the size becomes zero or negative, remove the order if (sideToUpdate[existingOrderIndex][1] <= 0) { sideToUpdate.splice(existingOrderIndex, 1); } } // If the order doesn't exist in our book, we don't need to do anything // Recombine manual orders with AMM prices const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); const combinedBids = combinePrices(newOrderBook.manualOrders.bids, ammPrices.bids); const combinedAsks = combinePrices(newOrderBook.manualOrders.asks, ammPrices.asks); // Sort combined orders combinedBids.sort((a, b) => b[0] - a[0]); combinedAsks.sort((a, b) => b[0] - a[0]); // Update bids and asks newOrderBook.bids = combinedBids; newOrderBook.asks = combinedAsks; } // Update the block number newOrderBook.blockNumber = parseInt(tradeEvent.blockNumber, 10); return newOrderBook; } static async getFormattedL2OrderBook(providerOrSigner, orderbookAddress, marketParams, l2Book, contractVaultParams) { // First get the regular order book const orderBook = await OrderBook.getL2OrderBook(providerOrSigner, orderbookAddress, marketParams, l2Book, contractVaultParams); // Calculate decimals based on pricePrecision and tickSize const pricePrecisionDecimals = (0, utils_1.log10BigNumber)(marketParams.pricePrecision); const tickSizeDecimals = (0, utils_1.log10BigNumber)(marketParams.tickSize); const decimals = pricePrecisionDecimals - tickSizeDecimals; // Helper functions to format price according to precision const formatAskPrice = (price) => { const multiplier = Math.pow(10, decimals); return Math.ceil(price * multiplier) / multiplier; }; const formatBidPrice = (price) => { const multiplier = Math.pow(10, decimals); return Math.floor(price * multiplier) / multiplier; }; // Helper function to group orders by price and sum sizes const groupOrders = (orders, roundingFn) => { const priceMap = new Map(); orders.forEach(([price, size]) => { const roundedPrice = roundingFn(price); priceMap.set(roundedPrice, (priceMap.get(roundedPrice) || 0) + size); }); return Array.from(priceMap.entries()).map(([price, size]) => [price, size]); }; // Format and group bids (round down) const formattedBids = groupOrders(orderBook.bids, formatBidPrice).sort((a, b) => b[0] - a[0]); // Format and group asks (round up) const formattedAsks = groupOrders(orderBook.asks, formatAskPrice).sort((a, b) => b[0] - a[0]); // Format and group manual orders const formattedManualBids = groupOrders(orderBook.manualOrders.bids, formatBidPrice).sort((a, b) => b[0] - a[0]); const formattedManualAsks = groupOrders(orderBook.manualOrders.asks, formatAskPrice).sort((a, b) => b[0] - a[0]); return { ...orderBook, bids: formattedBids, asks: formattedAsks, manualOrders: { bids: formattedManualBids, asks: formattedManualAsks, }, }; } static reconcileFormattedTradeEvent(existingOrderBook, marketParams, tradeEvent) { // Create deep copies to prevent mutations const newOrderBook = { ...existingOrderBook, vaultParams: { ...existingOrderBook.vaultParams }, manualOrders: { bids: [...existingOrderBook.manualOrders.bids], asks: [...existingOrderBook.manualOrders.asks], }, }; // Calculate decimals for price formatting const pricePrecisionDecimals = (0, utils_1.log10BigNumber)(marketParams.pricePrecision); const tickSizeDecimals = (0, utils_1.log10BigNumber)(marketParams.tickSize); const decimals = pricePrecisionDecimals - tickSizeDecimals; // Helper functions for price formatting const formatPrice = (price, isAsk) => { const multiplier = Math.pow(10, decimals); return isAsk ? Math.ceil(price * multiplier) / multiplier // Round up for asks : Math.floor(price * multiplier) / multiplier; // Round down for bids }; if (tradeEvent.orderId === 0) { // Handle AMM trade const spreadConstant = BigInt(newOrderBook.vaultParams.spread.toString()) / BigInt(10); const updatedSizeBN = BigInt(tradeEvent.updatedSize); if (tradeEvent.isBuy) { // AMM is selling (ask side) if (updatedSizeBN === BigInt(0)) { // Move to next price level and create new orders newOrderBook.vaultParams.vaultBestAsk = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestAsk, BigInt(1000) + spreadConstant, BigInt(1000)); // Update bid price based on new ask price newOrderBook.vaultParams.vaultBestBid = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestAsk, BigInt(1000), BigInt(1000) + spreadConstant); // Update order sizes newOrderBook.vaultParams.vaultAskOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultAskOrderSize, BigInt(2000), BigInt(2000) + spreadConstant); newOrderBook.vaultParams.vaultBidOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultAskOrderSize, BigInt(2000) + spreadConstant, BigInt(2000)); // Reset partially filled sizes for new price level newOrderBook.vaultParams.askPartiallyFilledSize = BigInt(0); newOrderBook.vaultParams.bidPartiallyFilledSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.bidPartiallyFilledSize, BigInt(1000), BigInt(1000) + spreadConstant); } else { // Update partially filled size for current price level newOrderBook.vaultParams.askPartiallyFilledSize = BigInt(newOrderBook.vaultParams.vaultAskOrderSize.toString()) - updatedSizeBN; } } else { // AMM is buying (bid side) if (updatedSizeBN === BigInt(0)) { // Move to next price level and create new orders newOrderBook.vaultParams.vaultBestBid = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestBid, BigInt(1000), BigInt(1000) + spreadConstant); // Update ask price based on new bid price newOrderBook.vaultParams.vaultBestAsk = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBestBid, BigInt(1000) + spreadConstant, BigInt(1000)); // Update order sizes newOrderBook.vaultParams.vaultBidOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBidOrderSize, BigInt(2000) + spreadConstant, BigInt(2000)); newOrderBook.vaultParams.vaultAskOrderSize = (0, utils_1.mulDivRound)(newOrderBook.vaultParams.vaultBidOrderSize, BigInt(2000), BigInt(2000) + spreadConstant); // Reset partially filled sizes for new price level newOrderBook.vaultParams.bidPartiallyFilledSize = BigInt(0); } else { // Update partially filled size for current price level newOrderBook.vaultParams.bidPartiallyFilledSize = BigInt(newOrderBook.vaultParams.vaultBidOrderSize.toString()) - updatedSizeBN; } } // Recalculate AMM prices with updated vault params const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); // Group and format orders const groupAndFormatOrders = (orders, isAsk) => { const priceMap = new Map(); orders.forEach(([price, size]) => { const formattedPrice = formatPrice(price, isAsk); priceMap.set(formattedPrice, (priceMap.get(formattedPrice) || 0) + size); }); return Array.from(priceMap.entries()) .filter(([_, size]) => size > 0) .sort((a, b) => b[0] - a[0]); }; // Update order book with formatted orders newOrderBook.bids = groupAndFormatOrders([...newOrderBook.manualOrders.bids, ...ammPrices.bids], false); newOrderBook.asks = groupAndFormatOrders([...newOrderBook.manualOrders.asks, ...ammPrices.asks], true); newOrderBook.manualOrders.bids = groupAndFormatOrders(newOrderBook.manualOrders.bids, false); newOrderBook.manualOrders.asks = groupAndFormatOrders(newOrderBook.manualOrders.asks, true); } else { // Handle manual order trade const filledSize = parseFloat((0, ethers_1.formatUnits)(tradeEvent.filledSize, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))); // Convert trade price to formatted number const rawTradePrice = parseFloat((0, ethers_1.formatUnits)(tradeEvent.price, 18)); const formattedTradePrice = formatPrice(rawTradePrice, tradeEvent.isBuy); // Update the appropriate side of the book const sideToUpdate = tradeEvent.isBuy ? newOrderBook.manualOrders.asks // If buyer, reduce ask side : newOrderBook.manualOrders.bids; // If seller, reduce bid side const existingOrderIndex = sideToUpdate.findIndex(([price]) => Math.abs(price - formattedTradePrice) < Number.EPSILON); if (existingOrderIndex !== -1) { sideToUpdate[existingOrderIndex][1] -= filledSize; if (sideToUpdate[existingOrderIndex][1] <= 0) { sideToUpdate.splice(existingOrderIndex, 1); } } // Group and format orders const groupAndFormatOrders = (orders, isAsk) => { const priceMap = new Map(); orders.forEach(([price, size]) => { const formattedPrice = formatPrice(price, isAsk); priceMap.set(formattedPrice, (priceMap.get(formattedPrice) || 0) + size); }); return Array.from(priceMap.entries()) .filter(([_, size]) => size > 0) .sort((a, b) => b[0] - a[0]); }; // Recombine manual orders with AMM prices const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); // Update order book with formatted orders newOrderBook.bids = groupAndFormatOrders([...newOrderBook.manualOrders.bids, ...ammPrices.bids], false); newOrderBook.asks = groupAndFormatOrders([...newOrderBook.manualOrders.asks, ...ammPrices.asks], true); newOrderBook.manualOrders.bids = groupAndFormatOrders(newOrderBook.manualOrders.bids, false); newOrderBook.manualOrders.asks = groupAndFormatOrders(newOrderBook.manualOrders.asks, true); } // Update block number newOrderBook.blockNumber = parseInt(tradeEvent.blockNumber, 10); return newOrderBook; } static reconcileFormattedCanceledOrders(existingOrderBook, marketParams, canceledOrderEvent) { // Create deep copies to prevent mutations const newOrderBook = { ...existingOrderBook, vaultParams: { ...existingOrderBook.vaultParams }, manualOrders: { bids: [...existingOrderBook.manualOrders.bids], asks: [...existingOrderBook.manualOrders.asks], }, }; // Calculate decimals for price formatting const pricePrecisionDecimals = (0, utils_1.log10BigNumber)(marketParams.pricePrecision); const tickSizeDecimals = (0, utils_1.log10BigNumber)(marketParams.tickSize); const decimals = pricePrecisionDecimals - tickSizeDecimals; // Helper functions for price formatting const formatPrice = (price, isAsk) => { const multiplier = Math.pow(10, decimals); return isAsk ? Math.ceil(price * multiplier) / multiplier // Round up for asks : Math.floor(price * multiplier) / multiplier; // Round down for bids }; for (const canceledOrder of canceledOrderEvent.canceledOrdersData) { // Convert size and price to floating-point numbers const orderSize = parseFloat((0, ethers_1.formatUnits)(canceledOrder.size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))); const rawPrice = parseFloat((0, ethers_1.formatUnits)(canceledOrder.price, (0, utils_1.log10BigNumber)(marketParams.pricePrecision))); // Format the price according to tick size const formattedPrice = formatPrice(rawPrice, !canceledOrder.isbuy); // Determine which side of the book to update const sideToUpdate = canceledOrder.isbuy ? newOrderBook.manualOrders.bids : newOrderBook.manualOrders.asks; // Find the existing order at this price - using exact match since these are manual orders const existingOrderIndex = sideToUpdate.findIndex(([price]) => price === formattedPrice); if (existingOrderIndex !== -1) { // If an order at this price exists, reduce its size sideToUpdate[existingOrderIndex][1] -= orderSize; // If the size becomes zero or negative, remove the order if (sideToUpdate[existingOrderIndex][1] <= 0) { sideToUpdate.splice(existingOrderIndex, 1); } } } // Group and format orders const groupAndFormatOrders = (orders, isAsk) => { const priceMap = new Map(); orders.forEach(([price, size]) => { const formattedPrice = formatPrice(price, isAsk); priceMap.set(formattedPrice, (priceMap.get(formattedPrice) || 0) + size); }); return Array.from(priceMap.entries()) .filter(([_, size]) => size > 0) .sort((a, b) => b[0] - a[0]); }; // Recombine manual orders with AMM prices const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); // Update order book with formatted orders newOrderBook.bids = groupAndFormatOrders([...newOrderBook.manualOrders.bids, ...ammPrices.bids], false); newOrderBook.asks = groupAndFormatOrders([...newOrderBook.manualOrders.asks, ...ammPrices.asks], true); newOrderBook.manualOrders.bids = groupAndFormatOrders(newOrderBook.manualOrders.bids, false); newOrderBook.manualOrders.asks = groupAndFormatOrders(newOrderBook.manualOrders.asks, true); // Update the block number newOrderBook.blockNumber = parseInt(canceledOrderEvent.canceledOrdersData[0].blocknumber, 16); return newOrderBook; } static reconcileFormattedOrderCreated(existingOrderBook, marketParams, orderEvent) { // Create deep copies to prevent mutations const newOrderBook = { ...existingOrderBook, manualOrders: { bids: [...existingOrderBook.manualOrders.bids], asks: [...existingOrderBook.manualOrders.asks], }, }; // Calculate decimals for price formatting const pricePrecisionDecimals = (0, utils_1.log10BigNumber)(marketParams.pricePrecision); const tickSizeDecimals = (0, utils_1.log10BigNumber)(marketParams.tickSize); const decimals = pricePrecisionDecimals - tickSizeDecimals; // Helper functions for price formatting const formatPrice = (price, isAsk) => { const multiplier = Math.pow(10, decimals); return isAsk ? Math.ceil(price * multiplier) / multiplier // Round up for asks : Math.floor(price * multiplier) / multiplier; // Round down for bids }; // Convert size and price to floating-point numbers const orderSize = parseFloat((0, ethers_1.formatUnits)(orderEvent.size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))); const rawPrice = parseFloat((0, ethers_1.formatUnits)(orderEvent.price, (0, utils_1.log10BigNumber)(marketParams.pricePrecision))); // Format the price according to tick size const formattedPrice = formatPrice(rawPrice, !orderEvent.isBuy); // Determine which side of the book to update const sideToUpdate = orderEvent.isBuy ? newOrderBook.manualOrders.bids : newOrderBook.manualOrders.asks; // Find if there's an existing order at this price - using exact match since these are manual orders const existingOrderIndex = sideToUpdate.findIndex(([price]) => price === formattedPrice); if (existingOrderIndex !== -1) { // If an order at this price exists, update its size sideToUpdate[existingOrderIndex][1] += orderSize; } else { // If no order at this price exists, add a new order sideToUpdate.push([formattedPrice, orderSize]); } // Group and format orders const groupAndFormatOrders = (orders, isAsk) => { const priceMap = new Map(); orders.forEach(([price, size]) => { const formattedPrice = formatPrice(price, isAsk); priceMap.set(formattedPrice, (priceMap.get(formattedPrice) || 0) + size); }); return Array.from(priceMap.entries()) .filter(([_, size]) => size > 0) .sort((a, b) => b[0] - a[0]); }; // Recombine manual orders with AMM prices const ammPrices = getAmmPricesFromVaultParams(newOrderBook.vaultParams, marketParams); // Update order book with formatted orders newOrderBook.bids = groupAndFormatOrders([...newOrderBook.manualOrders.bids, ...ammPrices.bids], false); newOrderBook.asks = groupAndFormatOrders([...newOrderBook.manualOrders.asks, ...ammPrices.asks], true); newOrderBook.manualOrders.bids = groupAndFormatOrders(newOrderBook.manualOrders.bids, false); newOrderBook.manualOrders.asks = groupAndFormatOrders(newOrderBook.manualOrders.asks, true); // Update the block number newOrderBook.blockNumber = Number(orderEvent.blockNumber.toString()); return newOrderBook; } } exports.OrderBook = OrderBook; /** * @dev Retrieves only the AMM prices from the order book contract. * @param providerOrSigner - The ethers.js provider to interact with the blockchain. * @param orderbookAddress - The address of the order book contract. * @param marketParams - The market parameters including price and size precision. * @returns A promise that resolves to the AMM prices data. */ async function getAmmPrices(providerOrSigner, orderbookAddress, marketParams, _, contractVaultParams) { const orderbook = new ethers_1.ethers.Contract(orderbookAddress, OrderBook_json_1.default.abi, providerOrSigner); let vaultParamsData = contractVaultParams; if (!vaultParamsData) { vaultParamsData = await providerOrSigner.call({ to: orderbookAddress, data: orderbook.interface.encodeFunctionData('getVaultParams'), from: ethers_1.ZeroAddress, }); vaultParamsData = orderbook.interface.decodeFunctionResult('getVaultParams', vaultParamsData); } const vaultParams = { kuruAmmVault: vaultParamsData[0], vaultBestBid: BigInt(vaultParamsData[1].toString()), bidPartiallyFilledSize: BigInt(vaultParamsData[2].toString()), vaultBestAsk: BigInt(vaultParamsData[3].toString()), askPartiallyFilledSize: BigInt(vaultParamsData[4].toString()), vaultBidOrderSize: BigInt(vaultParamsData[5].toString()), vaultAskOrderSize: BigInt(vaultParamsData[6].toString()), spread: BigInt(vaultParamsData[7].toString()), }; const ammPrices = getAmmPricesFromVaultParams(vaultParams, marketParams); return { ammPrices, vaultParams }; } /** * @dev Recalculates the AMM prices based on updated vault parameters. * @param vaultParams - The updated vault parameters. * @param marketParams - The market parameters including price and size precision. * @returns The recalculated AMM prices. */ function getAmmPricesFromVaultParams(vaultParams, marketParams) { let { vaultBestAsk, vaultBestBid, vaultBidOrderSize, vaultAskOrderSize, bidPartiallyFilledSize, askPartiallyFilledSize, spread, } = vaultParams; let bids = []; let asks = []; if (BigInt(vaultBidOrderSize.toString()) === BigInt(0) || vaultParams.kuruAmmVault === ethers_1.ZeroAddress) { return { bids, asks }; } const spreadConstant = BigInt(spread.toString()) / BigInt(10); // Calculate remaining sizes at current price levels const firstBidOrderSize = BigInt(vaultBidOrderSize.toString()) - BigInt(bidPartiallyFilledSize.toString()); const firstAskOrderSize = BigInt(vaultAskOrderSize.toString()) - BigInt(askPartiallyFilledSize.toString()); let currentBidPrice = BigInt(vaultBestBid.toString()); let currentAskPrice = BigInt(vaultBestAsk.toString()); let currentBidSize = BigInt(vaultBidOrderSize.toString()); let currentAskSize = BigInt(vaultAskOrderSize.toString()); // Add vault bid orders to AMM prices for (let i = 0; i < 300; i++) { if (currentBidPrice === BigInt(0)) break; const size = i === 0 ? firstBidOrderSize : currentBidSize; bids.push([ parseFloat((0, ethers_1.formatUnits)(currentBidPrice, 18)), parseFloat((0, ethers_1.formatUnits)(size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))), ]); // Next bid price = currentPrice * 1000 / (1000 + spreadConstant) currentBidPrice = (0, utils_1.mulDivRound)(currentBidPrice, BigInt(1000), BigInt(1000) + spreadConstant); // Next bid size = currentSize * (2000 + spreadConstant) / 2000 currentBidSize = (0, utils_1.mulDivRound)(currentBidSize, BigInt(2000) + spreadConstant, BigInt(2000)); } // Add vault ask orders to AMM prices for (let i = 0; i < 300; i++) { if (currentAskPrice >= ethers_1.MaxUint256) break; const size = i === 0 ? firstAskOrderSize : currentAskSize; asks.push([ parseFloat((0, ethers_1.formatUnits)(currentAskPrice, 18)), parseFloat((0, ethers_1.formatUnits)(size, (0, utils_1.log10BigNumber)(marketParams.sizePrecision))), ]); // Next ask price = currentPrice * (1000 + spreadConstant) / 1000 currentAskPrice = (0, utils_1.mulDivRound)(currentAskPrice, BigInt(1000) + spreadConstant, BigInt(1000)); // Next ask size = currentSize * 2000 / (2000 + spreadConstant) currentAskSize = (0, utils_1.mulDivRound)(currentAskSize, BigInt(2000), BigInt(2000) + spreadConstant); } return { bids, asks }; } /** * @dev Combines two price arrays, summing sizes for duplicate prices. * @param originalPrices - The original prices array. * @param additionalPrices - The additional prices array to merge. * @returns The combined prices array. */ function combinePrices(originalPrices, additionalPrices) { const priceMap = new Map(); // Add original prices to map originalPrices.forEach(([price, size]) => { priceMap.set(price, (priceMap.get(price) || 0) + size); }); // Add additional prices to map additionalPrices.forEach(([price, size]) => { priceMap.set(price, (priceMap.get(price) || 0) + size); }); // Convert map back to array return Array.from(priceMap.entries()).map(([price, size]) => [price, size]); } //# sourceMappingURL=orderBook.js.map