From bab81264d2e1dbda8abc0f64519e4d76f7864189 Mon Sep 17 00:00:00 2001 From: David Meister Date: Wed, 17 Jun 2026 02:03:51 +0000 Subject: [PATCH 1/5] fix: guard weighted-average merge against zero outputAmountSum in historicalOrderCharts When two or more trades share a timestamp and their outputVaultBalanceChange.amount values sum to zero, dividing ioratioSum by outputAmountSum produced NaN/Infinity. Falls back to unweighted mean of ioratio values when outputAmountSum === 0. Closes #2766 Co-Authored-By: Claude --- .../src/lib/services/historicalOrderCharts.ts | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/packages/ui-components/src/lib/services/historicalOrderCharts.ts b/packages/ui-components/src/lib/services/historicalOrderCharts.ts index c0d6dfd539..3347440b46 100644 --- a/packages/ui-components/src/lib/services/historicalOrderCharts.ts +++ b/packages/ui-components/src/lib/services/historicalOrderCharts.ts @@ -48,7 +48,11 @@ export function prepareHistoricalOrderChartData( (acc, d) => acc + d.outputAmount, 0, ); - const ioratioAverage = ioratioSum / outputAmountSum; + const ioratioAverage = + outputAmountSum === 0 + ? objectsWithSameTimestamp.reduce((acc, d) => acc + d.value, 0) / + objectsWithSameTimestamp.length + : ioratioSum / outputAmountSum; finalData.push({ value: ioratioAverage, time: timestamp, @@ -466,4 +470,86 @@ if (import.meta.vitest) { expect(result.length).toEqual(1); expect(result[0].value).toEqual(ioratioAverage); }); + + it("falls back to unweighted mean when same-timestamp outputAmounts sum to zero", () => { + // outputVaultBalanceChange.amount (BigInt) = 0 for both trades → outputAmountSum = 0. + // outputVaultBalanceChange.formattedAmount = "100" (non-zero) so value is computable. + // Trade 1: value = |200/100| = 2, trade 2: value = |400/100| = 4. + // Fallback: unweighted mean = (2 + 4) / 2 = 3. + const makeZeroAmountTrade = ( + id: string, + inputFormattedAmount: string, + ): RaindexTrade => ({ + id, + timestamp: BigInt(1632000000), + transaction: { + id: "tx", + from: "0xsender", + timestamp: BigInt(1632000000), + blockNumber: BigInt(0), + } as unknown as RaindexTransaction, + outputVaultBalanceChange: { + amount: BigInt(0), + formattedAmount: "100", + vaultId: BigInt(1), + __typename: "Withdraw", + token: { + id: "tok", + address: "0xtok", + name: "tok", + symbol: "tok", + decimals: BigInt(1), + } as unknown as RaindexVaultToken, + newBalance: BigInt(0), + formattedNewBalance: "0", + oldBalance: BigInt(0), + formattedOldBalance: "0", + timestamp: BigInt(0), + transaction: { + id: "tx", + from: "0xsender", + timestamp: BigInt(1632000000), + blockNumber: BigInt(0), + } as unknown as RaindexTransaction, + raindex: "0x1", + } as unknown as RaindexVaultBalanceChange, + orderHash: "orderHash", + inputVaultBalanceChange: { + vaultId: BigInt(1), + token: { + id: "tok", + address: "0xtok", + name: "tok", + symbol: "tok", + decimals: BigInt(1), + } as unknown as RaindexVaultToken, + amount: BigInt(0), + formattedAmount: inputFormattedAmount, + __typename: "Withdraw", + newBalance: BigInt(0), + formattedNewBalance: "0", + oldBalance: BigInt(0), + formattedOldBalance: "0", + timestamp: BigInt(0), + transaction: { + id: "tx", + from: "0xsender", + timestamp: BigInt(1632000000), + blockNumber: BigInt(0), + } as unknown as RaindexTransaction, + raindex: "0x1", + } as unknown as RaindexVaultBalanceChange, + raindex: "0x00", + }); + + const takeOrderEntities: RaindexTrade[] = [ + makeZeroAmountTrade("1", "200"), + makeZeroAmountTrade("2", "400"), + ]; + + const result = prepareHistoricalOrderChartData(takeOrderEntities, "dark"); + expect(result.length).toEqual(1); + expect(isFinite(result[0].value)).toBe(true); + expect(result[0].value).toEqual(3); + }); } From 4e8332e035b0326d3222888c63471a6ce6e57a23 Mon Sep 17 00:00:00 2001 From: David Meister Date: Wed, 17 Jun 2026 13:20:34 +0000 Subject: [PATCH 2/5] fix(ci): correct 0x-prefixed string types in makeZeroAmountTrade test helper id parameter and orderHash literal in the inline test helper must satisfy the `0x${string}` template literal type required by RaindexTrade. Co-Authored-By: Claude --- .../src/lib/services/historicalOrderCharts.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui-components/src/lib/services/historicalOrderCharts.ts b/packages/ui-components/src/lib/services/historicalOrderCharts.ts index 3347440b46..8eadfd84f7 100644 --- a/packages/ui-components/src/lib/services/historicalOrderCharts.ts +++ b/packages/ui-components/src/lib/services/historicalOrderCharts.ts @@ -477,7 +477,7 @@ if (import.meta.vitest) { // Trade 1: value = |200/100| = 2, trade 2: value = |400/100| = 4. // Fallback: unweighted mean = (2 + 4) / 2 = 3. const makeZeroAmountTrade = ( - id: string, + id: `0x${string}`, inputFormattedAmount: string, ): RaindexTrade => ({ id, @@ -513,7 +513,7 @@ if (import.meta.vitest) { } as unknown as RaindexTransaction, raindex: "0x1", } as unknown as RaindexVaultBalanceChange, - orderHash: "orderHash", + orderHash: "0xorderHash" as `0x${string}`, inputVaultBalanceChange: { vaultId: BigInt(1), token: { @@ -543,8 +543,8 @@ if (import.meta.vitest) { }); const takeOrderEntities: RaindexTrade[] = [ - makeZeroAmountTrade("1", "200"), - makeZeroAmountTrade("2", "400"), + makeZeroAmountTrade("0x1", "200"), + makeZeroAmountTrade("0x2", "400"), ]; const result = prepareHistoricalOrderChartData(takeOrderEntities, "dark"); From 91ec40c5665db04d05e82c5188776c19c62da380 Mon Sep 17 00:00:00 2001 From: David Meister Date: Wed, 17 Jun 2026 13:24:18 +0000 Subject: [PATCH 3/5] fix(ci): cast makeZeroAmountTrade return to RaindexTrade via unknown RaindexTrade is a WASM class with a private constructor and additional required fields (chainId, formattedIoRatio, ioRatio, owner). The inline test helper builds a partial object literal, so cast through unknown rather than enumerate all required properties. Co-Authored-By: Claude --- .../ui-components/src/lib/services/historicalOrderCharts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-components/src/lib/services/historicalOrderCharts.ts b/packages/ui-components/src/lib/services/historicalOrderCharts.ts index 8eadfd84f7..f4c81593a9 100644 --- a/packages/ui-components/src/lib/services/historicalOrderCharts.ts +++ b/packages/ui-components/src/lib/services/historicalOrderCharts.ts @@ -479,7 +479,7 @@ if (import.meta.vitest) { const makeZeroAmountTrade = ( id: `0x${string}`, inputFormattedAmount: string, - ): RaindexTrade => ({ + ) => ({ id, timestamp: BigInt(1632000000), transaction: { @@ -540,7 +540,7 @@ if (import.meta.vitest) { raindex: "0x1", } as unknown as RaindexVaultBalanceChange, raindex: "0x00", - }); + }) as unknown as RaindexTrade; const takeOrderEntities: RaindexTrade[] = [ makeZeroAmountTrade("0x1", "200"), From 671e15fdb7bce03212cb3e283cce87bb2d9cf6ad Mon Sep 17 00:00:00 2001 From: David Meister Date: Wed, 17 Jun 2026 17:17:47 +0000 Subject: [PATCH 4/5] fix(ci): svelte-lint-format-check [3b-attempt] Run prettier on historicalOrderCharts.ts. --- .../src/lib/services/historicalOrderCharts.ts | 111 +++++++++--------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/packages/ui-components/src/lib/services/historicalOrderCharts.ts b/packages/ui-components/src/lib/services/historicalOrderCharts.ts index f4c81593a9..cc098a58a3 100644 --- a/packages/ui-components/src/lib/services/historicalOrderCharts.ts +++ b/packages/ui-components/src/lib/services/historicalOrderCharts.ts @@ -479,68 +479,69 @@ if (import.meta.vitest) { const makeZeroAmountTrade = ( id: `0x${string}`, inputFormattedAmount: string, - ) => ({ - id, - timestamp: BigInt(1632000000), - transaction: { - id: "tx", - from: "0xsender", + ) => + ({ + id, timestamp: BigInt(1632000000), - blockNumber: BigInt(0), - } as unknown as RaindexTransaction, - outputVaultBalanceChange: { - amount: BigInt(0), - formattedAmount: "100", - vaultId: BigInt(1), - __typename: "Withdraw", - token: { - id: "tok", - address: "0xtok", - name: "tok", - symbol: "tok", - decimals: BigInt(1), - } as unknown as RaindexVaultToken, - newBalance: BigInt(0), - formattedNewBalance: "0", - oldBalance: BigInt(0), - formattedOldBalance: "0", - timestamp: BigInt(0), transaction: { id: "tx", from: "0xsender", timestamp: BigInt(1632000000), blockNumber: BigInt(0), } as unknown as RaindexTransaction, - raindex: "0x1", - } as unknown as RaindexVaultBalanceChange, - orderHash: "0xorderHash" as `0x${string}`, - inputVaultBalanceChange: { - vaultId: BigInt(1), - token: { - id: "tok", - address: "0xtok", - name: "tok", - symbol: "tok", - decimals: BigInt(1), - } as unknown as RaindexVaultToken, - amount: BigInt(0), - formattedAmount: inputFormattedAmount, - __typename: "Withdraw", - newBalance: BigInt(0), - formattedNewBalance: "0", - oldBalance: BigInt(0), - formattedOldBalance: "0", - timestamp: BigInt(0), - transaction: { - id: "tx", - from: "0xsender", - timestamp: BigInt(1632000000), - blockNumber: BigInt(0), - } as unknown as RaindexTransaction, - raindex: "0x1", - } as unknown as RaindexVaultBalanceChange, - raindex: "0x00", - }) as unknown as RaindexTrade; + outputVaultBalanceChange: { + amount: BigInt(0), + formattedAmount: "100", + vaultId: BigInt(1), + __typename: "Withdraw", + token: { + id: "tok", + address: "0xtok", + name: "tok", + symbol: "tok", + decimals: BigInt(1), + } as unknown as RaindexVaultToken, + newBalance: BigInt(0), + formattedNewBalance: "0", + oldBalance: BigInt(0), + formattedOldBalance: "0", + timestamp: BigInt(0), + transaction: { + id: "tx", + from: "0xsender", + timestamp: BigInt(1632000000), + blockNumber: BigInt(0), + } as unknown as RaindexTransaction, + raindex: "0x1", + } as unknown as RaindexVaultBalanceChange, + orderHash: "0xorderHash" as `0x${string}`, + inputVaultBalanceChange: { + vaultId: BigInt(1), + token: { + id: "tok", + address: "0xtok", + name: "tok", + symbol: "tok", + decimals: BigInt(1), + } as unknown as RaindexVaultToken, + amount: BigInt(0), + formattedAmount: inputFormattedAmount, + __typename: "Withdraw", + newBalance: BigInt(0), + formattedNewBalance: "0", + oldBalance: BigInt(0), + formattedOldBalance: "0", + timestamp: BigInt(0), + transaction: { + id: "tx", + from: "0xsender", + timestamp: BigInt(1632000000), + blockNumber: BigInt(0), + } as unknown as RaindexTransaction, + raindex: "0x1", + } as unknown as RaindexVaultBalanceChange, + raindex: "0x00", + }) as unknown as RaindexTrade; const takeOrderEntities: RaindexTrade[] = [ makeZeroAmountTrade("0x1", "200"), From e159359d9522d04752fcdbc44bca6c31b3d00912 Mon Sep 17 00:00:00 2001 From: David Meister Date: Thu, 18 Jun 2026 21:31:11 +0000 Subject: [PATCH 5/5] fix: filter non-finite ioratio values before unweighted-mean fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When outputVaultBalanceChange.amount is BigInt(0) the formattedAmount is also "0", making the per-trade value Infinity/NaN. The previous fallback divided Infinity × N / N = Infinity. Filter non-finite values before the unweighted mean; if every entry is non-finite (i.e. all output amounts are zero), return 0. Update the test fixture to use formattedAmount:"0" (consistent with amount BigInt(0)) and tighten the assertion to expect value 0. Co-Authored-By: Claude --- .../src/lib/services/historicalOrderCharts.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/ui-components/src/lib/services/historicalOrderCharts.ts b/packages/ui-components/src/lib/services/historicalOrderCharts.ts index cc098a58a3..4c375b8a15 100644 --- a/packages/ui-components/src/lib/services/historicalOrderCharts.ts +++ b/packages/ui-components/src/lib/services/historicalOrderCharts.ts @@ -48,10 +48,14 @@ export function prepareHistoricalOrderChartData( (acc, d) => acc + d.outputAmount, 0, ); + const finiteValues = objectsWithSameTimestamp + .map((d) => d.value) + .filter((v) => isFinite(v)); const ioratioAverage = outputAmountSum === 0 - ? objectsWithSameTimestamp.reduce((acc, d) => acc + d.value, 0) / - objectsWithSameTimestamp.length + ? finiteValues.length > 0 + ? finiteValues.reduce((acc, v) => acc + v, 0) / finiteValues.length + : 0 : ioratioSum / outputAmountSum; finalData.push({ value: ioratioAverage, @@ -471,11 +475,10 @@ if (import.meta.vitest) { expect(result[0].value).toEqual(ioratioAverage); }); - it("falls back to unweighted mean when same-timestamp outputAmounts sum to zero", () => { + it("falls back to zero when same-timestamp outputAmounts sum to zero and all ioratios are non-finite", () => { // outputVaultBalanceChange.amount (BigInt) = 0 for both trades → outputAmountSum = 0. - // outputVaultBalanceChange.formattedAmount = "100" (non-zero) so value is computable. - // Trade 1: value = |200/100| = 2, trade 2: value = |400/100| = 4. - // Fallback: unweighted mean = (2 + 4) / 2 = 3. + // outputVaultBalanceChange.formattedAmount = "0" (consistent with amount = 0) → value = Infinity (non-finite). + // Fallback: filter non-finite values → empty → return 0. const makeZeroAmountTrade = ( id: `0x${string}`, inputFormattedAmount: string, @@ -491,7 +494,7 @@ if (import.meta.vitest) { } as unknown as RaindexTransaction, outputVaultBalanceChange: { amount: BigInt(0), - formattedAmount: "100", + formattedAmount: "0", vaultId: BigInt(1), __typename: "Withdraw", token: { @@ -550,7 +553,6 @@ if (import.meta.vitest) { const result = prepareHistoricalOrderChartData(takeOrderEntities, "dark"); expect(result.length).toEqual(1); - expect(isFinite(result[0].value)).toBe(true); - expect(result[0].value).toEqual(3); + expect(result[0].value).toEqual(0); }); }