From 9f229f425dcb30efd9b351f2261a42ae9d412774 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Thu, 26 Oct 2023 17:40:12 +0200 Subject: [PATCH 1/5] refactor: use orderbook.json api instead of parsing html --- src/components/Earn.tsx | 14 +-- src/components/Orderbook.tsx | 166 ++++++++++++++++++++++++----------- src/libs/JmObwatchApi.ts | 84 ++++-------------- src/utils.ts | 8 +- 4 files changed, 142 insertions(+), 130 deletions(-) diff --git a/src/components/Earn.tsx b/src/components/Earn.tsx index 857980a98..3acb9da2f 100644 --- a/src/components/Earn.tsx +++ b/src/components/Earn.tsx @@ -2,10 +2,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { Formik, FormikErrors } from 'formik' import * as rb from 'react-bootstrap' import { useTranslation } from 'react-i18next' +import { TFunction } from 'i18next' import { useSettings } from '../context/SettingsContext' import { CurrentWallet, useCurrentWalletInfo, useReloadCurrentWalletInfo } from '../context/WalletContext' import { useServiceInfo, useReloadServiceInfo, Offer } from '../context/ServiceInfoContext' -import { factorToPercentage, isValidNumber, percentageToFactor } from '../utils' +import { factorToPercentage, isAbsoluteOffer, isRelativeOffer, isValidNumber, percentageToFactor } from '../utils' import * as Api from '../libs/JmWalletApi' import * as fb from './fb/utils' import Sprite from './Sprite' @@ -19,7 +20,6 @@ import { OrderbookOverlay } from './Orderbook' import Balance from './Balance' import styles from './Earn.module.css' import Accordion from './Accordion' -import { TFunction } from 'i18next' // In order to prevent state mismatch, the 'maker stop' response is delayed shortly. // Even though the API response suggests that the maker has started or stopped immediately, it seems that this is not always the case. @@ -31,14 +31,8 @@ const MAKER_STOP_RESPONSE_DELAY_MS = 2_000 // that the UTXO corresponding to the fidelity bond is correctly marked as such. const RELOAD_FIDELITY_BONDS_DELAY_MS = 2_000 -const OFFERTYPE_REL = 'sw0reloffer' -const OFFERTYPE_ABS = 'sw0absoffer' - -// can be any of ['sw0reloffer', 'swreloffer', 'reloffer'] -const isRelativeOffer = (offertype: string) => offertype.includes('reloffer') - -// can be any of ['sw0absoffer', 'swabsoffer', 'absoffer'] -const isAbsoluteOffer = (offertype: string) => offertype.includes('absoffer') +const OFFERTYPE_REL: Api.OfferType = 'sw0reloffer' +const OFFERTYPE_ABS: Api.OfferType = 'sw0absoffer' const FORM_INPUT_LOCAL_STORAGE_KEYS = { offertype: 'jm-offertype', diff --git a/src/components/Orderbook.tsx b/src/components/Orderbook.tsx index 5e192d127..1ad8e6efa 100644 --- a/src/components/Orderbook.tsx +++ b/src/components/Orderbook.tsx @@ -13,17 +13,9 @@ import { useSettings } from '../context/SettingsContext' import Balance from './Balance' import Sprite from './Sprite' import TablePagination from './TablePagination' +import { isAbsoluteOffer, isRelativeOffer } from '../utils' import styles from './Orderbook.module.css' - -const SORT_KEYS = { - type: 'TYPE', - counterparty: 'COUNTERPARTY', - fee: 'FEE', - minimumSize: 'MINIMUM_SIZE', - maximumSize: 'MAXIMUM_SIZE', - minerFeeContribution: 'MINER_FEE_CONTRIBUTION', - bondValue: 'BOND_VALUE', -} +import { isDevMode } from '../constants/debugFeatures' const TABLE_THEME = { Table: ` @@ -84,17 +76,67 @@ const withTooltip = (node: ReactElement, tooltip: string) => { ) } -// `TableNode` is known to have same properties as `ObwatchApi.Order`, hence prefer casting over object destructuring -const toOrder = (tableNode: TableTypes.TableNode) => tableNode as unknown as ObwatchApi.Order +// `TableNode` is known to have same properties as `OrderTableEntry`, hence prefer casting over object destructuring +const asOrderTableEntry = (tableNode: TableTypes.TableNode) => tableNode as unknown as OrderTableEntry + +const renderOrderType = (type: OrderTypeProps) => { + const elem = {type.displayValue} + return type.tooltip ? withTooltip(elem, type.tooltip) : elem +} + +type OrderTypeProps = { + value: string // original value, example: 'sw0reloffer', 'swreloffer', 'reloffer', 'sw0absoffer', 'swabsoffer', 'absoffer' + displayValue: string // example: "absolute" or "relative" (respecting i18n) + badgeColor: 'info' | 'primary' | 'secondary' + tooltip?: 'Native SW Absolute Fee' | 'Native SW Relative Fee' | string + isAbsolute?: boolean + isRelative?: boolean +} +interface OrderTableEntry { + type: OrderTypeProps + counterparty: string // example: "J5Bv3JSxPFWm2Yjb" + orderId: string // example: "0" (not unique!) + fee: string // example: "250" (abs offers) or "0.000100%" (rel offers) + minerFeeContribution: string // example: "0" + minimumSize: string // example: "27300" + maximumSize: string // example: "237499972700" + bondValue: string // example: "0" (no fb) or "0.0000052877962973" +} -const renderOrderType = (val: string, t: TFunction) => { - if (val === ObwatchApi.ABSOLUTE_ORDER_TYPE_VAL) { - return withTooltip({t('orderbook.text_offer_type_absolute')}, val) +const SORT_KEYS = { + type: 'TYPE', + counterparty: 'COUNTERPARTY', + fee: 'FEE', + minimumSize: 'MINIMUM_SIZE', + maximumSize: 'MAXIMUM_SIZE', + minerFeeContribution: 'MINER_FEE_CONTRIBUTION', + bondValue: 'BOND_VALUE', +} + +const orderTypeProps = (offer: ObwatchApi.Offer, t: TFunction): OrderTypeProps => { + if (isAbsoluteOffer(offer.ordertype)) { + return { + value: offer.ordertype, + displayValue: t('orderbook.text_offer_type_absolute'), + badgeColor: 'info', + tooltip: offer.ordertype === 'sw0absoffer' ? 'Native SW Absolute Fee' : offer.ordertype, + isAbsolute: true, + } + } + if (isRelativeOffer(offer.ordertype)) { + return { + value: offer.ordertype, + displayValue: t('orderbook.text_offer_type_relative'), + badgeColor: 'primary', + tooltip: offer.ordertype === 'sw0reloffer' ? 'Native SW Relative Fee' : offer.ordertype, + isRelative: true, + } } - if (val === ObwatchApi.RELATIVE_ORDER_TYPE_VAL) { - return withTooltip({t('orderbook.text_offer_type_relative')}, val) + return { + value: offer.ordertype, + displayValue: offer.ordertype, + badgeColor: 'secondary', } - return {val} } const renderOrderFee = (val: string, settings: any) => { @@ -134,19 +176,19 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { }, sortToggleType: SortToggleType.AlternateWithReset, sortFns: { - [SORT_KEYS.type]: (array) => array.sort((a, b) => a.type.localeCompare(b.type)), + [SORT_KEYS.type]: (array) => array.sort((a, b) => a.type.value.localeCompare(b.type.value)), [SORT_KEYS.fee]: (array) => array.sort((a, b) => { - const aOrder = toOrder(a) - const bOrder = toOrder(b) + const aOrder = asOrderTableEntry(a) + const bOrder = asOrderTableEntry(b) - if (aOrder.type !== bOrder.type) { - return aOrder.type === ObwatchApi.ABSOLUTE_ORDER_TYPE_VAL ? 1 : -1 + if (aOrder.type.value !== bOrder.type.value) { + return aOrder.type.isAbsolute === true ? 1 : -1 } - if (aOrder.type === ObwatchApi.ABSOLUTE_ORDER_TYPE_VAL) { + if (aOrder.type.isAbsolute === true) { return +aOrder.fee - +bOrder.fee - } else { + } else if (aOrder.type.isRelative === true) { const aIndexOfPercent = aOrder.fee.indexOf('%') const bIndexOfPercent = bOrder.fee.indexOf('%') @@ -205,12 +247,12 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { {tableList.map((item) => { - const order = toOrder(item) + const order = asOrderTableEntry(item) return ( {order.counterparty} {order.orderId} - {renderOrderType(order.type, t)} + {renderOrderType(order.type)} {renderOrderFee(order.fee, settings)} @@ -240,35 +282,48 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { ) } +const offerToTableEntry = (offer: ObwatchApi.Offer, t: TFunction): OrderTableEntry => { + return { + type: orderTypeProps(offer, t), + counterparty: offer.counterparty, + orderId: String(offer.oid), + fee: typeof offer.cjfee === 'number' ? String(offer.cjfee) : (parseFloat(offer.cjfee) * 100).toFixed(6) + '%', + minerFeeContribution: String(offer.txfee), + minimumSize: String(offer.minsize), + maximumSize: String(offer.maxsize), + bondValue: String(offer.fidelity_bond_value), + } +} + interface OrderbookProps { - orders: ObwatchApi.Order[] + entries: OrderTableEntry[] refresh: (signal: AbortSignal) => Promise nickname?: string } -export function Orderbook({ orders, refresh, nickname }: OrderbookProps) { +export function Orderbook({ entries, refresh, nickname }: OrderbookProps) { const { t } = useTranslation() const settings = useSettings() const [search, setSearch] = useState('') const [isLoadingRefresh, setIsLoadingRefresh] = useState(false) const [isHighlightOwnOffers, setIsHighlightOwnOffers] = useState(false) - const [highlightedOrders, setHighlightedOrders] = useState([]) + const [highlightedOrders, setHighlightedOrders] = useState([]) const tableData: TableTypes.Data = useMemo(() => { const searchVal = search.replace('.', '').toLowerCase() const filteredOrders = searchVal === '' - ? orders - : orders.filter((order) => { + ? entries + : entries.filter((entry) => { return ( - order.type.toLowerCase().includes(searchVal) || - order.counterparty.toLowerCase().includes(searchVal) || - order.fee.replace('.', '').toLowerCase().includes(searchVal) || - order.minimumSize.replace('.', '').toLowerCase().includes(searchVal) || - order.maximumSize.replace('.', '').toLowerCase().includes(searchVal) || - order.minerFeeContribution.replace('.', '').toLowerCase().includes(searchVal) || - order.bondValue.replace('.', '').toLowerCase().includes(searchVal) || - order.orderId.toLowerCase().includes(searchVal) + entry.type.displayValue.toLowerCase().includes(searchVal) || + entry.counterparty.toLowerCase().includes(searchVal) || + entry.fee.replace('.', '').toLowerCase().includes(searchVal) || + entry.minimumSize.replace('.', '').toLowerCase().includes(searchVal) || + entry.maximumSize.replace('.', '').toLowerCase().includes(searchVal) || + entry.minerFeeContribution.replace('.', '').toLowerCase().includes(searchVal) || + entry.bondValue.replace('.', '').toLowerCase().includes(searchVal) || + entry.orderId.toLowerCase().includes(searchVal) ) }) const nodes = filteredOrders.map((order) => ({ @@ -278,9 +333,9 @@ export function Orderbook({ orders, refresh, nickname }: OrderbookProps) { })) return { nodes } - }, [orders, search, highlightedOrders]) + }, [entries, search, highlightedOrders]) - const counterpartyCount = useMemo(() => new Set(orders.map((it) => it.counterparty)).size, [orders]) + const counterpartyCount = useMemo(() => new Set(entries.map((it) => it.counterparty)).size, [entries]) const counterpartyCountFiltered = useMemo( () => new Set(tableData.nodes.map((it) => it.counterparty)).size, [tableData], @@ -290,9 +345,9 @@ export function Orderbook({ orders, refresh, nickname }: OrderbookProps) { if (!nickname || !isHighlightOwnOffers) { setHighlightedOrders([]) } else { - setHighlightedOrders(orders.filter((it) => it.counterparty === nickname)) + setHighlightedOrders(entries.filter((it) => it.counterparty === nickname)) } - }, [orders, nickname, isHighlightOwnOffers]) + }, [entries, nickname, isHighlightOwnOffers]) return (
@@ -323,7 +378,7 @@ export function Orderbook({ orders, refresh, nickname }: OrderbookProps) { {search === '' ? ( <> {t('orderbook.text_orderbook_summary', { - count: orders.length, + count: entries.length, counterpartyCount, })} @@ -352,7 +407,7 @@ export function Orderbook({ orders, refresh, nickname }: OrderbookProps) {
- {orders.length === 0 ? ( + {entries.length === 0 ? ( {t('orderbook.alert_empty_orderbook')} ) : ( <> @@ -385,24 +440,29 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro const [alert, setAlert] = useState() const [isInitialized, setIsInitialized] = useState(false) const [isLoading, setIsLoading] = useState(true) - const [orders, setOrders] = useState(null) + const [offers, setOffers] = useState() + const tableEntries = useMemo(() => offers && offers.map((offer) => offerToTableEntry(offer, t)), [offers, t]) const refresh = useCallback( (signal: AbortSignal) => { - return ObwatchApi.refreshOrderbook({ signal }) + return ObwatchApi.refreshOffers({ signal }) .then((res) => { if (!res.ok) { // e.g. error is raised if ob-watcher is not running return ApiHelper.throwError(res) } - return ObwatchApi.fetchOrderbook({ signal }) + return ObwatchApi.fetchOffers({ signal }) }) - .then((orders) => { + .then((offers) => { if (signal.aborted) return - setOrders(orders) + setOffers(offers) setAlert(undefined) + + if (isDevMode()) { + console.table(offers) + } }) .catch((e) => { if (signal.aborted) return @@ -468,10 +528,10 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro ) : ( <> {alert && {alert.message}} - {orders && ( + {tableEntries && ( - + )} diff --git a/src/libs/JmObwatchApi.ts b/src/libs/JmObwatchApi.ts index 6cff3e4e5..44c8f6815 100644 --- a/src/libs/JmObwatchApi.ts +++ b/src/libs/JmObwatchApi.ts @@ -1,83 +1,35 @@ -import { Helper as ApiHelper } from '../libs/JmWalletApi' +import { AmountSats, Helper as ApiHelper } from '../libs/JmWalletApi' const basePath = () => `${window.JM.PUBLIC_PATH}/obwatch` -export const ABSOLUTE_ORDER_TYPE_VAL = 'Native SW Absolute Fee' -export const RELATIVE_ORDER_TYPE_VAL = 'Native SW Relative Fee' - -export interface Order { - type: string // example: "Native SW Absolute Fee" or "Native SW Relative Fee" +export interface Offer { counterparty: string // example: "J5Bv3JSxPFWm2Yjb" - orderId: string // example: "0" (not unique!) - fee: string // example: "0.00000250" (abs offers) or "0.000100%" (rel offers) - minerFeeContribution: string // example: "0.00000000" - minimumSize: string // example: "0.00027300" - maximumSize: string // example: "2374.99972700" - bondValue: string // example: "0" (no fb) or "0.0000052877962973" + oid: number // example: 0 (not unique!) + ordertype: string // example: "sw0absoffer" or "sw0reloffer" + minsize: AmountSats // example: 27300 + maxsize: AmountSats // example: 237499972700 + txfee: AmountSats // example: 0 + cjfee: AmountSats | string // example: 250 (abs offers) or "0.00017" (rel offers) + fidelity_bond_value: number // example: 0 (no fb) or 0.0000052877962973 } -const ORDER_KEYS: (keyof Order)[] = [ - 'type', - 'counterparty', - 'orderId', - 'fee', - 'minerFeeContribution', - 'minimumSize', - 'maximumSize', - 'bondValue', -] - -const parseOrderbook = (res: Response): Promise => { - if (!res.ok) { - // e.g. error is raised if ob-watcher is not running - return ApiHelper.throwError(res) - } - - return res.text().then((html) => { - var parser = new DOMParser() - var doc = parser.parseFromString(html, 'text/html') - - const tables = doc.getElementsByTagName('table') - if (tables.length !== 1) { - throw new Error('Cannot find orderbook table') - } - const orderbookTable = tables[0] - const tbodies = [...orderbookTable.children].filter((child) => child.tagName.toLowerCase() === 'tbody') - if (tbodies.length !== 1) { - throw new Error('Cannot find orderbook table body') - } - - const tbody = tbodies[0] - - const orders: Order[] = [...tbody.children] - .filter((row) => row.tagName.toLowerCase() === 'tr') - .filter((row) => row.children.length > 0) - .map((row) => [...row.children].filter((child) => child.tagName.toLowerCase() === 'td')) - .filter((cols) => cols.length === ORDER_KEYS.length) - .map((cols) => { - const data: unknown = ORDER_KEYS.map((key, index) => ({ [key]: cols[index].innerHTML })).reduce( - (acc, curr) => ({ ...acc, ...curr }), - {}, - ) - return data as Order - }) - - return orders +const orderbookJson = async ({ signal }: { signal: AbortSignal }) => { + return await fetch(`${basePath()}/orderbook.json`, { + signal, }) } -// TODO: why is "orderbook.json" always empty? -> Parse HTML in the meantime.. ¯\_(ツ)_/¯ -const fetchOrderbook = async ({ signal }: { signal: AbortSignal }) => { - return await fetch(`${basePath()}/`, { - signal, - }).then((res) => parseOrderbook(res)) +const fetchOffers = async (options: { signal: AbortSignal }) => { + return orderbookJson(options) + .then((res) => (res.ok ? res.json() : ApiHelper.throwError(res))) + .then((res) => (res.offers || []) as Offer[]) } -const refreshOrderbook = async ({ signal }: { signal: AbortSignal }) => { +const refreshOffers = async ({ signal }: { signal: AbortSignal }) => { return await fetch(`${basePath()}/refreshorderbook`, { method: 'POST', signal, }) } -export { fetchOrderbook, refreshOrderbook } +export { fetchOffers, refreshOffers } diff --git a/src/utils.ts b/src/utils.ts index 9c68b985c..4cdd7effb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import { WalletFileName } from './libs/JmWalletApi' +import { OfferType, WalletFileName } from './libs/JmWalletApi' const BTC_FORMATTER = new Intl.NumberFormat('en-US', { minimumIntegerDigits: 1, @@ -74,6 +74,12 @@ export const factorToPercentage = (val: number, precision = 6) => { return Number((val * 100).toFixed(precision)) } +// can be any of ['sw0reloffer', 'swreloffer', 'reloffer'] +export const isRelativeOffer = (offertype: OfferType) => offertype.includes('reloffer') + +// can be any of ['sw0absoffer', 'swabsoffer', 'absoffer'] +export const isAbsoluteOffer = (offertype: OfferType) => offertype.includes('absoffer') + export const isValidNumber = (val: number | undefined) => typeof val === 'number' && !isNaN(val) export const UNKNOWN_VERSION: SemVer = { major: 0, minor: 0, patch: 0, raw: 'unknown' } From 690b8e0c0ea36692cdb8dd97a9fe04be493b6bf9 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Thu, 26 Oct 2023 17:59:35 +0200 Subject: [PATCH 2/5] refactor(orderbook): improve displaying relative fee values --- src/components/Orderbook.tsx | 49 ++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/components/Orderbook.tsx b/src/components/Orderbook.tsx index 1ad8e6efa..1c1e9b184 100644 --- a/src/components/Orderbook.tsx +++ b/src/components/Orderbook.tsx @@ -13,7 +13,7 @@ import { useSettings } from '../context/SettingsContext' import Balance from './Balance' import Sprite from './Sprite' import TablePagination from './TablePagination' -import { isAbsoluteOffer, isRelativeOffer } from '../utils' +import { factorToPercentage, isAbsoluteOffer, isRelativeOffer } from '../utils' import styles from './Orderbook.module.css' import { isDevMode } from '../constants/debugFeatures' @@ -96,7 +96,10 @@ interface OrderTableEntry { type: OrderTypeProps counterparty: string // example: "J5Bv3JSxPFWm2Yjb" orderId: string // example: "0" (not unique!) - fee: string // example: "250" (abs offers) or "0.000100%" (rel offers) + fee: { + value: number + displayValue: string // example: "250" (abs offers) or "0.000100%" (rel offers) + } minerFeeContribution: string // example: "0" minimumSize: string // example: "27300" maximumSize: string // example: "237499972700" @@ -140,7 +143,11 @@ const orderTypeProps = (offer: ObwatchApi.Offer, t: TFunction): OrderTypeProps = } const renderOrderFee = (val: string, settings: any) => { - return val.includes('%') ? <>{val} : + return val.includes('%') ? ( + {val} + ) : ( + + ) } interface OrderbookTableProps { @@ -176,28 +183,16 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { }, sortToggleType: SortToggleType.AlternateWithReset, sortFns: { - [SORT_KEYS.type]: (array) => array.sort((a, b) => a.type.value.localeCompare(b.type.value)), + [SORT_KEYS.type]: (array) => array.sort((a, b) => a.type.displayValue.localeCompare(b.type.displayValue)), [SORT_KEYS.fee]: (array) => array.sort((a, b) => { const aOrder = asOrderTableEntry(a) const bOrder = asOrderTableEntry(b) - if (aOrder.type.value !== bOrder.type.value) { + if (aOrder.type.isAbsolute !== bOrder.type.isAbsolute) { return aOrder.type.isAbsolute === true ? 1 : -1 } - - if (aOrder.type.isAbsolute === true) { - return +aOrder.fee - +bOrder.fee - } else if (aOrder.type.isRelative === true) { - const aIndexOfPercent = aOrder.fee.indexOf('%') - const bIndexOfPercent = bOrder.fee.indexOf('%') - - if (aIndexOfPercent > 0 && bIndexOfPercent > 0) { - return +aOrder.fee.substring(0, aIndexOfPercent) - +bOrder.fee.substring(0, bIndexOfPercent) - } - } - - return 0 + return aOrder.fee.value - bOrder.fee.value }), [SORT_KEYS.minimumSize]: (array) => array.sort((a, b) => a.minimumSize - b.minimumSize), [SORT_KEYS.maximumSize]: (array) => array.sort((a, b) => a.maximumSize - b.maximumSize), @@ -253,7 +248,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { {order.counterparty} {order.orderId} {renderOrderType(order.type)} - {renderOrderFee(order.fee, settings)} + {renderOrderFee(order.fee.displayValue, settings)} @@ -287,7 +282,19 @@ const offerToTableEntry = (offer: ObwatchApi.Offer, t: TFunction): OrderTableEnt type: orderTypeProps(offer, t), counterparty: offer.counterparty, orderId: String(offer.oid), - fee: typeof offer.cjfee === 'number' ? String(offer.cjfee) : (parseFloat(offer.cjfee) * 100).toFixed(6) + '%', + fee: + typeof offer.cjfee === 'number' + ? { + value: offer.cjfee, + displayValue: String(offer.cjfee), + } + : (() => { + const value = parseFloat(offer.cjfee) + return { + value, + displayValue: factorToPercentage(value).toFixed(4) + '%', + } + })(), minerFeeContribution: String(offer.txfee), minimumSize: String(offer.minsize), maximumSize: String(offer.maxsize), @@ -318,7 +325,7 @@ export function Orderbook({ entries, refresh, nickname }: OrderbookProps) { return ( entry.type.displayValue.toLowerCase().includes(searchVal) || entry.counterparty.toLowerCase().includes(searchVal) || - entry.fee.replace('.', '').toLowerCase().includes(searchVal) || + entry.fee.displayValue.replace('.', '').toLowerCase().includes(searchVal) || entry.minimumSize.replace('.', '').toLowerCase().includes(searchVal) || entry.maximumSize.replace('.', '').toLowerCase().includes(searchVal) || entry.minerFeeContribution.replace('.', '').toLowerCase().includes(searchVal) || From 8e2b8aad772012f470722d1dd56e287e9006829d Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Thu, 26 Oct 2023 18:01:53 +0200 Subject: [PATCH 3/5] ui(orderbook): monospace font for bond value --- src/components/Orderbook.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Orderbook.tsx b/src/components/Orderbook.tsx index 1c1e9b184..f416cb6d3 100644 --- a/src/components/Orderbook.tsx +++ b/src/components/Orderbook.tsx @@ -245,7 +245,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { const order = asOrderTableEntry(item) return ( - {order.counterparty} + {order.counterparty} {order.orderId} {renderOrderType(order.type)} {renderOrderFee(order.fee.displayValue, settings)} @@ -262,7 +262,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { showBalance={true} /> - {order.bondValue} + {order.bondValue} ) })} From 0f55eae53e911d5f90fc40d9543bed38fff0e849 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Thu, 26 Oct 2023 23:48:08 +0200 Subject: [PATCH 4/5] ui(orderbook): normalized bond value --- src/components/Orderbook.tsx | 32 ++++++++++++++++++++------------ src/libs/JmObwatchApi.ts | 16 +++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/components/Orderbook.tsx b/src/components/Orderbook.tsx index f416cb6d3..d0d16177d 100644 --- a/src/components/Orderbook.tsx +++ b/src/components/Orderbook.tsx @@ -14,8 +14,8 @@ import Balance from './Balance' import Sprite from './Sprite' import TablePagination from './TablePagination' import { factorToPercentage, isAbsoluteOffer, isRelativeOffer } from '../utils' -import styles from './Orderbook.module.css' import { isDevMode } from '../constants/debugFeatures' +import styles from './Orderbook.module.css' const TABLE_THEME = { Table: ` @@ -70,9 +70,11 @@ const TABLE_THEME = { `, } -const withTooltip = (node: ReactElement, tooltip: string) => { +const withTooltip = (node: ReactElement, tooltip: string, overlayProps?: Partial) => { return ( - {tooltip}}>{node} + {tooltip}}> + {node} + ) } @@ -103,7 +105,10 @@ interface OrderTableEntry { minerFeeContribution: string // example: "0" minimumSize: string // example: "27300" maximumSize: string // example: "237499972700" - bondValue: string // example: "0" (no fb) or "0.0000052877962973" + bondValue: { + value: number + displayValue: string // example: "0" (no fb) or "114557102085.28133" + } } const SORT_KEYS = { @@ -203,7 +208,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { const val = a.counterparty.localeCompare(b.counterparty) return val !== 0 ? val : +a.orderId - +b.orderId }), - [SORT_KEYS.bondValue]: (array) => array.sort((a, b) => a.bondValue - b.bondValue), + [SORT_KEYS.bondValue]: (array) => array.sort((a, b) => a.bondValue.value - b.bondValue.value), }, }, ) @@ -262,7 +267,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { showBalance={true} /> - {order.bondValue} + {order.bondValue.displayValue} ) })} @@ -298,7 +303,10 @@ const offerToTableEntry = (offer: ObwatchApi.Offer, t: TFunction): OrderTableEnt minerFeeContribution: String(offer.txfee), minimumSize: String(offer.minsize), maximumSize: String(offer.maxsize), - bondValue: String(offer.fidelity_bond_value), + bondValue: { + value: offer.fidelity_bond_value, + displayValue: String(offer.fidelity_bond_value.toFixed(0)), + }, } } @@ -329,7 +337,7 @@ export function Orderbook({ entries, refresh, nickname }: OrderbookProps) { entry.minimumSize.replace('.', '').toLowerCase().includes(searchVal) || entry.maximumSize.replace('.', '').toLowerCase().includes(searchVal) || entry.minerFeeContribution.replace('.', '').toLowerCase().includes(searchVal) || - entry.bondValue.replace('.', '').toLowerCase().includes(searchVal) || + entry.bondValue.displayValue.replace('.', '').toLowerCase().includes(searchVal) || entry.orderId.toLowerCase().includes(searchVal) ) }) @@ -452,20 +460,20 @@ export function OrderbookOverlay({ nickname, show, onHide }: OrderbookOverlayPro const refresh = useCallback( (signal: AbortSignal) => { - return ObwatchApi.refreshOffers({ signal }) + return ObwatchApi.refreshOrderbook({ signal }) .then((res) => { if (!res.ok) { // e.g. error is raised if ob-watcher is not running return ApiHelper.throwError(res) } - return ObwatchApi.fetchOffers({ signal }) + return ObwatchApi.fetchOrderbook({ signal }) }) - .then((offers) => { + .then((orderbook) => { if (signal.aborted) return - setOffers(offers) setAlert(undefined) + setOffers(orderbook.offers || []) if (isDevMode()) { console.table(offers) diff --git a/src/libs/JmObwatchApi.ts b/src/libs/JmObwatchApi.ts index 44c8f6815..23d009872 100644 --- a/src/libs/JmObwatchApi.ts +++ b/src/libs/JmObwatchApi.ts @@ -10,7 +10,11 @@ export interface Offer { maxsize: AmountSats // example: 237499972700 txfee: AmountSats // example: 0 cjfee: AmountSats | string // example: 250 (abs offers) or "0.00017" (rel offers) - fidelity_bond_value: number // example: 0 (no fb) or 0.0000052877962973 + fidelity_bond_value: number // example: 0 (no fb) or 114557102085.28133 +} + +export interface OrderbookJson { + offers?: Offer[] } const orderbookJson = async ({ signal }: { signal: AbortSignal }) => { @@ -19,17 +23,15 @@ const orderbookJson = async ({ signal }: { signal: AbortSignal }) => { }) } -const fetchOffers = async (options: { signal: AbortSignal }) => { - return orderbookJson(options) - .then((res) => (res.ok ? res.json() : ApiHelper.throwError(res))) - .then((res) => (res.offers || []) as Offer[]) +const fetchOrderbook = async (options: { signal: AbortSignal }): Promise => { + return orderbookJson(options).then((res) => (res.ok ? res.json() : ApiHelper.throwError(res))) } -const refreshOffers = async ({ signal }: { signal: AbortSignal }) => { +const refreshOrderbook = async ({ signal }: { signal: AbortSignal }) => { return await fetch(`${basePath()}/refreshorderbook`, { method: 'POST', signal, }) } -export { fetchOffers, refreshOffers } +export { fetchOrderbook, refreshOrderbook } From 4559d081bff62d48e9a9b483889d56b450a90c74 Mon Sep 17 00:00:00 2001 From: theborakompanioni Date: Fri, 27 Oct 2023 00:15:32 +0200 Subject: [PATCH 5/5] ui(orderbook): hide column miner fee contribution --- src/components/Orderbook.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/Orderbook.tsx b/src/components/Orderbook.tsx index d0d16177d..10c0c58f6 100644 --- a/src/components/Orderbook.tsx +++ b/src/components/Orderbook.tsx @@ -19,7 +19,7 @@ import styles from './Orderbook.module.css' const TABLE_THEME = { Table: ` - --data-table-library_grid-template-columns: 1fr 5rem 1fr 1fr 2fr 2fr 2fr 2fr; + --data-table-library_grid-template-columns: 1fr 5rem 1fr 2fr 2fr 2fr 2fr; font-size: 0.9rem; `, BaseCell: ` @@ -203,6 +203,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { [SORT_KEYS.maximumSize]: (array) => array.sort((a, b) => a.maximumSize - b.maximumSize), [SORT_KEYS.minerFeeContribution]: (array) => array.sort((a, b) => a.minerFeeContribution - b.minerFeeContribution), + [SORT_KEYS.counterparty]: (array) => array.sort((a, b) => { const val = a.counterparty.localeCompare(b.counterparty) @@ -239,7 +240,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { {t('orderbook.table.heading_maximum_size')} - + {t('orderbook.table.heading_miner_fee_contribution')} {t('orderbook.table.heading_bond_value')} @@ -250,7 +251,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { const order = asOrderTableEntry(item) return ( - {order.counterparty} + {order.counterparty} {order.orderId} {renderOrderType(order.type)} {renderOrderFee(order.fee.displayValue, settings)} @@ -260,7 +261,7 @@ const OrderbookTable = ({ data }: OrderbookTableProps) => { - + {