diff --git a/package.json b/package.json index 4c49aa50..12966557 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "axios": "^1.6.7", "axios-retry": "^4.0.0", "base64-arraybuffer": "^1.0.2", + "better-react-mathjax": "^2.0.3", "dexie": "^4.0.8", "dexie-react-hooks": "^1.1.7", "fflate": "^0.8.2", diff --git a/src/App.tsx b/src/App.tsx index 750101ee..82b82cea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import { createRoutesFromElements, } from 'react-router-dom'; +import { MathJaxContext } from 'better-react-mathjax'; import GlobalDataProvider from './components/GlobalDataProvider'; import WalletProvider from './components/WalletProvider'; import AppRouterLayout from './layout/AppRouterLayout'; @@ -53,7 +54,8 @@ function App() { } - />, + /> + , } - />, + /> + , } - />, + /> + , , - }> @@ -114,7 +118,9 @@ function App() { - + + + diff --git a/src/components/modals/BaseModal.tsx b/src/components/modals/BaseModal.tsx index 95e80ae2..94661b81 100644 --- a/src/components/modals/BaseModal.tsx +++ b/src/components/modals/BaseModal.tsx @@ -20,16 +20,18 @@ const BaseModal = ({ aria-hidden="true" /> -
+
{showCloseButton && ( )} - {children} +
+ {children} +
diff --git a/src/components/modals/StakingModal.tsx b/src/components/modals/StakingModal.tsx index 18535869..ec2609d0 100644 --- a/src/components/modals/StakingModal.tsx +++ b/src/components/modals/StakingModal.tsx @@ -1,11 +1,18 @@ import { IOToken, mIOToken } from '@ar.io/sdk/web'; -import { EAY_TOOLTIP_TEXT, IO_LABEL, WRITE_OPTIONS, log } from '@src/constants'; +import { + EAY_TOOLTIP_FORMULA, + EAY_TOOLTIP_TEXT, + IO_LABEL, + WRITE_OPTIONS, + log, +} from '@src/constants'; import useGateway from '@src/hooks/useGateway'; import useRewardsInfo from '@src/hooks/useRewardsInfo'; import { useGlobalState } from '@src/store'; import { formatWithCommas } from '@src/utils'; import { showErrorToast } from '@src/utils/toast'; import { useQueryClient } from '@tanstack/react-query'; +import { MathJax } from 'better-react-mathjax'; import { useState } from 'react'; import Button, { ButtonType } from '../Button'; import Tooltip from '../Tooltip'; @@ -265,7 +272,8 @@ const StakingModal = ({
{tab == 0 - ? balances && `Available: ${formatWithCommas(balances.io)} ${IO_LABEL}` + ? balances && + `Available: ${formatWithCommas(balances.io)} ${IO_LABEL}` : `Available to Unstake: ${formatWithCommas(currentStake)} ${IO_LABEL}`}
@@ -349,16 +357,22 @@ const StakingModal = ({ label="EAY:" value={EAY} rightIcon={ - + +

{EAY_TOOLTIP_TEXT}

+ {EAY_TOOLTIP_FORMULA} + + } + >
} />
- +
-
diff --git a/src/constants.ts b/src/constants.ts index 0d956a93..a5270169 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -47,5 +47,7 @@ export const log = loglevel; export const EAY_TOOLTIP_TEXT = 'EAY = Estimated yield ratio determined by projecting the current nominal reward conditions over the course of a year. Does NOT include potential observation rewards.'; +export const EAY_TOOLTIP_FORMULA = + '\\(EAY = \\frac{RewardsSharedPerEpoch}{TotalDelegatedStake} * EpochsPerYear\\)'; export const IO_LABEL = 'tIO'; diff --git a/src/pages/Gateway/PropertyDisplayPanel.tsx b/src/pages/Gateway/PropertyDisplayPanel.tsx index f3f08667..137c6647 100644 --- a/src/pages/Gateway/PropertyDisplayPanel.tsx +++ b/src/pages/Gateway/PropertyDisplayPanel.tsx @@ -1,15 +1,22 @@ import { AoGateway, mIOToken } from '@ar.io/sdk/web'; +import Button, { ButtonType } from '@src/components/Button'; import Placeholder from '@src/components/Placeholder'; +import ConnectModal from '@src/components/modals/ConnectModal'; +import StakingModal from '@src/components/modals/StakingModal'; import { IO_LABEL } from '@src/constants'; +import { useGlobalState } from '@src/store'; +import { useState } from 'react'; const DisplayRow = ({ label, value, type, + rightComponent, }: { label: string; value: string | number | boolean | undefined; type?: string; + rightComponent?: React.ReactNode; }) => { return ( <> @@ -22,9 +29,12 @@ const DisplayRow = ({ {value === undefined ? ( ) : typeof value === 'boolean' ? ( - - {value ? 'Enabled' : 'Disabled'} - +
+ + {value ? 'Enabled' : 'Disabled'} + + {rightComponent} +
) : type == 'address' || type == 'tx' ? ( { + const walletAddress = useGlobalState((state) => state.walletAddress); + + const [stakingModalWalletAddress, setStakingModalWalletAddress] = + useState(); + + const [isConnectModalOpen, setIsConnectModalOpen] = useState(false); + const gatewayAddress = gateway ? `${gateway.settings.protocol}://${gateway.settings.fqdn}:${gateway.settings.port}` : undefined; @@ -66,7 +83,7 @@ const PropertyDisplayPanel = ({ ? [ { label: 'Reward Share Ratio:', - value: gateway?.settings.delegateRewardShareRatio, + value: `${gateway?.settings.delegateRewardShareRatio}%`, }, { label: `Minimum Delegated Stake (${IO_LABEL}):`, @@ -107,15 +124,49 @@ const PropertyDisplayPanel = ({ { label: 'Delegated Staking:', value: gateway?.settings.allowDelegatedStaking, + rightComponent: gateway?.settings.allowDelegatedStaking ? ( +
{ .toIO() .valueOf(), status: gateway.status, - rewardRatio: gateway.settings.delegateRewardShareRatio, + rewardRatio: gateway.settings.allowDelegatedStaking + ? gateway.settings.delegateRewardShareRatio + : -1, streak: gateway.stats.failedConsecutiveEpochs > 0 ? -gateway.stats.failedConsecutiveEpochs @@ -100,13 +102,13 @@ const Gateways = () => { }), columnHelper.accessor('start', { id: 'start', - header: 'Start', + header: 'Join Date', sortDescFirst: true, cell: ({ row }) => formatDate(row.original.start), }), columnHelper.accessor('totalStake', { id: 'totalStake', - header: 'Total Stake', + header: `Total Stake (${IO_LABEL})`, sortDescFirst: true, cell: ({ row }) => ( { {IO_LABEL}
- Total Delegated Stake:{' '} + Delegated Stake:{' '} {formatWithCommas(row.original.totalDelegatedStake)} {IO_LABEL}
@@ -136,7 +138,8 @@ const Gateways = () => { id: 'rewardRatio', header: 'Reward Share Ratio', sortDescFirst: true, - cell: ({ row }) => `${row.original.rewardRatio}%`, + cell: ({ row }) => + row.original.rewardRatio >= 0 ? `${row.original.rewardRatio}%` : 'N/A', }), columnHelper.accessor('streak', { id: 'streak', diff --git a/src/pages/Staking/DelegateStakeTable.tsx b/src/pages/Staking/DelegateStakeTable.tsx index 1decb9e0..371f79f8 100644 --- a/src/pages/Staking/DelegateStakeTable.tsx +++ b/src/pages/Staking/DelegateStakeTable.tsx @@ -6,13 +6,18 @@ import Tooltip from '@src/components/Tooltip'; import { InfoIcon } from '@src/components/icons'; import ConnectModal from '@src/components/modals/ConnectModal'; import StakingModal from '@src/components/modals/StakingModal'; -import { EAY_TOOLTIP_TEXT } from '@src/constants'; +import { + EAY_TOOLTIP_FORMULA, + EAY_TOOLTIP_TEXT, + IO_LABEL, +} from '@src/constants'; import useGateways from '@src/hooks/useGateways'; import useProtocolBalance from '@src/hooks/useProtocolBalance'; import { useGlobalState } from '@src/store'; import { formatWithCommas } from '@src/utils'; import { calculateGatewayRewards } from '@src/utils/rewards'; import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; +import { MathJax } from 'better-react-mathjax'; import { useEffect, useState } from 'react'; interface TableData { @@ -22,6 +27,8 @@ interface TableData { failedConsecutiveEpochs: number; rewardRatio: number; totalDelegatedStake: number; + totalStake: number; + operatorStake: number; eay: number; } @@ -57,9 +64,19 @@ const DelegateStake = () => { failedConsecutiveEpochs: gateway.stats.failedConsecutiveEpochs, rewardRatio: gateway.settings.delegateRewardShareRatio, + totalDelegatedStake: new mIOToken(gateway.totalDelegatedStake) .toIO() .valueOf(), + operatorStake: new mIOToken(gateway.operatorStake) + .toIO() + .valueOf(), + totalStake: new mIOToken( + gateway.totalDelegatedStake + gateway.operatorStake, + ) + .toIO() + .valueOf(), + eay: calculateGatewayRewards( new mIOToken(protocolBalance).toIO(), Object.keys(gateways).length, @@ -102,10 +119,28 @@ const DelegateStake = () => { sortDescFirst: false, cell: ({ row }) => , }), - columnHelper.accessor('totalDelegatedStake', { - id: 'totalDelegatedStake', - header: 'Total Stake', + columnHelper.accessor('totalStake', { + id: 'totalStake', + header: `Total Stake (${IO_LABEL})`, sortDescFirst: true, + cell: ({ row }) => ( + +
+ Operator Stake: {formatWithCommas(row.original.operatorStake)}{' '} + {IO_LABEL} +
+
+ Delegated Stake:{' '} + {formatWithCommas(row.original.totalDelegatedStake)} {IO_LABEL} +
+ + } + > + {formatWithCommas(row.getValue('totalStake'))} +
+ ), }), columnHelper.accessor('failedConsecutiveEpochs', { id: 'failedConsecutiveEpochs', @@ -123,7 +158,14 @@ const DelegateStake = () => { header: () => (
EAY - + +

{EAY_TOOLTIP_TEXT}

+ {EAY_TOOLTIP_FORMULA} +
+ } + >
@@ -183,9 +225,9 @@ const DelegateStake = () => { /> )} - {isConnectModalOpen && ( - setIsConnectModalOpen(false)} /> - )} + {isConnectModalOpen && ( + setIsConnectModalOpen(false)} /> + )} ); }; diff --git a/yarn.lock b/yarn.lock index b1cd0864..ffeb7660 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4266,6 +4266,13 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +better-react-mathjax@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz#202dc6fe5c7263278f2491516f43f70ba188122f" + integrity sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ== + dependencies: + mathjax-full "^3.2.2" + bigint-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" @@ -4841,6 +4848,11 @@ commander@12.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== +commander@9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" + integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== + commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -5883,6 +5895,11 @@ eslint@^8.56.0: strip-ansi "^6.0.1" text-table "^0.2.0" +esm@^3.2.25: + version "3.2.25" + resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" + integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== + espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" @@ -8134,6 +8151,16 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== +mathjax-full@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.2.2.tgz#43f02e55219db393030985d2b6537ceae82f1fa7" + integrity sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w== + dependencies: + esm "^3.2.25" + mhchemparser "^4.1.0" + mj-context-menu "^0.6.1" + speech-rule-engine "^4.0.6" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -8179,6 +8206,11 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +mhchemparser@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/mhchemparser/-/mhchemparser-4.2.1.tgz#d73982e66bc06170a85b1985600ee9dabe157cb0" + integrity sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ== + micromatch@4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.6.tgz#ab4e37c42726b9cd788181ba4a2a4fead8e394a3" @@ -8304,6 +8336,11 @@ mixme@^0.5.1: resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.10.tgz#d653b2984b75d9018828f1ea333e51717ead5f51" integrity sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q== +mj-context-menu@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz#a043c5282bf7e1cf3821de07b13525ca6f85aa69" + integrity sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA== + mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -9866,6 +9903,15 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== +speech-rule-engine@^4.0.6: + version "4.0.7" + resolved "https://registry.yarnpkg.com/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz#b655dacbad3dae04acc0f7665e26ef258397dd09" + integrity sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g== + dependencies: + commander "9.2.0" + wicked-good-xpath "1.3.0" + xmldom-sre "0.1.31" + split2@^3.0.0, split2@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -10887,6 +10933,11 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +wicked-good-xpath@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz#81b0e95e8650e49c94b22298fff8686b5553cf6c" + integrity sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw== + winston-transport@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.0.tgz#e302e6889e6ccb7f383b926df6936a5b781bd1f0" @@ -11007,6 +11058,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldom-sre@0.1.31: + version "0.1.31" + resolved "https://registry.yarnpkg.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz#10860d5bab2c603144597d04bf2c4980e98067f4" + integrity sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw== + xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"