Skip to content

Commit b9b5ab9

Browse files
ci(release): publish latest release
1 parent 0f117d8 commit b9b5ab9

File tree

7 files changed

+184
-69
lines changed

7 files changed

+184
-69
lines changed

RELEASE

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
IPFS hash of the deployment:
2-
- CIDv0: `QmTc9oJFYAwPhhTQi5NnPteHcvSYp7ZWiQ2V1WoanZjHU3`
3-
- CIDv1: `bafybeicoime5g2ighwvdo4r4x32cc3zpa7khfzrke4aym7gv7wbnl2jika`
2+
- CIDv0: `QmSuyBts5bYKUUmyvJymu3umtqsPx2JM2vyitDAowreoFj`
3+
- CIDv1: `bafybeicd67hq3fop37jiroeheeas66epmbb3g62e7qwvgpx2h5nivtbdmy`
44

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

@@ -10,14 +10,14 @@ 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://bafybeicoime5g2ighwvdo4r4x32cc3zpa7khfzrke4aym7gv7wbnl2jika.ipfs.dweb.link/
14-
- [ipfs://QmTc9oJFYAwPhhTQi5NnPteHcvSYp7ZWiQ2V1WoanZjHU3/](ipfs://QmTc9oJFYAwPhhTQi5NnPteHcvSYp7ZWiQ2V1WoanZjHU3/)
13+
- https://bafybeicd67hq3fop37jiroeheeas66epmbb3g62e7qwvgpx2h5nivtbdmy.ipfs.dweb.link/
14+
- [ipfs://QmSuyBts5bYKUUmyvJymu3umtqsPx2JM2vyitDAowreoFj/](ipfs://QmSuyBts5bYKUUmyvJymu3umtqsPx2JM2vyitDAowreoFj/)
1515

16-
### 5.119.1 (2025-11-20)
16+
### 5.119.2 (2025-11-21)
1717

1818

1919
### Bug Fixes
2020

21-
* **web:** show txns hash on activity table for all swaps until protocol data improves (#25649) 8571bc3
21+
* **web:** portfolio balance chart match/mismatch logic (#25687) e2596db
2222

2323

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
web/5.119.1
1+
web/5.119.2

apps/web/src/components/Charts/PriceChart/index.tsx

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export type PriceChartData = CandlestickData<UTCTimestamp> & AreaData<UTCTimesta
3434
interface PriceChartModelParams extends ChartModelParams<PriceChartData> {
3535
type: PriceChartType
3636
timePeriod?: GraphQLApi.HistoryDuration
37+
hideYAxis?: boolean
3738
}
3839

3940
const LOW_PRICE_RANGE_THRESHOLD = 0.2
@@ -157,7 +158,7 @@ export class PriceChartModel extends ChartModel<PriceChartData> {
157158
}
158159

159160
updateOptions(params: PriceChartModelParams) {
160-
const { data, colors, type, locale, format, tokenFormatType } = params
161+
const { data, colors, type, locale, format, tokenFormatType, hideYAxis } = params
161162
const { min, max } = getCandlestickPriceBounds(data)
162163

163164
// Handles changes in time period
@@ -192,11 +193,13 @@ export class PriceChartModel extends ChartModel<PriceChartData> {
192193
)
193194
},
194195
},
195-
...(scaleMargins && {
196-
rightPriceScale: {
196+
rightPriceScale: {
197+
borderVisible: false,
198+
...(hideYAxis && { visible: false, minimumWidth: 0 }),
199+
...(scaleMargins && {
197200
scaleMargins,
198-
},
199-
}),
201+
}),
202+
},
200203
})
201204

202205
// Handles changing between line/candlestick view
@@ -372,6 +375,8 @@ interface PriceChartProps {
372375
timePeriod?: GraphQLApi.HistoryDuration
373376
pricePercentChange24h?: number
374377
overrideColor?: string
378+
headerTotalValueOverride?: number
379+
hideYAxis?: boolean
375380
}
376381

377382
const CandlestickTooltipRow = styled(Flex, {
@@ -414,6 +419,8 @@ export function PriceChart({
414419
timePeriod,
415420
pricePercentChange24h,
416421
overrideColor,
422+
headerTotalValueOverride,
423+
hideYAxis,
417424
}: PriceChartProps) {
418425
const startingPrice = data[0]
419426
const lastPrice = data[data.length - 1]
@@ -427,31 +434,36 @@ export function PriceChart({
427434
return (
428435
<Chart
429436
Model={PriceChartModel}
430-
params={useMemo(() => ({ data, type, stale, timePeriod }), [data, stale, type, timePeriod])}
437+
params={useMemo(() => ({ data, type, stale, timePeriod, hideYAxis }), [data, stale, type, timePeriod, hideYAxis])}
431438
height={height}
432439
overrideColor={overrideColor}
433440
TooltipBody={type === PriceChartType.CANDLESTICK ? CandlestickTooltip : undefined}
434441
showDottedBackground={true}
435442
showLeftFadeOverlay={type === PriceChartType.LINE}
436443
showCustomHoverMarker={type === PriceChartType.LINE}
437444
>
438-
{(crosshairData) => (
439-
<ChartHeader
440-
value={(crosshairData ?? lastPrice).value}
441-
additionalFields={
442-
<PriceChartDelta
443-
startingPrice={startingPrice.close}
444-
endingPrice={(crosshairData ?? lastPrice).close}
445-
shouldIncludeFiatDelta
446-
shouldTreatAsStablecoin={shouldTreatAsStablecoin}
447-
pricePercentChange24h={pricePercentChange24h}
448-
isHovering={!!crosshairData}
449-
/>
450-
}
451-
valueFormatterType={NumberType.FiatTokenPrice}
452-
time={crosshairData?.time}
453-
/>
454-
)}
445+
{(crosshairData) => {
446+
// Use override value when provided, otherwise use chart data value
447+
const headerValue = crosshairData ? crosshairData.value : (headerTotalValueOverride ?? lastPrice.value)
448+
449+
return (
450+
<ChartHeader
451+
value={headerValue}
452+
additionalFields={
453+
<PriceChartDelta
454+
startingPrice={startingPrice.close}
455+
endingPrice={(crosshairData ?? lastPrice).close}
456+
shouldIncludeFiatDelta
457+
shouldTreatAsStablecoin={shouldTreatAsStablecoin}
458+
pricePercentChange24h={pricePercentChange24h}
459+
isHovering={!!crosshairData}
460+
/>
461+
}
462+
valueFormatterType={NumberType.FiatTokenPrice}
463+
time={crosshairData?.time}
464+
/>
465+
)
466+
}}
455467
</Chart>
456468
)
457469
}

apps/web/src/pages/Portfolio/Overview/Overview.tsx

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1+
import { ChartPeriod } from '@uniswap/client-data-api/dist/data/v1/api_pb'
12
import { EmptyWalletCards } from 'components/emptyWallet/EmptyWalletCards'
23
import { usePortfolioRoutes } from 'pages/Portfolio/Header/hooks/usePortfolioRoutes'
34
import { usePortfolioAddresses } from 'pages/Portfolio/hooks/usePortfolioAddresses'
45
import { OverviewActionTiles } from 'pages/Portfolio/Overview/ActionTiles'
56
import { OVERVIEW_RIGHT_COLUMN_WIDTH } from 'pages/Portfolio/Overview/constants'
7+
import { useIsPortfolioZero } from 'pages/Portfolio/Overview/hooks/useIsPortfolioZero'
68
import { PortfolioOverviewTables } from 'pages/Portfolio/Overview/OverviewTables'
79
import { PortfolioChart } from 'pages/Portfolio/Overview/PortfolioChart'
810
import { OverviewStatsTiles } from 'pages/Portfolio/Overview/StatsTiles'
9-
import { memo, useMemo } from 'react'
11+
import { checkBalanceDiffWithinRange } from 'pages/Portfolio/Overview/utils/checkBalanceDiffWithinRange'
12+
import { memo, useMemo, useState } from 'react'
1013
import { Flex, Separator, styled, useMedia } from 'ui/src'
14+
import { useGetPortfolioHistoricalValueChartQuery } from 'uniswap/src/data/rest/getPortfolioChart'
1115
import { useActivityData } from 'uniswap/src/features/activity/hooks/useActivityData'
1216
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
1317
import { usePortfolioTotalValue } from 'uniswap/src/features/dataApi/balances/balancesRest'
1418
import { ElementName, InterfacePageName, SectionName } from 'uniswap/src/features/telemetry/constants'
1519
import { Trace } from 'uniswap/src/features/telemetry/Trace'
1620
import { filterDefinedWalletAddresses } from 'utils/filterDefinedWalletAddresses'
1721

22+
const BALANCE_PERCENT_DIFFERENCE_THRESHOLD = 2
23+
1824
const ActionsAndStatsContainer = styled(Flex, {
1925
width: OVERVIEW_RIGHT_COLUMN_WIDTH,
2026
gap: '$spacing16',
@@ -35,18 +41,50 @@ export const PortfolioOverview = memo(function PortfolioOverview() {
3541
const isFullWidth = media.xl
3642
const { chainId } = usePortfolioRoutes()
3743
const portfolioAddresses = usePortfolioAddresses()
38-
const { isTestnetModeEnabled } = useEnabledChains()
44+
const { chains: allChainIds } = useEnabledChains()
45+
46+
const isPortfolioZero = useIsPortfolioZero()
47+
48+
const [selectedPeriod, setSelectedPeriod] = useState<ChartPeriod>(ChartPeriod.DAY)
49+
50+
const filterChainIds = useMemo(() => (chainId ? [chainId] : allChainIds), [chainId, allChainIds])
3951

40-
// Fetch portfolio total value to determine if portfolio is zero
4152
const { data: portfolioData } = usePortfolioTotalValue({
4253
evmAddress: portfolioAddresses.evmAddress,
4354
svmAddress: portfolioAddresses.svmAddress,
55+
chainIds: filterChainIds,
4456
})
4557

46-
const { balanceUSD } = portfolioData || {}
58+
// Fetch portfolio historical value chart data
59+
const {
60+
data: portfolioChartData,
61+
isPending: isChartPending,
62+
error: chartError,
63+
} = useGetPortfolioHistoricalValueChartQuery({
64+
input: {
65+
evmAddress: portfolioAddresses.evmAddress,
66+
svmAddress: portfolioAddresses.svmAddress,
67+
chainIds: filterChainIds,
68+
chartPeriod: selectedPeriod,
69+
},
70+
enabled: !!(portfolioAddresses.evmAddress || portfolioAddresses.svmAddress),
71+
})
72+
73+
// Get the latest value from chart endpoint (last point in the array) for comparison
74+
const chartTotalBalanceUSD = useMemo(() => {
75+
if (!portfolioChartData?.points || portfolioChartData.points.length === 0) {
76+
return undefined
77+
}
78+
const lastPoint = portfolioChartData.points[portfolioChartData.points.length - 1]
79+
return lastPoint.value
80+
}, [portfolioChartData])
4781

48-
// Calculate isPortfolioZero - denominated portfolio balance on testnet is always 0
49-
const isPortfolioZero = useMemo(() => !isTestnetModeEnabled && balanceUSD === 0, [isTestnetModeEnabled, balanceUSD])
82+
// Compare portfolio balance (EVM + Solana) with chart endpoint balance (for debugging/validation)
83+
const isTotalValueMatch = checkBalanceDiffWithinRange({
84+
chartTotalBalanceUSD,
85+
portfolioTotalBalanceUSD: portfolioData?.balanceUSD,
86+
percentDifferenceThreshold: BALANCE_PERCENT_DIFFERENCE_THRESHOLD,
87+
})
5088

5189
// Fetch activity data once at the top level to share between useSwapsThisWeek and MiniActivityTable
5290
const activityData = useActivityData({
@@ -63,7 +101,16 @@ export const PortfolioOverview = memo(function PortfolioOverview() {
63101
<Flex gap="$spacing40" mb="$spacing40">
64102
<Flex row gap="$spacing40" $xl={{ flexDirection: 'column' }}>
65103
<Trace section={SectionName.PortfolioOverviewTab} element={ElementName.PortfolioChart}>
66-
<PortfolioChart isPortfolioZero={isPortfolioZero} />
104+
<PortfolioChart
105+
portfolioTotalBalanceUSD={portfolioData?.balanceUSD}
106+
isPortfolioZero={isPortfolioZero}
107+
chartData={portfolioChartData}
108+
isPending={isChartPending}
109+
error={chartError}
110+
selectedPeriod={selectedPeriod}
111+
setSelectedPeriod={setSelectedPeriod}
112+
isTotalValueMatch={isTotalValueMatch}
113+
/>
67114
</Trace>
68115
{isPortfolioZero ? (
69116
<ActionsAndStatsContainer minHeight={120} fullWidth={isFullWidth}>

apps/web/src/pages/Portfolio/Overview/PortfolioChart.tsx

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { ChartPeriod } from '@uniswap/client-data-api/dist/data/v1/api_pb'
1+
import { ChartPeriod, GetPortfolioChartResponse } from '@uniswap/client-data-api/dist/data/v1/api_pb'
22
import { GraphQLApi } from '@universe/api'
33
import { ChartSkeleton } from 'components/Charts/LoadingState'
44
import { PriceChart, PriceChartData } from 'components/Charts/PriceChart'
55
import { ChartType, PriceChartType } from 'components/Charts/utils'
66
import { UTCTimestamp } from 'lightweight-charts'
7-
import { usePortfolioRoutes } from 'pages/Portfolio/Header/hooks/usePortfolioRoutes'
8-
import { usePortfolioAddresses } from 'pages/Portfolio/hooks/usePortfolioAddresses'
9-
import { useMemo, useState } from 'react'
7+
import { useMemo } from 'react'
108
import { useTranslation } from 'react-i18next'
119
import {
1210
Flex,
@@ -18,8 +16,6 @@ import {
1816
useMedia,
1917
useSporeColors,
2018
} from 'ui/src'
21-
import { useGetPortfolioHistoricalValueChartQuery } from 'uniswap/src/data/rest/getPortfolioChart'
22-
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
2319
import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext'
2420
import { NumberType } from 'utilities/src/format/types'
2521

@@ -70,17 +66,29 @@ function convertPortfolioChartDataToPriceChartData(
7066

7167
interface PortfolioChartProps {
7268
isPortfolioZero: boolean
69+
chartData?: GetPortfolioChartResponse
70+
isPending: boolean
71+
error?: Error | null
72+
selectedPeriod: ChartPeriod
73+
setSelectedPeriod: (period: ChartPeriod) => void
74+
portfolioTotalBalanceUSD?: number
75+
isTotalValueMatch: boolean
7376
}
7477

75-
export function PortfolioChart({ isPortfolioZero }: PortfolioChartProps): JSX.Element {
78+
export function PortfolioChart({
79+
isPortfolioZero,
80+
chartData: portfolioChartData,
81+
isPending,
82+
error,
83+
portfolioTotalBalanceUSD,
84+
selectedPeriod,
85+
setSelectedPeriod,
86+
isTotalValueMatch,
87+
}: PortfolioChartProps): JSX.Element {
7688
const { t } = useTranslation()
7789
const media = useMedia()
7890
const colors = useSporeColors()
79-
const { chainId } = usePortfolioRoutes()
80-
const portfolioAddresses = usePortfolioAddresses()
81-
const { chains: chainIds } = useEnabledChains()
8291
const { convertFiatAmountFormatted } = useLocalizationContext()
83-
const [selectedPeriod, setSelectedPeriod] = useState<ChartPeriod>(ChartPeriod.DAY)
8492

8593
const periodOptions = useMemo<Array<SegmentedControlOption<string>>>(() => {
8694
const options: Array<[ChartPeriod, string]> = [
@@ -100,22 +108,12 @@ export function PortfolioChart({ isPortfolioZero }: PortfolioChartProps): JSX.El
100108
}))
101109
}, [selectedPeriod, t])
102110

103-
const { data, isPending, error } = useGetPortfolioHistoricalValueChartQuery({
104-
input: {
105-
evmAddress: portfolioAddresses.evmAddress,
106-
svmAddress: portfolioAddresses.svmAddress,
107-
chainIds: chainId ? [chainId] : chainIds,
108-
chartPeriod: selectedPeriod,
109-
},
110-
enabled: !!(portfolioAddresses.evmAddress || portfolioAddresses.svmAddress),
111-
})
112-
113111
const chartData = useMemo(() => {
114-
if (!data?.points) {
112+
if (!portfolioChartData?.points) {
115113
return []
116114
}
117-
return convertPortfolioChartDataToPriceChartData(data.points)
118-
}, [data])
115+
return convertPortfolioChartDataToPriceChartData(portfolioChartData.points)
116+
}, [portfolioChartData])
119117

120118
// Determine color based on portfolio balance change
121119
const chartColor = useMemo(() => {
@@ -163,14 +161,18 @@ export function PortfolioChart({ isPortfolioZero }: PortfolioChartProps): JSX.El
163161
<Separator borderBottomWidth={3} borderColor="$surface3" position="absolute" top="60%" left="0" right="0" />
164162
</Flex>
165163
) : (
166-
<PriceChart
167-
data={chartData}
168-
height={CHART_HEIGHT}
169-
type={PriceChartType.LINE}
170-
stale={false}
171-
timePeriod={chartPeriodToHistoryDuration(selectedPeriod)}
172-
overrideColor={chartColor}
173-
/>
164+
<Flex pointerEvents={isTotalValueMatch ? 'auto' : 'none'}>
165+
<PriceChart
166+
data={chartData}
167+
height={CHART_HEIGHT}
168+
type={PriceChartType.LINE}
169+
stale={false}
170+
timePeriod={chartPeriodToHistoryDuration(selectedPeriod)}
171+
overrideColor={chartColor}
172+
headerTotalValueOverride={portfolioTotalBalanceUSD}
173+
hideYAxis={!isTotalValueMatch}
174+
/>
175+
</Flex>
174176
)}
175177
<Flex
176178
$md={{ width: '100%' }}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { usePortfolioAddresses } from 'pages/Portfolio/hooks/usePortfolioAddresses'
2+
import { useMemo } from 'react'
3+
import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains'
4+
import { usePortfolioTotalValue } from 'uniswap/src/features/dataApi/balances/balancesRest'
5+
6+
/**
7+
* Hook to determine if the portfolio balance is zero.
8+
* Denominated portfolio balance on testnet is always 0, so we don't consider it zero in testnet mode.
9+
*/
10+
export function useIsPortfolioZero(): boolean {
11+
const portfolioAddresses = usePortfolioAddresses()
12+
const { isTestnetModeEnabled } = useEnabledChains()
13+
14+
// Fetch portfolio total value to determine if portfolio is zero
15+
const { data: portfolioData } = usePortfolioTotalValue({
16+
evmAddress: portfolioAddresses.evmAddress,
17+
svmAddress: portfolioAddresses.svmAddress,
18+
})
19+
20+
const { balanceUSD } = portfolioData || {}
21+
22+
// Calculate isPortfolioZero - denominated portfolio balance on testnet is always 0
23+
return useMemo(() => !isTestnetModeEnabled && balanceUSD === 0, [isTestnetModeEnabled, balanceUSD])
24+
}

0 commit comments

Comments
 (0)