Skip to content

Commit ff6a079

Browse files
ci(release): publish latest release
1 parent bcc1bec commit ff6a079

6 files changed

Lines changed: 94 additions & 7 deletions

File tree

RELEASE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
IPFS hash of the deployment:
2-
- CIDv0: `QmVjGEHerSD81dhou2gzGRJFYnKcbVf5oZbXuQuRWL8NuA`
3-
- CIDv1: `bafybeidnzs75ddd5ysduzxa4goru7tjgruipttu4gegc6vc7gx6i66fbfu`
2+
- CIDv0: `QmaR8WRjuT9xkFxFJTTQ4BFXvA8BfkgA2XHcADdWe8cAeN`
3+
- CIDv1: `bafybeiftoh7zj7xup6f3aee5tkqqm7ubp5vbvspvyt5oi4xcxmwutbt7am`
44

55
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
66

@@ -10,5 +10,5 @@ You can also access the Uniswap Interface from an IPFS gateway.
1010
Your Uniswap settings are never remembered across different URLs.
1111

1212
IPFS gateways:
13-
- https://bafybeidnzs75ddd5ysduzxa4goru7tjgruipttu4gegc6vc7gx6i66fbfu.ipfs.dweb.link/
14-
- [ipfs://QmVjGEHerSD81dhou2gzGRJFYnKcbVf5oZbXuQuRWL8NuA/](ipfs://QmVjGEHerSD81dhou2gzGRJFYnKcbVf5oZbXuQuRWL8NuA/)
13+
- https://bafybeiftoh7zj7xup6f3aee5tkqqm7ubp5vbvspvyt5oi4xcxmwutbt7am.ipfs.dweb.link/
14+
- [ipfs://QmaR8WRjuT9xkFxFJTTQ4BFXvA8BfkgA2XHcADdWe8cAeN/](ipfs://QmaR8WRjuT9xkFxFJTTQ4BFXvA8BfkgA2XHcADdWe8cAeN/)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
web/5.149.1
1+
web/5.149.2
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { getCurrentUTCTimestamp } from '~/components/Charts/utils'
2+
3+
describe('getCurrentUTCTimestamp', () => {
4+
it('returns whole integer seconds, not a fractional value', () => {
5+
// Date.now() is millisecond-precision; lightweight-charts UTCTimestamp must be an integer second.
6+
vi.spyOn(Date, 'now').mockReturnValue(1781279676509)
7+
8+
const result = getCurrentUTCTimestamp()
9+
10+
expect(result).toBe(1781279676)
11+
expect(Number.isInteger(result)).toBe(true)
12+
})
13+
})

apps/web/src/components/Charts/utils.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ export function withUTCTimestamp<T extends { timestamp: number }>(entry: T): T &
8787
return { ...entry, time: entry.timestamp as UTCTimestamp }
8888
}
8989

90-
/** Current time as lightweight-charts UTCTimestamp (seconds since epoch). */
90+
/** Current time as lightweight-charts UTCTimestamp (whole seconds since epoch). */
9191
export function getCurrentUTCTimestamp(): UTCTimestamp {
92-
return (Date.now() / 1000) as UTCTimestamp
92+
// lightweight-charts requires integer UTCTimestamps; Date.now() is millisecond-precision,
93+
// so floor to whole seconds to avoid fractional times in the chart series.
94+
return Math.floor(Date.now() / 1000) as UTCTimestamp
9395
}
9496

9597
/**

apps/web/src/hooks/useTokenPriceChartData.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ChartType, DataQuality, PriceChartType } from '~/components/Charts/util
55
import {
66
getCalculatedPricePercentChange,
77
getDisplayedPricePercentChange,
8+
toStrictlyAscendingByTime,
89
useTokenPriceChartData,
910
} from '~/hooks/useTokenPriceChartData'
1011
import { renderHook } from '~/test-utils/render'
@@ -245,6 +246,33 @@ describe('useTokenPriceChartData', () => {
245246
expect(result.current.dataQuality).toBe(DataQuality.VALID)
246247
expect(result.current.entries[0].value).toBe(9)
247248
})
249+
250+
it('produces strictly ascending timestamps when CoinGecko returns a duplicate trailing timestamp', () => {
251+
// Upstream CoinGecko history can end with two points at the same second. lightweight-charts
252+
// requires strictly-ascending times; a zero delta breaks curved-line interpolation and paints
253+
// a spurious diagonal line/wedge across the chart.
254+
mockUseTokenPriceHistoryQuery.mockReturnValue(
255+
makeCoinGeckoResult([
256+
priceHistoryEntry(1000, 20),
257+
priceHistoryEntry(2000, 21),
258+
priceHistoryEntry(3000, 22),
259+
priceHistoryEntry(3000, 22),
260+
]),
261+
)
262+
263+
const { result } = renderHook(() =>
264+
useTokenPriceChartData({
265+
variables: BASE_VARIABLES,
266+
skip: false,
267+
priceChartType: PriceChartType.LINE,
268+
}),
269+
)
270+
271+
const times = result.current.entries.map((entry) => entry.time)
272+
for (let i = 1; i < times.length; i++) {
273+
expect(times[i]).toBeGreaterThan(times[i - 1])
274+
}
275+
})
248276
})
249277

250278
function point(time: number, close: number): PriceChartData {
@@ -286,3 +314,21 @@ describe('getDisplayedPricePercentChange', () => {
286314
).toBe(300)
287315
})
288316
})
317+
318+
describe('toStrictlyAscendingByTime', () => {
319+
it('collapses duplicate timestamps, keeping the latest value', () => {
320+
const result = toStrictlyAscendingByTime([point(1000, 10), point(2000, 11), point(3000, 12), point(3000, 13)])
321+
expect(result.map((entry) => entry.time)).toEqual([1000, 2000, 3000])
322+
expect(result[result.length - 1].value).toBe(13)
323+
})
324+
325+
it('drops out-of-order timestamps', () => {
326+
const result = toStrictlyAscendingByTime([point(1000, 10), point(3000, 12), point(2000, 11)])
327+
expect(result.map((entry) => entry.time)).toEqual([1000, 3000])
328+
})
329+
330+
it('leaves already-ascending data untouched', () => {
331+
const entries = [point(1000, 10), point(2000, 11), point(3000, 12)]
332+
expect(toStrictlyAscendingByTime(entries)).toEqual(entries)
333+
})
334+
})

apps/web/src/hooks/useTokenPriceChartData.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,27 @@ function toPriceChartData(ohlc: GraphQLApi.CandlestickOhlcFragment): PriceChartD
3636
return { time, value: close.value, open: open.value, high: high.value, low: low.value, close: close.value }
3737
}
3838

39+
/**
40+
* Enforces the strictly-ascending timestamps lightweight-charts requires. Upstream price history
41+
* (e.g. CoinGecko) occasionally returns a duplicate trailing timestamp; a zero time delta breaks
42+
* the curved line/area interpolation and paints a spurious diagonal line and filled wedge across
43+
* the chart. Duplicates collapse to the latest value; any out-of-order point is dropped.
44+
*/
45+
export function toStrictlyAscendingByTime(entries: PriceChartData[]): PriceChartData[] {
46+
const result: PriceChartData[] = []
47+
let lastTime: number | undefined
48+
for (const entry of entries) {
49+
if (lastTime === undefined || entry.time > lastTime) {
50+
result.push(entry)
51+
lastTime = entry.time
52+
} else if (entry.time === lastTime) {
53+
result[result.length - 1] = entry
54+
}
55+
// entry.time < lastTime (out of order) -> drop
56+
}
57+
return result
58+
}
59+
3960
const CANDLESTICK_FALLBACK_THRESHOLD = 0.1
4061

4162
export function useTokenPriceChartData({
@@ -209,6 +230,11 @@ export function useTokenPriceChartData({
209230
}
210231
}
211232

233+
// Sanitize timestamps before appending: drop duplicate/out-of-order points so the chart's
234+
// curved interpolation doesn't break, and so the granularity calc below isn't poisoned by a
235+
// zero delta between two identical trailing timestamps.
236+
entries = toStrictlyAscendingByTime(entries)
237+
212238
// Append current price to end of array to ensure data freshness and that each time period ends with same price
213239
if (currentPrice && entries.length > 1) {
214240
const lastEntry = entries[entries.length - 1]

0 commit comments

Comments
 (0)