diff --git a/.changeset/empty-bags-worry.md b/.changeset/empty-bags-worry.md new file mode 100644 index 00000000000..fea365a8911 --- /dev/null +++ b/.changeset/empty-bags-worry.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Replace old MarketDataProvider with new implem and TanStackQuery diff --git a/.changeset/few-radios-eat.md b/.changeset/few-radios-eat.md new file mode 100644 index 00000000000..3c30dc4c9f6 --- /dev/null +++ b/.changeset/few-radios-eat.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +New MArket Api usage diff --git a/.changeset/moody-cherries-float.md b/.changeset/moody-cherries-float.md new file mode 100644 index 00000000000..5614df9830a --- /dev/null +++ b/.changeset/moody-cherries-float.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Replace old import with new paths diff --git a/.changeset/odd-bees-matter.md b/.changeset/odd-bees-matter.md new file mode 100644 index 00000000000..a40a575d1fe --- /dev/null +++ b/.changeset/odd-bees-matter.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +New MArket API usage diff --git a/.changeset/red-clouds-care.md b/.changeset/red-clouds-care.md new file mode 100644 index 00000000000..3ec77a320bb --- /dev/null +++ b/.changeset/red-clouds-care.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-common": patch +--- + +Reorg files diff --git a/.changeset/shiny-fireants-chew.md b/.changeset/shiny-fireants-chew.md new file mode 100644 index 00000000000..b61377e59f2 --- /dev/null +++ b/.changeset/shiny-fireants-chew.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-common": minor +--- + +Usage of CVS v3 instead of V2 on Market part diff --git a/apps/ledger-live-desktop/src/renderer/actions/market.ts b/apps/ledger-live-desktop/src/renderer/actions/market.ts index e027c41e169..733993889df 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/market.ts +++ b/apps/ledger-live-desktop/src/renderer/actions/market.ts @@ -1,4 +1,4 @@ -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; export const setMarketOptions = (payload: MarketListRequestParams) => ({ type: "MARKET_SET_VALUES", diff --git a/apps/ledger-live-desktop/src/renderer/reducers/market.ts b/apps/ledger-live-desktop/src/renderer/reducers/market.ts index 543d6385ea3..38cef3c991f 100644 --- a/apps/ledger-live-desktop/src/renderer/reducers/market.ts +++ b/apps/ledger-live-desktop/src/renderer/reducers/market.ts @@ -1,6 +1,6 @@ import { handleActions } from "redux-actions"; import { Handlers } from "./types"; -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams, Order } from "@ledgerhq/live-common/market/utils/types"; export type MarketState = { marketParams: MarketListRequestParams; @@ -11,10 +11,8 @@ const initialState: MarketState = { marketParams: { range: "24h", limit: 50, - ids: [], starred: [], - orderBy: "market_cap", - order: "desc", + order: Order.MarketCapDesc, search: "", liveCompatible: false, page: 1, diff --git a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/__tests__/useMarketPerformanceWidget.test.ts b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/__tests__/useMarketPerformanceWidget.test.ts index 2a75b10bf95..2e41ab17966 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/__tests__/useMarketPerformanceWidget.test.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/__tests__/useMarketPerformanceWidget.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "@jest/globals"; import { Order } from "../types"; -import { MarketItemPerformer } from "@ledgerhq/live-common/market/types"; +import { MarketItemPerformer } from "@ledgerhq/live-common/market/utils/types"; import { getChangePercentage, getSlicedList } from "../utils"; const createElem = (change: number): MarketItemPerformer => ({ diff --git a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/components/WidgetList.tsx b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/components/WidgetList.tsx index 29454d4851b..30ffb024037 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/components/WidgetList.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/components/WidgetList.tsx @@ -86,7 +86,7 @@ function WidgetRow({ data, index, isFirst, range }: PropsBodyElem) { {!data.price ? "-" : counterValueFormatter({ - value: Number(parseFloat(String(data.price)).toFixed(data.price > 1 ? 2 : 8)), + value: Number(parseFloat(String(data.price)).toFixed(data.price > 1 ? 2 : 6)), currency: counterValueCurrency.ticker, locale, })} diff --git a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/types.ts b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/types.ts index c30e16236fe..21a009dfd18 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/types.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/types.ts @@ -1,4 +1,4 @@ -import { MarketItemPerformer } from "@ledgerhq/live-common/market/types"; +import { MarketItemPerformer } from "@ledgerhq/live-common/market/utils/types"; import { ABTestingVariants, PortfolioRange } from "@ledgerhq/types-live"; import { Dispatch, SetStateAction } from "react"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/useMarketPerformanceWidget.ts b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/useMarketPerformanceWidget.ts index cb1a007d14a..50349e9b950 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/useMarketPerformanceWidget.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/useMarketPerformanceWidget.ts @@ -6,7 +6,7 @@ import { import { useState } from "react"; import { Order } from "./types"; -import { useMarketPerformers } from "@ledgerhq/live-common/market/v2/useMarketPerformers"; +import { useMarketPerformers } from "@ledgerhq/live-common/market/hooks/useMarketPerformers"; import { getSlicedList } from "./utils"; import { useMarketPerformanceFeatureFlag } from "~/renderer/actions/marketperformance"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/utils/index.ts b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/utils/index.ts index 63e6e4875bc..a321e397687 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/utils/index.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/dashboard/MarketPerformanceWidget/utils/index.ts @@ -1,4 +1,4 @@ -import { MarketItemPerformer } from "@ledgerhq/live-common/market/types"; +import { MarketItemPerformer } from "@ledgerhq/live-common/market/utils/types"; import { PortfolioRange } from "@ledgerhq/types-live"; import { Order } from "~/renderer/screens/dashboard/MarketPerformanceWidget/types"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketCoinChart.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketCoinChart.tsx index 69d02593c2f..c39622bbd56 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketCoinChart.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketCoinChart.tsx @@ -10,6 +10,8 @@ import { dayFormat, hourFormat, useDateFormatter } from "~/renderer/hooks/useDat import ChartPlaceholder from "../../assets/ChartPlaceholder"; import CountervalueSelect from "../../components/CountervalueSelect"; import { useTranslation } from "react-i18next"; +import { MarketCoinDataChart } from "@ledgerhq/live-common/market/utils/types"; +import { formatPercentage, formatPrice } from "../../utils"; const Title = styled(Text).attrs({ variant: "h3", color: "neutral.c100", mt: 1, mb: 5 })` font-size: 28px; @@ -72,7 +74,7 @@ function Tooltip({ data, counterCurrency, locale, formatDay, formatHour }: Toolt type Props = { price?: number; priceChangePercentage?: number; - chartData?: Record; + chartData?: MarketCoinDataChart; range: string; counterCurrency: string; refreshChart: (range: string) => void; @@ -149,7 +151,7 @@ function MarkeCoinChartComponent({ {counterValueFormatter({ currency: counterCurrency, - value: price, + value: formatPrice(price ?? 0), locale, })} @@ -158,7 +160,7 @@ function MarkeCoinChartComponent({ diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketInfo.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketInfo.tsx index 949eaad3bed..88dd21e9722 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketInfo.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/components/MarketInfo.tsx @@ -6,6 +6,7 @@ import FormattedVal from "~/renderer/components/FormattedVal"; import LoadingPlaceholder from "~/renderer/components/LoadingPlaceholder"; import counterValueFormatter from "@ledgerhq/live-common/market/utils/countervalueFormatter"; import { dayAndHourFormat, useDateFormatted } from "~/renderer/hooks/useDateFormatter"; +import { KeysPriceChange } from "@ledgerhq/live-common/market/utils/types"; const Title = styled(Text).attrs({ variant: "h5", color: "neutral.c100", mb: 2 })` font-size: 20px; @@ -82,7 +83,7 @@ type Props = { high24h?: number; low24h?: number; price?: number; - priceChangePercentage?: number; + priceChangePercentage?: Record; marketCapChangePercentage24h?: number; circulatingSupply?: number; totalSupply?: number; @@ -94,6 +95,7 @@ type Props = { counterCurrency: string; loading: boolean; locale: string; + range: string; }; function MarketInfo({ @@ -115,6 +117,7 @@ function MarketInfo({ counterCurrency, loading, locale, + range, }: Props) { const { t } = useTranslation(); @@ -123,6 +126,8 @@ function MarketInfo({ const athText = useDateFormatted(athDateD, dayAndHourFormat); const atlText = useDateFormatted(atlDateD, dayAndHourFormat); + const currentPriceChangePercentage = priceChangePercentage?.[range as KeysPriceChange]; + return ( @@ -134,11 +139,11 @@ function MarketInfo({ {counterValueFormatter({ value: price, currency: counterCurrency, locale })} - {priceChangePercentage ? ( + {currentPriceChangePercentage ? ( diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx index 73a333e4422..50b0b5d41f3 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx @@ -10,6 +10,7 @@ import { Button } from ".."; import MarketCoinChart from "./components/MarketCoinChart"; import MarketInfo from "./components/MarketInfo"; import { useMarketCoin } from "~/renderer/screens/market/hooks/useMarketCoin"; +import { KeysPriceChange } from "@ledgerhq/live-common/market/utils/types"; const CryptoCurrencyIconWrapper = styled.div` height: 56px; @@ -52,8 +53,6 @@ export default function MarketCoinScreen() { availableOnSwap, color, dataChart, - dataCurrency, - isLoadingData, isLoadingDataChart, isLoadingCurrency, range, @@ -68,9 +67,10 @@ export default function MarketCoinScreen() { changeCounterCurrency, } = useMarketCoin(); - const { price, priceChangePercentage } = dataCurrency || {}; + const { name, ticker, image, internalCurrency, price } = currency || {}; + + const currentPriceChangePercentage = currency?.priceChangePercentage[range as KeysPriceChange]; - const { name, ticker, image, internalCurrency } = currency || {}; return ( ); diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketItemChart.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketItemChart.tsx index 39a2d95523c..71b77efaa04 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketItemChart.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketItemChart.tsx @@ -1,6 +1,6 @@ import React, { memo } from "react"; import { useTheme } from "styled-components"; -import { SparklineSvgData } from "@ledgerhq/live-common/market/types"; +import { SparklineSvgData } from "@ledgerhq/live-common/market/utils/types"; type Props = { sparklineIn7d: SparklineSvgData; diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx index 25493a23f17..f958dc64163 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx @@ -7,11 +7,12 @@ import { setTrackingSource } from "~/renderer/analytics/TrackPage"; import counterValueFormatter from "@ledgerhq/live-common/market/utils/countervalueFormatter"; import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; import { SmallMarketItemChart } from "./MarketItemChart"; -import { CurrencyData } from "@ledgerhq/live-common/market/types"; +import { CurrencyData, KeysPriceChange } from "@ledgerhq/live-common/market/utils/types"; import { Button } from "../.."; import { useTranslation } from "react-i18next"; import { TableRow, TableCell } from "../../components/Table"; import { Page, useMarketActions } from "../../hooks/useMarketActions"; +import { formatPercentage, formatPrice } from "../../utils"; const CryptoCurrencyIconWrapper = styled.div` height: 32px; @@ -39,6 +40,7 @@ type Props = { locale: string; isStarred: boolean; toggleStar: () => void; + range?: string; }; export const MarketRow = memo(function MarketRowItem({ @@ -49,13 +51,14 @@ export const MarketRow = memo(function MarketRowItem({ loading, isStarred, toggleStar, + range, }: Props) { + const history = useHistory(); + const { t } = useTranslation(); const { onBuy, onStake, onSwap, availableOnBuy, availableOnSwap, availableOnStake } = useMarketActions({ currency, page: Page.Market }); - const history = useHistory(); - const onCurrencyClick = useCallback(() => { if (currency) { setTrackingSource("Page Market"); @@ -78,6 +81,8 @@ export const MarketRow = memo(function MarketRowItem({ const hasActions = currency?.internalCurrency && (availableOnBuy || availableOnSwap || availableOnStake); + const currentPriceChangePercentage = currency?.priceChangePercentage[range as KeysPriceChange]; + return (
{loading || !currency ? ( @@ -161,15 +166,19 @@ export const MarketRow = memo(function MarketRowItem({ - {counterValueFormatter({ value: currency.price, currency: counterCurrency, locale })} + {counterValueFormatter({ + value: formatPrice(currency.price ?? 0), + currency: counterCurrency, + locale, + })} - {currency.priceChangePercentage ? ( + {currentPriceChangePercentage ? ( @@ -225,6 +234,7 @@ export const CurrencyRow = memo(function CurrencyRowItem({ starredMarketCoins, locale, style, + range, }: CurrencyRowProps) { const currency = data ? data[index] : null; const isStarred = currency && starredMarketCoins.includes(currency.id); @@ -239,6 +249,7 @@ export const CurrencyRow = memo(function CurrencyRowItem({ key={index} locale={locale} style={{ ...style }} + range={range} /> ); }); diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/NoCryptoPlaceholder.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/NoCryptoPlaceholder.tsx index 96b7c53a7c0..44abf869986 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/NoCryptoPlaceholder.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/NoCryptoPlaceholder.tsx @@ -1,4 +1,4 @@ -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; import { Flex, Text } from "@ledgerhq/react-ui"; import { TFunction } from "i18next"; import React from "react"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/index.tsx index e56e565dfb3..f6b9c711efb 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/index.tsx @@ -4,10 +4,7 @@ import { TFunction } from "i18next"; import { FixedSizeList as List } from "react-window"; import InfiniteLoader from "react-window-infinite-loader"; import AutoSizer from "react-virtualized-auto-sizer"; -import { - MarketListRequestParams, - MarketListRequestResult, -} from "@ledgerhq/live-common/market/types"; +import { CurrencyData, MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; import TrackPage from "~/renderer/analytics/TrackPage"; import { SortTableCell } from "../components/SortTableCell"; import { TableCell, TableRow, listItemHeight } from "../components/Table"; @@ -23,10 +20,10 @@ type MarketListProps = { itemCount: number; locale: string; fromCurrencies?: string[]; - marketResult: MarketListRequestResult; + marketData: CurrencyData[]; resetSearch: () => void; toggleFilterByStarredAccounts: () => void; - toggleSortBy: (newOrderBy: string) => void; + toggleSortBy: () => void; toggleStar: (id: string, isStarred: boolean) => void; t: TFunction; isItemLoaded: (index: number) => boolean; @@ -43,7 +40,7 @@ function MarketList({ currenciesLength, locale, fromCurrencies, - marketResult, + marketData, resetSearch, isItemLoaded, toggleFilterByStarredAccounts, @@ -53,7 +50,7 @@ function MarketList({ checkIfDataIsStaleAndRefetch, t, }: MarketListProps) { - const { order, orderBy, search, starred, range, counterCurrency } = marketParams; + const { order, search, starred, range, counterCurrency } = marketParams; return ( @@ -63,13 +60,7 @@ function MarketList({ <> {search && currenciesLength > 0 && } - + # {t("market.marketList.crypto")} @@ -132,7 +123,7 @@ function MarketList({ itemCount={itemCount} onItemsRendered={onItemsRendered} itemSize={listItemHeight} - itemData={marketResult.data} + itemData={marketData} style={{ overflowX: "hidden" }} ref={ref} overscanCount={10} diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/components/SideDrawerFilter.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/components/SideDrawerFilter.tsx index f879a167749..1cb8a6ee421 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/components/SideDrawerFilter.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/components/SideDrawerFilter.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from "react"; import { TFunction } from "i18next"; import Dropdown from "./DropDown"; -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; export default function SideDrawerFilter({ refresh, diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/components/SortTableCell.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/components/SortTableCell.tsx index 32a0cda0fb0..86fc9f3f1a1 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/components/SortTableCell.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/components/SortTableCell.tsx @@ -5,22 +5,18 @@ import { TableCellBase } from "./Table"; export const SortTableCell = ({ onClick, - orderByKey, - orderBy, order, children, ...props }: { loading?: boolean; - onClick?: (key: string) => void; - orderByKey: string; - orderBy?: string; + onClick?: () => void; order?: string; children?: React.ReactNode; }) => ( - !!onClick && onClick(orderByKey)} {...props}> + !!onClick && onClick()} {...props}> {children} - + diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarket.ts b/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarket.ts index 90e241340a4..b942e79e337 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarket.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarket.ts @@ -1,11 +1,11 @@ import { useFetchCurrencyFrom } from "@ledgerhq/live-common/exchange/swap/hooks/index"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams, Order } from "@ledgerhq/live-common/market/utils/types"; import { rangeDataTable } from "@ledgerhq/live-common/market/utils/rangeDataTable"; import { useMarketDataProvider, useMarketData as useMarketDataHook, -} from "@ledgerhq/live-common/market/v2/useMarketDataProvider"; +} from "@ledgerhq/live-common/market/hooks/useMarketDataProvider"; import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; @@ -25,28 +25,26 @@ export function useMarket() { const starredMarketCoins: string[] = useSelector(starredMarketCoinsSelector); const locale = useSelector(localeSelector); + const REFRESH_RATE = + Number(lldRefreshMarketDataFeature?.params?.refreshTime) > 0 + ? REFETCH_TIME_ONE_MINUTE * Number(lldRefreshMarketDataFeature?.params?.refreshTime) + : REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH; + + const { range, starred = [], liveCompatible, order, search = "" } = marketParams; + + const starFilterOn = starred.length > 0; + useInitSupportedCounterValues(); const { data: fromCurrencies } = useFetchCurrencyFrom(); - const { supportedCurrencies, liveCoinsList, supportedCounterCurrencies } = - useMarketDataProvider(); + const { liveCoinsList, supportedCounterCurrencies } = useMarketDataProvider(); const marketResult = useMarketDataHook({ ...marketParams, - liveCoinsList, - supportedCoinsList: supportedCurrencies, + liveCoinsList: liveCompatible ? liveCoinsList : [], }); - const REFRESH_RATE = - Number(lldRefreshMarketDataFeature?.params?.refreshTime) > 0 - ? REFETCH_TIME_ONE_MINUTE * Number(lldRefreshMarketDataFeature?.params?.refreshTime) - : REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH; - - const { range, starred = [], liveCompatible, orderBy, order, search = "" } = marketParams; - - const starFilterOn = starred.length > 0; - const timeRanges = useMemo( () => Object.keys(rangeDataTable) @@ -115,7 +113,7 @@ export function useMarket() { const toggleFilterByStarredAccounts = useCallback(() => { if (starredMarketCoins.length > 0 || starFilterOn) { const starred = starFilterOn ? [] : starredMarketCoins; - refresh({ starred, search: "" }); + refresh({ starred, search: "", page: 1 }); } }, [refresh, starFilterOn, starredMarketCoins]); @@ -134,20 +132,11 @@ export function useMarket() { [dispatch], ); - const toggleSortBy = useCallback( - (newOrderBy: string) => { - const isFreshSort = newOrderBy !== orderBy; - refresh( - isFreshSort - ? { orderBy: newOrderBy, order: "desc" } - : { - orderBy: newOrderBy, - order: order === "asc" ? "desc" : "asc", - }, - ); - }, - [order, orderBy, refresh], - ); + const toggleSortBy = useCallback(() => { + refresh({ + order: order === Order.MarketCapAsc ? Order.MarketCapDesc : Order.MarketCapAsc, + }); + }, [order, refresh]); const isItemLoaded = useCallback( (index: number) => !!marketResult.data[index], @@ -161,8 +150,8 @@ export function useMarket() { const refetchData = useCallback( (pageToRefetch: number) => { - const elem = marketResult.cachedMetadataMap.get(String(pageToRefetch ?? 1)); - + const page = pageToRefetch - 1 || 0; + const elem = marketResult.cachedMetadataMap.get(String(page)); if (elem && isDataStale(elem.updatedAt, REFRESH_RATE)) { elem.refetch(); } @@ -208,7 +197,7 @@ export function useMarket() { t, liveCompatible, starFilterOn, - marketResult, + marketData: marketResult.data, starredMarketCoins, timeRanges, marketParams, diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketActions.ts b/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketActions.ts index 078890a791d..4063dcd270b 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketActions.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketActions.ts @@ -1,4 +1,4 @@ -import { CurrencyData } from "@ledgerhq/live-common/market/types"; +import { CurrencyData } from "@ledgerhq/live-common/market/utils/types"; import { useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useHistory } from "react-router-dom"; diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketCoin.ts b/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketCoin.ts index a2453bbad0e..04cf11ee154 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketCoin.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/market/hooks/useMarketCoin.ts @@ -1,18 +1,18 @@ import { useSelector, useDispatch } from "react-redux"; -import { useParams } from "react-router-dom"; -import { localeSelector, starredMarketCoinsSelector } from "~/renderer/reducers/settings"; -import { useCallback, useMemo } from "react"; import { useTheme } from "styled-components"; import { getCurrencyColor } from "~/renderer/getCurrencyColor"; import { useCurrencyChartData, useCurrencyData, useMarketDataProvider, -} from "@ledgerhq/live-common/market/v2/useMarketDataProvider"; +} from "@ledgerhq/live-common/market/hooks/useMarketDataProvider"; import { Page, useMarketActions } from "./useMarketActions"; +import { useCallback } from "react"; +import { useParams } from "react-router"; import { setMarketOptions } from "~/renderer/actions/market"; -import { marketParamsSelector } from "~/renderer/reducers/market"; import { removeStarredMarketCoins, addStarredMarketCoins } from "~/renderer/actions/settings"; +import { marketParamsSelector } from "~/renderer/reducers/market"; +import { starredMarketCoinsSelector, localeSelector } from "~/renderer/reducers/settings"; export const useMarketCoin = () => { const marketParams = useSelector(marketParamsSelector); @@ -34,20 +34,16 @@ export const useMarketCoin = () => { range, }); - const { currencyData, currencyInfo } = useCurrencyData({ + const { data: currency, isLoading } = useCurrencyData({ counterCurrency, id: currencyId, - range, }); - const currency = useMemo(() => currencyInfo?.data, [currencyInfo]); - const isLoadingCurrency = useMemo(() => currencyInfo?.isLoading, [currencyInfo]); - const { id, internalCurrency } = currency || {}; const { onBuy, onStake, onSwap, availableOnBuy, availableOnStake, availableOnSwap } = useMarketActions({ - currency, + currency: currency, page: Page.MarketCoin, }); @@ -94,11 +90,9 @@ export const useMarketCoin = () => { onSwap, toggleStar, color, - dataCurrency: currencyData.data, dataChart: resCurrencyChartData.data, isLoadingDataChart: resCurrencyChartData.isLoading, - isLoadingData: currencyData.isLoading, - isLoadingCurrency, + isLoadingCurrency: isLoading, changeRange, range, counterCurrency, diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/utils/index.ts b/apps/ledger-live-desktop/src/renderer/screens/market/utils/index.ts index e537e630741..a6b8e3d829f 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/utils/index.ts +++ b/apps/ledger-live-desktop/src/renderer/screens/market/utils/index.ts @@ -16,3 +16,11 @@ export function getCurrentPage(scrollPosition: number, pageSize: number): number const size = listItemHeight * pageSize; return Math.floor(scrollPosition / size) + 1; } + +export function formatPrice(price: number): number { + return parseFloat(price.toFixed(price >= 1 ? 2 : 6)); +} + +export function formatPercentage(percentage: number, decimals = 2): number { + return parseFloat(percentage.toFixed(decimals)); +} diff --git a/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-btc-page-linux.png b/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-btc-page-linux.png index a0f0f762e4b..1fce5279281 100644 Binary files a/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-btc-page-linux.png and b/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-btc-page-linux.png differ diff --git a/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-page-search-bitcoin-linux.png b/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-page-search-bitcoin-linux.png index 250d25a1054..9d16c0f7a8f 100644 Binary files a/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-page-search-bitcoin-linux.png and b/apps/ledger-live-desktop/tests/specs/market/market.spec.ts-snapshots/market-page-search-bitcoin-linux.png differ diff --git a/apps/ledger-live-mobile/__mocks__/api/market/markets.json b/apps/ledger-live-mobile/__mocks__/api/market/markets.json index 62cb7a84a9d..b8112f35099 100644 --- a/apps/ledger-live-mobile/__mocks__/api/market/markets.json +++ b/apps/ledger-live-mobile/__mocks__/api/market/markets.json @@ -1,582 +1,806 @@ [ { "id": "bitcoin", - "symbol": "btc", + "ledgerIds": ["bitcoin"], + "ticker": "btc", "name": "Bitcoin", - "image": "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1696501400", - "current_price": 205609, - "market_cap": 4004278275821, - "market_cap_rank": 1, - "fully_diluted_valuation": 4297151012318, - "total_volume": 97875468097, - "high_24h": 207182, - "low_24h": 202169, - "price_change_24h": -784.959269785526, - "price_change_percentage_24h": -0.38032, - "market_cap_change_24h": -44654880099.0332, - "market_cap_change_percentage_24h": -1.10288, - "circulating_supply": 19568743.0, - "total_supply": 21000000.0, - "max_supply": 21000000.0, - "ath": 380542, - "ath_change_percentage": -46.30963, - "ath_date": "2021-11-09T04:09:45.771Z", - "atl": 149.66, - "atl_change_percentage": 136420.41563, - "atl_date": "2013-07-05T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:53.096Z", - "price_change_percentage_1y_in_currency": 122.7037621644335 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/1/large/bitcoin.png", + "marketCap": 1267793184097, + "marketCapRank": 1, + "fullyDilutedValuation": 1351547281973, + "totalVolume": 29144313260, + "high24h": 64496, + "low24h": 61109, + "price": 64340, + "priceChange24h": 2750.07, + "priceChangePercentage1h": -0.0509643193795179, + "priceChangePercentage24h": 4.4651507102015, + "priceChangePercentage7d": 2.96752304779716, + "priceChangePercentage30d": -2.28897955960788, + "priceChangePercentage1y": 134.795529255386, + "marketCapChange24h": 50634775104, + "marketCapChangePercentage24h": 4.16008, + "circulatingSupply": 19698650, + "totalSupply": 21000000, + "maxSupply": 21000000, + "allTimeHigh": 73738, + "allTimeLow": 67.81, + "allTimeHighDate": "2024-03-14T07:10:36.635Z", + "allTimeLowDate": "2013-07-06T00:00:00Z", + "sparkline": [ + 62657.195, 62470.03, 61191.613, 61645.637, 61652.16, 60901.527, 61360.547, 62194.473, + 63284.332, 62892.17, 63166.668, 62982.42, 61168.273, 60728.492, 60832.766, 60870.69, 60954.93, + 60779.45, 60833.8, 61201.348, 60900.914, 60918.93, 60943.727, 61129.688, 61137.1, 61359.223, + 61289.84, 61045.938, 61523.223, 62696.383, 62747.48, 62719.96, 62796.348, 62530.746, + 62014.664, 61715.742, 61587.24, 61386.57, 61678.824, 61745.58, 61964.195, 62698.41 + ], + "updatedAt": "2024-05-15T14:48:15Z" }, { "id": "ethereum", - "symbol": "eth", + "ledgerIds": ["base", "arbitrum", "ethereum", "boba", "optimism", "linea"], + "ticker": "eth", "name": "Ethereum", - "image": "https://assets.coingecko.com/coins/images/279/large/ethereum.png?1696501628", - "current_price": 10888.91, - "market_cap": 1306993999856, - "market_cap_rank": 2, - "fully_diluted_valuation": 1306993999856, - "total_volume": 63272423335, - "high_24h": 11013.7, - "low_24h": 10700.54, - "price_change_24h": -66.10423198596618, - "price_change_percentage_24h": -0.60342, - "market_cap_change_24h": -15850362873.552246, - "market_cap_change_percentage_24h": -1.1982, - "circulating_supply": 120211761.826141, - "total_supply": 120211761.826141, - "max_supply": null, - "ath": 26931, - "ath_change_percentage": -59.81207, - "ath_date": "2021-11-09T04:03:16.303Z", - "atl": 1.69, - "atl_change_percentage": 639750.49177, - "atl_date": "2015-10-20T00:00:00.000Z", - "roi": { "times": 69.83868173394059, "currency": "btc", "percentage": 6983.868173394058 }, - "last_updated": "2023-12-13T13:16:46.411Z", - "price_change_percentage_1y_in_currency": 59.38295044504124 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/279/large/ethereum.png", + "marketCap": 356824544811, + "marketCapRank": 2, + "fullyDilutedValuation": 356824544811, + "totalVolume": 12162636256, + "high24h": 2981.24, + "low24h": 2866.02, + "price": 2966.26, + "priceChange24h": 78.86, + "priceChangePercentage1h": -0.134101485686711, + "priceChangePercentage24h": 2.73123077324749, + "priceChangePercentage7d": -1.89505825839966, + "priceChangePercentage30d": -7.07201445813865, + "priceChangePercentage1y": 62.5162195610494, + "marketCapChange24h": 8177616478, + "marketCapChangePercentage24h": 2.34553, + "circulatingSupply": 120116737.016333, + "totalSupply": 120116737.016333, + "maxSupply": null, + "allTimeHigh": 4878.26, + "allTimeLow": 0.432979, + "allTimeHighDate": "2021-11-10T14:24:19.604Z", + "allTimeLowDate": "2015-10-20T00:00:00Z", + "sparkline": [ + 3019.3953, 3008.4812, 2965.0803, 2998.3618, 3002.7224, 2954.849, 2986.9253, 3014.6978, + 3054.3909, 3026.052, 3052.062, 3028.0156, 2937.0725, 2907.646, 2904.2188, 2917.9622, + 2920.3308, 2906.0405, 2905.786, 2932.144, 2915.898, 2918.8271, 2918.5112, 2929.6787, + 2928.3936, 2928.5955, 2930.5498, 2885.8953, 2922.4243, 2959.4775, 2949.5981, 2946.8352, + 2941.6138, 2947.8, 2916.3062, 2905.4883, 2897.6135, 2882.347, 2888.1667, 2886.7322, 2905.3943, + 2908.4165 + ], + "updatedAt": "2024-05-15T14:48:22Z" }, { "id": "tether", - "symbol": "usdt", + "ledgerIds": [ + "ethereum/erc20/usd_tether__erc20_", + "solana/spl/es9vmfrzacermjfrf4h2fyd4kconky11mcce8benwnyb", + "algorand/asa/312769", + "avalanche_c_chain/erc20/tethertoken", + "tron/trc20/tr7nhqjekqxgtci8q8zy4pl8otszgjlj6t" + ], + "ticker": "usdt", "name": "Tether", - "image": "https://assets.coingecko.com/coins/images/325/large/Tether.png?1696501661", - "current_price": 4.98, - "market_cap": 450075133006, - "market_cap_rank": 3, - "fully_diluted_valuation": 450075133006, - "total_volume": 208230681615, - "high_24h": 4.97, - "low_24h": 4.93, - "price_change_24h": 0.04136296, - "price_change_percentage_24h": 0.83833, - "market_cap_change_24h": 2638170804, - "market_cap_change_percentage_24h": 0.58962, - "circulating_supply": 90653779562.3468, - "total_supply": 90653779562.3468, - "max_supply": null, - "ath": 5.96, - "ath_change_percentage": -16.85163, - "ath_date": "2020-05-14T14:04:32.298Z", - "atl": 1.72, - "atl_change_percentage": 188.49109, - "atl_date": "2015-03-02T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:15:04.700Z", - "price_change_percentage_1y_in_currency": -5.962750391765175 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/325/large/Tether.png", + "marketCap": 110972239135, + "marketCapRank": 3, + "fullyDilutedValuation": 110972239135, + "totalVolume": 44471547952, + "high24h": 1.003, + "low24h": 0.997865, + "price": 1, + "priceChange24h": 0.00206102, + "priceChangePercentage1h": -0.0144281115582721, + "priceChangePercentage24h": 0.206446063624742, + "priceChangePercentage7d": -0.0865659087990033, + "priceChangePercentage30d": -0.112870442749288, + "priceChangePercentage1y": -0.0465085734020258, + "marketCapChange24h": 43957579, + "marketCapChangePercentage24h": 0.03963, + "circulatingSupply": 110952273350.244, + "totalSupply": 110952273350.244, + "maxSupply": null, + "allTimeHigh": 1.32, + "allTimeLow": 0.572521, + "allTimeHighDate": "2018-07-24T00:00:00Z", + "allTimeLowDate": "2015-03-02T00:00:00Z", + "sparkline": [ + 0.9997615, 0.9997809, 0.9996473, 0.99941814, 1.0000937, 0.9974203, 0.99952626, 0.9997282, + 0.9999109, 1.0000697, 1.0000215, 0.9998755, 0.99563307, 1.0005046, 0.99974746, 0.99928164, + 0.99969465, 0.99971604, 0.99972934, 0.9995428, 0.99975425, 0.9998999, 0.9995791, 0.9996272, + 0.9995591, 0.99959487, 0.9994083, 0.99961823, 0.99987376, 1.0000235, 0.9993343, 0.99995553, + 0.99998915, 0.9995554, 0.99975014, 0.9994067, 1.002085, 0.9992855, 0.9995147, 0.9995943, + 0.9998037, 0.9996699 + ], + "updatedAt": "2024-05-15T14:45:17Z" }, { "id": "binancecoin", - "symbol": "bnb", + "ledgerIds": ["bsc", "binance_beacon_chain", "ethereum/erc20/bnb"], + "ticker": "bnb", "name": "BNB", - "image": "https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png?1696501970", - "current_price": 1247.58, - "market_cap": 191006568360, - "market_cap_rank": 4, - "fully_diluted_valuation": 191006568360, - "total_volume": 8565952447, - "high_24h": 1273.91, - "low_24h": 1215.57, - "price_change_24h": 8.44, - "price_change_percentage_24h": 0.68093, - "market_cap_change_24h": -196696085.7067566, - "market_cap_change_percentage_24h": -0.10287, - "circulating_supply": 153856150.0, - "total_supply": 153856150.0, - "max_supply": 200000000.0, - "ath": 3720.06, - "ath_change_percentage": -66.66874, - "ath_date": "2021-11-07T10:13:53.906Z", - "atl": 0.126083, - "atl_change_percentage": 983337.16139, - "atl_date": "2017-10-19T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:49.481Z", - "price_change_percentage_1y_in_currency": -11.607938511530975 - }, - { - "id": "ripple", - "symbol": "xrp", - "name": "XRP", - "image": "https://assets.coingecko.com/coins/images/44/large/xrp-symbol-white-128.png?1696501442", - "current_price": 3.02, - "market_cap": 162907702875, - "market_cap_rank": 5, - "fully_diluted_valuation": 301883057200, - "total_volume": 6842120612, - "high_24h": 3.08, - "low_24h": 2.97, - "price_change_24h": -0.041739604358680626, - "price_change_percentage_24h": -1.36162, - "market_cap_change_24h": -3080276856.0314026, - "market_cap_change_percentage_24h": -1.85572, - "circulating_supply": 53957460767.0, - "total_supply": 99988170772.0, - "max_supply": 100000000000.0, - "ath": 11.16, - "ath_change_percentage": -73.10702, - "ath_date": "2021-04-14T05:40:00.104Z", - "atl": 0.00605732, - "atl_change_percentage": 49462.03668, - "atl_date": "2013-08-16T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:51.707Z", - "price_change_percentage_1y_in_currency": 47.608929868142205 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/825/large/bnb-icon2_2x.png", + "marketCap": 89208309844, + "marketCapRank": 4, + "fullyDilutedValuation": 89208309844, + "totalVolume": 1173132516, + "high24h": 581.83, + "low24h": 561.91, + "price": 579, + "priceChange24h": 11.03, + "priceChangePercentage1h": -0.352378663421871, + "priceChangePercentage24h": 1.94119895841773, + "priceChangePercentage7d": -1.18753602549864, + "priceChangePercentage30d": 1.41458732358996, + "priceChangePercentage1y": 83.4138114579877, + "marketCapChange24h": 1233437534, + "marketCapChangePercentage24h": 1.40203, + "circulatingSupply": 153856150, + "totalSupply": 153856150, + "maxSupply": 200000000, + "allTimeHigh": 686.31, + "allTimeLow": 0.0398177, + "allTimeHighDate": "2021-05-10T07:24:17.097Z", + "allTimeLowDate": "2017-10-19T00:00:00Z", + "sparkline": [ + 585.9531, 585.954, 587.1107, 598.35944, 599.4558, 589.50916, 597.43506, 594.9008, 599.6289, + 591.21844, 595.581, 593.9234, 586.5735, 587.1595, 585.7367, 583.55383, 586.01196, 587.6292, + 590.2012, 592.51984, 592.4529, 590.94806, 589.9293, 591.5477, 592.1661, 597.5357, 595.1515, + 588.0582, 593.3402, 595.2053, 593.1983, 592.7321, 592.2753, 587.9918, 587.25726, 586.0821, + 566.8174, 567.013, 565.43365, 566.4182, 569.42053, 567.91486 + ], + "updatedAt": "2024-05-15T14:48:58Z" }, { "id": "solana", - "symbol": "sol", + "ledgerIds": ["solana"], + "ticker": "sol", "name": "Solana", - "image": "https://assets.coingecko.com/coins/images/4128/large/solana.png?1696504756", - "current_price": 332.06, - "market_cap": 141290069493, - "market_cap_rank": 6, - "fully_diluted_valuation": 187115292902, - "total_volume": 11824096132, - "high_24h": 353.03, - "low_24h": 319.57, - "price_change_24h": -17.15469973101318, - "price_change_percentage_24h": -4.91238, - "market_cap_change_24h": -8131895200.720123, - "market_cap_change_percentage_24h": -5.44224, - "circulating_supply": 426405621.658286, - "total_supply": 564703613.481902, - "max_supply": null, - "ath": 1441.03, - "ath_change_percentage": -77.22114, - "ath_date": "2021-11-06T21:54:35.825Z", - "atl": 2.71, - "atl_change_percentage": 12005.19023, - "atl_date": "2020-04-21T11:37:15.012Z", - "roi": null, - "last_updated": "2023-12-13T13:16:52.221Z", - "price_change_percentage_1y_in_currency": 370.60794420571693 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/4128/large/solana.png", + "marketCap": 68433744759, + "marketCapRank": 5, + "fullyDilutedValuation": 87858939690, + "totalVolume": 2758105035, + "high24h": 152.91, + "low24h": 141.55, + "price": 152.29, + "priceChange24h": 8.51, + "priceChangePercentage1h": -0.0321207568024643, + "priceChangePercentage24h": 5.91662067520528, + "priceChangePercentage7d": 3.1629065717832, + "priceChangePercentage30d": 1.79317210684906, + "priceChangePercentage1y": 611.924424885099, + "marketCapChange24h": 3783090644, + "marketCapChangePercentage24h": 5.85159, + "circulatingSupply": 448657840.741922, + "totalSupply": 576011181.470747, + "maxSupply": null, + "allTimeHigh": 259.96, + "allTimeLow": 0.500801, + "allTimeHighDate": "2021-11-06T21:54:35.825Z", + "allTimeLowDate": "2020-05-11T19:35:23.449Z", + "sparkline": [ + 147.60242, 147.23459, 141.0417, 144.24721, 144.88745, 140.83401, 144.66696, 147.48576, + 152.89209, 153.4424, 155.38895, 153.92587, 147.22972, 148.06512, 145.81874, 144.44762, + 145.51242, 144.07492, 144.83005, 146.10063, 145.64526, 146.59106, 146.51698, 145.41992, + 144.33853, 145.05293, 143.15129, 140.44177, 141.43762, 145.21169, 145.75146, 147.32007, + 146.9828, 146.6148, 146.10094, 146.08061, 143.63188, 143.69243, 142.84915, 142.8639, + 143.54529, 144.6397 + ], + "updatedAt": "2024-05-15T14:48:52Z" }, { "id": "usd-coin", - "symbol": "usdc", + "ledgerIds": [ + "optimism/erc20/usd_coin", + "elrond/esdt/555344432d633736663166", + "polygon/erc20/usd_coin", + "algorand/asa/31566704", + "arbitrum/erc20/usd_coin", + "ethereum/erc20/usd__coin", + "stellar/asset/usdc:ga5zsejyb37jrc5avcia5mop4rhtm335x2kgx3ihojapp5re34k4kzvn", + "moonbeam/erc20/usd_coin", + "base/erc20/usd_coin", + "tron/trc20/tekxitehnzsmse2xqrbj4w32run966rdz8", + "solana/spl/epjfwdd5aufqssqem2qn1xzybapc8g4weggkzwytdt1v", + "avalanche_c_chain/erc20/usd_coin" + ], + "ticker": "usdc", "name": "USDC", - "image": "https://assets.coingecko.com/coins/images/6319/large/usdc.png?1696506694", - "current_price": 4.98, - "market_cap": 119903185809, - "market_cap_rank": 7, - "fully_diluted_valuation": 119842521458, - "total_volume": 32061396757, - "high_24h": 4.98, - "low_24h": 4.93, - "price_change_24h": 0.04482732, - "price_change_percentage_24h": 0.90876, - "market_cap_change_24h": 462605294, - "market_cap_change_percentage_24h": 0.38731, - "circulating_supply": 24089302072.4732, - "total_supply": 24077114223.9167, - "max_supply": null, - "ath": 5.97, - "ath_change_percentage": -16.89973, - "ath_date": "2020-05-14T14:07:45.849Z", - "atl": 3.66, - "atl_change_percentage": 35.56688, - "atl_date": "2019-01-10T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:53.444Z", - "price_change_percentage_1y_in_currency": -6.0887894596327055 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/6319/large/usdc.png", + "marketCap": 32966082232, + "marketCapRank": 6, + "fullyDilutedValuation": 32973154386, + "totalVolume": 5999012444, + "high24h": 1.006, + "low24h": 0.998171, + "price": 1.001, + "priceChange24h": 0.00232284, + "priceChangePercentage1h": 0.0279345839187226, + "priceChangePercentage24h": 0.23265841855982, + "priceChangePercentage7d": 0.0534421709488871, + "priceChangePercentage30d": 0.204957736705401, + "priceChangePercentage1y": 0.0378593637830182, + "marketCapChange24h": -93504461.0456963, + "marketCapChangePercentage24h": -0.28284, + "circulatingSupply": 32927436423.6292, + "totalSupply": 32934500287.1743, + "maxSupply": null, + "allTimeHigh": 1.17, + "allTimeLow": 0.877647, + "allTimeHighDate": "2019-05-08T00:40:28.300Z", + "allTimeLowDate": "2023-03-11T08:02:13.981Z", + "sparkline": [ + 0.9999258, 0.99969923, 0.9999768, 0.99998045, 0.99993086, 0.99930614, 1.000704, 1.000056, + 0.9995984, 1.0000048, 0.9990393, 1.0009893, 0.9996846, 1.000096, 0.9992139, 0.9998493, + 1.0003057, 0.99991375, 1.0004326, 1.0003217, 1.000017, 0.9998975, 0.9999778, 1.0003474, + 0.99995124, 1.0000898, 0.9997798, 1.0005147, 1.0021964, 1.0002855, 1.0002387, 1.0000032, + 0.9999825, 0.9999229, 1.0003145, 0.99975836, 0.9999368, 0.99981743, 1.000167, 1.0001553, + 0.9998695, 1.0006797 + ], + "updatedAt": "2024-05-15T14:48:42Z" }, { - "id": "cardano", - "symbol": "ada", - "name": "Cardano", - "image": "https://assets.coingecko.com/coins/images/975/large/cardano.png?1696502090", - "current_price": 2.9, - "market_cap": 100837089661, - "market_cap_rank": 8, - "fully_diluted_valuation": 129595943165, - "total_volume": 5276931054, - "high_24h": 2.97, - "low_24h": 2.72, - "price_change_24h": -0.037978120400622206, - "price_change_percentage_24h": -1.29088, - "market_cap_change_24h": -2485215109.93013, - "market_cap_change_percentage_24h": -2.4053, - "circulating_supply": 35013974387.9469, - "total_supply": 45000000000.0, - "max_supply": 45000000000.0, - "ath": 16.01, - "ath_change_percentage": -82.03663, - "ath_date": "2021-09-02T06:00:10.474Z", - "atl": 0.069678, - "atl_change_percentage": 4027.62534, - "atl_date": "2017-11-02T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:51.669Z", - "price_change_percentage_1y_in_currency": 78.30338410541216 + "id": "ripple", + "ledgerIds": ["ripple"], + "ticker": "xrp", + "name": "XRP", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/44/large/xrp-symbol-white-128.png", + "marketCap": 28260991502, + "marketCapRank": 7, + "fullyDilutedValuation": 51043925286, + "totalVolume": 876028921, + "high24h": 0.510662, + "low24h": 0.497653, + "price": 0.510722, + "priceChange24h": 0.00941303, + "priceChangePercentage1h": 0.326468466742202, + "priceChangePercentage24h": 1.8776917634215, + "priceChangePercentage7d": -2.87310460464971, + "priceChangePercentage30d": 0.69008243089786, + "priceChangePercentage1y": 19.7716981250522, + "marketCapChange24h": 445904902, + "marketCapChangePercentage24h": 1.6031, + "circulatingSupply": 55359176420, + "totalSupply": 99987633657, + "maxSupply": 100000000000, + "allTimeHigh": 3.4, + "allTimeLow": 0.00268621, + "allTimeHighDate": "2018-01-07T00:00:00Z", + "allTimeLowDate": "2014-05-22T00:00:00Z", + "sparkline": [ + 0.5266193, 0.5270535, 0.5166041, 0.52149796, 0.52046347, 0.5099221, 0.51273763, 0.51828605, + 0.52115995, 0.5180987, 0.5177485, 0.5145132, 0.49867815, 0.50575167, 0.50144553, 0.50398844, + 0.50553405, 0.5023539, 0.50526136, 0.5064213, 0.5057429, 0.5071312, 0.50609237, 0.5050932, + 0.50330263, 0.5017274, 0.4992411, 0.49195737, 0.49866146, 0.50776076, 0.50627255, 0.5060036, + 0.50416404, 0.505221, 0.50382847, 0.5078337, 0.50306684, 0.50471085, 0.50036794, 0.50033724, + 0.49892935, 0.5016387 + ], + "updatedAt": "2024-05-15T14:48:54Z" }, { "id": "staked-ether", - "symbol": "steth", + "ledgerIds": ["ethereum/erc20/steth"], + "ticker": "steth", "name": "Lido Staked Ether", - "image": "https://assets.coingecko.com/coins/images/13442/large/steth_logo.png?1696513206", - "current_price": 10876.84, - "market_cap": 100123220735, - "market_cap_rank": 9, - "fully_diluted_valuation": 100123220735, - "total_volume": 286984904, - "high_24h": 10998.22, - "low_24h": 10689.12, - "price_change_24h": -76.63386356281626, - "price_change_percentage_24h": -0.69963, - "market_cap_change_24h": -1123979190.428009, - "market_cap_change_percentage_24h": -1.11013, - "circulating_supply": 9214708.39586071, - "total_supply": 9214708.39586071, - "max_supply": 9214708.39586071, - "ath": 26759, - "ath_change_percentage": -59.60261, - "ath_date": "2021-11-09T05:59:27.697Z", - "atl": 2474.06, - "atl_change_percentage": 336.92827, - "atl_date": "2020-12-22T04:08:21.854Z", - "roi": null, - "last_updated": "2023-12-13T13:16:33.439Z", - "price_change_percentage_1y_in_currency": 60.33924834029486 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/13442/large/steth_logo.png", + "marketCap": 27761757681, + "marketCapRank": 8, + "fullyDilutedValuation": 27761757681, + "totalVolume": 72682472, + "high24h": 2980.8, + "low24h": 2865.01, + "price": 2963.13, + "priceChange24h": 75.63, + "priceChangePercentage1h": 0.0585949043565619, + "priceChangePercentage24h": 2.61915166126093, + "priceChangePercentage7d": -2.16489190403567, + "priceChangePercentage30d": -7.06159367316768, + "priceChangePercentage1y": 62.4341580659837, + "marketCapChange24h": 602760505, + "marketCapChangePercentage24h": 2.21938, + "circulatingSupply": 9362863.68264761, + "totalSupply": 9362911.32427392, + "maxSupply": null, + "allTimeHigh": 4829.57, + "allTimeLow": 482.9, + "allTimeHighDate": "2021-11-10T14:40:47.256Z", + "allTimeLowDate": "2020-12-22T04:08:21.854Z", + "sparkline": [ + 3016.6682, 3010.6316, 2962.3262, 2996.9583, 2998.4885, 2955.8462, 2980.8438, 3014.3232, + 3049.5745, 3028.8167, 3044.8994, 3026.6282, 2932.0835, 2905.7847, 2906.095, 2917.1833, + 2918.532, 2905.445, 2902.1538, 2930.7507, 2914.2334, 2916.155, 2916.5803, 2926.9321, 2926.609, + 2927.9692, 2929.5059, 2881.3572, 2914.2417, 2960.7954, 2950.6438, 2940.7524, 2939.2048, + 2948.4666, 2913.261, 2901.9316, 2889.5906, 2882.3435, 2886.3472, 2884.4995, 2902.221, 2904.396 + ], + "updatedAt": "2024-05-15T14:48:28Z" }, { - "id": "avalanche-2", - "symbol": "avax", - "name": "Avalanche", - "image": "https://assets.coingecko.com/coins/images/12559/large/Avalanche_Circle_RedWhite_Trans.png?1696512369", - "current_price": 188.27, - "market_cap": 68264026555, - "market_cap_rank": 10, - "fully_diluted_valuation": 80961321673, - "total_volume": 10150358504, - "high_24h": 198.86, - "low_24h": 170.37, - "price_change_24h": -8.3460117907604, - "price_change_percentage_24h": -4.24493, - "market_cap_change_24h": -4244545761.6068954, - "market_cap_change_percentage_24h": -5.85385, - "circulating_supply": 365762970.094816, - "total_supply": 433795880.094816, - "max_supply": 720000000.0, - "ath": 813.77, - "ath_change_percentage": -77.2898, - "ath_date": "2021-11-21T14:18:56.538Z", - "atl": 14.39, - "atl_change_percentage": 1183.87076, - "atl_date": "2020-12-09T08:34:53.550Z", - "roi": null, - "last_updated": "2023-12-13T13:16:53.245Z", - "price_change_percentage_1y_in_currency": 172.5837888877856 + "id": "the-open-network", + "ledgerIds": ["ton", "bsc/bep20/wrapped_ton_coin", "ethereum/erc20/wrapped_ton_coin"], + "ticker": "ton", + "name": "Toncoin", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/17980/large/ton_symbol.png", + "marketCap": 24288585699, + "marketCapRank": 9, + "fullyDilutedValuation": 35701636251, + "totalVolume": 462192039, + "high24h": 7.05, + "low24h": 6.59, + "price": 6.99, + "priceChange24h": 0.337376, + "priceChangePercentage1h": -0.052040368831294, + "priceChangePercentage24h": 5.07290863694363, + "priceChangePercentage7d": 19.7521422432585, + "priceChangePercentage30d": 0.825830209175098, + "priceChangePercentage1y": 251.845864043224, + "marketCapChange24h": 1072258170, + "marketCapChangePercentage24h": 4.61855, + "circulatingSupply": 3474153822.66886, + "totalSupply": 5106636409.14248, + "maxSupply": null, + "allTimeHigh": 7.63, + "allTimeLow": 0.519364, + "allTimeHighDate": "2024-04-11T05:55:53.682Z", + "allTimeLowDate": "2021-09-21T00:33:11.092Z", + "sparkline": [ + 5.854216, 5.8081875, 5.7762685, 5.892911, 6.0360875, 5.937141, 6.025485, 6.344385, 6.3830214, + 6.3800955, 6.7669806, 6.9685383, 6.6657205, 6.7165, 6.705566, 6.8060784, 6.8768916, 6.7040715, + 6.7848415, 6.895431, 6.850175, 6.870988, 6.9030204, 6.9636674, 7.034313, 6.9712424, 6.9274926, + 6.8823295, 7.163029, 7.356615, 7.260922, 7.2986007, 7.0486293, 6.967607, 7.047161, 6.91046, + 6.637433, 6.995799, 6.883593, 6.9363832, 7.0104523, 6.8899474 + ], + "updatedAt": "2024-05-15T14:48:57Z" }, { "id": "dogecoin", - "symbol": "doge", + "ledgerIds": ["dogecoin"], + "ticker": "doge", "name": "Dogecoin", - "image": "https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1696501409", - "current_price": 0.463277, - "market_cap": 65736432382, - "market_cap_rank": 11, - "fully_diluted_valuation": 65736367670, - "total_volume": 5735210207, - "high_24h": 0.477346, - "low_24h": 0.449248, - "price_change_24h": -0.009901449265711739, - "price_change_percentage_24h": -2.09254, - "market_cap_change_24h": -1924381357.8200684, - "market_cap_change_percentage_24h": -2.84416, - "circulating_supply": 142216776383.705, - "total_supply": 142216636383.705, - "max_supply": null, - "ath": 3.83, - "ath_change_percentage": -87.99194, - "ath_date": "2021-05-08T05:08:23.458Z", - "atl": 0.00024014, - "atl_change_percentage": 191419.03185, - "atl_date": "2014-08-18T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:50.572Z", - "price_change_percentage_1y_in_currency": -2.809059787098743 - }, - { - "id": "tron", - "symbol": "trx", - "name": "TRON", - "image": "https://assets.coingecko.com/coins/images/1094/large/tron-logo.png?1696502193", - "current_price": 0.513844, - "market_cap": 45464941485, - "market_cap_rank": 12, - "fully_diluted_valuation": 45465002837, - "total_volume": 2075136060, - "high_24h": 0.517011, - "low_24h": 0.506653, - "price_change_24h": -0.00104586707512977, - "price_change_percentage_24h": -0.20312, - "market_cap_change_24h": -197753802.0641632, - "market_cap_change_percentage_24h": -0.43308, - "circulating_supply": 88440140513.4899, - "total_supply": 88440259856.7903, - "max_supply": null, - "ath": 0.994777, - "ath_change_percentage": -48.54559, - "ath_date": "2021-04-17T03:43:18.701Z", - "atl": 0.00591448, - "atl_change_percentage": 8554.28757, - "atl_date": "2017-11-12T00:00:00.000Z", - "roi": { "times": 53.385767571022, "currency": "usd", "percentage": 5338.5767571022 }, - "last_updated": "2023-12-13T13:16:53.415Z", - "price_change_percentage_1y_in_currency": 82.06397995824528 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/5/large/dogecoin.png", + "marketCap": 21881106867, + "marketCapRank": 10, + "fullyDilutedValuation": 21881311575, + "totalVolume": 1280013670, + "high24h": 0.15344, + "low24h": 0.144533, + "price": 0.152031, + "priceChange24h": 0.00277867, + "priceChangePercentage1h": -0.390992310587998, + "priceChangePercentage24h": 1.86173082598688, + "priceChangePercentage7d": 1.4266088327776, + "priceChangePercentage30d": -4.93976516623158, + "priceChangePercentage1y": 109.022043359089, + "marketCapChange24h": 263945334, + "marketCapChangePercentage24h": 1.221, + "circulatingSupply": 144300556383.705, + "totalSupply": 144301906383.705, + "maxSupply": null, + "allTimeHigh": 0.731578, + "allTimeLow": 0.0000869, + "allTimeHighDate": "2021-05-08T05:08:23.458Z", + "allTimeLowDate": "2015-05-06T00:00:00Z", + "sparkline": [ + 0.14976273, 0.14859755, 0.14237858, 0.14708596, 0.14748488, 0.14416611, 0.14786883, + 0.14942943, 0.1535368, 0.15023115, 0.15231374, 0.15150443, 0.14458291, 0.1446889, 0.14340827, + 0.1445574, 0.14453529, 0.14326382, 0.14362857, 0.14474642, 0.1434494, 0.14370988, 0.14329967, + 0.14318709, 0.14259319, 0.14114523, 0.14091884, 0.13712521, 0.14045705, 0.14287186, + 0.15028833, 0.14899188, 0.14800581, 0.14895535, 0.15033984, 0.14839932, 0.14946638, + 0.14620373, 0.14545225, 0.14622784, 0.1466549, 0.15016145 + ], + "updatedAt": "2024-05-15T14:48:27Z" }, { - "id": "polkadot", - "symbol": "dot", - "name": "Polkadot", - "image": "https://assets.coingecko.com/coins/images/12171/large/polkadot.png?1696512008", - "current_price": 34.12, - "market_cap": 44452607237, - "market_cap_rank": 13, - "fully_diluted_valuation": 47205546149, - "total_volume": 2322473911, - "high_24h": 36.03, - "low_24h": 33.05, - "price_change_24h": -1.535384167760668, - "price_change_percentage_24h": -4.30647, - "market_cap_change_24h": -2322956834.6354218, - "market_cap_change_percentage_24h": -4.96618, - "circulating_supply": 1307093558.97153, - "total_supply": 1388041538.03351, - "max_supply": null, - "ath": 307.72, - "ath_change_percentage": -89.02334, - "ath_date": "2021-11-04T14:10:09.301Z", - "atl": 14.81, - "atl_change_percentage": 128.02819, - "atl_date": "2020-08-19T03:44:11.556Z", - "roi": null, - "last_updated": "2023-12-13T13:16:52.037Z", - "price_change_percentage_1y_in_currency": 25.008138552432975 + "id": "cardano", + "ledgerIds": ["cardano"], + "ticker": "ada", + "name": "Cardano", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/975/large/cardano.png", + "marketCap": 15737941344, + "marketCapRank": 11, + "fullyDilutedValuation": 20035350952, + "totalVolume": 263217352, + "high24h": 0.447276, + "low24h": 0.426531, + "price": 0.444963, + "priceChange24h": 0.01385242, + "priceChangePercentage1h": 0.0191756845920904, + "priceChangePercentage24h": 3.21319067062242, + "priceChangePercentage7d": -0.017761118306903, + "priceChangePercentage30d": -7.48778209951361, + "priceChangePercentage1y": 20.2755056191615, + "marketCapChange24h": 462996514, + "marketCapChangePercentage24h": 3.03108, + "circulatingSupply": 35347888947.3203, + "totalSupply": 45000000000, + "maxSupply": 45000000000, + "allTimeHigh": 3.09, + "allTimeLow": 0.01925275, + "allTimeHighDate": "2021-09-02T06:00:10.474Z", + "allTimeLowDate": "2020-03-13T02:22:55.044Z", + "sparkline": [ + 0.4584815, 0.4634913, 0.45286816, 0.4628828, 0.4580508, 0.45075914, 0.45218703, 0.45877424, + 0.46350923, 0.4648622, 0.4665452, 0.46315864, 0.45098487, 0.44812873, 0.44646174, 0.44788554, + 0.4467873, 0.44237947, 0.44262335, 0.4413735, 0.43944308, 0.44128522, 0.43997085, 0.44046968, + 0.44171676, 0.43815804, 0.4366339, 0.42845997, 0.43853173, 0.44765437, 0.4433328, 0.4402367, + 0.43673578, 0.43470168, 0.4338322, 0.43495002, 0.43214953, 0.43176976, 0.42704347, 0.4290453, + 0.42842388, 0.43311864 + ], + "updatedAt": "2024-05-15T14:48:58Z" }, { - "id": "chainlink", - "symbol": "link", - "name": "Chainlink", - "image": "https://assets.coingecko.com/coins/images/877/large/chainlink-new-logo.png?1696502009", - "current_price": 71.2, - "market_cap": 39423869457, - "market_cap_rank": 14, - "fully_diluted_valuation": 70798009327, - "total_volume": 3676839187, - "high_24h": 74.28, - "low_24h": 69.34, - "price_change_24h": -2.5374862922000148, - "price_change_percentage_24h": -3.44104, - "market_cap_change_24h": -1789731724.6812973, - "market_cap_change_percentage_24h": -4.34258, - "circulating_supply": 556849971.2305644, - "total_supply": 1000000000.0, - "max_supply": 1000000000.0, - "ath": 276.85, - "ath_change_percentage": -74.49309, - "ath_date": "2021-05-05T07:29:57.467Z", - "atl": 0.481059, - "atl_change_percentage": 14579.17478, - "atl_date": "2017-11-29T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:49.623Z", - "price_change_percentage_1y_in_currency": 100.23312736322798 + "id": "shiba-inu", + "ledgerIds": ["linea/erc20/shiba_inu", "bsc/bep20/shiba_inu", "ethereum/erc20/shiba_inu"], + "ticker": "shib", + "name": "Shiba Inu", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/11939/large/shiba.png", + "marketCap": 14188664977, + "marketCapRank": 12, + "fullyDilutedValuation": 24078237074, + "totalVolume": 674579840, + "high24h": 0.00002428, + "low24h": 0.00002291, + "price": 0.00002408, + "priceChange24h": 6.76836e-7, + "priceChangePercentage1h": 0.147929548037171, + "priceChangePercentage24h": 2.8914737832155, + "priceChangePercentage7d": 5.20385867582226, + "priceChangePercentage30d": 4.7866399015205, + "priceChangePercentage1y": 171.096585275724, + "marketCapChange24h": 429195604, + "marketCapChangePercentage24h": 3.11927, + "circulatingSupply": 589263020389618, + "totalSupply": 999982361071363, + "maxSupply": null, + "allTimeHigh": 0.00008616, + "allTimeLow": 5.6366e-11, + "allTimeHighDate": "2021-10-28T03:54:55.568Z", + "allTimeLowDate": "2020-11-28T11:26:25.838Z", + "sparkline": [ + 0.0000230553, 0.000023022205, 0.000022473681, 0.00002284627, 0.000022971262, 0.00002245292, + 0.000022636044, 0.000023193998, 0.000023682025, 0.000023385624, 0.000023533134, + 0.000023388944, 0.000022421262, 0.000022466502, 0.000022345841, 0.000022619279, 0.00002254475, + 0.000022495838, 0.000022604387, 0.000022725751, 0.000022538425, 0.000022615912, + 0.000022557173, 0.000022542741, 0.000022513143, 0.00002239812, 0.000022318556, 0.000021750271, + 0.000022001304, 0.00002330186, 0.000023794977, 0.000023598512, 0.000023217648, 0.000023194294, + 0.000023572169, 0.000023554625, 0.000023402274, 0.000023265542, 0.00002300576, 0.00002302593, + 0.000023213357, 0.000023907405 + ], + "updatedAt": "2024-05-15T14:49:01Z" }, { - "id": "matic-network", - "symbol": "matic", - "name": "Polygon", - "image": "https://assets.coingecko.com/coins/images/4713/large/polygon.png?1698233745", - "current_price": 4.22, - "market_cap": 39076455551, - "market_cap_rank": 15, - "fully_diluted_valuation": 42094897241, - "total_volume": 3668922090, - "high_24h": 4.44, - "low_24h": 4.14, - "price_change_24h": -0.14402545543624434, - "price_change_percentage_24h": -3.29821, - "market_cap_change_24h": -1590144273.9434052, - "market_cap_change_percentage_24h": -3.9102, - "circulating_supply": 9282943566.203985, - "total_supply": 10000000000.0, - "max_supply": 10000000000.0, - "ath": 16.54, - "ath_change_percentage": -74.70374, - "ath_date": "2021-12-27T02:08:34.307Z", - "atl": 0.01240808, - "atl_change_percentage": 33625.10596, - "atl_date": "2019-05-10T00:00:00.000Z", - "roi": { "times": 321.8844218316348, "currency": "usd", "percentage": 32188.442183163483 }, - "last_updated": "2023-12-13T13:16:52.250Z", - "price_change_percentage_1y_in_currency": -11.519730791724017 + "id": "avalanche-2", + "ledgerIds": ["avalanche_c_chain"], + "ticker": "avax", + "name": "Avalanche", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/12559/large/Avalanche_Circle_RedWhite_Trans.png", + "marketCap": 13159720883, + "marketCapRank": 13, + "fullyDilutedValuation": 15181619707, + "totalVolume": 429187118, + "high24h": 34.46, + "low24h": 31.42, + "price": 34.35, + "priceChange24h": 2.38, + "priceChangePercentage1h": 0.00736625237759751, + "priceChangePercentage24h": 7.44683444383987, + "priceChangePercentage7d": -1.11134465589259, + "priceChangePercentage30d": -9.43557253523738, + "priceChangePercentage1y": 125.387244860953, + "marketCapChange24h": 898502792, + "marketCapChangePercentage24h": 7.32801, + "circulatingSupply": 381987043.452672, + "totalSupply": 440676673.79611, + "maxSupply": 720000000, + "allTimeHigh": 144.96, + "allTimeLow": 2.8, + "allTimeHighDate": "2021-11-21T14:18:56.538Z", + "allTimeLowDate": "2020-12-31T13:15:21.540Z", + "sparkline": [ + 34.784294, 34.609623, 33.80005, 34.35657, 34.552494, 33.806793, 34.0954, 34.42683, 35.262913, + 35.464718, 35.78605, 35.621937, 34.375103, 34.239697, 33.2958, 33.63421, 33.617767, 33.39566, + 33.477787, 33.799294, 33.544006, 33.61532, 33.570015, 33.727917, 33.56361, 33.47636, 33.18843, + 32.21995, 32.737877, 33.44616, 32.965733, 32.617302, 32.413086, 32.157257, 32.33291, 32.86291, + 31.922855, 32.005623, 31.617971, 31.912663, 32.497448, 32.94441 + ], + "updatedAt": "2024-05-15T14:48:29Z" }, { - "id": "the-open-network", - "symbol": "ton", - "name": "Toncoin", - "image": "https://assets.coingecko.com/coins/images/17980/large/ton_symbol.png?1696517498", - "current_price": 10.2, - "market_cap": 35190911083, - "market_cap_rank": 16, - "fully_diluted_valuation": 51942448836, - "total_volume": 187190459, - "high_24h": 10.62, - "low_24h": 9.89, - "price_change_24h": -0.14169901716467947, - "price_change_percentage_24h": -1.36972, - "market_cap_change_24h": -568346534.461525, - "market_cap_change_percentage_24h": -1.58937, - "circulating_supply": 3454898330.72461, - "total_supply": 5099495132.46762, - "max_supply": null, - "ath": 28.57, - "ath_change_percentage": -64.47806, - "ath_date": "2021-11-12T06:50:02.476Z", - "atl": 2.77, - "atl_change_percentage": 266.89292, - "atl_date": "2021-09-21T00:33:11.092Z", - "roi": null, - "last_updated": "2023-12-13T13:16:38.623Z", - "price_change_percentage_1y_in_currency": -16.123463350261687 + "id": "tron", + "ledgerIds": ["tron"], + "ticker": "trx", + "name": "TRON", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/1094/large/tron-logo.png", + "marketCap": 11023243767, + "marketCapRank": 14, + "fullyDilutedValuation": 11023246755, + "totalVolume": 251842518, + "high24h": 0.126445, + "low24h": 0.124711, + "price": 0.12602, + "priceChange24h": 0.00130835, + "priceChangePercentage1h": -0.0735658155100665, + "priceChangePercentage24h": 1.04910149016337, + "priceChangePercentage7d": 2.70504110294169, + "priceChangePercentage30d": 10.7962328874592, + "priceChangePercentage1y": 79.450606830917, + "marketCapChange24h": 109113807, + "marketCapChangePercentage24h": 0.99975, + "circulatingSupply": 87463840458.2667, + "totalSupply": 87463864160.3922, + "maxSupply": null, + "allTimeHigh": 0.231673, + "allTimeLow": 0.00180434, + "allTimeHighDate": "2018-01-05T00:00:00Z", + "allTimeLowDate": "2017-11-12T00:00:00Z", + "sparkline": [ + 0.12258068, 0.123549506, 0.12281498, 0.12370612, 0.124208696, 0.1245937, 0.12703244, + 0.12668729, 0.12618169, 0.12616628, 0.12684278, 0.124840595, 0.12562087, 0.12717699, + 0.12673779, 0.1267699, 0.12694426, 0.1268106, 0.12623182, 0.12633023, 0.12661718, 0.1269238, + 0.12691177, 0.1264906, 0.12663198, 0.12693585, 0.12711936, 0.12668999, 0.12674987, 0.12599795, + 0.12585458, 0.12595612, 0.12583506, 0.12535277, 0.12497228, 0.124879174, 0.12514152, + 0.12527645, 0.12541293, 0.12544909, 0.12535642, 0.1254923 + ], + "updatedAt": "2024-05-15T14:48:31Z" }, { "id": "wrapped-bitcoin", - "symbol": "wbtc", + "ledgerIds": [ + "polygon/erc20/(pos)_wrapped_btc", + "telos_evm/erc20/wrapped_bitcoin", + "linea/erc20/wrapped_btc", + "optimism/erc20/wrapped_btc", + "avalanche_c_chain/erc20/wrapped_btc_(bridged)", + "ethereum/erc20/wrapped_bitcoin", + "astar/erc20/wrapped_btc", + "neon_evm/erc20/wrapped_bitcoin", + "arbitrum/erc20/wrapped_btc", + "syscoin/erc20/wrapped_bitcoin_(multichain)", + "fantom/erc20/bitcoin", + "cronos/erc20/wrapped_btc", + "polygon_zk_evm/erc20/wrapped_btc" + ], + "ticker": "wbtc", "name": "Wrapped Bitcoin", - "image": "https://assets.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857", - "current_price": 205442, - "market_cap": 31761351877, - "market_cap_rank": 17, - "fully_diluted_valuation": 31761351877, - "total_volume": 874405537, - "high_24h": 207143, - "low_24h": 201491, - "price_change_24h": -848.6460119594703, - "price_change_percentage_24h": -0.41138, - "market_cap_change_24h": -268757584.6622925, - "market_cap_change_percentage_24h": -0.83908, - "circulating_supply": 154744.26789025, - "total_supply": 154744.26789025, - "max_supply": 154744.26789025, - "ath": 384950, - "ath_change_percentage": -46.9414, - "ath_date": "2021-11-10T14:40:19.650Z", - "atl": 12091.78, - "atl_change_percentage": 1589.15633, - "atl_date": "2019-04-02T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:52.049Z", - "price_change_percentage_1y_in_currency": 122.9757907875177 + "image": "https://proxycgassets.api.live.ledger.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png", + "marketCap": 9992791741, + "marketCapRank": 15, + "fullyDilutedValuation": 9992791741, + "totalVolume": 260669491, + "high24h": 64330, + "low24h": 61194, + "price": 64303, + "priceChange24h": 2750.51, + "priceChangePercentage1h": 0.0747657671741505, + "priceChangePercentage24h": 4.46858233308011, + "priceChangePercentage7d": 3.19977633828359, + "priceChangePercentage30d": -2.50490314626866, + "priceChangePercentage1y": 134.540268253676, + "marketCapChange24h": 400228844, + "marketCapChangePercentage24h": 4.17228, + "circulatingSupply": 155304.87472143, + "totalSupply": 155304.87472143, + "maxSupply": 155304.87472143, + "allTimeHigh": 73505, + "allTimeLow": 3139.17, + "allTimeHighDate": "2024-03-14T07:10:23.403Z", + "allTimeLowDate": "2019-04-02T00:00:00Z", + "sparkline": [ + 62467.496, 62491.43, 61160.383, 61739.19, 61554.07, 60775.074, 61616.98, 62327.06, 63179.426, + 62819.723, 63186.176, 62951.64, 61227.418, 60740.984, 60817.156, 60859.773, 60912.676, + 60683.168, 60885.22, 61101.066, 60934.734, 60852.566, 60957.99, 61169.812, 61181.613, + 61298.91, 61309.39, 60833.54, 61636.117, 62511.47, 62646.594, 62791.01, 62729.027, 62495.18, + 62067.543, 61801.03, 61641.934, 61430.113, 61607.445, 61805.688, 61958.297, 62684.336 + ], + "updatedAt": "2024-05-15T14:48:57Z" }, { - "id": "shiba-inu", - "symbol": "shib", - "name": "Shiba Inu", - "image": "https://assets.coingecko.com/coins/images/11939/large/shiba.png?1696511800", - "current_price": 4.695e-5, - "market_cap": 27635338530, - "market_cap_rank": 18, - "fully_diluted_valuation": 46892974642, - "total_volume": 1058762420, - "high_24h": 4.776e-5, - "low_24h": 4.566e-5, - "price_change_24h": -3.78842633583e-7, - "price_change_percentage_24h": -0.80043, - "market_cap_change_24h": -340491172.0766411, - "market_cap_change_percentage_24h": -1.21709, - "circulating_supply": 589317528983273.2, - "total_supply": 999982392571662.0, - "max_supply": null, - "ath": 0.00047701, - "ath_change_percentage": -90.22295, - "ath_date": "2021-10-28T03:54:55.568Z", - "atl": 2.86944e-10, - "atl_change_percentage": 16253052.84171, - "atl_date": "2020-12-11T05:57:22.476Z", - "roi": null, - "last_updated": "2023-12-13T13:16:54.123Z", - "price_change_percentage_1y_in_currency": -2.536699358617614 + "id": "polkadot", + "ledgerIds": ["polkadot"], + "ticker": "dot", + "name": "Polkadot", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/12171/large/polkadot.png", + "marketCap": 9387497633, + "marketCapRank": 16, + "fullyDilutedValuation": 9944551507, + "totalVolume": 192369097, + "high24h": 6.92, + "low24h": 6.47, + "price": 6.89, + "priceChange24h": 0.281748, + "priceChangePercentage1h": -0.450501959656659, + "priceChangePercentage24h": 4.26669440683288, + "priceChangePercentage7d": -2.47705390182749, + "priceChangePercentage30d": -2.29806572154937, + "priceChangePercentage1y": 28.0100291798888, + "marketCapChange24h": 356054752, + "marketCapChangePercentage24h": 3.94239, + "circulatingSupply": 1364158529.92179, + "totalSupply": 1445107662.82179, + "maxSupply": null, + "allTimeHigh": 54.98, + "allTimeLow": 2.7, + "allTimeHighDate": "2021-11-04T14:10:09.301Z", + "allTimeLowDate": "2020-08-20T05:48:11.359Z", + "sparkline": [ + 7.146806, 7.0923047, 6.9858136, 7.007853, 6.9929857, 6.788605, 6.90348, 7.0047355, 7.0666695, + 7.074595, 7.0987244, 7.0782356, 6.85487, 6.829148, 6.7338724, 6.7446537, 6.76722, 6.6693707, + 6.7202005, 6.7247276, 6.6856637, 6.6914563, 6.698286, 6.719442, 6.7255535, 6.6847715, + 6.6195474, 6.5121765, 6.584909, 6.742404, 6.7130146, 6.6714516, 6.6642838, 6.643025, + 6.5860977, 6.6079116, 6.5997944, 6.557529, 6.4943366, 6.519899, 6.4902596, 6.6441603 + ], + "updatedAt": "2024-05-15T14:48:37Z" }, { - "id": "litecoin", - "symbol": "ltc", - "name": "Litecoin", - "image": "https://assets.coingecko.com/coins/images/2/large/litecoin.png?1696501400", - "current_price": 357.03, - "market_cap": 26436046404, - "market_cap_rank": 19, - "fully_diluted_valuation": 30017689896, - "total_volume": 1702664545, - "high_24h": 361.05, - "low_24h": 351.0, - "price_change_24h": -1.4985690071131899, - "price_change_percentage_24h": -0.41797, - "market_cap_change_24h": -187137241.22453308, - "market_cap_change_percentage_24h": -0.70291, - "circulating_supply": 73977308.2334713, - "total_supply": 84000000.0, - "max_supply": 84000000.0, - "ath": 2148.55, - "ath_change_percentage": -83.45505, - "ath_date": "2021-05-10T03:13:07.904Z", - "atl": 3.02, - "atl_change_percentage": 11681.60947, - "atl_date": "2015-01-14T00:00:00.000Z", - "roi": null, - "last_updated": "2023-12-13T13:16:49.249Z", - "price_change_percentage_1y_in_currency": -11.804501633759713 + "id": "bitcoin-cash", + "ledgerIds": ["bitcoin_cash"], + "ticker": "bch", + "name": "Bitcoin Cash", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/780/large/bitcoin-cash-circle.png", + "marketCap": 8754025935, + "marketCapRank": 17, + "fullyDilutedValuation": 9328911844, + "totalVolume": 240547582, + "high24h": 455.73, + "low24h": 424.82, + "price": 443.49, + "priceChange24h": 9.85, + "priceChangePercentage1h": -1.5501825019966, + "priceChangePercentage24h": 2.27246744961323, + "priceChangePercentage7d": -4.38919939735473, + "priceChangePercentage30d": -16.554636748717, + "priceChangePercentage1y": 277.308856899426, + "marketCapChange24h": 189550516, + "marketCapChangePercentage24h": 2.21322, + "circulatingSupply": 19705893.6466508, + "totalSupply": 21000000, + "maxSupply": 21000000, + "allTimeHigh": 3785.82, + "allTimeLow": 76.93, + "allTimeHighDate": "2017-12-20T00:00:00Z", + "allTimeLowDate": "2018-12-16T00:00:00Z", + "sparkline": [ + 463.5754, 460.12888, 445.894, 455.58884, 451.4487, 445.86404, 446.8979, 447.555, 454.75186, + 451.72427, 455.84668, 453.20068, 433.4107, 427.29486, 425.9203, 430.7456, 429.33304, + 427.37115, 431.0011, 432.00452, 430.30698, 430.67712, 432.51508, 437.51672, 432.23755, + 430.7697, 433.14297, 424.362, 434.0329, 441.22513, 441.16437, 441.26947, 437.622, 433.84973, + 433.0171, 435.68076, 435.86185, 431.66513, 429.1921, 428.6377, 429.43994, 429.1287 + ], + "updatedAt": "2024-05-15T14:48:29Z" }, { - "id": "dai", - "symbol": "dai", - "name": "Dai", - "image": "https://assets.coingecko.com/coins/images/9956/large/Badge_Dai.png?1696509996", - "current_price": 4.97, - "market_cap": 26231930324, - "market_cap_rank": 20, - "fully_diluted_valuation": 26231930324, - "total_volume": 1494989198, - "high_24h": 4.97, - "low_24h": 4.92, - "price_change_24h": 0.04746639, - "price_change_percentage_24h": 0.96434, - "market_cap_change_24h": -100895363.19909286, - "market_cap_change_percentage_24h": -0.38315, - "circulating_supply": 5282558251.89807, - "total_supply": 5282558251.89807, - "max_supply": null, - "ath": 6.01, - "ath_change_percentage": -17.50987, - "ath_date": "2020-05-14T14:09:14.858Z", - "atl": 3.79, - "atl_change_percentage": 30.75587, - "atl_date": "2019-11-25T00:04:18.137Z", - "roi": null, - "last_updated": "2023-12-13T13:15:16.921Z", - "price_change_percentage_1y_in_currency": -6.291841659229985 + "id": "near", + "ledgerIds": ["near"], + "ticker": "near", + "name": "NEAR Protocol", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/10365/large/near.jpg", + "marketCap": 8300340263, + "marketCapRank": 18, + "fullyDilutedValuation": 9126230760, + "totalVolume": 488522237, + "high24h": 7.73, + "low24h": 6.91, + "price": 7.75, + "priceChange24h": 0.663033, + "priceChangePercentage1h": 1.939411408338, + "priceChangePercentage24h": 9.35171016952677, + "priceChangePercentage7d": 8.17314768922293, + "priceChangePercentage30d": 39.6011269853427, + "priceChangePercentage1y": 363.59687439976, + "marketCapChange24h": 718692472, + "marketCapChangePercentage24h": 9.47937, + "circulatingSupply": 1076166720.91241, + "totalSupply": 1183246170.6779, + "maxSupply": null, + "allTimeHigh": 20.44, + "allTimeLow": 0.526762, + "allTimeHighDate": "2022-01-16T22:09:45.873Z", + "allTimeLowDate": "2020-11-04T16:09:15.137Z", + "sparkline": [ + 7.1171484, 7.005506, 6.8221536, 7.0155625, 7.242098, 6.96699, 7.2568116, 7.1692495, 7.4857078, + 7.555961, 7.5444703, 7.5104475, 7.3398886, 7.2963104, 7.192645, 7.2312255, 7.173561, + 7.1674075, 7.0487328, 7.1037083, 7.044606, 7.024968, 6.969458, 7.008387, 6.974627, 6.902136, + 6.865781, 6.7292852, 6.8305564, 7.0599904, 7.253971, 7.1597867, 7.2632213, 7.159971, + 7.2187934, 7.1860104, 7.089032, 7.001808, 7.030193, 6.961305, 6.972693, 7.190066 + ], + "updatedAt": "2024-05-15T14:48:37Z" + }, + { + "id": "chainlink", + "ledgerIds": [ + "telos_evm/erc20/chainlink_token", + "ethereum/erc20/link_chainlink", + "linea/erc20/chainlink_token", + "polygon/erc20/chainlink_token", + "fantom/erc20/chainlink", + "polygon_zk_evm/erc20/chainlink_token", + "bsc/bep20/binance-peg_chainlink_token", + "arbitrum/erc20/chainlink_token", + "cronos/erc20/chainlink_token", + "optimism/erc20/chainlink_token", + "avalanche_c_chain/erc20/chainlink_token_(bridged)" + ], + "ticker": "link", + "name": "Chainlink", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/877/large/chainlink-new-logo.png", + "marketCap": 7969279610, + "marketCapRank": 19, + "fullyDilutedValuation": 13573973769, + "totalVolume": 358158059, + "high24h": 13.63, + "low24h": 12.84, + "price": 13.56, + "priceChange24h": 0.381302, + "priceChangePercentage1h": -0.251717995556605, + "priceChangePercentage24h": 2.89377817834872, + "priceChangePercentage7d": -3.12126529927258, + "priceChangePercentage30d": -6.0156204102492, + "priceChangePercentage1y": 104.0525709586, + "marketCapChange24h": 199190900, + "marketCapChangePercentage24h": 2.56356, + "circulatingSupply": 587099971.308341, + "totalSupply": 1000000000, + "maxSupply": 1000000000, + "allTimeHigh": 52.7, + "allTimeLow": 0.148183, + "allTimeHighDate": "2021-05-10T00:13:57.214Z", + "allTimeLowDate": "2017-11-29T00:00:00Z", + "sparkline": [ + 14.061462, 14.007142, 13.865917, 14.021732, 14.030632, 13.995071, 13.902331, 14.216178, + 14.341153, 14.280597, 14.318934, 14.289838, 13.821878, 13.679843, 13.528457, 13.667454, + 13.599427, 13.507352, 13.484882, 13.46367, 13.358525, 13.4093, 13.394172, 13.434845, + 13.5096655, 13.45807, 13.557987, 13.282213, 13.35756, 13.447565, 13.478754, 13.358292, + 13.380402, 13.461963, 13.3122225, 13.375667, 13.173788, 13.084595, 12.9830065, 12.942201, + 13.004883, 13.127375 + ], + "updatedAt": "2024-05-15T14:48:31Z" + }, + { + "id": "matic-network", + "ledgerIds": [ + "bsc/bep20/matic_token", + "polygon/erc20/matic_token", + "polygon", + "moonbeam/erc20/matic", + "ethereum/erc20/matic" + ], + "ticker": "matic", + "name": "Polygon", + "image": "https://proxycgassets.api.live.ledger.com/coins/images/4713/large/polygon.png", + "marketCap": 6274749518, + "marketCapRank": 20, + "fullyDilutedValuation": 6759439475, + "totalVolume": 277875402, + "high24h": 0.676284, + "low24h": 0.645395, + "price": 0.675435, + "priceChange24h": 0.02198214, + "priceChangePercentage1h": 0.339274489807851, + "priceChangePercentage24h": 3.36399926556075, + "priceChangePercentage7d": -1.73638527317055, + "priceChangePercentage30d": -8.13065660761741, + "priceChangePercentage1y": -22.1104428924695, + "marketCapChange24h": 186299377, + "marketCapChangePercentage24h": 3.05988, + "circulatingSupply": 9282943566.20399, + "totalSupply": 10000000000, + "maxSupply": 10000000000, + "allTimeHigh": 2.92, + "allTimeLow": 0.00314376, + "allTimeHighDate": "2021-12-27T02:08:34.307Z", + "allTimeLowDate": "2019-05-10T00:00:00Z", + "sparkline": [ + 0.6885055, 0.68996376, 0.68130785, 0.68870735, 0.68711394, 0.67408085, 0.6816523, 0.68900865, + 0.6961657, 0.69442683, 0.7003287, 0.698874, 0.67339766, 0.67286164, 0.6693012, 0.6853905, + 0.6813321, 0.68212163, 0.6834285, 0.68382174, 0.6794348, 0.67896974, 0.6772547, 0.6787339, + 0.6792699, 0.6759835, 0.6714718, 0.6530839, 0.66719157, 0.67193013, 0.6677646, 0.66395426, + 0.6630664, 0.6594885, 0.6576574, 0.6598124, 0.6542934, 0.6557534, 0.64937675, 0.6522604, + 0.65056443, 0.6598375 + ], + "updatedAt": "2024-05-15T14:48:55Z" } ] diff --git a/apps/ledger-live-mobile/__tests__/handlers/market.ts b/apps/ledger-live-mobile/__tests__/handlers/market.ts index 44dee561c39..e44ef0ab560 100644 --- a/apps/ledger-live-mobile/__tests__/handlers/market.ts +++ b/apps/ledger-live-mobile/__tests__/handlers/market.ts @@ -4,9 +4,14 @@ import supportedVsCurrenciesMock from "@mocks/api/market/supportedVsCurrencies.j import coinsListMock from "@mocks/api/market/coinsList.json"; const handlers = [ - http.get("https://proxycg.api.live.ledger.com/api/v3/coins/markets", ({ request }) => { + http.get("https://countervalues.live.ledger.com/v3/markets", ({ request }) => { const searchParams = new URLSearchParams(request.url); // When we perform a search + if (searchParams.get("filter")) { + const coins = searchParams.get("filter")?.toLowerCase().split(",") || []; + return HttpResponse.json(marketsMock.filter(({ ticker }) => coins.includes(ticker))); + } + // When we perform starred if (searchParams.get("ids")) { const coins = searchParams.get("ids")?.split(",") || []; return HttpResponse.json(marketsMock.filter(({ id }) => coins.includes(id))); diff --git a/apps/ledger-live-mobile/__tests__/test-renderer.tsx b/apps/ledger-live-mobile/__tests__/test-renderer.tsx index ac61604b0fd..8cdcc42230c 100644 --- a/apps/ledger-live-mobile/__tests__/test-renderer.tsx +++ b/apps/ledger-live-mobile/__tests__/test-renderer.tsx @@ -25,6 +25,7 @@ import { INITIAL_STATE as DYNAMIC_CONTENT_INITIAL_STATE } from "~/reducers/dynam import { INITIAL_STATE as WALLET_CONNECT_INITIAL_STATE } from "~/reducers/walletconnect"; import { INITIAL_STATE as PROTECT_INITIAL_STATE } from "~/reducers/protect"; import { INITIAL_STATE as NFT_INITIAL_STATE } from "~/reducers/nft"; +import { INITIAL_STATE as MARKET_INITIAL_STATE } from "~/reducers/market"; import { initialState as WALLET_INITIAL_STATE } from "@ledgerhq/live-wallet/store"; const initialState = { @@ -41,6 +42,7 @@ const initialState = { postOnboarding: POST_ONBOARDING_INITIAL_STATE, protect: PROTECT_INITIAL_STATE, nft: NFT_INITIAL_STATE, + market: MARKET_INITIAL_STATE, wallet: WALLET_INITIAL_STATE, }; diff --git a/apps/ledger-live-mobile/e2e/models/market/marketPage.ts b/apps/ledger-live-mobile/e2e/models/market/marketPage.ts index ad25e03b695..8abc59c8c7c 100644 --- a/apps/ledger-live-mobile/e2e/models/market/marketPage.ts +++ b/apps/ledger-live-mobile/e2e/models/market/marketPage.ts @@ -4,7 +4,7 @@ export default class MarketPage { searchBar = () => getElementById("search-box"); starButton = () => getElementById("star-asset"); assetCardBackBtn = () => getElementById("market-back-btn"); - starMarketListButton = () => getElementById("starred"); + starMarketListButton = () => getElementById("toggle-starred-currencies"); buyAssetButton = () => getElementById("market-buy-btn"); searchAsset(asset: string) { diff --git a/apps/ledger-live-mobile/src/AppProviders.tsx b/apps/ledger-live-mobile/src/AppProviders.tsx index aef39f38e20..ae66d422048 100644 --- a/apps/ledger-live-mobile/src/AppProviders.tsx +++ b/apps/ledger-live-mobile/src/AppProviders.tsx @@ -9,7 +9,6 @@ import { OnboardingContextProvider } from "~/screens/Onboarding/onboardingContex import CounterValuesProvider from "~/components/CounterValuesProvider"; import NotificationsProvider from "~/screens/NotificationCenter/NotificationsProvider"; import SnackbarContainer from "~/screens/NotificationCenter/Snackbar/SnackbarContainer"; -import MarketDataProvider from "LLM/features/Market/components//MarketDataProviderWrapper"; import PostOnboardingProviderWrapped from "~/logic/postOnboarding/PostOnboardingProviderWrapped"; import { CounterValuesStateRaw } from "@ledgerhq/live-countervalues/types"; import { CountervaluesMarketcap } from "@ledgerhq/live-countervalues-react/index"; @@ -34,7 +33,7 @@ function AppProviders({ initialCountervalues, children }: AppProvidersProps) { - {children} + {children} diff --git a/apps/ledger-live-mobile/src/actions/market.ts b/apps/ledger-live-mobile/src/actions/market.ts new file mode 100644 index 00000000000..588084a0d0c --- /dev/null +++ b/apps/ledger-live-mobile/src/actions/market.ts @@ -0,0 +1,20 @@ +import { createAction } from "redux-actions"; +import { + MarketSetMarketRequestParamsPayload, + MarketStateActionTypes, + MarketSetCurrentPagePayload, + MarketSetMarketFilterByStarredCurrenciesPayload, +} from "./types"; + +export const setMarketRequestParams = createAction( + MarketStateActionTypes.SET_MARKET_REQUEST_PARAMS, +); + +export const setMarketFilterByStarredCurrencies = + createAction( + MarketStateActionTypes.SET_MARKET_FILTER_BY_STARRED_CURRENCIES, + ); + +export const setMarketCurrentPage = createAction( + MarketStateActionTypes.MARKET_SET_CURRENT_PAGE, +); diff --git a/apps/ledger-live-mobile/src/actions/settings.ts b/apps/ledger-live-mobile/src/actions/settings.ts index aab9c40e51e..6d59a73a153 100755 --- a/apps/ledger-live-mobile/src/actions/settings.ts +++ b/apps/ledger-live-mobile/src/actions/settings.ts @@ -6,7 +6,6 @@ import type { PortfolioRange } from "@ledgerhq/types-live"; import { selectedTimeRangeSelector } from "../reducers/settings"; import { SettingsAcceptSwapProviderPayload, - SettingsAddStarredMarketcoinsPayload, SettingsBlacklistTokenPayload, DangerouslyOverrideStatePayload, SettingsDismissBannerPayload, @@ -16,7 +15,6 @@ import { SettingsImportPayload, SettingsSetHasInstalledAnyAppPayload, SettingsLastSeenDeviceInfoPayload, - SettingsRemoveStarredMarketcoinsPayload, SettingsSetAnalyticsPayload, SettingsSetPersonalizedRecommendationsPayload, SettingsSetAvailableUpdatePayload, @@ -30,8 +28,6 @@ import { SettingsSetLocalePayload, SettingsSetCustomImageBackupPayload, SettingsSetMarketCounterCurrencyPayload, - SettingsSetMarketFilterByStarredAccountsPayload, - SettingsSetMarketRequestParamsPayload, SettingsSetNotificationsPayload, SettingsSetNeverClickedOnAllowNotificationsButton, SettingsSetOrderAccountsPayload, @@ -71,6 +67,8 @@ import { SettingsSetHasSeenAnalyticsOptInPrompt, SettingsSetDismissedContentCardsPayload, SettingsClearDismissedContentCardsPayload, + SettingsAddStarredMarketcoinsPayload, + SettingsRemoveStarredMarketcoinsPayload, } from "./types"; import { ImageType } from "~/components/CustomImage/types"; @@ -197,12 +195,6 @@ const setHasSeenStaxEnabledNftsPopupAction = ); export const setHasSeenStaxEnabledNftsPopup = (hasSeenStaxEnabledNftsPopup: boolean) => setHasSeenStaxEnabledNftsPopupAction({ hasSeenStaxEnabledNftsPopup }); -export const addStarredMarketCoins = createAction( - SettingsActionTypes.ADD_STARRED_MARKET_COINS, -); -export const removeStarredMarketCoins = createAction( - SettingsActionTypes.REMOVE_STARRED_MARKET_COINS, -); export const setLastConnectedDevice = createAction( SettingsActionTypes.SET_LAST_CONNECTED_DEVICE, ); @@ -217,16 +209,9 @@ export const setCustomImageType = (imageType: ImageType) => export const setHasOrderedNano = createAction( SettingsActionTypes.SET_HAS_ORDERED_NANO, ); -export const setMarketRequestParams = createAction( - SettingsActionTypes.SET_MARKET_REQUEST_PARAMS, -); export const setMarketCounterCurrency = createAction( SettingsActionTypes.SET_MARKET_COUNTER_CURRENCY, ); -export const setMarketFilterByStarredAccounts = - createAction( - SettingsActionTypes.SET_MARKET_FILTER_BY_STARRED_ACCOUNTS, - ); export const setSensitiveAnalytics = createAction( SettingsActionTypes.SET_SENSITIVE_ANALYTICS, ); @@ -300,6 +285,13 @@ export const clearDismissedContentCards = createAction( + SettingsActionTypes.ADD_STARRED_MARKET_COINS, +); +export const removeStarredMarketCoins = createAction( + SettingsActionTypes.REMOVE_STARRED_MARKET_COINS, +); + type PortfolioRangeOption = { key: PortfolioRange; value: string; diff --git a/apps/ledger-live-mobile/src/actions/types.ts b/apps/ledger-live-mobile/src/actions/types.ts index 1d05e05364f..f731977bfbb 100644 --- a/apps/ledger-live-mobile/src/actions/types.ts +++ b/apps/ledger-live-mobile/src/actions/types.ts @@ -31,6 +31,7 @@ import type { DynamicContentState, ProtectState, NftState, + MarketState, } from "../reducers/types"; import type { Unpacked } from "../types/helpers"; import { HandlersPayloads } from "@ledgerhq/live-wallet/store"; @@ -252,13 +253,10 @@ export enum SettingsActionTypes { SET_KNOWN_DEVICE_MODEL_IDS = "SET_KNOWN_DEVICE_MODEL_IDS", SET_HAS_SEEN_STAX_ENABLED_NFTS_POPUP = "SET_HAS_SEEN_STAX_ENABLED_NFTS_POPUP", SET_LAST_SEEN_CUSTOM_IMAGE = "SET_LAST_SEEN_CUSTOM_IMAGE", - ADD_STARRED_MARKET_COINS = "ADD_STARRED_MARKET_COINS", - REMOVE_STARRED_MARKET_COINS = "REMOVE_STARRED_MARKET_COINS", SET_LAST_CONNECTED_DEVICE = "SET_LAST_CONNECTED_DEVICE", SET_CUSTOM_IMAGE_TYPE = "SET_CUSTOM_IMAGE_TYPE", SET_CUSTOM_IMAGE_BACKUP = "SET_CUSTOM_IMAGE_BACKUP", SET_HAS_ORDERED_NANO = "SET_HAS_ORDERED_NANO", - SET_MARKET_REQUEST_PARAMS = "SET_MARKET_REQUEST_PARAMS", SET_MARKET_COUNTER_CURRENCY = "SET_MARKET_COUNTER_CURRENCY", SET_MARKET_FILTER_BY_STARRED_ACCOUNTS = "SET_MARKET_FILTER_BY_STARRED_ACCOUNTS", SET_SENSITIVE_ANALYTICS = "SET_SENSITIVE_ANALYTICS", @@ -281,6 +279,9 @@ export enum SettingsActionTypes { SET_HAS_SEEN_ANALYTICS_OPT_IN_PROMPT = "SET_HAS_SEEN_ANALYTICS_OPT_IN_PROMPT", SET_DISMISSED_CONTENT_CARD = "SET_DISMISSED_CONTENT_CARD", CLEAR_DISMISSED_CONTENT_CARDS = "CLEAR_DISMISSED_CONTENT_CARDS", + + ADD_STARRED_MARKET_COINS = "ADD_STARRED_MARKET_COINS", + REMOVE_STARRED_MARKET_COINS = "REMOVE_STARRED_MARKET_COINS", } export type SettingsImportPayload = Partial; @@ -334,8 +335,6 @@ export type SettingsLastSeenDevicePayload = NonNullable< export type SettingsLastSeenDeviceInfoPayload = DeviceModelInfo; export type SettingsLastSeenDeviceLanguagePayload = DeviceInfo["languageId"]; export type SettingsSetKnownDeviceModelIdsPayload = { [key in DeviceModelId]?: boolean }; -export type SettingsAddStarredMarketcoinsPayload = Unpacked; -export type SettingsRemoveStarredMarketcoinsPayload = Unpacked; export type SettingsSetLastConnectedDevicePayload = Device; export type SettingsSetHasSeenStaxEnabledNftsPopupPayload = Pick< SettingsState, @@ -348,10 +347,7 @@ export type SettingsSetCustomImageBackupPayload = { } | null; export type SettingsSetCustomImageTypePayload = Pick; export type SettingsSetHasOrderedNanoPayload = SettingsState["hasOrderedNano"]; -export type SettingsSetMarketRequestParamsPayload = SettingsState["marketRequestParams"]; export type SettingsSetMarketCounterCurrencyPayload = SettingsState["marketCounterCurrency"]; -export type SettingsSetMarketFilterByStarredAccountsPayload = - SettingsState["marketFilterByStarredAccounts"]; export type SettingsSetSensitiveAnalyticsPayload = SettingsState["sensitiveAnalytics"]; export type SettingsSetOnboardingHasDevicePayload = SettingsState["onboardingHasDevice"]; @@ -388,6 +384,9 @@ export type SettingsSetHasSeenAnalyticsOptInPrompt = SettingsState["hasSeenAnaly export type SettingsSetDismissedContentCardsPayload = SettingsState["dismissedContentCards"]; export type SettingsClearDismissedContentCardsPayload = string[]; +export type SettingsAddStarredMarketcoinsPayload = Unpacked; +export type SettingsRemoveStarredMarketcoinsPayload = Unpacked; + export type SettingsPayload = | SettingsImportPayload | SettingsImportDesktopPayload @@ -421,13 +420,9 @@ export type SettingsPayload = | SettingsLastSeenDeviceLanguagePayload | SettingsLastSeenDeviceInfoPayload | SettingsSetLastSeenCustomImagePayload - | SettingsAddStarredMarketcoinsPayload - | SettingsRemoveStarredMarketcoinsPayload | SettingsSetLastConnectedDevicePayload | SettingsSetHasOrderedNanoPayload - | SettingsSetMarketRequestParamsPayload | SettingsSetMarketCounterCurrencyPayload - | SettingsSetMarketFilterByStarredAccountsPayload | SettingsSetSensitiveAnalyticsPayload | SettingsSetOnboardingHasDevicePayload | SettingsSetNotificationsPayload @@ -446,7 +441,9 @@ export type SettingsPayload = | SettingsSetSupportedCounterValues | SettingsSetHasSeenAnalyticsOptInPrompt | SettingsSetDismissedContentCardsPayload - | SettingsClearDismissedContentCardsPayload; + | SettingsClearDismissedContentCardsPayload + | SettingsAddStarredMarketcoinsPayload + | SettingsRemoveStarredMarketcoinsPayload; // === WALLET CONNECT ACTIONS === export enum WalletConnectActionTypes { @@ -517,3 +514,20 @@ export type NftStateGalleryFilterDrawerVisiblePayload = NftState["filterDrawerVi export type NftStatePayload = | NftStateGalleryChainFiltersPayload | NftStateGalleryFilterDrawerVisiblePayload; + +// === MARKET ACTIONS === +export enum MarketStateActionTypes { + SET_MARKET_REQUEST_PARAMS = "SET_MARKET_REQUEST_PARAMS", + SET_MARKET_FILTER_BY_STARRED_CURRENCIES = "SET_MARKET_FILTER_BY_STARRED_CURRENCIES", + MARKET_SET_CURRENT_PAGE = "MARKET_SET_CURRENT_PAGE", +} + +export type MarketSetMarketFilterByStarredCurrenciesPayload = + MarketState["marketFilterByStarredCurrencies"]; +export type MarketSetCurrentPagePayload = MarketState["marketCurrentPage"]; +export type MarketSetMarketRequestParamsPayload = MarketState["marketParams"]; + +export type MarketStatePayload = + | MarketSetMarketFilterByStarredCurrenciesPayload + | MarketSetMarketRequestParamsPayload + | MarketSetCurrentPagePayload; diff --git a/apps/ledger-live-mobile/src/components/WalletTab/CollapsibleHeaderFlatList.tsx b/apps/ledger-live-mobile/src/components/WalletTab/CollapsibleHeaderFlatList.tsx index 45d1eb2f2d7..0577d9b4de3 100644 --- a/apps/ledger-live-mobile/src/components/WalletTab/CollapsibleHeaderFlatList.tsx +++ b/apps/ledger-live-mobile/src/components/WalletTab/CollapsibleHeaderFlatList.tsx @@ -24,6 +24,7 @@ function CollapsibleHeaderFlatList({ return ( + {...otherProps} scrollToOverflowEnabled={true} ref={(ref: FlatList) => onGetRef({ key: route.name, value: ref })} scrollEventThrottle={16} @@ -46,7 +47,6 @@ function CollapsibleHeaderFlatList({ ]} showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} - {...otherProps} > {children} diff --git a/apps/ledger-live-mobile/src/db.ts b/apps/ledger-live-mobile/src/db.ts index 5c1ed29785d..95f105eb83e 100644 --- a/apps/ledger-live-mobile/src/db.ts +++ b/apps/ledger-live-mobile/src/db.ts @@ -45,6 +45,7 @@ export async function getSettings(): Promise> { export async function saveSettings(obj: Partial): Promise { await store.save("settings", obj); } + export async function getWCSession(): Promise { const wcsession = await store.get("wcsession"); return wcsession; diff --git a/apps/ledger-live-mobile/src/index.tsx b/apps/ledger-live-mobile/src/index.tsx index 777ba377f0b..0e5f1166ed9 100644 --- a/apps/ledger-live-mobile/src/index.tsx +++ b/apps/ledger-live-mobile/src/index.tsx @@ -300,7 +300,6 @@ export default class Root extends Component { - diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index aaf636c4667..13dbc6c06e7 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -2245,7 +2245,13 @@ "marketPriceSection": { "title": "{{currencyTicker}} market price", "currencyPrice": "1 {{currencyTicker}} price", - "currencyPriceChange": "Last 24h change" + "currencyPriceChange": { + "day": "Last 24h change", + "week": "Last 7 days change", + "month": "Last 30 days change", + "year": "Last year change", + "all": "Last year change" + } }, "quickActions": { "buy": "Buy", @@ -6238,9 +6244,10 @@ }, "order": { "topGainers": "Top gainers", - "market_cap": "Rank", - "market_cap_asc": "Rank (Market cap) asc.", - "market_cap_desc": "Rank (Market cap) desc." + "topLosers": "Top losers", + "marketCap": "Rank", + "asc": "Rank (Market cap) asc.", + "desc": "Rank (Market cap) desc." }, "currency": "Currency", "time": "Time", diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/changeCurrency.integration.test.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/changeCurrency.integration.test.tsx index e33ac458213..52be5d1bb97 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/changeCurrency.integration.test.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/changeCurrency.integration.test.tsx @@ -38,11 +38,11 @@ describe("Market integration test", () => { }); expect(await screen.findByText("Bitcoin (BTC)")).toBeOnTheScreen(); - expect(await screen.findByText("$4.004 tn")).toBeOnTheScreen(); + expect(await screen.findByText("$1.267 tn")).toBeOnTheScreen(); await user.press(screen.getByText("Currency")); expect(await screen.findByText("Euro - EUR")).toBeOnTheScreen(); await user.press(screen.getByText("Euro - EUR")); - expect(await screen.findByText("€4.004 tn")).toBeOnTheScreen(); + expect(await screen.findByText("€1.267 tn")).toBeOnTheScreen(); }); }); diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/setFavorites.integration.test.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/setFavorites.integration.test.tsx index e47a25915fd..05725b0c022 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/setFavorites.integration.test.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/setFavorites.integration.test.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { screen } from "@testing-library/react-native"; +import { screen, waitFor } from "@testing-library/react-native"; import { render } from "@tests/test-renderer"; import { MarketPages } from "./shared"; @@ -9,26 +9,28 @@ describe("Market integration test", () => { //Set BTC as favorite expect(await screen.findByText("Bitcoin (BTC)")).toBeOnTheScreen(); - await user.press(screen.getByText("Bitcoin (BTC)")); await user.press(await screen.findByTestId("star-asset")); await user.press(screen.getByTestId("market-back-btn")); - const ethRow = await screen.findByText("Ethereum (ETH)"); - - await user.press(await screen.findByTestId("starred")); + await waitFor(() => screen.findByTestId("toggle-starred-currencies")); + const ethRow = await screen.findByText("Ethereum (ETH)"); expect(await screen.findByText("Bitcoin (BTC)")).toBeOnTheScreen(); + + await user.press(await screen.findByTestId("toggle-starred-currencies")); expect(ethRow).not.toBeOnTheScreen(); //Set BNB as favorite - await user.press(await screen.findByTestId("starred")); + await user.press(await screen.findByTestId("toggle-starred-currencies")); await user.press(await screen.findByText("BNB (BNB)")); await user.press(await screen.findByTestId("star-asset")); await user.press(screen.getByTestId("market-back-btn")); + + await waitFor(() => screen.findByTestId("toggle-starred-currencies")); const ethRow2 = await screen.findByText("Ethereum (ETH)"); - await user.press(await screen.findByTestId("starred")); + await user.press(await screen.findByTestId("toggle-starred-currencies")); expect(await screen.findByText("Bitcoin (BTC)")).toBeOnTheScreen(); expect(await screen.findByText("BNB (BNB)")).toBeOnTheScreen(); diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/shared.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/shared.tsx index dec31861ff9..992d1c0baf7 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/shared.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/__integrations__/shared.tsx @@ -1,25 +1,25 @@ -/* eslint-disable i18next/no-literal-string */ import * as React from "react"; import MarketNavigator, { MarketNavigatorStackParamList } from "LLM/features/Market/Navigator"; -import MarketDataProviderWrapper from "../components/MarketDataProviderWrapper"; + import WalletTabNavigatorScrollManager from "~/components/WalletTab/WalletTabNavigatorScrollManager"; import { createStackNavigator } from "@react-navigation/stack"; import { ScreenName } from "~/const"; import MarketList from "../screens/MarketList"; import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const Stack = createStackNavigator(); const StackWalletTab = createStackNavigator(); export function MarketPages() { return ( - + {MarketNavigator({ Stack })} - + ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/components/MarketDataProviderWrapper/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/components/MarketDataProviderWrapper/index.tsx deleted file mode 100644 index c61c1f2e82a..00000000000 --- a/apps/ledger-live-mobile/src/newArch/features/Market/components/MarketDataProviderWrapper/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { ReactElement } from "react"; -import { useSelector } from "react-redux"; -import apiMock from "@ledgerhq/live-common/market/api/api.mock"; -import Config from "react-native-config"; -import { MarketDataProvider } from "@ledgerhq/live-common/market/MarketDataProvider"; -import { useNetInfo } from "@react-native-community/netinfo"; -import { Currency } from "@ledgerhq/types-cryptoassets"; -import { - counterValueCurrencySelector, - marketCounterCurrencySelector, - marketFilterByStarredAccountsSelector, - marketRequestParamsSelector, - starredMarketCoinsSelector, -} from "~/reducers/settings"; - -type Props = { - children: React.ReactNode; -}; - -export default function MarketDataProviderWrapper({ children }: Props): ReactElement { - const counterValueCurrency = useSelector(counterValueCurrencySelector); - const marketRequestParams = useSelector(marketRequestParamsSelector); - const marketCounterCurrency = useSelector(marketCounterCurrencySelector); - const starredMarketCoins = useSelector(starredMarketCoinsSelector); - const filterByStarredAccount = useSelector(marketFilterByStarredAccountsSelector); - const { isConnected } = useNetInfo(); - - const counterCurrency = !isConnected - ? undefined // without coutervalues service is not initialized with cg data, this forces it to fetch it at least once the network is on - : marketCounterCurrency // If there is a stored market counter currency we use it, otherwise we use the setting countervalue currency - ? { ticker: marketCounterCurrency } - : counterValueCurrency - ? // @TODO move this toLowercase check on live-common - { ticker: counterValueCurrency.ticker?.toLowerCase() } - : counterValueCurrency; - - return ( - - {children} - - ); -} diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/components/MarketRowItem/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/components/MarketRowItem/index.tsx index 73c16a42991..886a766582f 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/components/MarketRowItem/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/components/MarketRowItem/index.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Flex, Text } from "@ledgerhq/native-ui"; -import { CurrencyData } from "@ledgerhq/live-common/market/types"; +import { CurrencyData, KeysPriceChange } from "@ledgerhq/live-common/market/utils/types"; import { Image } from "react-native"; import { useTranslation } from "react-i18next"; import CircleCurrencyIcon from "~/components/CircleCurrencyIcon"; @@ -13,21 +13,15 @@ type Props = { index: number; item: CurrencyData; counterCurrency?: string; + range?: string; }; -function MarketRowItem({ item, index, counterCurrency }: Props) { +function MarketRowItem({ item, index, counterCurrency, range }: Props) { const { locale } = useLocale(); const { t } = useTranslation(); - const { - internalCurrency, - image, - name, - marketcap, - marketcapRank, - price, - priceChangePercentage, - ticker, - } = item; + const { internalCurrency, image, name, marketcap, marketcapRank, price, ticker } = item; + + const priceChangePercentage = item?.priceChangePercentage[range as KeysPriceChange]; return ( { + dispatch(setMarketRequestParams(payload ?? {})); + }, + [dispatch], + ); + + return { + dispatch, + refresh, + starredMarketCoins, + filterByStarredCurrencies, + marketParams, + liveCoinsList, + supportedCurrencies, + supportedCounterCurrencies, + marketCurrentPage, + }; +} diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/hooks/useMarketCoinData.ts b/apps/ledger-live-mobile/src/newArch/features/Market/hooks/useMarketCoinData.ts new file mode 100644 index 00000000000..a3a8bad5c8b --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Market/hooks/useMarketCoinData.ts @@ -0,0 +1,38 @@ +import { + useCurrencyChartData, + useCurrencyData, +} from "@ledgerhq/live-common/market/hooks/useMarketDataProvider"; + +import { useSelector } from "react-redux"; +import { marketParamsSelector } from "~/reducers/market"; + +type HookProps = { + currencyId: string; +}; + +export const useMarketCoinData = ({ currencyId }: HookProps) => { + const marketParams = useSelector(marketParamsSelector); + + const { counterCurrency = "usd", range = "24h" } = marketParams; + + const resCurrencyChartData = useCurrencyChartData({ + counterCurrency, + id: currencyId, + range, + }); + + const { data: currency, isLoading } = useCurrencyData({ + counterCurrency, + id: currencyId, + }); + + return { + counterCurrency, + range, + currency, + dataChart: resCurrencyChartData.data, + loadingChart: resCurrencyChartData.isLoading, + loading: isLoading, + marketParams, + }; +}; diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketCurrencySelect/useMarketCurrencySelectViewModel.ts b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketCurrencySelect/useMarketCurrencySelectViewModel.ts index fef80c2e219..0e7d2822c6b 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketCurrencySelect/useMarketCurrencySelectViewModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketCurrencySelect/useMarketCurrencySelectViewModel.ts @@ -1,18 +1,20 @@ -import { useMarketData } from "@ledgerhq/live-common/market/MarketDataProvider"; import { useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; import { supportedCounterValuesSelector } from "~/reducers/settings"; -import { setMarketCounterCurrency } from "~/actions/settings"; import { useNavigation } from "@react-navigation/native"; +import { useMarket } from "../../hooks/useMarket"; +import { setMarketRequestParams } from "~/actions/market"; function useMarketCurrencySelectViewModel() { const navigation = useNavigation(); const dispatch = useDispatch(); const supportedCountervalues = useSelector(supportedCounterValuesSelector); - const { counterCurrency, supportedCounterCurrencies, setCounterCurrency } = useMarketData(); + + const { supportedCounterCurrencies, marketParams } = useMarket(); + const { counterCurrency } = marketParams; const items = supportedCountervalues - .filter(({ ticker }) => supportedCounterCurrencies.includes(ticker.toLowerCase())) + .filter(({ ticker }) => supportedCounterCurrencies?.includes(ticker.toLowerCase())) .map(cur => ({ value: cur.ticker.toLowerCase(), label: cur.label, @@ -20,12 +22,11 @@ function useMarketCurrencySelectViewModel() { .sort(a => (a.value === counterCurrency ? -1 : 0)); const onSelectCurrency = useCallback( - (value: string) => { - dispatch(setMarketCounterCurrency(value)); - setCounterCurrency(value); + (counterCurrency: string) => { + dispatch(setMarketRequestParams({ counterCurrency })); navigation.goBack(); }, - [dispatch, navigation, setCounterCurrency], + [dispatch, navigation], ); return { diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/components/MarketGraph/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/components/MarketGraph/index.tsx index 0639f4955d6..79a6198a391 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/components/MarketGraph/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/components/MarketGraph/index.tsx @@ -1,43 +1,39 @@ import React, { useMemo, useCallback, memo } from "react"; import { useTheme } from "styled-components/native"; -import { Flex, GraphTabs, InfiniteLoader, Transitions } from "@ledgerhq/native-ui"; -import { rangeDataTable } from "@ledgerhq/live-common/market/utils/rangeDataTable"; +import { Flex, GraphTabs, InfiniteLoader, Transitions, ensureContrast } from "@ledgerhq/native-ui"; import { useTranslation } from "react-i18next"; -import { SingleCoinProviderData } from "@ledgerhq/live-common/market/MarketDataProvider"; import Graph from "~/components/Graph"; import getWindowDimensions from "~/logic/getWindowDimensions"; import { Item } from "~/components/Graph/types"; +import { RANGES } from "LLM/features/Market/utils"; +import { MarketCoinDataChart } from "@ledgerhq/live-common/market/utils/types"; +import { getCurrencyColor } from "@ledgerhq/live-common/currencies/index"; +import { CryptoOrTokenCurrency } from "@ledgerhq/types-cryptoassets"; const { width } = getWindowDimensions(); function MarketGraph({ setHoverItem, - chartRequestParams, - loading, - loadingChart, + isLoading, refreshChart, chartData, + range, + currency, }: { setHoverItem: (_: Item | null | undefined) => void; - chartRequestParams: SingleCoinProviderData["chartRequestParams"]; - loading?: boolean; - loadingChart?: boolean; + isLoading?: boolean; refreshChart: (_: { range: string }) => void; - chartData: Record; + chartData?: MarketCoinDataChart; + range: string; + currency?: CryptoOrTokenCurrency; }) { const { t } = useTranslation(); const { colors } = useTheme(); - const ranges = Object.keys(rangeDataTable) - .filter(key => key !== "1h") - .map(r => ({ label: t(`market.range.${r}`), value: r })); + const ranges = RANGES.map(r => ({ label: t(`market.range.${r}`), value: r })); const rangesLabels = ranges.map(({ label }) => label); - const isLoading = loading || loadingChart; - - const { range } = chartRequestParams; - const activeRangeIndex = ranges.findIndex(r => r.value === range); const data = useMemo( () => @@ -61,6 +57,14 @@ function MarketGraph({ const mapGraphValue = useCallback((d: Item) => d?.value || 0, []); + const graphColor = useMemo( + () => + !currency + ? colors.primary.c80 + : ensureContrast(getCurrencyColor(currency), colors.background.main), + [colors.background.main, colors.primary.c80, currency], + ); + return ( @@ -70,7 +74,7 @@ function MarketGraph({ isInteractive height={100} width={width} - color={colors.primary.c80} + color={graphColor} data={data} mapValue={mapGraphValue} onItemHover={setHoverItem} @@ -81,6 +85,7 @@ function MarketGraph({ )} + + {isDefined && value ? ( + <> + {counterValueFormatter({ + currency: withCounterValue ? counterCurrency : undefined, + value: value, + locale, + ticker, + t, + })} + + ) : ( + + - + + )} + + ); + } + return ( {t("market.detailsPage.priceStatistics")} - - {counterValueFormatter({ - currency: counterCurrency, - value: price, - locale, - t, - })} - - {priceChangePercentage !== null && !isNaN(priceChangePercentage) ? ( + + + {!!currency && !!priceChangePercentage && !isNaN(priceChangePercentage) ? ( ) : ( @@ -85,52 +112,39 @@ export default function MarketStats({ )} - - {counterValueFormatter({ - currency: counterCurrency, - value: totalVolume, - locale, - t, - })} - + - - {counterValueFormatter({ - currency: counterCurrency, - value: low24h, - locale, - t, - })}{" "} - /{" "} - {counterValueFormatter({ - currency: counterCurrency, - value: high24h, - locale, - t, - })} - + {currency && low24h && high24h ? ( + + {counterValueFormatter({ + currency: counterCurrency, + value: low24h, + locale, + t, + })}{" "} + /{" "} + {counterValueFormatter({ + currency: counterCurrency, + value: high24h, + locale, + t, + })} + + ) : ( + + - + + )} - - {counterValueFormatter({ - currency: counterCurrency, - value: ath, - locale, - t, - })}{" "} - + + {athDate ? dateFormatter.format(athDate) : "-"} - - {counterValueFormatter({ - currency: counterCurrency, - value: atl, - locale, - t, - })}{" "} - + + {atlDate ? dateFormatter.format(atlDate) : "-"} @@ -155,34 +169,23 @@ export default function MarketStats({ {t("market.detailsPage.supply")} - - {counterValueFormatter({ - value: circulatingSupply, - locale, - ticker, - t, - })} - + - - {counterValueFormatter({ - value: totalSupply, - locale, - ticker, - t, - })} - + - - {counterValueFormatter({ - value: maxSupply, - locale, - ticker, - t, - })} - + ); diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/index.tsx index 80e5912ae4e..61b2f60ede2 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/index.tsx @@ -21,8 +21,10 @@ import BackButton from "./components/BackButton"; import { Item } from "~/components/Graph/types"; import { CurrencyData, + MarketCoinDataChart, + KeysPriceChange, MarketCurrencyChartDataRequestParams, -} from "@ledgerhq/live-common/market/types"; +} from "@ledgerhq/live-common/market/utils/types"; import usePullToRefresh from "../../hooks/usePullToRefresh"; import useMarketDetailViewModel from "./useMarketDetailViewModel"; @@ -32,12 +34,13 @@ interface ViewProps { refresh: (param?: MarketCurrencyChartDataRequestParams) => void; defaultAccount?: AccountLike; toggleStar: () => void; - currency?: CurrencyData; isStarred: boolean; accounts: AccountLike[]; counterCurrency?: string; - chartRequestParams: MarketCurrencyChartDataRequestParams; allAccounts: AccountLike[]; + range: string; + dataChart?: MarketCoinDataChart; + currency?: CurrencyData; } function View({ @@ -47,14 +50,15 @@ function View({ defaultAccount, toggleStar, currency, + dataChart, isStarred, accounts, counterCurrency, - chartRequestParams, allAccounts, + range, }: ViewProps) { - const { range } = chartRequestParams; - const { name, image, price, priceChangePercentage, internalCurrency, chartData } = currency || {}; + const { name, image, internalCurrency, price } = currency || {}; + const { handlePullToRefresh, refreshControlVisible } = usePullToRefresh({ loading, refresh }); const [hoveredItem, setHoverItem] = useState(null); const { t } = useTranslation(); @@ -66,6 +70,8 @@ function View({ [locale, range], ); + const priceChangePercentage = currency?.priceChangePercentage[range as KeysPriceChange]; + return ( + {hoveredItem && hoveredItem.date ? ( @@ -129,6 +136,7 @@ function View({ )} + {internalCurrency ? ( } > - {chartData ? ( - - ) : null} + {accounts?.length > 0 ? ( @@ -185,9 +191,12 @@ function View({ /> ) : null} - {currency && counterCurrency && ( - - )} + + ); diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/useMarketDetailViewModel.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/useMarketDetailViewModel.tsx index b518cf3aa09..4e52973b635 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/useMarketDetailViewModel.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketDetail/useMarketDetailViewModel.tsx @@ -1,8 +1,6 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { useSingleCoinMarketData } from "@ledgerhq/live-common/market/MarketDataProvider"; -import { readOnlyModeEnabledSelector, starredMarketCoinsSelector } from "~/reducers/settings"; -import { addStarredMarketCoins, removeStarredMarketCoins } from "~/actions/settings"; +import { readOnlyModeEnabledSelector } from "~/reducers/settings"; import { flattenAccountsByCryptoCurrencyScreenSelector } from "~/reducers/accounts"; import { screen, track } from "~/analytics"; import { ScreenName } from "~/const"; @@ -10,6 +8,10 @@ import useNotifications from "~/logic/notifications"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import { MarketNavigatorStackParamList } from "LLM/features/Market/Navigator"; +import { useMarket } from "LLM/features/Market/hooks/useMarket"; +import { useMarketCoinData } from "LLM/features/Market/hooks/useMarketCoinData"; +import { addStarredMarketCoins, removeStarredMarketCoins } from "~/actions/settings"; + type NavigationProps = BaseComposite< StackNavigatorProps >; @@ -17,34 +19,38 @@ type NavigationProps = BaseComposite< function useMarketDetailViewModel({ navigation, route }: NavigationProps) { const { params } = route; const { currencyId, resetSearchOnUmount } = params; + + const { marketParams, dataChart, loadingChart, loading, currency } = useMarketCoinData({ + currencyId, + }); + const dispatch = useDispatch(); - const starredMarketCoins: string[] = useSelector(starredMarketCoinsSelector); - const isStarred = starredMarketCoins.includes(currencyId); const { triggerMarketPushNotificationModal } = useNotifications(); - const [hasRetried, setHasRetried] = useState(false); + const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector); - const { - selectedCoinData: currency, - selectCurrency, - chartRequestParams, - loading, - loadingChart, - refreshChart, - counterCurrency, - } = useSingleCoinMarketData(); + const { starredMarketCoins, refresh } = useMarket(); + + const isStarred = starredMarketCoins.includes(currencyId); + + const { counterCurrency = "usd", range = "24h" } = marketParams; const { name, internalCurrency } = currency || {}; useEffect(() => { - if (!loading) { - if (currency === undefined && !hasRetried) { - selectCurrency(currencyId); - setHasRetried(true); - } else if (currency && hasRetried) { - setHasRetried(false); - } + if (name) { + track("Page Market Coin", { + currencyName: name, + starred: isStarred, + timeframe: range, + }); + } + }, [name, isStarred, range]); + + useEffect(() => { + if (readOnlyModeEnabled) { + screen("ReadOnly", "Market Coin"); } - }, [currency, selectCurrency, currencyId, hasRetried, loading]); + }, [readOnlyModeEnabled]); useEffect(() => { const resetState = () => { @@ -54,7 +60,7 @@ function useMarketDetailViewModel({ navigation, route }: NavigationProps) { return () => { sub(); }; - }, [selectCurrency, resetSearchOnUmount, navigation]); + }, [resetSearchOnUmount, navigation]); const allAccounts = useSelector(flattenAccountsByCryptoCurrencyScreenSelector(internalCurrency)); @@ -75,36 +81,19 @@ function useMarketDetailViewModel({ navigation, route }: NavigationProps) { if (!isStarred) triggerMarketPushNotificationModal(); }, [dispatch, isStarred, currencyId, triggerMarketPushNotificationModal]); - useEffect(() => { - if (name) { - track("Page Market Coin", { - currencyName: name, - starred: isStarred, - timeframe: chartRequestParams.range, - }); - } - }, [name, isStarred, chartRequestParams.range]); - - const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector); - - useEffect(() => { - if (readOnlyModeEnabled) { - screen("ReadOnly", "Market Coin"); - } - }, [readOnlyModeEnabled]); - return { - refresh: refreshChart, - currency, - loading, - loadingChart, - toggleStar, - chartRequestParams, defaultAccount, isStarred, accounts: filteredAccounts, counterCurrency, allAccounts, + range, + currency, + dataChart, + loadingChart, + loading, + refresh, + toggleStar, }; } diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/BottomSection/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/BottomSection/index.tsx index 5e670b03652..fa87897bd17 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/BottomSection/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/BottomSection/index.tsx @@ -1,94 +1,96 @@ import React from "react"; -import { - Text, - ScrollContainerHeader, - Icon, - ScrollContainer, - IconsLegacy, -} from "@ledgerhq/native-ui"; +import { Text, ScrollContainerHeader, Icon, ScrollContainer, Icons } from "@ledgerhq/native-ui"; import { useNavigation } from "@react-navigation/native"; import { useTranslation } from "react-i18next"; -import { rangeDataTable } from "@ledgerhq/live-common/market/utils/rangeDataTable"; import { TouchableOpacity } from "react-native"; import SortBadge from "../SortBadge"; import { StyledBadge } from "../SortBadge/SortBadge.styled"; import { ScreenName } from "~/const"; -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams, Order } from "@ledgerhq/live-common/market/utils/types"; import TrackScreen from "~/analytics/TrackScreen"; import useBottomSectionViewModel from "./useBottomSectionViewModel"; +import { RANGES } from "~/newArch/features/Market/utils"; +import { LIMIT } from "~/reducers/market"; const SORT_OPTIONS = { - top100: { + top100G: { requestParam: { - ids: [], starred: [], - orderBy: "market_cap", - order: "desc", + order: Order.topGainers, search: "", liveCompatible: false, - sparkline: false, - top100: true, }, - value: "top100", + value: "top100_gainers", + }, + top100L: { + requestParam: { + starred: [], + order: Order.topLosers, + search: "", + liveCompatible: false, + }, + value: "top100_losers", }, market_cap_asc: { requestParam: { - order: "asc", - orderBy: "market_cap", - top100: false, - limit: 20, + order: Order.MarketCapAsc, + limit: LIMIT, }, value: "market_cap_asc", }, market_cap_desc: { requestParam: { - order: "desc", - orderBy: "market_cap", - top100: false, - limit: 20, + order: Order.MarketCapDesc, + limit: LIMIT, }, value: "market_cap_desc", }, }; -const getIcon = (top100?: boolean, order?: string) => - top100 - ? IconsLegacy.GraphGrowMedium - : order === "asc" - ? IconsLegacy.ArrowTopMedium - : IconsLegacy.ArrowBottomMedium; +const getIcon = (order?: Order) => { + switch (order) { + case Order.topGainers: + return ; + case Order.topLosers: + return ; + case Order.MarketCapDesc: + return ; + case Order.MarketCapAsc: + default: + return ; + } +}; -const TIME_RANGES = Object.keys(rangeDataTable) - .filter(key => key !== "1h") - .map(value => ({ - requestParam: { range: value }, - value, - })); +const TIME_RANGES = RANGES.map(value => ({ + requestParam: { range: value }, + value, +})); interface ViewProps { - top100?: boolean; - orderBy?: string; - order?: string; + order?: Order; range?: string; counterCurrency?: string; onFilterChange: (_: MarketListRequestParams) => void; - toggleFilterByStarredAccounts: () => void; - filterByStarredAccount: boolean; + toggleFilterByStarredCurrencies: () => void; + filterByStarredCurrencies: boolean; } function View({ - top100, - orderBy, order, range, counterCurrency, onFilterChange, - toggleFilterByStarredAccounts, - filterByStarredAccount, + toggleFilterByStarredCurrencies, + filterByStarredCurrencies, }: ViewProps) { const navigation = useNavigation(); const { t } = useTranslation(); + const top100G = order === Order.topGainers; + const top100L = order === Order.topLosers; + + const top100 = top100G || top100L; + const timeRanges = TIME_RANGES.map(timeRange => ({ ...timeRange, label: t(`market.range.${timeRange.value}`), @@ -106,33 +108,42 @@ function View({ showsHorizontalScrollIndicator={false} > - - + + { - if (firstMount.current) { - // We don't want to refresh the market data directly on mount, the data is already refreshed with wanted parameters from MarketDataProviderWrapper - firstMount.current = false; - return; - } - if (filterByStarredAccount) { - refresh({ starred: starredMarketCoins }); - } else { - refresh({ starred: [], search: "" }); - } - }, [refresh, filterByStarredAccount, starredMarketCoins]); + const { range, order, counterCurrency } = marketParams; + + const resetMarketPage = useCallback(() => { + dispatch(setMarketCurrentPage(1)); + dispatch(setMarketRequestParams({ page: 1 })); + }, [dispatch]); - const toggleFilterByStarredAccounts = useCallback(() => { - if (!filterByStarredAccount) { + const toggleFilterByStarredCurrencies = useCallback(() => { + if (!filterByStarredCurrencies) { track( "Page Market Favourites", - getAnalyticsProperties(requestParams, { + getAnalyticsProperties(marketParams, { currencies: starredMarketCoins, }), ); } - dispatch(setMarketFilterByStarredAccounts(!filterByStarredAccount)); - }, [dispatch, filterByStarredAccount, requestParams, starredMarketCoins]); + + dispatch(setMarketFilterByStarredCurrencies(!filterByStarredCurrencies)); + resetMarketPage(); + }, [dispatch, filterByStarredCurrencies, marketParams, resetMarketPage, starredMarketCoins]); const onFilterChange = useCallback( (value: MarketListRequestParams) => { track( "Page Market", getAnalyticsProperties({ - ...requestParams, + ...marketParams, ...value, }), ); + dispatch(setMarketRequestParams(value)); - refresh(value); + resetMarketPage(); }, - [dispatch, refresh, requestParams], + [dispatch, marketParams, resetMarketPage], ); return { onFilterChange, - filterByStarredAccount, - toggleFilterByStarredAccounts, + filterByStarredCurrencies, + toggleFilterByStarredCurrencies, range, - orderBy, order, - top100, counterCurrency, }; } diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListEmpty/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListEmpty/index.tsx index b8bf7d57a23..964dd090608 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListEmpty/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListEmpty/index.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useNetInfo } from "@react-native-community/netinfo"; -import { Text, InfiniteLoader } from "@ledgerhq/native-ui"; +import { Text } from "@ledgerhq/native-ui"; import { Trans, useTranslation } from "react-i18next"; import EmptyState from "../EmptyState"; import EmptyStarredCoins from "../EmptyStarredCoins"; @@ -60,9 +60,9 @@ function ListEmpty({ } else if (hasEmptyStarredCoins) { // Empty starred coins return ; + } else { + return null; } - - return ; } export default ListEmpty; diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListRow/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListRow/index.tsx index 53886479e52..dd23bca7bea 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListRow/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/ListRow/index.tsx @@ -3,27 +3,25 @@ import { TouchableOpacity } from "react-native"; import { useNavigation } from "@react-navigation/native"; import { ScreenName } from "~/const"; import MarketRowItem from "LLM/features/Market/components/MarketRowItem"; -import { CurrencyData } from "@ledgerhq/live-common/market/types"; +import { CurrencyData } from "@ledgerhq/live-common/market/utils/types"; interface ListRowProps { item: CurrencyData; index: number; counterCurrency?: string; range?: string; - selectCurrency: (id?: string, data?: CurrencyData, range?: string) => void; } -function ListRow({ item, index, counterCurrency, range, selectCurrency }: ListRowProps) { +function ListRow({ item, index, counterCurrency, range }: ListRowProps) { const navigation = useNavigation(); return ( { - selectCurrency(item.id, item, range); navigation.navigate(ScreenName.MarketDetail, { currencyId: item.id, }); }} > - + ); } diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SearchHeader/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SearchHeader/index.tsx index e0a571e4b53..8bbbef359b5 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SearchHeader/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SearchHeader/index.tsx @@ -2,8 +2,9 @@ import { SearchInput } from "@ledgerhq/native-ui"; import { useDebounce } from "@ledgerhq/live-common/hooks/useDebounce"; import React, { memo, useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; import { track } from "~/analytics"; +import { LIMIT } from "~/reducers/market"; type Props = { search?: string; @@ -23,7 +24,7 @@ function SearchHeader({ search, refresh }: Props) { search: debouncedSearch ? debouncedSearch.trim() : "", starred: [], liveCompatible: false, - limit: 20, + limit: LIMIT, }); }, [debouncedSearch, refresh]); diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SortBadge/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SortBadge/index.tsx index 9a93b90c7cc..9cb3937096d 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SortBadge/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/components/SortBadge/index.tsx @@ -1,9 +1,8 @@ -import React, { memo, useState, useCallback } from "react"; +import React, { memo, useState, useCallback, ReactNode } from "react"; import { TouchableOpacity } from "react-native"; import { Flex, Text, Icon as IconUI } from "@ledgerhq/native-ui"; -import { IconType } from "@ledgerhq/native-ui/components/Icon/type"; import QueuedDrawer from "~/components/QueuedDrawer"; -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; import { StyledBadge, StyledCheckIconContainer } from "./SortBadge.styled"; type Option = { @@ -16,7 +15,7 @@ type Props = { label: string; valueLabel: string; value: unknown; - Icon?: IconType; + Icon?: ReactNode; options: Option[]; disabled?: boolean; onChange: (_: MarketListRequestParams) => void; @@ -39,11 +38,7 @@ function SortBadge({ label, valueLabel, value, Icon, options, disabled, onChange {valueLabel} - {Icon ? ( - - - - ) : null} + {Icon ? {Icon} : null} diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/index.tsx index a0d376c4d24..23a1af6c5a1 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/index.tsx @@ -1,8 +1,8 @@ -import React, { useCallback, useContext } from "react"; +import React, { MutableRefObject, useCallback, useContext, useEffect } from "react"; import { Flex } from "@ledgerhq/native-ui"; -import { Platform, RefreshControl } from "react-native"; +import { Platform, RefreshControl, ViewToken } from "react-native"; import { TAB_BAR_SAFE_HEIGHT } from "~/components/TabBar/TabBarSafeAreaView"; -import { CurrencyData, MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { CurrencyData, MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; import { useFocusEffect } from "@react-navigation/native"; import { AnalyticsContext } from "~/analytics/AnalyticsContext"; import CollapsibleHeaderFlatList from "~/components/WalletTab/CollapsibleHeaderFlatList"; @@ -16,6 +16,7 @@ import BottomSection from "./components/BottomSection"; import globalSyncRefreshControl from "~/components/globalSyncRefreshControl"; import usePullToRefresh from "../../hooks/usePullToRefresh"; import useMarketListViewModel from "./useMarketListViewModel"; +import { LIMIT } from "~/reducers/market"; const RefreshableCollapsibleHeaderFlatList = globalSyncRefreshControl( CollapsibleHeaderFlatList, @@ -28,29 +29,44 @@ const keyExtractor = (item: CurrencyData, index: number) => item.id + index; interface ViewProps { marketData?: CurrencyData[]; - filterByStarredAccount: boolean; + filterByStarredCurrencies: boolean; starredMarketCoins: string[]; search?: string; loading: boolean; refresh: (param?: MarketListRequestParams) => void; counterCurrency?: string; range?: string; - selectCurrency: (id?: string, data?: CurrencyData, range?: string) => void; - isLoading: boolean; - onEndReached: () => Promise | undefined; + onEndReached?: () => void; + refetchData: (pageToRefetch: number) => void; + resetMarketPageToInital: (page: number) => void; + refreshRate: number; + marketParams: MarketListRequestParams; + marketCurrentPage: number; + viewabilityConfigCallbackPairs: MutableRefObject< + { + onViewableItemsChanged: ({ viewableItems }: { viewableItems: ViewToken[] }) => void; + viewabilityConfig: { + viewAreaCoveragePercentThreshold: number; + }; + }[] + >; } function View({ marketData, - filterByStarredAccount, + filterByStarredCurrencies, starredMarketCoins, search, loading, refresh, counterCurrency, range, - selectCurrency, - isLoading, onEndReached, + refreshRate, + marketCurrentPage, + refetchData, + viewabilityConfigCallbackPairs, + resetMarketPageToInital, + marketParams, }: ViewProps) { const { colors } = useTheme(); const { handlePullToRefresh, refreshControlVisible } = usePullToRefresh({ loading, refresh }); @@ -61,8 +77,7 @@ function View({ search: "", starred: [], liveCompatible: false, - top100: false, - limit: 20, + limit: LIMIT, }), [refresh], ); @@ -79,6 +94,23 @@ function View({ }, [setScreen, setSource]), ); + /** + * Reset the page to 1 when the component mounts to only refetch first page + * */ + useEffect(() => { + resetMarketPageToInital(marketParams.page ?? 1); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + /** + * Try to Refetch data every REFRESH_RATE time + */ + useEffect(() => { + const intervalId = setInterval(() => refetchData(marketCurrentPage ?? 1), refreshRate); + + return () => clearInterval(intervalId); + }, [marketCurrentPage, refetchData, refreshRate]); + const listProps = { contentContainerStyle: { paddingHorizontal: 16, @@ -86,24 +118,20 @@ function View({ }, data: marketData, renderItem: ({ item, index }: { item: CurrencyData; index: number }) => ( - + ), onEndReached, + maxToRenderPerBatch: 50, onEndReachedThreshold: 0.5, scrollEventThrottle: 50, initialNumToRender: 50, keyExtractor, - ListFooterComponent: , + viewabilityConfigCallbackPairs: viewabilityConfigCallbackPairs.current, + ListFooterComponent: , ListEmptyComponent: ( diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/useMarketListViewModel.ts b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/useMarketListViewModel.ts index 77c0684fe6a..025d4d76c9d 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/useMarketListViewModel.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Market/screens/MarketList/useMarketListViewModel.ts @@ -1,98 +1,136 @@ -import { useCallback, useState, useEffect } from "react"; -import { useSelector } from "react-redux"; +import { useCallback, useEffect, useRef } from "react"; +import { useDispatch } from "react-redux"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import { MarketNavigatorStackParamList } from "LLM/features/Market/Navigator"; import { ScreenName } from "~/const"; import { useRoute } from "@react-navigation/native"; -import { - marketFilterByStarredAccountsSelector, - starredMarketCoinsSelector, -} from "~/reducers/settings"; -import { useMarketData } from "@ledgerhq/live-common/market/MarketDataProvider"; - +import { useMarketData as useMarketDataHook } from "@ledgerhq/live-common/market/hooks/useMarketDataProvider"; +import { setMarketCurrentPage, setMarketRequestParams } from "~/actions/market"; +import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; +import { useMarket } from "../../hooks/useMarket"; +import { getCurrentPage, isDataStale } from "../../utils"; +import { ViewToken } from "react-native"; +import { Order } from "@ledgerhq/live-common/market/utils/types"; type NavigationProps = BaseComposite< StackNavigatorProps >; +export const REFETCH_TIME_ONE_MINUTE = 60 * 1000; + +export const BASIC_REFETCH = 3; // nb minutes + +export const viewabilityConfig = { + viewAreaCoveragePercentThreshold: 72, +}; + function useMarketListViewModel() { + const llmRefreshMarketDataFeature = useFeature("llmRefreshMarketData"); const { params } = useRoute(); - const [isLoading, setIsLoading] = useState(true); + + const REFRESH_RATE = + Number(llmRefreshMarketDataFeature?.params?.refreshTime) > 0 + ? REFETCH_TIME_ONE_MINUTE * Number(llmRefreshMarketDataFeature?.params?.refreshTime) + : REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH; + const initialTop100 = params?.top100; - const starredMarketCoins: string[] = useSelector(starredMarketCoinsSelector); - const filterByStarredAccount: boolean = useSelector(marketFilterByStarredAccountsSelector); + + const dispatch = useDispatch(); const { - requestParams, + marketParams, + starredMarketCoins, + filterByStarredCurrencies, + marketCurrentPage, refresh, - counterCurrency, - marketData, - loadNextPage, - loading, - page, - selectCurrency, - } = useMarketData(); + } = useMarket(); - const { limit, search, range, top100 } = requestParams; + const { search, counterCurrency, range } = marketParams; - const marketDataFiltered = filterByStarredAccount - ? marketData?.filter(d => starredMarketCoins.includes(d.id)) ?? undefined - : marketData; + const marketResult = useMarketDataHook({ + ...marketParams, + starred: filterByStarredCurrencies ? starredMarketCoins : [], + }); + + const marketDataFiltered = filterByStarredCurrencies + ? marketResult.data?.filter(d => starredMarketCoins.includes(d.id)) ?? undefined + : marketResult.data; useEffect(() => { if (initialTop100) { refresh({ limit: 100, - ids: [], starred: [], - orderBy: "market_cap", - order: "desc", + order: Order.MarketCapDesc, search: "", liveCompatible: false, - sparkline: false, - top100: true, }); } }, [initialTop100, refresh]); - useEffect(() => { - if (filterByStarredAccount && starredMarketCoins.length > 0) { - refresh({ starred: starredMarketCoins }); + const onEndReached = useCallback(() => { + dispatch(setMarketRequestParams({ page: (marketParams?.page || 1) + 1 })); + }, [dispatch, marketParams?.page]); + + /** + * + * Refresh mechanism ---------------------------------------------- + */ + + const refetchData = useCallback( + (pageToRefetch: number) => { + const elem = marketResult.cachedMetadataMap.get(String(pageToRefetch - 1 ?? 0)); + if (elem && isDataStale(elem.updatedAt, REFRESH_RATE)) { + elem.refetch(); + } + }, + [marketResult.cachedMetadataMap, REFRESH_RATE], + ); + + const checkIfDataIsStaleAndRefetch = useCallback( + (indexPosition: number) => { + const newCurrentPage = getCurrentPage(indexPosition, marketParams.limit || 50); + + if (marketCurrentPage !== newCurrentPage) { + dispatch(setMarketCurrentPage(newCurrentPage)); + } + + refetchData(newCurrentPage); + }, + [marketParams.limit, marketCurrentPage, refetchData, dispatch], + ); + + const onViewableItemsChanged = ({ viewableItems }: { viewableItems: ViewToken[] }) => { + const lastVisible = viewableItems.map((elem: ViewToken) => elem.index).at(-1); + if (lastVisible) { + checkIfDataIsStaleAndRefetch(Number(lastVisible) + 2 ?? 0); } - }, [refresh, starredMarketCoins, filterByStarredAccount]); + }; + const viewabilityConfigCallbackPairs = useRef([{ onViewableItemsChanged, viewabilityConfig }]); - const onEndReached = useCallback(() => { - if ( - !limit || - isNaN(limit) || - !marketData || - page * limit > marketData.length || - loading || - top100 - ) { - setIsLoading(false); - return Promise.resolve(); + const resetMarketPageToInital = (page: number) => { + if (page > 1) { + dispatch(setMarketRequestParams({ page: 1 })); + dispatch(setMarketCurrentPage(1)); } - setIsLoading(true); - const next = loadNextPage(); - return next - ?.then(() => { - // do nothing - }) - .finally(() => setIsLoading(false)); - }, [limit, marketData, page, loading, top100, loadNextPage]); + }; return { marketData: marketDataFiltered, - filterByStarredAccount, + filterByStarredCurrencies, starredMarketCoins, search, - loading, + loading: marketResult.isLoading, refresh, counterCurrency, range, - selectCurrency, - isLoading, onEndReached, + refetchData, + refreshRate: REFRESH_RATE, + marketCurrentPage, + checkIfDataIsStaleAndRefetch, + viewabilityConfigCallbackPairs, + marketParams, + resetMarketPageToInital, }; } diff --git a/apps/ledger-live-mobile/src/newArch/features/Market/utils/index.ts b/apps/ledger-live-mobile/src/newArch/features/Market/utils/index.ts index 757110a0cd3..8c561343370 100644 --- a/apps/ledger-live-mobile/src/newArch/features/Market/utils/index.ts +++ b/apps/ledger-live-mobile/src/newArch/features/Market/utils/index.ts @@ -1,6 +1,10 @@ -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; +import { getSortParam } from "@ledgerhq/live-common/market/utils/index"; +import { rangeDataTable } from "@ledgerhq/live-common/market/utils/rangeDataTable"; import { TFunction } from "i18next"; +export const RANGES = Object.keys(rangeDataTable).filter(key => key !== "1h"); + const indexes: [string, number][] = [ ["d", 1], ["K", 1000], @@ -114,9 +118,22 @@ export function getAnalyticsProperties

( return { ...otherProperties, access: false, - sort: `${requestParams.orderBy}_${requestParams.order}`, + ...(requestParams.order && + requestParams.range && { sort: getSortParam(requestParams.order, requestParams.range) }), "%change": requestParams.range, countervalue: requestParams.counterCurrency, view: requestParams.liveCompatible ? "Only Live Supported" : "All coins", }; } + +export const isDataStale = (lastUpdate: number, refreshRate: number) => { + const currentTime = new Date(); + const updatedAt = new Date(lastUpdate); + const elapsedTime = currentTime.getTime() - updatedAt.getTime(); + + return elapsedTime > refreshRate; +}; + +export function getCurrentPage(indexPosition: number, pageSize: number): number { + return Math.floor(indexPosition / pageSize) + 1; +} diff --git a/apps/ledger-live-mobile/src/reducers/index.ts b/apps/ledger-live-mobile/src/reducers/index.ts index 3b4a0c17064..43547f175ea 100644 --- a/apps/ledger-live-mobile/src/reducers/index.ts +++ b/apps/ledger-live-mobile/src/reducers/index.ts @@ -12,6 +12,7 @@ import dynamicContent from "./dynamicContent"; import walletconnect from "./walletconnect"; import protect from "./protect"; import nft from "./nft"; +import market from "./market"; import wallet from "./wallet"; import { State } from "./types"; import { ActionsPayload } from "../actions/types"; @@ -33,6 +34,7 @@ const appReducer = combineReducers({ protect, nft, wallet, + market, }); // TODO: EXPORT ALL POSSIBLE ACTION TYPES AND USE ACTION diff --git a/apps/ledger-live-mobile/src/reducers/market.ts b/apps/ledger-live-mobile/src/reducers/market.ts new file mode 100644 index 00000000000..c05a3836bb1 --- /dev/null +++ b/apps/ledger-live-mobile/src/reducers/market.ts @@ -0,0 +1,59 @@ +import { Action, ReducerMap, handleActions } from "redux-actions"; +import { MarketState, State } from "./types"; +import { + MarketSetCurrentPagePayload, + MarketSetMarketFilterByStarredCurrenciesPayload, + MarketSetMarketRequestParamsPayload, + MarketStateActionTypes, + MarketStatePayload, +} from "~/actions/types"; +import { Order } from "@ledgerhq/live-common/market/utils/types"; + +export const LIMIT = 20; + +export const INITIAL_STATE: MarketState = { + marketParams: { + range: "24h", + limit: LIMIT, + starred: [], + order: Order.MarketCapDesc, + search: "", + liveCompatible: false, + page: 1, + counterCurrency: "usd", + }, + marketFilterByStarredCurrencies: false, + marketCurrentPage: 1, +}; + +const handlers: ReducerMap = { + [MarketStateActionTypes.SET_MARKET_REQUEST_PARAMS]: (state, action) => ({ + ...state, + marketParams: { + ...state.marketParams, + ...(action as Action).payload, + }, + }), + [MarketStateActionTypes.SET_MARKET_FILTER_BY_STARRED_CURRENCIES]: (state, action) => ({ + ...state, + marketFilterByStarredCurrencies: ( + action as Action + ).payload, + }), + + [MarketStateActionTypes.MARKET_SET_CURRENT_PAGE]: (state, action) => ({ + ...state, + marketCurrentPage: (action as Action).payload, + }), +}; + +// Selectors + +export const marketParamsSelector = (state: State) => state.market.marketParams; +export const marketFilterByStarredCurrenciesSelector = (state: State) => + state.market.marketFilterByStarredCurrencies; +export const marketCurrentPageSelector = (state: State) => state.market.marketCurrentPage; + +// Exporting reducer + +export default handleActions(handlers, INITIAL_STATE); diff --git a/apps/ledger-live-mobile/src/reducers/settings.ts b/apps/ledger-live-mobile/src/reducers/settings.ts index 07e04faca96..b6598724be9 100644 --- a/apps/ledger-live-mobile/src/reducers/settings.ts +++ b/apps/ledger-live-mobile/src/reducers/settings.ts @@ -16,7 +16,6 @@ import { currencySettingsDefaults } from "../helpers/CurrencySettingsDefaults"; import { getDefaultLanguageLocale, getDefaultLocale } from "../languages"; import type { SettingsAcceptSwapProviderPayload, - SettingsAddStarredMarketcoinsPayload, SettingsBlacklistTokenPayload, SettingsDismissBannerPayload, SettingsHideEmptyTokenAccountsPayload, @@ -27,7 +26,6 @@ import type { SettingsSetHasInstalledAnyAppPayload, SettingsLastSeenDeviceInfoPayload, SettingsPayload, - SettingsRemoveStarredMarketcoinsPayload, SettingsSetAnalyticsPayload, SettingsSetPersonalizedRecommendationsPayload, SettingsSetAvailableUpdatePayload, @@ -40,8 +38,6 @@ import type { SettingsSetMarketCounterCurrencyPayload, SettingsSetCustomImageBackupPayload, SettingsSetLastSeenCustomImagePayload, - SettingsSetMarketFilterByStarredAccountsPayload, - SettingsSetMarketRequestParamsPayload, SettingsSetNotificationsPayload, SettingsSetNeverClickedOnAllowNotificationsButton, SettingsSetOrderAccountsPayload, @@ -81,6 +77,8 @@ import type { SettingsSetHasSeenAnalyticsOptInPrompt, SettingsSetDismissedContentCardsPayload, SettingsClearDismissedContentCardsPayload, + SettingsAddStarredMarketcoinsPayload, + SettingsRemoveStarredMarketcoinsPayload, } from "../actions/types"; import { SettingsActionTypes, @@ -149,18 +147,8 @@ export const INITIAL_STATE: SettingsState = { europa: false, }, hasSeenStaxEnabledNftsPopup: false, - starredMarketCoins: [], lastConnectedDevice: null, - marketRequestParams: { - range: "24h", - orderBy: "market_cap", - order: "desc", - liveCompatible: false, - sparkline: false, - top100: false, - }, marketCounterCurrency: null, - marketFilterByStarredAccounts: false, sensitiveAnalytics: false, onboardingHasDevice: null, notifications: { @@ -185,6 +173,7 @@ export const INITIAL_STATE: SettingsState = { supportedCounterValues: [], hasSeenAnalyticsOptInPrompt: false, dismissedContentCards: {}, + starredMarketCoins: [], }; const pairHash = (from: { ticker: string }, to: { ticker: string }) => @@ -496,21 +485,6 @@ const handlers: ReducerMap = { }; }, - [SettingsActionTypes.ADD_STARRED_MARKET_COINS]: (state, action) => ({ - ...state, - starredMarketCoins: [ - ...state.starredMarketCoins, - (action as Action).payload, - ], - }), - - [SettingsActionTypes.REMOVE_STARRED_MARKET_COINS]: (state, action) => ({ - ...state, - starredMarketCoins: state.starredMarketCoins.filter( - id => id !== (action as Action).payload, - ), - }), - [SettingsActionTypes.SET_CUSTOM_IMAGE_BACKUP]: (state, action) => ({ ...state, customLockScreenBackup: (action as Action).payload, @@ -530,26 +504,11 @@ const handlers: ReducerMap = { hasOrderedNano: (action as Action).payload, }), - [SettingsActionTypes.SET_MARKET_REQUEST_PARAMS]: (state, action) => ({ - ...state, - marketRequestParams: { - ...state.marketRequestParams, - ...(action as Action).payload, - }, - }), - [SettingsActionTypes.SET_MARKET_COUNTER_CURRENCY]: (state, action) => ({ ...state, marketCounterCurrency: (action as Action).payload, }), - [SettingsActionTypes.SET_MARKET_FILTER_BY_STARRED_ACCOUNTS]: (state, action) => ({ - ...state, - marketFilterByStarredAccounts: ( - action as Action - ).payload, - }), - [SettingsActionTypes.SET_SENSITIVE_ANALYTICS]: (state, action) => ({ ...state, sensitiveAnalytics: (action as Action).payload, @@ -678,6 +637,21 @@ const handlers: ReducerMap = { dismissedContentCards, }; }, + + [SettingsActionTypes.ADD_STARRED_MARKET_COINS]: (state, action) => ({ + ...state, + starredMarketCoins: [ + ...state.starredMarketCoins, + (action as Action).payload, + ], + }), + + [SettingsActionTypes.REMOVE_STARRED_MARKET_COINS]: (state, action) => ({ + ...state, + starredMarketCoins: state.starredMarketCoins.filter( + id => id !== (action as Action).payload, + ), + }), }; export default handleActions(handlers, INITIAL_STATE); @@ -854,7 +828,6 @@ export const knownDeviceModelIdsSelector = (state: State) => state.settings.know export const hasSeenStaxEnabledNftsPopupSelector = (state: State) => state.settings.hasSeenStaxEnabledNftsPopup; export const customImageTypeSelector = (state: State) => state.settings.customLockScreenType; -export const starredMarketCoinsSelector = (state: State) => state.settings.starredMarketCoins; export const lastSeenDeviceSelector = (state: State) => { const { lastSeenDevice } = state.settings; @@ -871,10 +844,7 @@ export const lastConnectedDeviceSelector = (state: State) => { }; export const hasOrderedNanoSelector = (state: State) => state.settings.hasOrderedNano; -export const marketRequestParamsSelector = (state: State) => state.settings.marketRequestParams; export const marketCounterCurrencySelector = (state: State) => state.settings.marketCounterCurrency; -export const marketFilterByStarredAccountsSelector = (state: State) => - state.settings.marketFilterByStarredAccounts; export const customImageBackupSelector = (state: State) => state.settings.customLockScreenBackup; export const sensitiveAnalyticsSelector = (state: State) => state.settings.sensitiveAnalytics; export const onboardingHasDeviceSelector = (state: State) => state.settings.onboardingHasDevice; @@ -904,3 +874,5 @@ export const supportedCounterValuesSelector = (state: State) => export const hasSeenAnalyticsOptInPromptSelector = (state: State) => state.settings.hasSeenAnalyticsOptInPrompt; export const dismissedContentCardsSelector = (state: State) => state.settings.dismissedContentCards; + +export const starredMarketCoinsSelector = (state: State) => state.settings.starredMarketCoins; diff --git a/apps/ledger-live-mobile/src/reducers/types.ts b/apps/ledger-live-mobile/src/reducers/types.ts index 239b2eae615..b48cdcba59b 100644 --- a/apps/ledger-live-mobile/src/reducers/types.ts +++ b/apps/ledger-live-mobile/src/reducers/types.ts @@ -9,7 +9,7 @@ import type { import type { Device } from "@ledgerhq/live-common/hw/actions/types"; import type { DeviceModelId } from "@ledgerhq/devices"; import type { CryptoCurrencyId, Currency, Unit } from "@ledgerhq/types-cryptoassets"; -import { MarketListRequestParams } from "@ledgerhq/live-common/market/types"; +import { MarketListRequestParams } from "@ledgerhq/live-common/market/utils/types"; import { PostOnboardingState } from "@ledgerhq/types-live"; import { AvailableProviderV3, ExchangeRate } from "@ledgerhq/live-common/exchange/swap/types"; import { Transaction } from "@ledgerhq/live-common/generated/types"; @@ -227,11 +227,8 @@ export type SettingsState = { lastSeenDevice: DeviceModelInfo | null; knownDeviceModelIds: Record; hasSeenStaxEnabledNftsPopup: boolean; - starredMarketCoins: string[]; lastConnectedDevice: Device | null; - marketRequestParams: MarketListRequestParams; marketCounterCurrency: string | null | undefined; - marketFilterByStarredAccounts: boolean; sensitiveAnalytics: boolean; onboardingHasDevice: boolean | null; onboardingType: OnboardingType | null; @@ -263,6 +260,7 @@ export type SettingsState = { supportedCounterValues: supportedCountervaluesData[]; hasSeenAnalyticsOptInPrompt: boolean; dismissedContentCards: { [id: string]: number }; + starredMarketCoins: string[]; }; export type NotificationsSettings = { @@ -333,6 +331,14 @@ export type NftGalleryChainFiltersState = Pick< "polygon" | "ethereum" >; +// === MARKET STATE === + +export type MarketState = { + marketParams: MarketListRequestParams; + marketFilterByStarredCurrencies: boolean; + marketCurrentPage: number; +}; + // === ROOT STATE === export type State = { @@ -349,5 +355,6 @@ export type State = { postOnboarding: PostOnboardingState; protect: ProtectState; nft: NftState; + market: MarketState; wallet: WalletState; }; diff --git a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AssetMarketSection.tsx b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AssetMarketSection.tsx index 01f3eebe3b5..401d2be72b4 100644 --- a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AssetMarketSection.tsx +++ b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/AssetMarketSection.tsx @@ -1,11 +1,11 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Flex } from "@ledgerhq/native-ui"; import { CryptoOrTokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { useSingleCoinMarketData } from "@ledgerhq/live-common/market/MarketDataProvider"; import SectionContainer from "../WalletCentricSections/SectionContainer"; import SectionTitle from "../WalletCentricSections/SectionTitle"; import MarketPriceSection from "../WalletCentricSections/MarketPrice"; +import { useMarketCoinData } from "~/newArch/features/Market/hooks/useMarketCoinData"; // @FIXME workaround for main tokens const tokenIDToMarketID = { @@ -15,18 +15,19 @@ const tokenIDToMarketID = { const AssetMarketSection = ({ currency }: { currency: CryptoOrTokenCurrency }) => { const { t } = useTranslation(); - const { selectedCoinData, selectCurrency, counterCurrency } = useSingleCoinMarketData(); + const [selectedCurrency, setSelectedCurrency] = useState(currency.id); + const { currency: fetchedCurrency, counterCurrency } = useMarketCoinData({ + currencyId: selectedCurrency, + }); useEffect(() => { - selectCurrency( + setSelectedCurrency( tokenIDToMarketID[currency.id as keyof typeof tokenIDToMarketID] || currency.id, - undefined, - "24h", ); - return () => selectCurrency(); - }, [currency, selectCurrency]); + }, [currency]); + + if (!fetchedCurrency?.price) return null; - if (!selectedCoinData?.price) return null; return ( diff --git a/apps/ledger-live-mobile/src/screens/WalletCentricSections/MarketPrice.tsx b/apps/ledger-live-mobile/src/screens/WalletCentricSections/MarketPrice.tsx index c85cdd7fe20..1af7e73398f 100644 --- a/apps/ledger-live-mobile/src/screens/WalletCentricSections/MarketPrice.tsx +++ b/apps/ledger-live-mobile/src/screens/WalletCentricSections/MarketPrice.tsx @@ -4,16 +4,18 @@ import { useTranslation } from "react-i18next"; import { useNavigation } from "@react-navigation/native"; import counterValueFormatter from "@ledgerhq/live-common/market/utils/countervalueFormatter"; import { CryptoOrTokenCurrency } from "@ledgerhq/types-cryptoassets"; -import { SingleCoinProviderData } from "@ledgerhq/live-common/market/MarketDataProvider"; import { withDiscreetMode } from "~/context/DiscreetModeContext"; import { ScreenName } from "~/const"; import DeltaVariation from "LLM/features/Market/components/DeltaVariation"; import Touchable from "~/components/Touchable"; import { useSettings } from "~/hooks"; +import { CurrencyData, KeysPriceChange } from "@ledgerhq/live-common/market/utils/types"; +import { useTimeRange } from "~/actions/settings"; +import { PortfolioRange } from "@ledgerhq/types-live"; type Props = { currency: CryptoOrTokenCurrency; - selectedCoinData: SingleCoinProviderData["selectedCoinData"]; + selectedCoinData: CurrencyData; counterCurrency: string | undefined; }; @@ -22,12 +24,20 @@ const MarketPrice = ({ currency, selectedCoinData, counterCurrency }: Props) => const { locale } = useSettings(); const navigation = useNavigation(); + const [range] = useTimeRange(); + const goToMarketPage = useCallback(() => { navigation.navigate(ScreenName.MarketDetail, { currencyId: currency.id, }); }, [currency, navigation]); + const getPrice = (selectedCoinData: CurrencyData, range: PortfolioRange) => { + const key: KeysPriceChange = range === "all" ? KeysPriceChange.year : KeysPriceChange[range]; + return selectedCoinData.priceChangePercentage[key]; + }; + + const priceChange = getPrice(selectedCoinData, range); return ( - {t("portfolio.marketPriceSection.currencyPriceChange")} + {t(`portfolio.marketPriceSection.currencyPriceChange.${range}`)} - + diff --git a/libs/ledger-live-common/.unimportedrc.json b/libs/ledger-live-common/.unimportedrc.json index aa01cfc1b22..8fd965fddd9 100644 --- a/libs/ledger-live-common/.unimportedrc.json +++ b/libs/ledger-live-common/.unimportedrc.json @@ -217,9 +217,6 @@ "src/manager/index.ts", "src/manager/localization.ts", "src/manager/provider.ts", - "src/market/api/api.mock.ts", - "src/market/MarketDataProvider.tsx", - "src/market/types.ts", "src/market/utils/countervalueFormatter.ts", "src/market/utils/rangeDataTable.ts", "src/mock/account.ts", @@ -2688,11 +2685,14 @@ "src/index.ts", "src/jest.d.ts", "src/manager/index.test.ts", - "src/market/v2", - "src/market/v2/queryKeys.ts", - "src/market/v2/timers.ts", - "src/market/v2/useMarketDataProvider.ts", - "src/market/v2/useMarketPerformers.ts", + "src/market/api/index.ts", + "src/market/utils/types.ts", + "src/market/utils/index.ts", + "src/market/utils/queryKeys.ts", + "src/market/utils/timers.ts", + "src/market/utils/currencyFormatter.ts", + "src/market/hooks/useMarketDataProvider.ts", + "src/market/hooks/useMarketPerformers.ts", "src/mock/fixtures", "src/mock/fixtures/aDeviceInfo.ts", "src/mock/fixtures/aLatestFirmwareContext.ts", diff --git a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts index b431ad0cb7a..12d5ce53791 100644 --- a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts +++ b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts @@ -463,6 +463,12 @@ export const DEFAULT_FEATURES: Features = { refreshTime: 3, //nb minutes }, }, + llmRefreshMarketData: { + ...DEFAULT_FEATURE, + params: { + refreshTime: 3, //nb minutes + }, + }, spamReportNfts: DEFAULT_FEATURE, lldWalletSync: DEFAULT_FEATURE, lldNftsGalleryNewArch: DEFAULT_FEATURE, diff --git a/libs/ledger-live-common/src/market/MarketDataProvider.tsx b/libs/ledger-live-common/src/market/MarketDataProvider.tsx deleted file mode 100644 index 6de165f70b0..00000000000 --- a/libs/ledger-live-common/src/market/MarketDataProvider.tsx +++ /dev/null @@ -1,403 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -// @flow -import React, { - createContext, - useCallback, - useContext, - ReactElement, - useReducer, - useEffect, -} from "react"; - -import { useDebounce } from "../hooks/useDebounce"; -import { - State, - MarketDataApi, - MarketListRequestParams, - MarketCurrencyChartDataRequestParams, - CurrencyData, - SingleCoinState, - SupportedCoins, -} from "./types"; -import defaultFetchApi from "./api/api"; -import type { Currency } from "@ledgerhq/types-cryptoassets"; -type Props = { - children: React.ReactNode; - fetchApi?: MarketDataApi; - countervalue?: Currency; - initState?: Partial; -}; -type API = { - refresh: (param?: MarketListRequestParams) => void; - refreshChart: (param?: MarketCurrencyChartDataRequestParams) => void; - selectCurrency: (id?: string, data?: CurrencyData, range?: string) => void; - loadNextPage: (startIndex?: number, stopIndex?: number) => void | Promise; - setCounterCurrency: (counterCurrency: string) => void; -}; - -export type MarketDataContextType = State & API; - -const initialState: State = { - ready: false, - marketData: [], - selectedCurrency: undefined, - requestParams: { - range: "24h", - limit: 50, - ids: [], - starred: [], - orderBy: "market_cap", - order: "desc", - search: "", - liveCompatible: false, - }, - page: 1, - chartRequestParams: { - range: "24h", - }, - loading: false, - loadingChart: false, - endOfList: false, - error: undefined, - totalCoinsAvailable: 0, - supportedCounterCurrencies: [], - selectedCoinData: undefined, - counterCurrency: undefined, -}; - -const MarketDataContext = createContext({ - ...initialState, - refresh: () => {}, - refreshChart: () => {}, - selectCurrency: () => {}, - loadNextPage: () => Promise.resolve(), - setCounterCurrency: () => {}, -}); - -const ACTIONS = { - IS_READY: "IS_READY", - - UPDATE_MARKET_DATA: "UPDATE_MARKET_DATA", - UPDATE_SINGLE_MARKET_DATA: "UPDATE_SINGLE_MARKET_DATA", - UPDATE_SINGLE_CHART_DATA: "UPDATE_SINGLE_CHART_DATA", - - REFRESH_MARKET_DATA: "REFRESH_MARKET_DATA", - REFRESH_CHART_DATA: "REFRESH_CHART_DATA", - - SET_LOADING: "SET_LOADING", - SET_LOADING_CHART: "SET_LOADING_CHART", - SET_ERROR: "SET_ERROR", - - SELECT_CURRENCY: "SELECT_CURRENCY", - UPDATE_COUNTERVALUE: "UPDATE_COUNTERVALUE", -}; - -function marketDataReducer(state, action) { - switch (action.type) { - case ACTIONS.IS_READY: - return { ...state, ...action.payload, isReady: true }; - case ACTIONS.UPDATE_MARKET_DATA: { - const newData = action.payload.marketData; - const page = action.payload.page || state.requestParams.page; - const marketData = [...state.marketData.map(data => ({ ...data }))]; - if (!newData.length || marketData.some(({ id }) => id === newData[0].id)) - return { ...state, loading: false }; - - return { - ...state, - marketData: marketData.concat(newData), - endOfList: newData.length < state.requestParams.limit, - loading: false, - page, - }; - } - case ACTIONS.UPDATE_SINGLE_MARKET_DATA: { - return { - ...state, - selectedCoinData: { ...state.selectedCoinData, ...action.payload }, - loading: false, - }; - } - case ACTIONS.UPDATE_SINGLE_CHART_DATA: { - const chartRequestParams = { - ...state.chartRequestParams, - loadingChart: false, - }; - const selectedCoinData = { ...state.selectedCoinData }; - - selectedCoinData.chartData = { - ...(selectedCoinData?.chartData ?? {}), - ...(action?.payload?.chartData ?? {}), - }; - - return { ...state, selectedCoinData, chartRequestParams }; - } - - case ACTIONS.REFRESH_MARKET_DATA: { - const requestParams = { - ...state.requestParams, - ...action.payload, - lastRequestTime: Date.now(), - }; - return { - ...state, - marketData: [], - requestParams, - loading: true, - page: 1, - }; - } - case ACTIONS.REFRESH_CHART_DATA: { - const chartRequestParams = { - ...state.chartRequestParams, - ...action.payload, - loadingChart: true, - lastRequestTime: Date.now(), - }; - return { ...state, chartRequestParams }; - } - - case ACTIONS.UPDATE_COUNTERVALUE: { - const requestParams = { - ...state.requestParams, - lastRequestTime: Date.now(), - page: 1, - counterCurrency: action.payload, - }; - const chartRequestParams = { - ...state.chartRequestParams, - lastRequestTime: Date.now(), - counterCurrency: action.payload, - }; - return { - ...state, - counterCurrency: action.payload, - marketData: [], - requestParams, - chartRequestParams, - loading: true, - }; - } - - case ACTIONS.SET_LOADING: - return { ...state, loading: action.payload }; - case ACTIONS.SET_LOADING_CHART: - return { ...state, loadingChart: action.payload }; - case ACTIONS.SET_ERROR: - return { ...state, error: action.payload, loading: false }; - case ACTIONS.SELECT_CURRENCY: { - const chartRequestParams = { - ...state.chartRequestParams, - counterCurrency: state.requestParams.counterCurrency, - range: action.payload.range || state.requestParams.range, - id: action.payload.id, - loadingChart: !!action.payload.id, - lastRequestTime: Date.now(), - }; - return { - ...state, - selectedCurrency: action.payload.id, - selectedCoinData: action.payload.data, - loading: !!action.payload.id, - chartRequestParams, - }; - } - default: - return state; - } -} - -export const MarketDataProvider = ({ - children, - fetchApi, - countervalue, - initState = {}, -}: Props): ReactElement => { - const [state, dispatch] = useReducer(marketDataReducer, { - ...initialState, - ...initState, - }); - const api = fetchApi || defaultFetchApi; - const { requestParams, chartRequestParams, loading, loadingChart, page, selectedCoinData } = - useDebounce(state, 300); - - const handleError = useCallback((payload: Error) => { - dispatch({ type: ACTIONS.SET_ERROR, payload }); - }, []); - - useEffect(() => { - if (countervalue) { - const ticker = countervalue.ticker.toLowerCase(); - api.supportedCounterCurrencies().then( - supportedCounterCurrencies => - api.setSupportedCoinsList().then((coins: SupportedCoins) => { - dispatch({ - type: ACTIONS.IS_READY, - payload: { - totalCoinsAvailable: coins.length, - supportedCounterCurrencies, - }, - }); - dispatch({ - type: ACTIONS.UPDATE_COUNTERVALUE, - payload: supportedCounterCurrencies.includes(ticker) ? ticker : "usd", - }); - }, handleError), - handleError, - ); - } - }, [api, countervalue, handleError]); - - useEffect(() => { - if (chartRequestParams?.id && chartRequestParams?.counterCurrency && !loadingChart) { - const range = chartRequestParams.range; - - if (selectedCoinData && !selectedCoinData?.chartData?.[range]) { - api.currencyChartData(chartRequestParams).then( - chartData => - dispatch({ - type: ACTIONS.UPDATE_SINGLE_CHART_DATA, - payload: { id: chartRequestParams.id, chartData }, - }), - handleError, - ); - } else - dispatch({ - type: ACTIONS.SET_LOADING_CHART, - payload: false, - }); - } - }, [chartRequestParams, selectedCoinData, api, handleError, loadingChart]); - - useEffect(() => { - if (chartRequestParams?.id && chartRequestParams?.counterCurrency) { - dispatch({ type: ACTIONS.SET_LOADING, payload: true }); - api - .listPaginated({ - ...chartRequestParams, - search: "", - starred: [], - liveCompatible: false, - ids: [chartRequestParams.id], - limit: 1, - page: 1, - }) - .then(([marketData]) => { - if (marketData) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { chartData, ...rest } = marketData; - dispatch({ - type: ACTIONS.UPDATE_SINGLE_MARKET_DATA, - payload: rest, - }); - } - }, handleError); - } - }, [api, chartRequestParams, handleError, loading]); - - useEffect(() => { - if (requestParams?.counterCurrency) { - api.listPaginated(requestParams).then( - marketData => - dispatch({ - type: ACTIONS.UPDATE_MARKET_DATA, - payload: { marketData }, - }), - handleError, - ); - } - }, [api, handleError, requestParams]); - - const refresh = useCallback((payload = {}) => { - dispatch({ type: ACTIONS.REFRESH_MARKET_DATA, payload }); - }, []); - - const refreshChart = useCallback((payload = {}) => { - dispatch({ type: ACTIONS.REFRESH_CHART_DATA, payload }); - }, []); - - const selectCurrency = useCallback((id, data, range) => { - dispatch({ type: ACTIONS.SELECT_CURRENCY, payload: { id, data, range } }); - }, []); - - const loadNextPage = useCallback( - () => - new Promise((resolve, reject) => { - if (loading) { - reject(new Error()); - } else { - const newPage = page + 1; - api.listPaginated({ ...requestParams, page: newPage }).then( - marketData => { - dispatch({ - type: ACTIONS.UPDATE_MARKET_DATA, - payload: { marketData, page: newPage }, - }); - resolve(true); - }, - err => { - handleError(err); - reject(new Error(err)); - }, - ); - } - }), - [loading, page, api, requestParams, handleError], - ); - - const setCounterCurrency = useCallback( - payload => dispatch({ type: ACTIONS.UPDATE_COUNTERVALUE, payload }), - [dispatch], - ); - - const value = { - ...state, - refresh, - refreshChart, - selectCurrency, - loadNextPage, - setCounterCurrency, - }; - - return {children}; -}; - -export function useMarketData(): MarketDataContextType { - return useContext(MarketDataContext); -} - -export type SingleCoinProviderData = SingleCoinState & { - selectCurrency: (id?: string, data?: CurrencyData, range?: string) => void; - refreshChart: (param?: MarketCurrencyChartDataRequestParams) => void; - setCounterCurrency: (counterCurrency: string) => void; -}; - -export function useSingleCoinMarketData(): SingleCoinProviderData { - const { - selectedCurrency, - selectedCoinData, - selectCurrency, - chartRequestParams, - loading, - loadingChart, - error, - counterCurrency, - refreshChart, - setCounterCurrency, - supportedCounterCurrencies, - } = useContext(MarketDataContext); - - return { - selectedCurrency, - selectedCoinData, - selectCurrency, - chartRequestParams, - loading, - loadingChart, - error, - counterCurrency, - refreshChart, - setCounterCurrency, - supportedCounterCurrencies, - }; -} diff --git a/libs/ledger-live-common/src/market/api/api.mock.ts b/libs/ledger-live-common/src/market/api/api.mock.ts deleted file mode 100644 index 16b76ccb79f..00000000000 --- a/libs/ledger-live-common/src/market/api/api.mock.ts +++ /dev/null @@ -1,403 +0,0 @@ -import { CurrencyData, MarketCoin, MarketListRequestParams, SupportedCoins } from "../types"; -import { listCryptoCurrencies, listTokens } from "../../currencies"; - -const cryptoCurrenciesList = [...listCryptoCurrencies(), ...listTokens()]; - -async function setSupportedCoinsList(): Promise { - const response = await Promise.resolve([ - { id: "bitcoin", symbol: "btc", name: "Bitcoin" }, - { id: "ethereum", symbol: "eth", name: "Ethereum" }, - { id: "ethereum-apex", symbol: "eapex", name: "Ethereum Apex" }, - { id: "ethereum-cash", symbol: "ecash", name: "Ethereum Cash" }, - ]); - - return response; -} - -const matchSearch = - (search: string) => - (currency: MarketCoin): boolean => { - if (!search) return false; - const match = `${currency.symbol}|${currency.name}`; - return match.toLowerCase().includes(search.toLowerCase()); - }; - -const paginatedData = [ - { - id: "bitcoin", - symbol: "btc", - name: "Bitcoin", - image: "https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579", - current_price: 46978, - market_cap: 888084713113, - market_cap_rank: 1, - fully_diluted_valuation: 986540621536, - total_volume: 27166733732, - high_24h: 47466, - low_24h: 45653, - price_change_24h: -448.517772161074, - price_change_percentage_24h: -0.94571, - market_cap_change_24h: -6925208740.524048, - market_cap_change_percentage_24h: -0.77376, - circulating_supply: 18904218, - total_supply: 21000000, - max_supply: 21000000, - ath: 69045, - ath_change_percentage: -32.0311, - ath_date: "2021-11-10T14:24:11.849Z", - atl: 67.81, - atl_change_percentage: 69107.58322, - atl_date: "2013-07-06T00:00:00.000Z", - roi: null, - last_updated: "2021-12-18T16:31:02.628Z", - price_change_percentage_24h_in_currency: -0.9457084615407927, - }, - { - id: "ethereum", - symbol: "eth", - name: "Ethereum", - image: "https://assets.coingecko.com/coins/images/279/large/ethereum.png?1595348880", - current_price: 3956.23, - market_cap: 469967313961, - market_cap_rank: 2, - fully_diluted_valuation: null, - total_volume: 22896047898, - high_24h: 3998.6, - low_24h: 3790.1, - price_change_24h: 31.34, - price_change_percentage_24h: 0.79852, - market_cap_change_24h: 4376247843, - market_cap_change_percentage_24h: 0.93993, - circulating_supply: 118791776.124, - total_supply: null, - max_supply: null, - ath: 4878.26, - ath_change_percentage: -19.07897, - ath_date: "2021-11-10T14:24:19.604Z", - atl: 0.432979, - atl_change_percentage: 911616.29321, - atl_date: "2015-10-20T00:00:00.000Z", - roi: { - times: 111.85400578727416, - currency: "btc", - percentage: 11185.400578727416, - }, - last_updated: "2021-12-18T16:30:24.123Z", - price_change_percentage_24h_in_currency: 0.7985206175407733, - }, - { - id: "binancecoin", - symbol: "bnb", - name: "Binance Coin", - image: "https://assets.coingecko.com/coins/images/825/large/binance-coin-logo.png?1547034615", - current_price: 530.99, - market_cap: 89278760378, - market_cap_rank: 3, - fully_diluted_valuation: 89278760378, - total_volume: 601931681, - high_24h: 536.2, - low_24h: 519.35, - price_change_24h: -1.431695982805, - price_change_percentage_24h: -0.2689, - market_cap_change_24h: -217891640.56773376, - market_cap_change_percentage_24h: -0.24346, - circulating_supply: 168137035.9, - total_supply: 168137035.9, - max_supply: 168137035.9, - ath: 686.31, - ath_change_percentage: -22.80102, - ath_date: "2021-05-10T07:24:17.097Z", - atl: 0.0398177, - atl_change_percentage: 1330518.574, - atl_date: "2017-10-19T00:00:00.000Z", - roi: null, - last_updated: "2021-12-18T16:30:40.065Z", - price_change_percentage_24h_in_currency: -0.26890361163187976, - }, - { - id: "tether", - symbol: "usdt", - name: "Tether", - image: "https://assets.coingecko.com/coins/images/325/large/Tether-logo.png?1598003707", - current_price: 1, - market_cap: 77439200796, - market_cap_rank: 4, - fully_diluted_valuation: null, - total_volume: 62554588893, - high_24h: 1.01, - low_24h: 0.987325, - price_change_24h: -0.011415856662, - price_change_percentage_24h: -1.1271, - market_cap_change_24h: -259872290.71488953, - market_cap_change_percentage_24h: -0.33446, - circulating_supply: 77328369536.52, - total_supply: 77328369536.52, - max_supply: null, - ath: 1.32, - ath_change_percentage: -24.41245, - ath_date: "2018-07-24T00:00:00.000Z", - atl: 0.572521, - atl_change_percentage: 74.68274, - atl_date: "2015-03-02T00:00:00.000Z", - roi: null, - last_updated: "2021-12-18T16:27:59.344Z", - price_change_percentage_24h_in_currency: -1.1271033891153508, - }, - { - id: "solana", - symbol: "sol", - name: "Solana", - image: "https://assets.coingecko.com/coins/images/4128/large/Solana.jpg?1635329178", - current_price: 182.14, - market_cap: 55791855479, - market_cap_rank: 5, - fully_diluted_valuation: null, - total_volume: 1864966986, - high_24h: 182.76, - low_24h: 171.62, - price_change_24h: 1.27, - price_change_percentage_24h: 0.70456, - market_cap_change_24h: 1062110605, - market_cap_change_percentage_24h: 1.94065, - circulating_supply: 307978914.997809, - total_supply: 508180963.57, - max_supply: null, - ath: 259.96, - ath_change_percentage: -30.21122, - ath_date: "2021-11-06T21:54:35.825Z", - atl: 0.500801, - atl_change_percentage: 36126.43086, - atl_date: "2020-05-11T19:35:23.449Z", - roi: null, - last_updated: "2021-12-18T16:31:38.005Z", - price_change_percentage_24h_in_currency: 0.704558493197451, - }, - { - id: "usd-coin", - symbol: "usdc", - name: "USD Coin", - image: "https://assets.coingecko.com/coins/images/6319/large/USD_Coin_icon.png?1547042389", - current_price: 0.999438, - market_cap: 41947712099, - market_cap_rank: 6, - fully_diluted_valuation: null, - total_volume: 3610141997, - high_24h: 1.01, - low_24h: 0.992322, - price_change_24h: -0.008300533333, - price_change_percentage_24h: -0.82368, - market_cap_change_24h: -15466850.668174744, - market_cap_change_percentage_24h: -0.03686, - circulating_supply: 41988882706.8525, - total_supply: 41974529053.5122, - max_supply: null, - ath: 1.17, - ath_change_percentage: -14.81075, - ath_date: "2019-05-08T00:40:28.300Z", - atl: 0.891848, - atl_change_percentage: 12.0168, - atl_date: "2021-05-19T13:14:05.611Z", - roi: null, - last_updated: "2021-12-18T16:31:29.480Z", - price_change_percentage_24h_in_currency: -0.823678944021661, - }, - { - id: "cardano", - symbol: "ada", - name: "Cardano", - image: "https://assets.coingecko.com/coins/images/975/large/cardano.png?1547034860", - current_price: 1.25, - market_cap: 40061737912, - market_cap_rank: 7, - fully_diluted_valuation: 56220178462, - total_volume: 1039172214, - high_24h: 1.27, - low_24h: 1.2, - price_change_24h: -0.00949416665, - price_change_percentage_24h: -0.7542, - market_cap_change_24h: -35615245.100372314, - market_cap_change_percentage_24h: -0.08882, - circulating_supply: 32066390668.4135, - total_supply: 45000000000, - max_supply: 45000000000, - ath: 3.09, - ath_change_percentage: -59.59586, - ath_date: "2021-09-02T06:00:10.474Z", - atl: 0.01925275, - atl_change_percentage: 6378.24434, - atl_date: "2020-03-13T02:22:55.044Z", - roi: null, - last_updated: "2021-12-18T16:31:09.050Z", - price_change_percentage_24h_in_currency: -0.754204745255466, - }, -]; -// fetches currencies data for selected currencies ids -async function listPaginated({ - search = "", - starred = [], - order = "desc", - range = "24h", - ids = [], -}: MarketListRequestParams): Promise { - const response = await Promise.resolve(paginatedData); - - let filteredResponse = response; - - if (order !== "desc") { - filteredResponse = filteredResponse.sort((x, y) => y.market_cap_rank - x.market_cap_rank); - } - - if (search) { - filteredResponse = filteredResponse.filter(matchSearch(search)); - } - - if (starred.length > 0) { - filteredResponse = filteredResponse.filter(currency => starred.includes(currency.id)); - } - - if (ids.length > 0) { - filteredResponse = filteredResponse.filter(currency => ids.includes(currency.id)); - } - - // @ts-expect-error issue in typing - return filteredResponse.map((currency: any) => ({ - id: currency.id, - name: currency.name, - image: currency.image, - internalCurrency: cryptoCurrenciesList.find( - ({ ticker }) => ticker.toLowerCase() === currency.symbol, - ), - marketcap: currency.market_cap, - marketcapRank: currency.market_cap_rank, - totalVolume: currency.total_volume, - high24h: currency.high_24h, - low24h: currency.low_24h, - ticker: currency.symbol, - price: currency.current_price, - priceChangePercentage: - range !== "24h" - ? currency.price_change_percentage_24h_in_currency * 7 - : currency.price_change_percentage_24h_in_currency, - marketCapChangePercentage24h: currency.market_cap_change_percentage_24h, - circulatingSupply: currency.circulating_supply, - totalSupply: currency.total_supply, - maxSupply: currency.max_supply, - ath: currency.ath, - athDate: currency.ath_date, - atl: currency.atl, - atlDate: currency.atl_date, - sparklineIn7d: null, - chartData: [], - })); -} - -// Fetches list of supported counterCurrencies -async function supportedCounterCurrencies(): Promise { - return await Promise.resolve([ - "btc", - "eth", - "ltc", - "bch", - "bnb", - "eos", - "xrp", - "xlm", - "link", - "dot", - "yfi", - "usd", - "aed", - "ars", - "aud", - "bdt", - "bhd", - "bmd", - "brl", - "cad", - "chf", - "clp", - "cny", - "czk", - "dkk", - "eur", - "gbp", - "hkd", - "huf", - "idr", - "ils", - "inr", - "jpy", - "krw", - "kwd", - "lkr", - "mmk", - "mxn", - "myr", - "ngn", - "nok", - "nzd", - "php", - "pkr", - "pln", - "rub", - "sar", - "sek", - "sgd", - "thb", - "try", - "twd", - "uah", - "vef", - "vnd", - "zar", - "xdr", - "xag", - "xau", - "bits", - "sats", - ]); -} - -// Fetches list of supported currencies -async function currencyChartData(): Promise<{ - [range: string]: number[][]; -}> { - const response = await Promise.resolve({ - prices: [ - [1639760425566, 47035.7730170554], - [1639764200520, 46779.28084436036], - [1639767793752, 46899.81670459505], - [1639771405996, 46806.89615030911], - [1639774854719, 46372.35410149477], - [1639778429112, 47091.66371795489], - [1639782342710, 46507.965042542106], - [1639785697940, 46328.6963654447], - [1639789279103, 46415.52050647275], - [1639793084532, 45826.54707719198], - [1639796561743, 45886.69785964224], - [1639800325023, 46535.85544547407], - [1639803752264, 46630.66364205827], - [1639807349369, 46796.65748955589], - [1639810831681, 46404.12378055947], - [1639814720824, 46429.86171498202], - [1639818226026, 46208.467967110075], - [1639821761024, 46794.55365462657], - [1639825359138, 47337.122321658026], - [1639829106656, 47070.8445217621], - [1639832543681, 47113.48747087277], - [1639836121826, 46857.76684001326], - [1639839860258, 47145.895183572036], - [1639843597758, 46876.27784614691], - [1639845270000, 47035.775042793095], - ], - }); - - return { "24h": response.prices }; -} - -export default { - setSupportedCoinsList, - listPaginated, - supportedCounterCurrencies, - currencyChartData, -}; diff --git a/libs/ledger-live-common/src/market/api/api.ts b/libs/ledger-live-common/src/market/api/api.ts deleted file mode 100644 index b822851e5fa..00000000000 --- a/libs/ledger-live-common/src/market/api/api.ts +++ /dev/null @@ -1,310 +0,0 @@ -import network from "@ledgerhq/live-network/network"; -import { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; -import { listCryptoCurrencies, listSupportedCurrencies, listTokens } from "../../currencies"; -import { getEnv } from "@ledgerhq/live-env"; -import { - CurrencyData, - MarketCoin, - MarketCurrencyChartDataRequestParams, - MarketListRequestParams, - RawCurrencyData, - MarketPerformersParams, - MarketItemResponse, - SupportedCoins, - MarketCurrencyRequestParams, -} from "../types"; -import { rangeDataTable } from "../utils/rangeDataTable"; -import { currencyFormatter } from "../utils/currencyFormatter"; -import URL from "url"; -import { getRange } from "../utils/rangeFormatter"; - -const cryptoCurrenciesList = [...listCryptoCurrencies(), ...listTokens()]; - -const supportedCurrencies = listSupportedCurrencies(); - -const liveCompatibleIds: string[] = supportedCurrencies - .map(({ id }: CryptoCurrency) => id) - .filter(Boolean); - -let LIVE_COINS_LIST: string[] = []; - -const baseURL = () => getEnv("LEDGER_COUNTERVALUES_API"); -const ROOT_PATH = getEnv("MARKET_API_URL"); - -let SUPPORTED_COINS_LIST: SupportedCoins = []; - -export async function setSupportedCoinsList(): Promise { - const url = `${ROOT_PATH}/coins/list`; - const { data } = await network({ method: "GET", url }); - - SUPPORTED_COINS_LIST = data; - - LIVE_COINS_LIST = SUPPORTED_COINS_LIST.filter(({ id }) => liveCompatibleIds.includes(id)).map( - ({ id }) => id, - ); - - return SUPPORTED_COINS_LIST; -} - -export async function getSupportedCoinsList(): Promise { - const url = `${ROOT_PATH}/coins/list`; - const { data } = await network({ method: "GET", url }); - return data; -} - -const matchSearch = - (search: string) => - (currency: MarketCoin): boolean => { - if (!search) return false; - const match = `${currency.symbol}|${currency.name}`; - return match.toLowerCase().includes(search.toLowerCase()); - }; - -// fetches currencies data for selected currencies ids -async function listPaginated({ - counterCurrency, - range = "24h", - limit = 50, - page = 1, - ids: _ids = [], - starred = [], - orderBy = "market_cap", - order = "desc", - search = "", - sparkline = true, - liveCompatible = false, - top100 = false, -}: MarketListRequestParams): Promise { - let ids = _ids; - - if (top100) { - limit = 100; - } else { - if (search) { - ids = SUPPORTED_COINS_LIST.filter(matchSearch(search)).map(({ id }) => id); - if (!ids.length) { - return []; - } - } - - if (liveCompatible) { - if (ids.length > 0) { - ids = LIVE_COINS_LIST.filter(id => ids.includes(id)); - } else { - ids = ids.concat(LIVE_COINS_LIST); - } - } - - if (starred.length > 0) { - if (ids.length > 0) { - ids = starred.filter(id => ids.includes(id)); - } else { - ids = ids.concat(starred); - } - } - } - - ids = ids.slice((page - 1) * limit, limit * page); - - const url = - `${ROOT_PATH}/coins/markets?vs_currency=${counterCurrency}&order=${orderBy}_${order}&per_page=${limit}` + - `&sparkline=${sparkline ? "true" : "false"}&price_change_percentage=${range}` + - `${ids.length > 0 ? `&page=1&&ids=${ids.toString()}` : `&page=${page}`}`; - - let { data } = await network({ - method: "GET", - url, - }); - - if (top100) { - // Perform a search by the user's input and order the result by change in percentage - data = data - .filter(currency => { - if (!search) return true; - const match = `${currency.symbol}|${currency.name}`; - return match.toLowerCase().includes(search.toLowerCase()); - }) - .sort( - (a, b) => - b[`price_change_percentage_${range}_in_currency`] - - a[`price_change_percentage_${range}_in_currency`], - ); - } - - return currencyFormatter(data, range, cryptoCurrenciesList); -} - -// fetches currencies data for selected currencies ids -export async function fetchList({ - counterCurrency, - range = "24h", - limit = 50, - page = 1, - ids: _ids = [], - starred = [], - orderBy = "market_cap", - order = "desc", - search = "", - sparkline = true, - liveCompatible = false, - top100 = false, - supportedCoinsList = [], - liveCoinsList = [], -}: MarketListRequestParams): Promise { - let ids = _ids; - - if (top100) { - limit = 100; - } else { - if (search) { - ids = supportedCoinsList.filter(matchSearch(search)).map(({ id }) => id); - if (!ids.length) { - return []; - } - } - - if (liveCompatible) { - if (ids.length > 0) { - ids = liveCoinsList.filter(id => ids.includes(id)); - } else { - ids = ids.concat(liveCoinsList); - } - } - - if (starred.length > 0) { - if (ids.length > 0) { - ids = starred.filter(id => ids.includes(id)); - } else { - ids = ids.concat(starred); - } - } - } - - ids = ids.slice((page - 1) * limit, limit * page); - - const url = - `${ROOT_PATH}/coins/markets?vs_currency=${counterCurrency}&order=${orderBy}_${order}&per_page=${limit}` + - `&sparkline=${sparkline ? "true" : "false"}&price_change_percentage=${range}` + - `${ids.length > 0 ? `&page=1&&ids=${ids.toString()}` : `&page=${page}`}`; - - if ((starred.length > 0 || search.length > 0) && ids.length === 0) return []; - - let { data } = await network({ - method: "GET", - url, - }); - - if (top100) { - // Perform a search by the user's input and order the result by change in percentage - data = data - .filter(currency => { - if (!search) return true; - const match = `${currency.symbol}|${currency.name}`; - return match.toLowerCase().includes(search.toLowerCase()); - }) - .sort( - (a, b) => - b[`price_change_percentage_${range}_in_currency`] - - a[`price_change_percentage_${range}_in_currency`], - ); - } - - return data; -} - -// Fetches list of supported counterCurrencies -export async function supportedCounterCurrencies(): Promise { - const url = `${ROOT_PATH}/simple/supported_vs_currencies`; - - const { data } = await network({ - method: "GET", - url, - }); - - return data; -} - -export async function fetchCurrencyChartData({ - id, - counterCurrency, - range = "24h", -}: MarketCurrencyChartDataRequestParams): Promise> { - const { days, interval } = rangeDataTable[range]; - - const url = `${ROOT_PATH}/coins/${id}/market_chart?vs_currency=${counterCurrency}&days=${days}&interval=${interval}`; - - const { data } = await network({ - method: "GET", - url, - }); - - return { [range]: data.prices }; -} - -export async function fetchCurrencyData({ - counterCurrency, - range = "24h", - id, -}: MarketCurrencyRequestParams): Promise { - const url = URL.format({ - pathname: `${ROOT_PATH}/coins/markets`, - query: { - vs_currency: counterCurrency, - sparkline: true, - price_change_percentage: range, - page: 1, - ids: id, - }, - }); - - const { data } = await network({ - method: "GET", - url, - }); - - return data[0]; -} - -export async function fetchCurrency({ id }: MarketCurrencyRequestParams): Promise { - const url = `${ROOT_PATH}/coins/${id}`; - - const { data } = await network({ - method: "GET", - url, - }); - - return data; -} - -export async function fetchMarketPerformers({ - counterCurrency, - range, - limit = 5, - top = 50, - sort, - supported, -}: MarketPerformersParams): Promise { - const sortParam = `${sort === "asc" ? "positive" : "negative"}-price-change-${getRange(range)}`; - - const url = URL.format({ - pathname: `${baseURL()}/v3/markets`, - query: { - to: counterCurrency, - limit, - top, - sort: sortParam, - supported, - }, - }); - - const { data } = await network({ method: "GET", url }); - - return data; -} - -export default { - setSupportedCoinsList, - listPaginated, - supportedCounterCurrencies, - currencyChartData: fetchCurrencyChartData, -}; diff --git a/libs/ledger-live-common/src/market/api/index.ts b/libs/ledger-live-common/src/market/api/index.ts new file mode 100644 index 00000000000..b9d771c492c --- /dev/null +++ b/libs/ledger-live-common/src/market/api/index.ts @@ -0,0 +1,138 @@ +import network from "@ledgerhq/live-network/network"; +import { getEnv } from "@ledgerhq/live-env"; +import { + MarketCurrencyChartDataRequestParams, + MarketListRequestParams, + MarketPerformersParams, + MarketItemResponse, + SupportedCoins, + MarketCurrencyRequestParams, + MarketCoinDataChart, + Order, +} from "../utils/types"; +import { rangeDataTable } from "../utils/rangeDataTable"; +import URL from "url"; +import { getRange, getSortParam } from "../utils"; + +const baseURL = () => getEnv("LEDGER_COUNTERVALUES_API"); +const ROOT_PATH = getEnv("MARKET_API_URL"); + +export async function getSupportedCoinsList(): Promise { + const url = `${ROOT_PATH}/coins/list`; + const { data } = await network({ method: "GET", url }); + return data; +} + +// fetches currencies data for selected currencies ids +export async function fetchList({ + counterCurrency, + limit = 50, + page = 1, + order = Order.MarketCapDesc, + search = "", + liveCoinsList = [], + starred = [], + range = "24", +}: MarketListRequestParams): Promise { + const url = URL.format({ + pathname: `${baseURL()}/v3/markets`, + query: { + page: page, + pageSize: limit, + to: counterCurrency, + sort: getSortParam(order, range), + ...(search.length >= 1 && { filter: search }), + ...(starred.length > 0 && { ids: starred.join(",") }), + ...(liveCoinsList.length > 1 && { ids: liveCoinsList.join(",") }), + ...([Order.topLosers, Order.topGainers].includes(order) && { top: 100 }), + }, + }); + + const { data } = await network({ + method: "GET", + url, + }); + + return data; +} + +// Fetches list of supported counterCurrencies +export async function supportedCounterCurrencies(): Promise { + const url = `${ROOT_PATH}/simple/supported_vs_currencies`; + + const { data } = await network({ + method: "GET", + url, + }); + + return data; +} + +export async function fetchCurrencyChartData({ + id, + counterCurrency, + range = "24h", +}: MarketCurrencyChartDataRequestParams): Promise { + const { days, interval } = rangeDataTable[range]; + + const url = URL.format({ + pathname: `${ROOT_PATH}/coins/${id}/market_chart`, + query: { + vs_currency: counterCurrency, + days, + interval, + }, + }); + + const { data } = await network({ + method: "GET", + url, + }); + + return { [range]: data.prices }; +} + +export async function fetchCurrency({ + counterCurrency, + id, +}: MarketCurrencyRequestParams): Promise { + const url = URL.format({ + pathname: `${baseURL()}/v3/markets`, + query: { + to: counterCurrency, + ids: id, + pageSize: 1, + limit: 1, + }, + }); + + const { data } = await network({ method: "GET", url }); + + return data[0]; +} + +export async function fetchMarketPerformers({ + counterCurrency, + range, + limit = 5, + top = 50, + sort, + supported, +}: MarketPerformersParams): Promise { + const sortParam = `${sort === "asc" ? "positive" : "negative"}-price-change-${getRange(range)}`; + + const url = URL.format({ + pathname: `${baseURL()}/v3/markets`, + query: { + to: counterCurrency, + limit, + top, + sort: sortParam, + supported, + }, + }); + + const { data } = await network({ method: "GET", url }); + + return data; +} diff --git a/libs/ledger-live-common/src/market/v2/useMarketDataProvider.ts b/libs/ledger-live-common/src/market/hooks/useMarketDataProvider.ts similarity index 71% rename from libs/ledger-live-common/src/market/v2/useMarketDataProvider.ts rename to libs/ledger-live-common/src/market/hooks/useMarketDataProvider.ts index 3a4ee90d204..dcc8054a724 100644 --- a/libs/ledger-live-common/src/market/v2/useMarketDataProvider.ts +++ b/libs/ledger-live-common/src/market/hooks/useMarketDataProvider.ts @@ -2,26 +2,28 @@ import { UseQueryResult, useQueries, useQuery } from "@tanstack/react-query"; import { fetchCurrency, fetchCurrencyChartData, - fetchCurrencyData, fetchList, getSupportedCoinsList, supportedCounterCurrencies, -} from "../api/api"; +} from "../api"; +import { listCryptoCurrencies } from "@ledgerhq/cryptoassets/currencies"; +import { listTokens } from "@ledgerhq/cryptoassets/tokens"; +import { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; + +import { useMemo } from "react"; +import { listSupportedCurrencies } from "../../currencies"; +import { currencyFormatter, format } from "../utils/currencyFormatter"; +import { QUERY_KEY } from "../utils/queryKeys"; +import { REFETCH_TIME_ONE_MINUTE, BASIC_REFETCH, ONE_DAY } from "../utils/timers"; import { - CurrencyData, - HashMapBody, MarketCurrencyRequestParams, MarketListRequestParams, + CurrencyData, + HashMapBody, + MarketItemResponse, MarketListRequestResult, - RawCurrencyData, -} from "../types"; -import { QUERY_KEY } from "./queryKeys"; -import { listCryptoCurrencies, listSupportedCurrencies, listTokens } from "../../currencies"; -import { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; -import { useMemo } from "react"; - -import { currencyFormatter, format } from "../utils/currencyFormatter"; -import { BASIC_REFETCH, ONE_DAY, REFETCH_TIME_ONE_MINUTE } from "./timers"; + Order, +} from "../utils/types"; const cryptoCurrenciesList = [...listCryptoCurrencies(), ...listTokens()]; @@ -57,26 +59,15 @@ export const useCurrencyChartData = ({ id, counterCurrency, range }: MarketCurre staleTime: REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH, }); -export function useCurrencyData({ id, counterCurrency, range }: MarketCurrencyRequestParams) { - const resultCurrencyData = useQuery({ - queryKey: [QUERY_KEY.CurrencyData, id, counterCurrency, range], - queryFn: () => fetchCurrencyData({ counterCurrency, range, id }), - refetchInterval: REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH, - staleTime: REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH, - select: (data: RawCurrencyData) => format(data, range ?? "24h", cryptoCurrenciesList), - }); - - const resultCurrency = useQuery({ - queryKey: [QUERY_KEY.CurrencyDataRaw, id], - queryFn: () => fetchCurrency({ id }), +export const useCurrencyData = ({ id, counterCurrency }: MarketCurrencyRequestParams) => + useQuery({ + queryKey: [QUERY_KEY.CurrencyDataRaw, id, counterCurrency], + queryFn: () => fetchCurrency({ id, counterCurrency }), refetchInterval: REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH, staleTime: REFETCH_TIME_ONE_MINUTE * BASIC_REFETCH, - select: data => format(data, "24h", cryptoCurrenciesList), + select: data => format(data, cryptoCurrenciesList), }); - return { currencyData: resultCurrencyData, currencyInfo: resultCurrency }; -} - export const useSupportedCounterCurrencies = () => useQuery({ queryKey: [QUERY_KEY.SupportedCounterCurrencies], @@ -95,14 +86,24 @@ export const useSupportedCurrencies = () => export function useMarketData(props: MarketListRequestParams): MarketListRequestResult { return useQueries({ - queries: Array.from({ length: props.page ?? 1 }, (_, i) => i + 1).map(page => ({ + queries: Array.from({ length: props.page ?? 1 }, (_, i) => i).map(page => ({ queryKey: [ QUERY_KEY.MarketData, - { ...props, page, liveCoinsList: [], supportedCoinsList: [] }, + page, + props.order, + { + counterCurrency: props.counterCurrency, + ...(props.search && props.search?.length >= 1 && { search: props.search }), + ...(props.starred && props.starred?.length >= 1 && { starred: props.starred }), + ...(props.liveCoinsList && + props.liveCoinsList?.length >= 1 && { liveCoinsList: props.liveCoinsList }), + ...(props.order && + [Order.topLosers, Order.topGainers].includes(props.order) && { range: props.range }), + }, ], queryFn: () => fetchList({ ...props, page }), - select: (data: RawCurrencyData[]) => ({ - formattedData: currencyFormatter(data, props.range ?? "24h", cryptoCurrenciesList), + select: (data: MarketItemResponse[]) => ({ + formattedData: currencyFormatter(data, cryptoCurrenciesList), page, }), refetchOnMount: false, diff --git a/libs/ledger-live-common/src/market/v2/useMarketPerformers.ts b/libs/ledger-live-common/src/market/hooks/useMarketPerformers.ts similarity index 81% rename from libs/ledger-live-common/src/market/v2/useMarketPerformers.ts rename to libs/ledger-live-common/src/market/hooks/useMarketPerformers.ts index 909c8e3df30..b3ea8171d60 100644 --- a/libs/ledger-live-common/src/market/v2/useMarketPerformers.ts +++ b/libs/ledger-live-common/src/market/hooks/useMarketPerformers.ts @@ -1,9 +1,9 @@ -import { fetchMarketPerformers } from "../api/api"; -import { MarketItemPerformer, MarketItemResponse, MarketPerformersParams } from "../types"; -import { QUERY_KEY } from "./queryKeys"; +import { fetchMarketPerformers } from "../api"; +import { MarketItemPerformer, MarketItemResponse, MarketPerformersParams } from "../utils/types"; +import { QUERY_KEY } from "../utils/queryKeys"; import { UseQueryResult, useQuery } from "@tanstack/react-query"; import { formatPerformer } from "../utils/currencyFormatter"; -import { REFETCH_TIME_ONE_MINUTE } from "./timers"; +import { REFETCH_TIME_ONE_MINUTE } from "../utils/timers"; export const useMarketPerformers = ({ counterCurrency, diff --git a/libs/ledger-live-common/src/market/utils/currencyFormatter.ts b/libs/ledger-live-common/src/market/utils/currencyFormatter.ts index ce53f37d358..5e927f66aa7 100644 --- a/libs/ledger-live-common/src/market/utils/currencyFormatter.ts +++ b/libs/ledger-live-common/src/market/utils/currencyFormatter.ts @@ -1,11 +1,11 @@ import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets"; import { CurrencyData, + KeysPriceChange, MarketItemPerformer, MarketItemResponse, - RawCurrencyData, SparklineSvgData, -} from "../types"; +} from "./types"; function distributedCopy(items: number[], n: number): number[] { if (!items) return []; @@ -46,42 +46,46 @@ function sparklineAsSvgData(points: number[]): SparklineSvgData { } export function currencyFormatter( - data: RawCurrencyData[], - range: string, + data: MarketItemResponse[], cryptoCurrenciesList: (CryptoCurrency | TokenCurrency)[], ): CurrencyData[] { - return data.map((currency: RawCurrencyData) => format(currency, range, cryptoCurrenciesList)); + return data.map((currency: MarketItemResponse) => format(currency, cryptoCurrenciesList)); } export const format = ( - currency: RawCurrencyData, - range: string, + currency: MarketItemResponse, cryptoCurrenciesList: (CryptoCurrency | TokenCurrency)[], -) => ({ +): CurrencyData => ({ id: currency.id, name: currency.name, - image: typeof currency.image === "string" ? currency.image : currency.image?.thumb, + image: currency.image, internalCurrency: cryptoCurrenciesList.find( - ({ ticker }) => ticker.toLowerCase() === currency.symbol, + ({ ticker }) => ticker.toLowerCase() === currency.ticker, ), - marketcap: currency.market_cap, - marketcapRank: currency.market_cap_rank, - totalVolume: currency.total_volume, - high24h: currency.high_24h, - low24h: currency.low_24h, - ticker: currency.symbol, - price: currency.current_price, - priceChangePercentage: currency[`price_change_percentage_${range}_in_currency`], - marketCapChangePercentage24h: currency.market_cap_change_percentage_24h, - circulatingSupply: currency.circulating_supply, - totalSupply: currency.total_supply, - maxSupply: currency.max_supply, - ath: currency.ath, - athDate: currency.ath_date, - atl: currency.atl, - atlDate: currency.atl_date, - sparklineIn7d: currency?.sparkline_in_7d?.price - ? sparklineAsSvgData(distributedCopy(currency.sparkline_in_7d.price, 6 * 7)) // keep 6 points per day + marketcap: currency.marketCap, + marketcapRank: currency.marketCapRank, + totalVolume: currency.totalVolume, + high24h: currency.high24h, + low24h: currency.low24h, + ticker: currency.ticker, + price: currency.price, + priceChangePercentage: { + [KeysPriceChange.hour]: currency.priceChangePercentage1h, + [KeysPriceChange.day]: currency.priceChangePercentage24h, + [KeysPriceChange.week]: currency.priceChangePercentage7d, + [KeysPriceChange.month]: currency.priceChangePercentage30d, + [KeysPriceChange.year]: currency.priceChangePercentage1y, + }, + marketCapChangePercentage24h: currency.marketCapChangePercentage24h, + circulatingSupply: currency.circulatingSupply, + totalSupply: currency.totalSupply, + maxSupply: currency.maxSupply, + ath: currency.allTimeHigh, + athDate: new Date(currency.allTimeHighDate), + atl: currency.allTimeLow, + atlDate: new Date(currency.allTimeLowDate), + sparklineIn7d: currency.sparkline + ? sparklineAsSvgData(distributedCopy(currency.sparkline, 6 * 7)) // keep 6 points per day : undefined, chartData: {}, }); diff --git a/libs/ledger-live-common/src/market/utils/index.ts b/libs/ledger-live-common/src/market/utils/index.ts new file mode 100644 index 00000000000..10d683785c6 --- /dev/null +++ b/libs/ledger-live-common/src/market/utils/index.ts @@ -0,0 +1,34 @@ +import { PortfolioRange } from "@ledgerhq/types-live"; +import { Order } from "./types"; + +export function getRange(range: PortfolioRange | string) { + switch (range) { + case "day": + case "24h": + return "1d"; + case "7d": + case "week": + return "1w"; + case "30d": + case "month": + return "1m"; + case "1y": + case "year": + case "all": + return "1y"; + } +} + +export const getSortParam = (order: Order, range: PortfolioRange | string) => { + switch (order) { + default: + case Order.MarketCapDesc: + return "market-cap-rank"; + case Order.MarketCapAsc: + return "market-cap-rank-desc"; + case Order.topLosers: + return `negative-price-change-${getRange(range)}`; + case Order.topGainers: + return `positive-price-change-${getRange(range)}`; + } +}; diff --git a/libs/ledger-live-common/src/market/v2/queryKeys.ts b/libs/ledger-live-common/src/market/utils/queryKeys.ts similarity index 100% rename from libs/ledger-live-common/src/market/v2/queryKeys.ts rename to libs/ledger-live-common/src/market/utils/queryKeys.ts diff --git a/libs/ledger-live-common/src/market/utils/rangeFormatter.ts b/libs/ledger-live-common/src/market/utils/rangeFormatter.ts deleted file mode 100644 index 2a7ecb112c6..00000000000 --- a/libs/ledger-live-common/src/market/utils/rangeFormatter.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PortfolioRange } from "@ledgerhq/types-live"; - -export function getRange(range: PortfolioRange) { - switch (range) { - case "day": - return "1d"; - case "week": - return "1w"; - case "month": - return "1m"; - case "year": - case "all": - return "1y"; - } -} diff --git a/libs/ledger-live-common/src/market/v2/timers.ts b/libs/ledger-live-common/src/market/utils/timers.ts similarity index 100% rename from libs/ledger-live-common/src/market/v2/timers.ts rename to libs/ledger-live-common/src/market/utils/timers.ts diff --git a/libs/ledger-live-common/src/market/types.ts b/libs/ledger-live-common/src/market/utils/types.ts similarity index 83% rename from libs/ledger-live-common/src/market/types.ts rename to libs/ledger-live-common/src/market/utils/types.ts index 69909fdea3c..3d03f13b551 100644 --- a/libs/ledger-live-common/src/market/types.ts +++ b/libs/ledger-live-common/src/market/utils/types.ts @@ -8,23 +8,28 @@ export type MarketCoin = { symbol: string; }; +export type ChartDataPoint = [number, number]; +export type MarketCoinDataChart = Record>; + +export enum Order { + MarketCapDesc = "desc", + MarketCapAsc = "asc", + topLosers = "topLosers", + topGainers = "topGainers", +} + export type SupportedCoins = MarketCoin[]; export type MarketListRequestParams = { counterCurrency?: string; - ids?: string[]; starred?: string[]; page?: number; limit?: number; range?: string; - orderBy?: string; - order?: string; + order?: Order; search?: string; lastRequestTime?: Date; - sparkline?: boolean; liveCompatible?: boolean; - top100?: boolean; - supportedCoinsList?: SupportedCoins; liveCoinsList?: string[]; }; @@ -68,6 +73,14 @@ export type SparklineSvgData = { isPositive: boolean; }; +export enum KeysPriceChange { + hour = "1h", + day = "24h", + week = "7d", + month = "30d", + year = "1y", +} + export type CurrencyData = { id: string; name: string; @@ -80,7 +93,7 @@ export type CurrencyData = { low24h: number; ticker: string; price: number; - priceChangePercentage: number; + priceChangePercentage: Record; marketCapChangePercentage24h: number; circulatingSupply: number; totalSupply: number; @@ -90,30 +103,7 @@ export type CurrencyData = { atl: number; atlDate: Date; sparklineIn7d?: SparklineSvgData; - chartData: Record; -}; - -export type RawCurrencyData = { - [x: string]: any; - id: string; - name: string; - image?: string | { thumb: string; small: string; large: string }; - ["market_cap"]: number; - ["market_cap_rank"]: number; - ["total_volume"]: number; - ["high_24h"]: number; - ["low_24h"]: number; - symbol: string; - ["current_price"]: number; - ["market_cap_change_percentage_24h"]: number; - ["circulating_supply"]: number; - ["total_supply"]: number; - ["max_supply"]: number; - ath: number; - ["ath_date"]: Date; - atl: number; - ["atl_date"]: Date; - ["sparkline_in_7d"]: { price: any }; + chartData: MarketCoinDataChart; }; export type SingleCoinState = { @@ -147,34 +137,34 @@ export type MarketPerformersParams = { }; export type MarketItemResponse = { + allTimeHigh: number; + allTimeHighDate: string; + allTimeLow: number; + allTimeLowDate: string; + circulatingSupply: number; + fullyDilutedValuation: number; + high24h: number; id: string; - ledgerIds: string[]; - ticker: string; - name: string; image: string; + ledgerIds: string[]; + low24h: number; marketCap: number; + marketCapChange24h: number; + marketCapChangePercentage24h: number; marketCapRank: number; - fullyDilutedValuation: number; - totalVolume: number; - high24h: number; - low24h: number; + maxSupply: number; + name: string; price: number; priceChange24h: number; priceChangePercentage1h: number; priceChangePercentage24h: number; - priceChangePercentage7d: number; priceChangePercentage30d: number; + priceChangePercentage7d: number; priceChangePercentage1y: number; - marketCapChange24h: number; - marketCapChangePercentage24h: number; - circulatingSupply: number; - totalSupply: number; - maxSupply: number; - allTimeHigh: number; - allTimeLow: number; - allTimeHighDate: string; - allTimeLowDate: string; sparkline: number[]; + ticker: string; + totalSupply: number; + totalVolume: number; updatedAt: string; }; export type MarketItemPerformer = { diff --git a/libs/ledgerjs/packages/types-live/src/feature.ts b/libs/ledgerjs/packages/types-live/src/feature.ts index 6de67b2169a..49978078564 100644 --- a/libs/ledgerjs/packages/types-live/src/feature.ts +++ b/libs/ledgerjs/packages/types-live/src/feature.ts @@ -175,6 +175,7 @@ export type Features = CurrencyFeatures & { supportDeviceStax: Feature_SupportDeviceStax; supportDeviceEuropa: Feature_SupportDeviceEuropa; lldRefreshMarketData: Feature_LldRefreshMarketData; + llmRefreshMarketData: Feature_LlmRefreshMarketData; spamReportNfts: Feature_SpamReportNfts; lldWalletSync: Feature_LldWalletSync; lldNftsGalleryNewArch: DefaultFeature; @@ -462,6 +463,9 @@ export type Feature_NftsFromSimpleHash = Feature<{ export type Feature_LldRefreshMarketData = Feature<{ refreshTime: number; }>; +export type Feature_LlmRefreshMarketData = Feature<{ + refreshTime: number; +}>; export type Feature_BuySellUiManifest = Feature<{ manifestId: string; // id of the app to use for the Buy/Sell UI, e.g. "multibuy-v2" diff --git a/libs/ui/packages/icons/src/svg/graph-asc.svg b/libs/ui/packages/icons/src/svg/graph-asc.svg new file mode 100644 index 00000000000..5793549b160 --- /dev/null +++ b/libs/ui/packages/icons/src/svg/graph-asc.svg @@ -0,0 +1,3 @@ + + + diff --git a/libs/ui/packages/icons/src/svg/graph-desc.svg b/libs/ui/packages/icons/src/svg/graph-desc.svg new file mode 100644 index 00000000000..0abe2de064a --- /dev/null +++ b/libs/ui/packages/icons/src/svg/graph-desc.svg @@ -0,0 +1,3 @@ + + +