From b983175678caf0a7d5446e1108a21f9c0ef86928 Mon Sep 17 00:00:00 2001 From: Dan Anderson Date: Tue, 10 Nov 2026 00:06:48 -0500 Subject: [PATCH 1/3] Fix weekly oracle inputs and exclude exhausted order-book levels --- .../pack/__tests__/coreOracle.test.ts | 12 +++++++++- .../pack/__tests__/onchainRoundTrip.test.ts | 22 ++++++++++++++++++- .../orders/[baseAsset]/[quoteAsset].tsx | 5 +++++ .../orders/__tests__/pair-pagination.test.tsx | 12 ++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/core/counterparty/pack/__tests__/coreOracle.test.ts b/src/core/counterparty/pack/__tests__/coreOracle.test.ts index 7389dd5e6..78debc39d 100644 --- a/src/core/counterparty/pack/__tests__/coreOracle.test.ts +++ b/src/core/counterparty/pack/__tests__/coreOracle.test.ts @@ -29,6 +29,7 @@ import { describe, expect, it } from 'vitest'; import { asBaseUnits } from '@/core/numeric'; +import { mpmaTableFormatAt } from '../../mpmaTableFormat'; import { unpackCounterpartyMessage } from '../../unpack'; import { bytesToHex } from '../../unpack/binary'; import { packComposeMessage } from '../messages'; @@ -406,6 +407,15 @@ async function composeDataFromCore(testCase: OracleCase): Promise { describe.skipIf(!API_URL)('local packing matches counterparty-core', () => { it.each(CASES)('$label', async (testCase) => { + // Like the wallet, establish the consensus table layout independently of compose bytes. + let mpmaTableFormat; + if (testCase.composeType === 'mpma') { + const response = await fetchOracle(`${API_URL}/v2/`); + if (!response.ok) throw new Error(`Core status returned ${response.status}`); + const { result } = await response.json() as { result: { network: string; counterparty_height: number } }; + mpmaTableFormat = mpmaTableFormatAt(result.network, result.counterparty_height + 1); + if (!mpmaTableFormat) throw new Error('Cannot establish the MPMA table format for the oracle'); + } const fromCore = await composeDataFromCore(testCase); let observed: Record | undefined; @@ -415,7 +425,7 @@ describe.skipIf(!API_URL)('local packing matches counterparty-core', () => { observed = unpacked.data as Record; } - const packed = packComposeMessage(testCase.composeType, testCase.params, observed); + const packed = packComposeMessage(testCase.composeType, testCase.params, observed, { mpmaTableFormat }); expect(packed, 'this case should be packable locally').not.toBeNull(); expect(bytesToHex(packed!.bytes).toLowerCase()).toBe(fromCore); }, 60_000); diff --git a/src/core/counterparty/pack/__tests__/onchainRoundTrip.test.ts b/src/core/counterparty/pack/__tests__/onchainRoundTrip.test.ts index ffd49c12f..8c030a66f 100644 --- a/src/core/counterparty/pack/__tests__/onchainRoundTrip.test.ts +++ b/src/core/counterparty/pack/__tests__/onchainRoundTrip.test.ts @@ -157,8 +157,11 @@ const PARAMS_FROM_DECODED: Record) => Record< // wallet, not an indexer, and reproducing every historical encoding is not its job. A struct // send is therefore one it can read and deliberately cannot rebuild, which counts as declined // rather than as a defect. + // The text-only composer cannot rebuild a binary memo from its display hex string. + // Compare raw bytes, so text which merely looks hexadecimal remains eligible. enhanced_send: (data) => - data.layout === 'legacy' + data.layout === 'legacy' || (data.memoBytes instanceof Uint8Array + && bytesToHex(new TextEncoder().encode(data.memo ?? '')) !== bytesToHex(data.memoBytes)) ? null : { asset: data.asset, @@ -424,6 +427,23 @@ describe.skipIf(!API_URL)('rebuilding real on-chain messages', () => { }, 60_000); }); +describe('on-chain enhanced-send memo classification', () => { + it('declines the binary memo from the failing weekly sample', () => { + const original = '434e54525052545902841b0000001c61620c4b1a000f42405501c4c987c235584a64990c94bb49cf6b87c778df7a5820ff397c86e327b1623cdc0b6f0875d3ab1a4e21ab8a00441d937a8cb0d2bb641c'; + const decoded = unpackCounterpartyMessage(original); + expect(decoded.success).toBe(true); + expect(PARAMS_FROM_DECODED.enhanced_send!(decoded.data as Record)).toBeNull(); + }); + it('still rebuilds hexadecimal-looking text exactly', () => { + const packed = packComposeMessage('send', { asset: 'XCP', quantity: 1, destination: '1BoatSLRHtKNngkdXEeobR76b53LETtpyT', memo: 'deadbeef' })!; + const decoded = unpackCounterpartyMessage(packed.bytes); + expect(decoded.success).toBe(true); + const params = PARAMS_FROM_DECODED.enhanced_send!(decoded.data as Record); + expect(params).not.toBeNull(); + expect(bytesToHex(packComposeMessage('send', params!)!.bytes)).toBe(bytesToHex(packed.bytes)); + }); +}); + describe('on-chain fairminter sample classification', () => { // The real shape, shortened: CNTRPRTY, type 0x5a, then an array head and a body. Only the head // and the trailing bytes matter to the rule under test. diff --git a/src/pages/market/orders/[baseAsset]/[quoteAsset].tsx b/src/pages/market/orders/[baseAsset]/[quoteAsset].tsx index 09720429d..34ec3ee8c 100644 --- a/src/pages/market/orders/[baseAsset]/[quoteAsset].tsx +++ b/src/pages/market/orders/[baseAsset]/[quoteAsset].tsx @@ -175,6 +175,11 @@ function AssetOrdersView({ baseAsset, quoteAsset, tab, setTab, onAutoSelectTab, const sell: Order[] = []; orders.forEach(order => { + const amount = getOrderBaseAmount(order, baseAsset); + const quote = getOrderQuoteAmount(order, baseAsset); + const price = getOrderPricePerUnit(order, baseAsset); + // Open orders can have no remaining liquidity while matches await settlement. + if (![amount, quote, price].every(value => Number.isFinite(value) && value > 0)) return; if (order.give_asset === baseAsset) { sell.push(order); } else if (order.give_asset === quoteAsset) { diff --git a/src/pages/market/orders/__tests__/pair-pagination.test.tsx b/src/pages/market/orders/__tests__/pair-pagination.test.tsx index e2665d746..f075c5128 100644 --- a/src/pages/market/orders/__tests__/pair-pagination.test.tsx +++ b/src/pages/market/orders/__tests__/pair-pagination.test.tsx @@ -66,6 +66,18 @@ beforeEach(() => { afterEach(cleanup); describe('Order pair pagination ownership', () => { + it('excludes exhausted open buy and sell orders from price levels', async () => { + const exhaustedSell = { ...order('empty-sell', 'OLD', '2'), give_remaining_normalized: asDisplayUnits('0') }; + const exhaustedBuy = { ...order('empty-buy', 'XCP', '3'), get_asset: 'OLD', get_remaining_normalized: asDisplayUnits('0') }; + vi.mocked(fetchOrdersByPair).mockResolvedValue(page([exhaustedSell, exhaustedBuy, order('available')])); + render(); + await flush(); + expect(screen.getAllByTestId('level')).toHaveLength(1); + expect(screen.getByTestId('level')).toHaveTextContent('1: 1'); + await act(async () => { fireEvent.click(screen.getByRole('tab', { name: 'Buy' })); }); + expect(screen.queryByTestId('level')).not.toBeInTheDocument(); + }); + it('publishes the complete order book atomically after all pages finish', async () => { const last = deferred>(); vi.mocked(fetchOrdersByPair) From 60d14f8a41d1f0344e054d20211c08abd728671e Mon Sep 17 00:00:00 2001 From: Dan Anderson Date: Tue, 10 Nov 2026 00:08:18 -0500 Subject: [PATCH 2/3] Type the oracle consensus format explicitly --- src/core/counterparty/pack/__tests__/coreOracle.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/counterparty/pack/__tests__/coreOracle.test.ts b/src/core/counterparty/pack/__tests__/coreOracle.test.ts index 78debc39d..6783c68db 100644 --- a/src/core/counterparty/pack/__tests__/coreOracle.test.ts +++ b/src/core/counterparty/pack/__tests__/coreOracle.test.ts @@ -29,7 +29,7 @@ import { describe, expect, it } from 'vitest'; import { asBaseUnits } from '@/core/numeric'; -import { mpmaTableFormatAt } from '../../mpmaTableFormat'; +import { type MpmaTableFormat, mpmaTableFormatAt } from '../../mpmaTableFormat'; import { unpackCounterpartyMessage } from '../../unpack'; import { bytesToHex } from '../../unpack/binary'; import { packComposeMessage } from '../messages'; @@ -408,7 +408,7 @@ async function composeDataFromCore(testCase: OracleCase): Promise { describe.skipIf(!API_URL)('local packing matches counterparty-core', () => { it.each(CASES)('$label', async (testCase) => { // Like the wallet, establish the consensus table layout independently of compose bytes. - let mpmaTableFormat; + let mpmaTableFormat: MpmaTableFormat | null | undefined; if (testCase.composeType === 'mpma') { const response = await fetchOracle(`${API_URL}/v2/`); if (!response.ok) throw new Error(`Core status returned ${response.status}`); From 7e7b02bb69fd7b13b6b0564d31786b91650741b3 Mon Sep 17 00:00:00 2001 From: Dan Anderson Date: Tue, 10 Nov 2026 00:09:55 -0500 Subject: [PATCH 3/3] Narrow the validated MPMA format before packing --- src/core/counterparty/pack/__tests__/coreOracle.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/counterparty/pack/__tests__/coreOracle.test.ts b/src/core/counterparty/pack/__tests__/coreOracle.test.ts index 6783c68db..a201fe57c 100644 --- a/src/core/counterparty/pack/__tests__/coreOracle.test.ts +++ b/src/core/counterparty/pack/__tests__/coreOracle.test.ts @@ -408,13 +408,14 @@ async function composeDataFromCore(testCase: OracleCase): Promise { describe.skipIf(!API_URL)('local packing matches counterparty-core', () => { it.each(CASES)('$label', async (testCase) => { // Like the wallet, establish the consensus table layout independently of compose bytes. - let mpmaTableFormat: MpmaTableFormat | null | undefined; + let mpmaTableFormat: MpmaTableFormat | undefined; if (testCase.composeType === 'mpma') { const response = await fetchOracle(`${API_URL}/v2/`); if (!response.ok) throw new Error(`Core status returned ${response.status}`); const { result } = await response.json() as { result: { network: string; counterparty_height: number } }; - mpmaTableFormat = mpmaTableFormatAt(result.network, result.counterparty_height + 1); - if (!mpmaTableFormat) throw new Error('Cannot establish the MPMA table format for the oracle'); + const format = mpmaTableFormatAt(result.network, result.counterparty_height + 1); + if (!format) throw new Error('Cannot establish the MPMA table format for the oracle'); + mpmaTableFormat = format; } const fromCore = await composeDataFromCore(testCase);