diff --git a/libs/oeth/shared/.env b/.env similarity index 100% rename from libs/oeth/shared/.env rename to .env diff --git a/apps/oeth/src/App.tsx b/apps/oeth/src/App.tsx index 95a4c00dc..0b44fde71 100644 --- a/apps/oeth/src/App.tsx +++ b/apps/oeth/src/App.tsx @@ -1,11 +1,8 @@ import { Container, CssBaseline, Stack } from '@mui/material'; -import { registerChart } from '@origin/shared/providers'; import { Outlet } from 'react-router-dom'; import { Topnav } from './components/Topnav'; -registerChart(); - export const App = () => { return ( <> diff --git a/apps/oeth/src/main.tsx b/apps/oeth/src/main.tsx index e6cbe3e94..2076a176c 100644 --- a/apps/oeth/src/main.tsx +++ b/apps/oeth/src/main.tsx @@ -6,7 +6,11 @@ import * as ReactDOM from 'react-dom/client'; import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material'; import { chains, queryClient, wagmiConfig } from '@origin/oeth/shared'; -import { CurveProvider, NotificationsProvider } from '@origin/shared/providers'; +import { + CurveProvider, + NotificationsProvider, + registerChart, +} from '@origin/shared/providers'; import { theme } from '@origin/shared/theme'; import { composeContexts } from '@origin/shared/utils'; import { darkTheme, RainbowKitProvider } from '@rainbow-me/rainbowkit'; @@ -22,6 +26,8 @@ import { routes } from './routes'; // https://github.com/dai-shi/proxy-compare/pull/8 setAutoFreeze(false); +registerChart(); + const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, ); diff --git a/libs/oeth/history/src/components/APYContainer.tsx b/libs/oeth/history/src/components/APYContainer.tsx index f3a05dfcc..9e95389ac 100644 --- a/libs/oeth/history/src/components/APYContainer.tsx +++ b/libs/oeth/history/src/components/APYContainer.tsx @@ -1,19 +1,31 @@ -import { Divider, Stack, Typography } from '@mui/material'; -import { valueFormat } from '@origin/shared/utils'; +import { Divider, Skeleton, Stack, Typography } from '@mui/material'; +import { tokens } from '@origin/shared/contracts'; +import { balanceFormat } from '@origin/shared/utils'; import { useIntl } from 'react-intl'; -import { useAccount } from 'wagmi'; +import { useAccount, useBalance } from 'wagmi'; +import { usePendingYield } from '../hooks'; import { useHistoryTableQuery } from '../queries.generated'; +import type { StackProps } from '@mui/material'; + export function APYContainer() { + const intl = useIntl(); const { address, isConnected } = useAccount(); - const { data } = useHistoryTableQuery( - { addressId: address?.toLowerCase(), offset: 0 }, + const { data: oethBalance, isLoading: oethLoading } = useBalance({ + address, + token: tokens.mainnet.OETH.address, + watch: true, + }); + const { data: earnings, isLoading: earningsLoading } = useHistoryTableQuery( + { address: address?.toLowerCase(), offset: 0 }, { enabled: isConnected, }, ); - const intl = useIntl(); + const { data: pendingYield, isLoading: pendingYieldLoading } = + usePendingYield(tokens.mainnet.OETH); + return ( ); } -interface Props { +type ValueContainerProps = { label: string; - value: number; -} + value: string; + isLoading?: boolean; +} & StackProps; -function ValueContainer(props: Props) { - const intl = useIntl(); +function ValueContainer({ + label, + value, + isLoading, + ...rest +}: ValueContainerProps) { return ( - + - {props.label} + {label} - {intl.formatNumber(props.value, valueFormat)} + {isLoading ? : value} ); diff --git a/libs/oeth/history/src/components/HistoryCard.tsx b/libs/oeth/history/src/components/HistoryCard.tsx index d87e068ee..65678ec3d 100644 --- a/libs/oeth/history/src/components/HistoryCard.tsx +++ b/libs/oeth/history/src/components/HistoryCard.tsx @@ -1,51 +1,31 @@ import { useState } from 'react'; import { Box, Button, Divider, Stack, Typography } from '@mui/material'; -import { graphqlClient } from '@origin/oeth/shared'; -import { useQuery } from '@tanstack/react-query'; import { useIntl } from 'react-intl'; import { useAccount } from 'wagmi'; -import { - HistoryTableDocument, - HistoryTableWithFiltersDocument, -} from '../queries.generated'; +import { useHistoryTableWithFiltersQuery } from '../queries.generated'; import { ExportData } from './ExportData'; import { HistoryFilters } from './Filters'; import { HistoryTable } from './HistoryTable'; -import type { HistoryTableQuery } from '../queries.generated'; - const PAGE_SIZE = 20; export function HistoryCard() { + const intl = useIntl(); const [page, setPage] = useState(0); const [filters, setFilters] = useState([]); const { address, isConnected } = useAccount(); - const { data, isFetching } = useQuery( - ['history-table', address, filters, page], - () => { - return graphqlClient< - HistoryTableQuery, - { addressId: string; filters?: string[]; offset: number } - >( - filters.length ? HistoryTableWithFiltersDocument : HistoryTableDocument, - { - addressId: address?.toLowerCase(), - filters: filters.length ? filters : undefined, - offset: page * PAGE_SIZE, - }, - )(); - }, - + const { data, isFetching } = useHistoryTableWithFiltersQuery( { - enabled: isConnected, + address: address.toLowerCase(), + filters: filters.length ? filters : undefined, + offset: page * PAGE_SIZE, }, + { enabled: isConnected }, ); - const intl = useIntl(); - return ( + useQuery({ + queryKey: ['usePendingYield', token.symbol], + queryFn: async () => { + const { address, isConnected } = getAccount(); + + if (!isConnected) { + return 0; + } + + // TODO endpoint for next userCredits is not yet available on subsquid + const [balance, data] = await Promise.all([ + fetchBalance({ address, token: token.address }), + queryClient.fetchQuery({ + queryKey: useHistoryTableQuery.getKey({ address, offset: 0 }), + queryFn: useHistoryTableQuery.fetcher({ + address: address.toLowerCase(), + offset: 0, + }), + }), + ]); + + if (isNilOrEmpty(data?.addressById) || balance?.value === 0n) return 0; + + return 0; + }, + }); diff --git a/libs/oeth/history/src/queries.generated.tsx b/libs/oeth/history/src/queries.generated.ts similarity index 55% rename from libs/oeth/history/src/queries.generated.tsx rename to libs/oeth/history/src/queries.generated.ts index 0082ab230..501572792 100644 --- a/libs/oeth/history/src/queries.generated.tsx +++ b/libs/oeth/history/src/queries.generated.ts @@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query'; import type * as Types from '@origin/oeth/shared'; import type { UseQueryOptions } from '@tanstack/react-query'; export type HistoryTableQueryVariables = Types.Exact<{ - addressId: Types.Scalars['String']['input']; + address: Types.Scalars['String']['input']; offset: Types.Scalars['Int']['input']; }>; @@ -29,7 +29,7 @@ export type HistoryTableQuery = { }; export type HistoryTableWithFiltersQueryVariables = Types.Exact<{ - addressId: Types.Scalars['String']['input']; + address: Types.Scalars['String']['input']; offset: Types.Scalars['Int']['input']; filters?: Types.InputMaybe< Array | Types.Scalars['String']['input'] @@ -44,6 +44,7 @@ export type HistoryTableWithFiltersQuery = { earned: number; isContract: boolean; rebasingOption: string; + credits: any; lastUpdated: any; history: Array<{ __typename?: 'History'; @@ -56,9 +57,16 @@ export type HistoryTableWithFiltersQuery = { } | null; }; +export type HistoryApyQueryVariables = Types.Exact<{ [key: string]: never }>; + +export type HistoryApyQuery = { + __typename?: 'Query'; + apies: Array<{ __typename?: 'APY'; apy7DayAvg: number; apy30DayAvg: number }>; +}; + export const HistoryTableDocument = ` - query HistoryTable($addressId: String!, $offset: Int!) { - addressById(id: $addressId) { + query HistoryTable($address: String!, $offset: Int!) { + addressById(id: $address) { balance earned isContract @@ -89,13 +97,28 @@ export const useHistoryTableQuery = < ), options, ); + +useHistoryTableQuery.getKey = (variables: HistoryTableQueryVariables) => [ + 'HistoryTable', + variables, +]; +useHistoryTableQuery.fetcher = ( + variables: HistoryTableQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient( + HistoryTableDocument, + variables, + options, + ); export const HistoryTableWithFiltersDocument = ` - query HistoryTableWithFilters($addressId: String!, $offset: Int!, $filters: [String!]) { - addressById(id: $addressId) { + query HistoryTableWithFilters($address: String!, $offset: Int!, $filters: [String!]) { + addressById(id: $address) { balance earned isContract rebasingOption + credits lastUpdated history( limit: 20 @@ -127,3 +150,47 @@ export const useHistoryTableWithFiltersQuery = < >(HistoryTableWithFiltersDocument, variables), options, ); + +useHistoryTableWithFiltersQuery.getKey = ( + variables: HistoryTableWithFiltersQueryVariables, +) => ['HistoryTableWithFilters', variables]; +useHistoryTableWithFiltersQuery.fetcher = ( + variables: HistoryTableWithFiltersQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient< + HistoryTableWithFiltersQuery, + HistoryTableWithFiltersQueryVariables + >(HistoryTableWithFiltersDocument, variables, options); +export const HistoryApyDocument = ` + query HistoryApy { + apies(limit: 1, orderBy: timestamp_DESC) { + apy7DayAvg + apy30DayAvg + } +} + `; +export const useHistoryApyQuery = ( + variables?: HistoryApyQueryVariables, + options?: UseQueryOptions, +) => + useQuery( + variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables], + graphqlClient( + HistoryApyDocument, + variables, + ), + options, + ); + +useHistoryApyQuery.getKey = (variables?: HistoryApyQueryVariables) => + variables === undefined ? ['HistoryApy'] : ['HistoryApy', variables]; +useHistoryApyQuery.fetcher = ( + variables?: HistoryApyQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient( + HistoryApyDocument, + variables, + options, + ); diff --git a/libs/oeth/history/src/queries.graphql b/libs/oeth/history/src/queries.graphql index 676841d7e..856707099 100644 --- a/libs/oeth/history/src/queries.graphql +++ b/libs/oeth/history/src/queries.graphql @@ -1,9 +1,10 @@ -query HistoryTable($addressId: String!, $offset: Int!) { - addressById(id: $addressId) { +query HistoryTable($address: String!, $offset: Int!) { + addressById(id: $address) { balance earned isContract rebasingOption + credits lastUpdated history(limit: 20, orderBy: timestamp_DESC, offset: $offset) { type @@ -16,11 +17,11 @@ query HistoryTable($addressId: String!, $offset: Int!) { } query HistoryTableWithFilters( - $addressId: String! + $address: String! $offset: Int! $filters: [String!] ) { - addressById(id: $addressId) { + addressById(id: $address) { balance earned isContract @@ -40,3 +41,10 @@ query HistoryTableWithFilters( } } } + +query HistoryApy { + apies(limit: 1, orderBy: timestamp_DESC) { + apy7DayAvg + apy30DayAvg + } +} diff --git a/libs/oeth/redeem/src/views/RedeemView.tsx b/libs/oeth/redeem/src/views/RedeemView.tsx index 02603585a..bac846849 100644 --- a/libs/oeth/redeem/src/views/RedeemView.tsx +++ b/libs/oeth/redeem/src/views/RedeemView.tsx @@ -78,7 +78,6 @@ function RedeemViewWrapped() { return ( (query: string, variables?: TVariables) => + ( + query: string, + variables?: TVariables, + options?: RequestInit['headers'], + ) => async () => { const res = await axiosInstance({ url: '/graphql', @@ -14,6 +18,7 @@ export const graphqlClient = headers: { 'Content-Type': 'application/json', }, + ...options, data: { query, variables }, }); diff --git a/libs/oeth/shared/src/components/ApyHeader.tsx b/libs/oeth/shared/src/components/ApyHeader.tsx deleted file mode 100644 index 65ecd5595..000000000 --- a/libs/oeth/shared/src/components/ApyHeader.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { useState } from 'react'; - -import { - alpha, - Box, - Divider, - IconButton, - Menu, - MenuItem, - Stack, - Typography, -} from '@mui/material'; -import { Icon } from '@origin/shared/components'; -import { tokens } from '@origin/shared/contracts'; -import { useIntl } from 'react-intl'; -import { useAccount, useBalance } from 'wagmi'; - -const days = [7, 30]; - -export function ApyHeader() { - const intl = useIntl(); - const [selectedPeriod, setSelectedPeriod] = useState(30); - const [anchorEl, setAnchorEl] = useState(null); - const { address } = useAccount(); - const { data: oethBalance } = useBalance({ - address, - token: tokens.mainnet.OUSD.address, - watch: true, - }); - - const handleClose = () => { - setAnchorEl(null); - }; - - return ( - <> - - {days.map((day) => ( - { - setSelectedPeriod(day); - setAnchorEl(null); - }} - > - {intl.formatMessage( - { defaultMessage: '{days} day trailing' }, - { days: day }, - )} - - ))} - - - - - {intl.formatMessage( - { defaultMessage: '{days} day trailing APY' }, - { days: selectedPeriod }, - )} - - - - {intl.formatNumber(6.71 / 100, { - minimumFractionDigits: 2, - style: 'percent', - })} - - setAnchorEl(e.currentTarget)} - sx={{ - backgroundColor: (theme) => - alpha(theme.palette.common.white, 0.15), - marginInlineStart: 1, - alignSelf: 'center', - position: 'relative', - height: '1rem', - width: '1rem', - borderRadius: '100%', - top: '-2px', - }} - > - - - - - - - - - - - - - - - - - - - ); -} - -function ValueContainer({ - text, - value, - icon, -}: { - text: string; - value: string; - icon?: string; -}) { - return ( - - - {text} - - - {icon ? ( - - ) : undefined} - - {value} - - - ); -} diff --git a/libs/oeth/shared/src/components/index.tsx b/libs/oeth/shared/src/components/index.ts similarity index 68% rename from libs/oeth/shared/src/components/index.tsx rename to libs/oeth/shared/src/components/index.ts index 89385b39e..45b4fb9d0 100644 --- a/libs/oeth/shared/src/components/index.tsx +++ b/libs/oeth/shared/src/components/index.ts @@ -1,3 +1,2 @@ -export * from './ApyHeader'; export * from './AccountPopover'; export * from './GasPopover'; diff --git a/libs/oeth/swap/src/components/ApyChart.tsx b/libs/oeth/swap/src/components/ApyChart.tsx new file mode 100644 index 000000000..968a89d5e --- /dev/null +++ b/libs/oeth/swap/src/components/ApyChart.tsx @@ -0,0 +1,348 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { + Box, + Button, + Menu, + MenuItem, + Skeleton, + Stack, + Typography, + useTheme, +} from '@mui/material'; +import { Card } from '@origin/shared/components'; +import { isNilOrEmpty } from '@origin/shared/utils'; +import { ascend, last, prop, sort } from 'ramda'; +import { Line } from 'react-chartjs-2'; +import { defineMessage, useIntl } from 'react-intl'; + +import { useApiesQuery } from '../queries.generated'; + +import type { StackProps } from '@mui/material'; +import type { Chart, ChartData, ChartOptions, Plugin } from 'chart.js'; + +const limitOptions = [ + { label: defineMessage({ defaultMessage: '1W' }), value: 7 }, + { label: defineMessage({ defaultMessage: '1M' }), value: 30 }, + { label: defineMessage({ defaultMessage: '6M' }), value: 180 }, + { label: defineMessage({ defaultMessage: '1YR' }), value: 365 }, + { label: defineMessage({ defaultMessage: 'All' }), value: undefined }, +]; + +const trailingOptions = [ + { label: defineMessage({ defaultMessage: '30 days trailing' }), value: 30 }, + { label: defineMessage({ defaultMessage: '7 days trailing' }), value: 7 }, +]; + +export const ApyChart = (props: StackProps) => { + const intl = useIntl(); + const theme = useTheme(); + const chartRef = useRef>(null); + const [apy, setApy] = useState(0); + const [timestamp, setTimestamp] = useState(null); + const [limit, setLimit] = useState(limitOptions[0]); + const [trailing, setTrailing] = useState(trailingOptions[0]); + const [anchorEl, setAnchorEl] = useState(null); + const { data: apies, isLoading: apiesLoading } = useApiesQuery( + { + limit: limit.value, + }, + { + select: (data) => sort(ascend(prop('timestamp')), data.apies), + }, + ); + + useEffect(() => { + if (!isNilOrEmpty(apies)) { + setApy( + trailing.value === 30 + ? last(apies).apy30DayAvg + : last(apies).apy7DayAvg, + ); + setTimestamp(last(apies).timestamp); + } + }, [apies, trailing.value]); + + useEffect(() => { + if (chartRef.current) { + chartRef.current.options.scales.x['time'].unit = + isNilOrEmpty(limit.value) || limit.value > 30 ? 'month' : 'day'; + chartRef.current.update(); + } + }, [limit.value]); + + const handleMouseLeave = useCallback(() => { + setApy( + trailing.value === 30 ? last(apies).apy30DayAvg : last(apies).apy7DayAvg, + ); + setTimestamp(last(apies).timestamp); + }, [apies, trailing.value]); + + const hoverline: Plugin = { + id: 'hoverline', + afterDatasetDraw: (chart, args, options) => { + const { + ctx, + tooltip, + chartArea: { top, bottom }, + scales: { x }, + } = chart; + + if ( + !isNilOrEmpty(tooltip?.['_active']) && + !isNilOrEmpty(tooltip?.dataPoints?.[0]?.parsed?.x) + ) { + const xCoor = x.getPixelForValue(tooltip.dataPoints[0].parsed.x); + + ctx.save(); + ctx.beginPath(); + ctx.lineWidth = 1; + ctx.strokeStyle = theme.palette.grey[200]; + ctx.setLineDash([3, 3]); + ctx.moveTo(xCoor, top); + ctx.lineTo(xCoor, bottom); + ctx.stroke(); + ctx.restore(); + } + }, + }; + + const chartData = useMemo>( + () => ({ + datasets: [ + { + data: apies?.map((item) => ({ + x: item.timestamp, + y: trailing.value === 7 ? item.apy7DayAvg : item.apy30DayAvg, + })), + }, + ], + }), + [apies, trailing.value], + ); + + const chartOptions = useMemo>( + () => ({ + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + }, + events: ['mousemove', 'mouseout', 'mouseenter'], + datasets: { + line: { + backgroundColor: 'transparent', + pointRadius: 0, + pointHoverRadius: 0, + borderWidth: 2, + tension: 0.4, + }, + }, + borderColor: (ctx) => { + const gradient = ctx.chart.ctx.createLinearGradient( + 0, + 0, + ctx.chart.width, + ctx.chart.height, + ); + gradient.addColorStop(0, theme.palette.primary.main); + gradient.addColorStop(1, theme.palette.primary.dark); + + return gradient; + }, + layout: { + padding: 0, + }, + scales: { + y: { + display: false, + grid: { + display: false, + }, + }, + x: { + type: 'time', + time: { + round: 'day', + }, + border: { display: false }, + grid: { + display: false, + }, + ticks: { + align: 'inner', + maxRotation: 0, + padding: 4, + font: { + size: 11, + family: 'Inter', + weight: '400', + }, + }, + }, + }, + title: { + display: false, + }, + plugins: { + legend: { + display: false, + }, + tooltip: { + backgroundColor: 'transparent', + titleColor: 'transparent', + bodyColor: 'transparent', + borderColor: 'transparent', + footerColor: 'transparent', + borderWidth: 0, + padding: 0, + boxHeight: 0, + boxWidth: 0, + mode: 'index', + }, + }, + onHover: (event, elements, chart) => { + if (!isNilOrEmpty(chart?.tooltip?.dataPoints?.[0]?.parsed)) { + setTimestamp(chart.tooltip.dataPoints[0].parsed.x); + setApy(chart.tooltip.dataPoints[0].parsed.y); + } + }, + }), + [theme.palette.primary.dark, theme.palette.primary.main], + ); + + return ( + + + {intl.formatMessage({ defaultMessage: 'APY' })} + + + {limitOptions.map((d) => ( + + ))} + + + } + > + + + + {apiesLoading ? ( + + ) : ( + + {intl.formatNumber(apy, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} + % + + )} + {apiesLoading ? ( + + ) : ( + + {intl.formatDate(new Date(timestamp), { + month: 'short', + day: 'numeric', + year: 'numeric', + })} + + )} + + + + { + setAnchorEl(null); + }} + MenuListProps={{ dense: true }} + > + {trailingOptions.map((t) => ( + { + setTrailing(t); + setAnchorEl(null); + }} + > + {intl.formatMessage(t.label)} + + ))} + + + + + + + + ); +}; diff --git a/libs/oeth/swap/src/queries.generated.ts b/libs/oeth/swap/src/queries.generated.ts new file mode 100644 index 000000000..4c5ec1771 --- /dev/null +++ b/libs/oeth/swap/src/queries.generated.ts @@ -0,0 +1,51 @@ +import { graphqlClient } from '@origin/oeth/shared'; +import { useQuery } from '@tanstack/react-query'; + +import type * as Types from '@origin/oeth/shared'; +import type { UseQueryOptions } from '@tanstack/react-query'; +export type ApiesQueryVariables = Types.Exact<{ + limit?: Types.InputMaybe; +}>; + +export type ApiesQuery = { + __typename?: 'Query'; + apies: Array<{ + __typename?: 'APY'; + id: string; + timestamp: any; + apy7DayAvg: number; + apy30DayAvg: number; + }>; +}; + +export const ApiesDocument = ` + query Apies($limit: Int) { + apies(limit: $limit, orderBy: timestamp_DESC) { + id + timestamp + apy7DayAvg + apy30DayAvg + } +} + `; +export const useApiesQuery = ( + variables?: ApiesQueryVariables, + options?: UseQueryOptions, +) => + useQuery( + variables === undefined ? ['Apies'] : ['Apies', variables], + graphqlClient(ApiesDocument, variables), + options, + ); + +useApiesQuery.getKey = (variables?: ApiesQueryVariables) => + variables === undefined ? ['Apies'] : ['Apies', variables]; +useApiesQuery.fetcher = ( + variables?: ApiesQueryVariables, + options?: RequestInit['headers'], +) => + graphqlClient( + ApiesDocument, + variables, + options, + ); diff --git a/libs/oeth/swap/src/queries.graphql b/libs/oeth/swap/src/queries.graphql new file mode 100644 index 000000000..5e2258a0b --- /dev/null +++ b/libs/oeth/swap/src/queries.graphql @@ -0,0 +1,8 @@ +query Apies($limit: Int) { + apies(limit: $limit, orderBy: timestamp_DESC) { + id + timestamp + apy7DayAvg + apy30DayAvg + } +} diff --git a/libs/oeth/swap/src/views/SwapView.tsx b/libs/oeth/swap/src/views/SwapView.tsx index 756fcc0db..a9490a550 100644 --- a/libs/oeth/swap/src/views/SwapView.tsx +++ b/libs/oeth/swap/src/views/SwapView.tsx @@ -10,13 +10,14 @@ import { Stack, Typography, } from '@mui/material'; -import { ApyHeader, GasPopover } from '@origin/oeth/shared'; +import { GasPopover } from '@origin/oeth/shared'; import { Card, TokenInput } from '@origin/shared/components'; import { ConnectedButton, usePrices } from '@origin/shared/providers'; import { composeContexts, isNilOrEmpty } from '@origin/shared/utils'; import { useIntl } from 'react-intl'; import { useAccount, useBalance } from 'wagmi'; +import { ApyChart } from '../components/ApyChart'; import { SwapRoute } from '../components/SwapRoute'; import { TokenSelectModal } from '../components/TokenSelectModal'; import { routeActionLabel } from '../constants'; @@ -154,7 +155,7 @@ function SwapViewWrapped() { return ( <> - + ({ + color: theme.palette.primary.contrastText, '&.Mui-selected': { backgroundColor: 'transparent', color: theme.palette.text.secondary, diff --git a/libs/shared/utils/src/formatters.ts b/libs/shared/utils/src/formatters.ts index 3e02d1058..b75382e95 100644 --- a/libs/shared/utils/src/formatters.ts +++ b/libs/shared/utils/src/formatters.ts @@ -20,6 +20,11 @@ export const quantityFormat: FormatNumberOptions = { maximumFractionDigits: 4, }; +export const balanceFormat: FormatNumberOptions = { + minimumFractionDigits: 2, + maximumFractionDigits: 2, +}; + // Format map [value, maxDigits] const mappings = [ [10000000, 0], diff --git a/package.json b/package.json index e8fd301f1..5bfd3eb8a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "@wagmi/core": "^1.4.1", "axios": "^1.5.0", "chart.js": "^4.4.0", + "chartjs-adapter-date-fns": "^3.0.0", + "date-fns": "^2.30.0", "graphql": "^16.8.0", "immer": "^10.0.2", "ramda": "^0.29.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de0bdc9ab..54e8e07a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,12 @@ dependencies: chart.js: specifier: ^4.4.0 version: 4.4.0 + chartjs-adapter-date-fns: + specifier: ^3.0.0 + version: 3.0.0(chart.js@4.4.0)(date-fns@2.30.0) + date-fns: + specifier: ^2.30.0 + version: 2.30.0 graphql: specifier: ^16.8.0 version: 16.8.0 @@ -1703,12 +1709,6 @@ packages: resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} dev: true - /@babel/runtime@7.22.10: - resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - /@babel/runtime@7.22.15: resolution: {integrity: sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==} engines: {node: '>=6.9.0'} @@ -1833,7 +1833,7 @@ packages: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: '@babel/helper-module-imports': 7.22.15 - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.15 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.2 @@ -6439,7 +6439,7 @@ packages: engines: {node: '>=14'} dependencies: '@babel/code-frame': 7.22.13 - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.15 '@types/aria-query': 5.0.1 aria-query: 5.1.3 chalk: 4.1.2 @@ -8425,7 +8425,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.22.10 + '@babel/runtime': 7.22.15 cosmiconfig: 7.1.0 resolve: 1.22.4 dev: false @@ -8885,6 +8885,16 @@ packages: '@kurkle/color': 0.3.2 dev: false + /chartjs-adapter-date-fns@3.0.0(chart.js@4.4.0)(date-fns@2.30.0): + resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==} + peerDependencies: + chart.js: '>=2.8.0' + date-fns: '>=2.0.0' + dependencies: + chart.js: 4.4.0 + date-fns: 2.30.0 + dev: false + /check-error@1.0.2: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} dev: true @@ -9309,6 +9319,13 @@ packages: resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} dev: true + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.22.15 + dev: false + /de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} dev: true