From 19461a6dda9976c76fee5f6ed72ef404d42b92f4 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 22 Oct 2024 15:26:39 -0400 Subject: [PATCH 01/36] feat: adjust card headers to use containerL3 background and normalize card header heights --- src/pages/Gateway/SnitchRow.tsx | 9 ++++--- src/pages/Gateway/SoftwareDetails.tsx | 2 +- src/pages/Gateway/index.tsx | 26 +++++++++++--------- src/pages/Gateways/index.tsx | 2 +- src/pages/Observe/ObservationsTable.tsx | 2 +- src/pages/Observers/ObserversTable.tsx | 2 +- src/pages/Report/GatewayAssessmentsTable.tsx | 8 +++--- src/pages/Reports/ReportsTable.tsx | 7 ++---- src/pages/Staking/ActiveStakes.tsx | 2 +- src/pages/Staking/DelegateStakeTable.tsx | 2 +- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/pages/Gateway/SnitchRow.tsx b/src/pages/Gateway/SnitchRow.tsx index bac59b4..a73501f 100644 --- a/src/pages/Gateway/SnitchRow.tsx +++ b/src/pages/Gateway/SnitchRow.tsx @@ -16,8 +16,9 @@ const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { if (gateway) { const observers = - selectedEpoch?.observations.failureSummaries[gateway.gatewayAddress] || - []; + selectedEpoch?.observations.failureSummaries[ + gateway.gatewayAddress + ] || []; setFailureObservers(observers); } } @@ -25,7 +26,7 @@ const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { return (
-
+
{epochs ? ( <>
@@ -112,7 +113,7 @@ const ReportedOnCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { return (
-
+
{epochs ? ( <>
diff --git a/src/pages/Gateway/SoftwareDetails.tsx b/src/pages/Gateway/SoftwareDetails.tsx index d501574..46ece04 100644 --- a/src/pages/Gateway/SoftwareDetails.tsx +++ b/src/pages/Gateway/SoftwareDetails.tsx @@ -27,7 +27,7 @@ const SoftwareDetails = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { return (
-
+
Software
diff --git a/src/pages/Gateway/index.tsx b/src/pages/Gateway/index.tsx index d25c916..f29ec61 100644 --- a/src/pages/Gateway/index.tsx +++ b/src/pages/Gateway/index.tsx @@ -400,7 +400,7 @@ const Gateway = () => {
-
+
Stats
{ } /> - ) : gateway && ( - + ) : ( + gateway && ( + + ) )} {/* */}
{gateway?.weights && gateway?.status === 'joined' && (
-
+
Weights
@@ -498,7 +500,7 @@ const Gateway = () => {
-
+
General Information
{ownerId === walletAddress?.toString() && diff --git a/src/pages/Gateways/index.tsx b/src/pages/Gateways/index.tsx index 50f2842..fc1b7d4 100644 --- a/src/pages/Gateways/index.tsx +++ b/src/pages/Gateways/index.tsx @@ -199,7 +199,7 @@ const Gateways = () => {
-
+
Gateways
-
+
Reports
{ return (
-
+
Observers
{ const [tableData, setTableData] = useState>([]); - const [observedHost, setObservedHost] = useState(); + const [observedHost, setObservedHost] = useState(); const [selectedAssessment, setSelectedAssessment] = useState(); useEffect(() => { @@ -96,8 +96,8 @@ const GatewayAssessmentsTable = ({ ]; return ( -
-
+
+
Reports
{ setObservedHost(row.observedHost); - setSelectedAssessment(row.assessment) + setSelectedAssessment(row.assessment); }} /> {selectedAssessment && gateway && ( diff --git a/src/pages/Reports/ReportsTable.tsx b/src/pages/Reports/ReportsTable.tsx index b7702d6..a86e682 100644 --- a/src/pages/Reports/ReportsTable.tsx +++ b/src/pages/Reports/ReportsTable.tsx @@ -16,10 +16,7 @@ const ReportsTable = ({ }) => { const navigate = useNavigate(); - const { - isLoading, - data: reports, - } = useReports(ownerId, gateway); + const { isLoading, data: reports } = useReports(ownerId, gateway); // Define columns for the table const columns: ColumnDef[] = [ @@ -59,7 +56,7 @@ const ReportsTable = ({ return (
-
+
Reports
{ return (
-
+
Active Stakes
{hasDelegatedStake && (
+ )}
+ {showChangLogModal && ( + setShowChangeLogModal(false)} + title="Changelog" + markdownText={FORMATTED_CHANGELOG} + /> + )} ); }; diff --git a/src/pages/Observe/ObservationsTable.tsx b/src/pages/Observe/ObservationsTable.tsx index 3dd6fa3..9530d9d 100644 --- a/src/pages/Observe/ObservationsTable.tsx +++ b/src/pages/Observe/ObservationsTable.tsx @@ -93,7 +93,7 @@ const ObservationsTable = ({ return (
-
+
Reports
Date: Wed, 23 Oct 2024 16:28:49 -0400 Subject: [PATCH 03/36] chore: remove debug logging --- src/layout/Sidebar.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/layout/Sidebar.tsx b/src/layout/Sidebar.tsx index 8e8cc9a..a79dbc3 100644 --- a/src/layout/Sidebar.tsx +++ b/src/layout/Sidebar.tsx @@ -54,10 +54,7 @@ const ROUTES_SECONDARY = [ const FORMATTED_CHANGELOG = changeLog .substring(changeLog.indexOf('## [Unreleased]') + 16) .trim() - .replace(/\[([\w.]+)\]/g, (match, text) => { - console.log(match, text); - return `v${text}`; - }); + .replace(/\[([\w.]+)\]/g, (match, text) => `v${text}`); const Sidebar = () => { const location = useLocation(); @@ -132,7 +129,7 @@ const Sidebar = () => { > {sidebarOpen && (
@@ -105,6 +111,9 @@ const ReportedOnCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { return acc; }, [] as string[]); setSnitchedOn(snitchedOn); + } else { + setSelectedForObservation(undefined); + setSnitchedOn([]); } } else { setSelectedForObservation(undefined); @@ -152,7 +161,9 @@ const ReportedOnCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { className="flex gap-1 border-t border-grey-500 px-6 py-4 text-xs text-low" > -
{observer}
+
+ {observer}{' '} +
))}
From b0d6016d4f34c9b291ec1bdc377f0d9a222cc5bf Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Thu, 24 Oct 2024 14:19:11 -0400 Subject: [PATCH 09/36] feat: implement next epoch countdown in header and refactor Header to DRY --- package.json | 1 + src/components/Header.tsx | 109 +++++++++++++++++++++------------ src/hooks/useEpochCountdown.ts | 38 ++++++++++++ yarn.lock | 5 ++ 4 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 src/hooks/useEpochCountdown.ts diff --git a/package.json b/package.json index ed70242..5413017 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "axios-retry": "^4.0.0", "base64-arraybuffer": "^1.0.2", "better-react-mathjax": "^2.0.3", + "dayjs": "^1.11.13", "dexie": "^4.0.8", "dexie-react-hooks": "^1.1.7", "fflate": "^0.8.2", diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 9aa48c2..615718e 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,15 +1,54 @@ +import { mIOToken } from '@ar.io/sdk/web'; import { NBSP } from '@src/constants'; +import useEpochCountdown from '@src/hooks/useEpochCountdown'; import useGateways from '@src/hooks/useGateways'; import useProtocolBalance from '@src/hooks/useProtocolBalance'; import { useGlobalState } from '@src/store'; import { formatWithCommas } from '@src/utils'; +import { ReactNode } from 'react'; import Placeholder from './Placeholder'; import Profile from './Profile'; -import { mIOToken } from '@ar.io/sdk/web'; + +interface HeaderItemProps { + value?: ReactNode; + label: string; + loading?: boolean; + + leftPadding?: boolean; +} + +const HeaderItem = ({ + value, + label, + loading = false, + leftPadding = true, +}: HeaderItemProps) => { + return ( +
+
+ {loading ? ( + + ) : value !== undefined ? ( + typeof value === 'number' ? ( + value.toLocaleString('en-US') + ) : ( + value + ) + ) : ( + NBSP + )} +
+
{label}
+
+ ); +}; const Header = () => { const blockHeight = useGlobalState((state) => state.blockHeight); const currentEpoch = useGlobalState((state) => state.currentEpoch); + const epochCountdown = useEpochCountdown(); const ticker = useGlobalState((state) => state.ticker); const { isLoading: gatewaysLoading, data: gateways } = useGateways(); @@ -17,45 +56,39 @@ const Header = () => { return (
-
-
- {currentEpoch?.epochIndex !== undefined - ? currentEpoch.epochIndex.toLocaleString('en-US') - : NBSP} -
-
AR.IO EPOCH
-
-
-
- {blockHeight ? blockHeight.toLocaleString('en-US') : NBSP} -
-
ARWEAVE BLOCK
-
-
-
- {gatewaysLoading ? ( - - ) : gateways ? ( - Object.values(gateways).filter((g) => g.status === 'joined').length - ) : ( - NBSP - )} -
-
GATEWAYS
-
- -
-
- {protocolBalance == undefined ? ( - - ) : ( + + + + + - {formatWithCommas(new mIOToken(protocolBalance).toIO().valueOf())} {ticker} + {formatWithCommas(new mIOToken(protocolBalance).toIO().valueOf())}{' '} + {ticker}
- )} -
-
PROTOCOL BALANCE
-
+ ) : undefined + } + label="PROTOCOL BALANCE" + loading={!protocolBalance} + />
diff --git a/src/hooks/useEpochCountdown.ts b/src/hooks/useEpochCountdown.ts new file mode 100644 index 0000000..27f45f2 --- /dev/null +++ b/src/hooks/useEpochCountdown.ts @@ -0,0 +1,38 @@ +import { useGlobalState } from '@src/store'; +import dayjs from 'dayjs'; +import { useEffect, useState } from 'react'; + +const useEpochCountdown = () => { + const currentEpoch = useGlobalState((state) => state.currentEpoch); + + const [countdown, setCountdown] = useState(); + + useEffect(() => { + if (currentEpoch) { + const updateCountdown = () => { + const now = dayjs(); + const end = dayjs(new Date(currentEpoch.endTimestamp)); + const diff = end.diff(now, 'seconds'); + + if (diff <= 0) { + setCountdown('0h 0m'); + } else { + const hours = Math.floor(diff / 3600); + const minutes = Math.floor(diff % 3600 / 60); + setCountdown(`${hours}h ${minutes}m`); + } + }; + + updateCountdown(); + const intervalId = setInterval(updateCountdown, 1000); // Update every minute + + return () => clearInterval(intervalId); + } else { + setCountdown(undefined); + } + }, [currentEpoch]); + + return countdown; +}; + +export default useEpochCountdown; diff --git a/yarn.lock b/yarn.lock index a0ed02e..96fb098 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5355,6 +5355,11 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +dayjs@^1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" From c37c09622f3aa5a4cf624304f1fe14be636f79d6 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 25 Oct 2024 10:08:22 -0400 Subject: [PATCH 10/36] feat: add rewards earned and update staked card to include both staked and pending withdrawals --- src/hooks/useRewardsEarned.ts | 39 ++++++++ src/pages/Staking/ConnectedLandingPage.tsx | 100 +++++++++++++++++---- 2 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 src/hooks/useRewardsEarned.ts diff --git a/src/hooks/useRewardsEarned.ts b/src/hooks/useRewardsEarned.ts new file mode 100644 index 0000000..3be2ec4 --- /dev/null +++ b/src/hooks/useRewardsEarned.ts @@ -0,0 +1,39 @@ +import { mIOToken } from '@ar.io/sdk'; +import { useEffect, useState } from 'react'; +import useEpochs from './useEpochs'; + +export type RewardsEarned = { + previousEpoch: number; + totalForPastAvailableEpochs: number; +}; + +const useRewardsEarned = (walletAddress?: string) => { + const [rewardsEarned, setRewardsEarned] = useState(); + const { data: epochs } = useEpochs(); + + useEffect(() => { + if (epochs && walletAddress) { + const sorted = epochs.sort((a, b) => a.epochIndex - b.epochIndex); + const previousEpoch = sorted[sorted.length - 2]; + const previousEpochDistributed = + previousEpoch.distributions.rewards.distributed; + const previousEpochRewards = previousEpochDistributed + ? previousEpochDistributed[walletAddress] + : 0; + + const totalForPastAvailableEpochs = epochs.reduce((acc, epoch) => { + const distributed = epoch.distributions.rewards.distributed; + return acc + (distributed ? distributed[walletAddress] : 0); + }, 0); + setRewardsEarned({ + previousEpoch: new mIOToken(previousEpochRewards).toIO().valueOf(), + totalForPastAvailableEpochs: new mIOToken(totalForPastAvailableEpochs) + .toIO() + .valueOf(), + }); + } + }, [epochs, walletAddress]); + return rewardsEarned; +}; + +export default useRewardsEarned; diff --git a/src/pages/Staking/ConnectedLandingPage.tsx b/src/pages/Staking/ConnectedLandingPage.tsx index 741fefa..59239ff 100644 --- a/src/pages/Staking/ConnectedLandingPage.tsx +++ b/src/pages/Staking/ConnectedLandingPage.tsx @@ -2,12 +2,69 @@ import { mIOToken } from '@ar.io/sdk/web'; import Placeholder from '@src/components/Placeholder'; import StakingModal from '@src/components/modals/StakingModal'; import useGateways from '@src/hooks/useGateways'; +import useRewardsEarned from '@src/hooks/useRewardsEarned'; import { useGlobalState } from '@src/store'; import { formatWithCommas } from '@src/utils'; import { useEffect, useState } from 'react'; import ActiveStakes from './ActiveStakes'; import DelegateStake from './DelegateStakeTable'; +const TopPanel = ({ + title, + value, + ticker, + leftTitle, + leftValue, + rightTitle, + rightValue, +}: { + title: string; + value?: string; + ticker: string; + leftTitle?: string; + leftValue?: string; + rightTitle?: string; + rightValue?: string; +}) => { + return ( +
+
{title}
+
+
+ {value ?? } +
+
{ticker}
+
+
+
+
+ {leftTitle && + (leftValue !== undefined ? ( + <> +
{leftValue}
+
{leftTitle}
+ + ) : ( + + ))} +
+
+
+ {rightTitle && + (rightValue !== undefined ? ( + <> +
{rightValue}
+
{rightTitle}
+ + ) : ( + + ))} +
+
+
+ ); +}; + const ConnectedLandingPage = () => { const walletAddress = useGlobalState((state) => state.walletAddress); const ticker = useGlobalState((state) => state.ticker); @@ -17,15 +74,20 @@ const ConnectedLandingPage = () => { const { data: gateways } = useGateways(); const balances = useGlobalState((state) => state.balances); + const rewardsEarned = useRewardsEarned(walletAddress?.toString()); useEffect(() => { if (gateways && walletAddress) { const amountStaking = Object.values(gateways).reduce((acc, gateway) => { - const delegate = gateway.delegates[walletAddress.toString()]; - if (delegate) { - return acc + delegate.delegatedStake; - } - return acc; + const userDelegate = gateway.delegates[walletAddress.toString()]; + const delegatedStake = userDelegate?.delegatedStake ?? 0; + const withdrawn = userDelegate?.vaults + ? Object.values(userDelegate.vaults).reduce((acc, withdrawal) => { + return acc + withdrawal.balance; + }, 0) + : 0; + + return acc + delegatedStake + withdrawn; }, 0); setAmountStaking(new mIOToken(amountStaking).toIO().valueOf()); } @@ -37,7 +99,13 @@ const ConnectedLandingPage = () => { balance: formatWithCommas(balances.io), }, { - title: 'Amount Staking', + title: 'Rewards Earned', + balance: `${formatWithCommas(rewardsEarned?.totalForPastAvailableEpochs || 0)} ${ticker}`, + leftTitle: 'LAST EPOCH', + leftValue: `${formatWithCommas(rewardsEarned?.previousEpoch || 0)} ${ticker}`, + }, + { + title: 'Amount Staking + Pending Withdrawals', balance: amountStaking !== undefined ? formatWithCommas(amountStaking) @@ -47,20 +115,16 @@ const ConnectedLandingPage = () => { return (
-
+
{topPanels.map((panel, index) => ( -
-
{panel.title}
-
-
- {panel.balance ?? } -
-
{ticker}
-
-
+ title={panel.title} + value={panel.balance} + ticker={ticker} + leftTitle={panel.leftTitle} + leftValue={panel.leftValue} + /> ))}
From edc063ad9d8ae0c10ba8508ac290aefcdbb6a799 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 25 Oct 2024 17:50:49 -0400 Subject: [PATCH 11/36] feat: implemented Pending Withdrawals table and ability to cancel pending withdrawal --- src/components/Dropdown.tsx | 16 +- src/components/TableView.tsx | 46 +- src/components/icons/cancel_button_x.svg | 16 + src/components/icons/index.ts | 4 + src/components/icons/instant_withdrawal.svg | 31 ++ .../modals/CancelWithdrawalModal.tsx | 140 +++++++ src/hooks/useGateways.ts | 2 + src/pages/Staking/ActiveStakes.tsx | 195 --------- src/pages/Staking/ConnectedLandingPage.tsx | 16 +- src/pages/Staking/MyStakesTable.tsx | 394 ++++++++++++++++++ 10 files changed, 629 insertions(+), 231 deletions(-) create mode 100644 src/components/icons/cancel_button_x.svg create mode 100644 src/components/icons/instant_withdrawal.svg create mode 100644 src/components/modals/CancelWithdrawalModal.tsx delete mode 100644 src/pages/Staking/ActiveStakes.tsx create mode 100644 src/pages/Staking/MyStakesTable.tsx diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index 8ea627d..0186b39 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -1,18 +1,22 @@ import { ChevronDownIcon } from './icons'; +type DropdownProps = { + options: { label: string; value: string }[]; + onChange: (e: React.ChangeEvent) => void; + value: string; + tightPadding?: boolean; +}; + const Dropdown = ({ options, onChange, value, -}: { - options: { label: string; value: string }[]; - onChange: (e: React.ChangeEvent) => void; - value: string; -}) => { + tightPadding = false, +}: DropdownProps) => { return (
setConfirmText(e.target.value)} + className={ + 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-3 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' + } + value={confirmText} + /> +
+ +
+
} + className={`w-full ${!termsAccepted && 'pointer-events-none opacity-30'}`} + /> +
+
+
+ + {showBlockingMessageModal && ( + setShowBlockingMessageModal(false)} + message="Sign the following data with your wallet to proceed." + > + )} + {showSuccessModal && ( + { + setShowSuccessModal(false); + onClose(); + }} + title="Confirmed" + bodyText={ +
+
You have successfully canceled the withdrawal.
+
+
Transaction ID:
+ +
+
+ } + /> + )} + + ); +}; + +export default CancelWithdrawalModal; diff --git a/src/hooks/useGateways.ts b/src/hooks/useGateways.ts index ae16fb0..6bd5fbf 100644 --- a/src/hooks/useGateways.ts +++ b/src/hooks/useGateways.ts @@ -29,6 +29,8 @@ const useGateways = () => { return fetchAllGateways(arIOReadSDK); } }, + + staleTime: 5 * 60 * 1000, }); return queryResults; diff --git a/src/pages/Staking/ActiveStakes.tsx b/src/pages/Staking/ActiveStakes.tsx deleted file mode 100644 index 5e4166b..0000000 --- a/src/pages/Staking/ActiveStakes.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { AoGateway, mIOToken } from '@ar.io/sdk/web'; -import AddressCell from '@src/components/AddressCell'; -import Button, { ButtonType } from '@src/components/Button'; -import TableView from '@src/components/TableView'; -import { GearIcon } from '@src/components/icons'; -import StakingModal from '@src/components/modals/StakingModal'; -import UnstakeAllModal from '@src/components/modals/UnstakeAllModal'; -import useGateways from '@src/hooks/useGateways'; -import { useGlobalState } from '@src/store'; -import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; - -interface TableData { - owner: string; - delegatedStake: number; - gateway: AoGateway; - pendingWithdrawals: number; -} - -const columnHelper = createColumnHelper(); - -const ActiveStakes = () => { - const walletAddress = useGlobalState((state) => state.walletAddress); - const ticker = useGlobalState((state) => state.ticker); - - const { isLoading, data: gateways } = useGateways(); - const [activeStakes, setActiveStakes] = useState>([]); - - const [showUnstakeAllModal, setShowUnstakeAllModal] = useState(false); - const [stakingModalWalletAddress, setStakingModalWalletAddress] = - useState(); - const [showQuickStake, setShowQuickStake] = useState(false); - - const navigate = useNavigate(); - - useEffect(() => { - const activeStakes: Array = - !walletAddress || !gateways - ? ([] as Array) - : Object.keys(gateways).reduce((acc, key) => { - const gateway = gateways[key]; - const delegate = gateway.delegates[walletAddress?.toString()]; - - if (delegate) { - return [ - ...acc, - { - owner: key, - delegatedStake: delegate.delegatedStake, - gateway, - pendingWithdrawals: Object.keys(delegate.vaults).length, - }, - ]; - } - return acc; - }, [] as Array); - setActiveStakes(activeStakes); - }, [gateways, walletAddress]); - - // Define columns for the table - const columns: ColumnDef[] = [ - columnHelper.accessor('gateway.settings.label', { - id: 'label', - header: 'Label', - sortDescFirst: false, - }), - columnHelper.accessor('gateway.settings.fqdn', { - id: 'domain', - header: 'Domain', - sortDescFirst: false, - cell: ({ row }) => ( - - ), - }), - columnHelper.accessor('owner', { - id: 'owner', - header: 'Address', - sortDescFirst: false, - cell: ({ row }) => , - }), - columnHelper.accessor('delegatedStake', { - id: 'delegatedStake', - header: `Current Stake (${ticker})`, - sortDescFirst: true, - cell: ({ row }) => { - return `${new mIOToken(row.original.delegatedStake).toIO().valueOf()}`; - }, - }), - columnHelper.accessor('pendingWithdrawals', { - id: 'pendingWithdrawals', - header: 'Pending Withdrawals', - sortDescFirst: true, - cell: ({ row }) => ( -
0 ? 'text-high' : 'text-low' - } - > - {`${row.original.pendingWithdrawals}`} -
- ), - }), - columnHelper.display({ - id: 'action', - header: '', - cell: ({ row }) => { - return ( -
-
- ); - }, - }), - ]; - - const hasDelegatedStake = - activeStakes?.some((v) => v.delegatedStake > 0) ?? false; - - return ( -
-
-
Active Stakes
- {hasDelegatedStake && ( -
- { - navigate(`/gateways/${row.owner}`); - }} - /> - {showUnstakeAllModal && ( - setShowUnstakeAllModal(false)} - /> - )} - {(stakingModalWalletAddress || showQuickStake) && ( - { - setStakingModalWalletAddress(undefined); - setShowQuickStake(false); - }} - ownerWallet={stakingModalWalletAddress} - /> - )} -
- ); -}; - -export default ActiveStakes; diff --git a/src/pages/Staking/ConnectedLandingPage.tsx b/src/pages/Staking/ConnectedLandingPage.tsx index 59239ff..13b8b08 100644 --- a/src/pages/Staking/ConnectedLandingPage.tsx +++ b/src/pages/Staking/ConnectedLandingPage.tsx @@ -6,7 +6,7 @@ import useRewardsEarned from '@src/hooks/useRewardsEarned'; import { useGlobalState } from '@src/store'; import { formatWithCommas } from '@src/utils'; import { useEffect, useState } from 'react'; -import ActiveStakes from './ActiveStakes'; +import MyStakesTable from './MyStakesTable'; import DelegateStake from './DelegateStakeTable'; const TopPanel = ({ @@ -98,12 +98,6 @@ const ConnectedLandingPage = () => { title: 'Your Balance', balance: formatWithCommas(balances.io), }, - { - title: 'Rewards Earned', - balance: `${formatWithCommas(rewardsEarned?.totalForPastAvailableEpochs || 0)} ${ticker}`, - leftTitle: 'LAST EPOCH', - leftValue: `${formatWithCommas(rewardsEarned?.previousEpoch || 0)} ${ticker}`, - }, { title: 'Amount Staking + Pending Withdrawals', balance: @@ -111,6 +105,12 @@ const ConnectedLandingPage = () => { ? formatWithCommas(amountStaking) : undefined, }, + { + title: 'Rewards Earned', + balance: `${formatWithCommas(rewardsEarned?.totalForPastAvailableEpochs || 0)} ${ticker}`, + leftTitle: 'LAST EPOCH', + leftValue: `${formatWithCommas(rewardsEarned?.previousEpoch || 0)} ${ticker}`, + }, ]; return ( @@ -127,7 +127,7 @@ const ConnectedLandingPage = () => { /> ))}
- + {isStakingModalOpen && ( (); +const columnHelperWithdrawals = + createColumnHelper(); + +const MyStakesTable = () => { + const walletAddress = useGlobalState((state) => state.walletAddress); + const ticker = useGlobalState((state) => state.ticker); + + const { isFetching, data: gateways } = useGateways(); + const [activeStakes, setActiveStakes] = useState< + Array + >([]); + const [pendingWithdrawals, setPendingWithdrawals] = useState< + Array + >([]); + + const [tableMode, setTableMode] = useState('activeStakes'); + + const [showUnstakeAllModal, setShowUnstakeAllModal] = useState(false); + const [stakingModalWalletAddress, setStakingModalWalletAddress] = + useState(); + const [showQuickStake, setShowQuickStake] = useState(false); + const [confirmCancelWithdrawal, setConfirmCancelWithdrawal] = useState<{ + gatewayAddress: string; + vaultId: string; + }>(); + + const navigate = useNavigate(); + + useEffect(() => { + const activeStakes: Array = + !walletAddress || !gateways + ? [] + : Object.keys(gateways).reduce((acc, key) => { + const gateway = gateways[key]; + const delegate = gateway.delegates[walletAddress?.toString()]; + + if (delegate) { + return [ + ...acc, + { + owner: key, + delegatedStake: delegate.delegatedStake, + gateway, + pendingWithdrawals: Object.keys(delegate.vaults).length, + streak: + gateway.status == 'leaving' + ? Number.NEGATIVE_INFINITY + : gateway.stats.failedConsecutiveEpochs > 0 + ? -gateway.stats.failedConsecutiveEpochs + : gateway.stats.passedConsecutiveEpochs, + }, + ]; + } + return acc; + }, [] as Array); + + const pendingWithdrawals: Array = + !walletAddress || !gateways + ? [] + : Object.keys(gateways).reduce((acc, key) => { + const gateway = gateways[key]; + const delegate = gateway.delegates[walletAddress?.toString()]; + + if (delegate?.vaults) { + const withdrawals = Object.entries(delegate.vaults).map( + ([withdrawalId, withdrawal]) => { + return { + owner: key, + gateway, + withdrawal, + withdrawalId, + }; + }, + ); + + return [...acc, ...withdrawals]; + } + return acc; + }, [] as Array); + setActiveStakes(activeStakes); + setPendingWithdrawals(pendingWithdrawals); + }, [gateways, walletAddress]); + + useEffect(() => { + if (isFetching) { + setActiveStakes([]); + setPendingWithdrawals([]); + } + }, [isFetching]); + + // Define columns for the active stakes table + const activeStakesColumns: ColumnDef[] = [ + columnHelper.accessor('gateway.settings.label', { + id: 'label', + header: 'Label', + sortDescFirst: false, + }), + columnHelper.accessor('gateway.settings.fqdn', { + id: 'domain', + header: 'Domain', + sortDescFirst: false, + cell: ({ row }) => ( + + ), + }), + columnHelper.accessor('owner', { + id: 'owner', + header: 'Address', + sortDescFirst: false, + cell: ({ row }) => , + }), + columnHelper.accessor('delegatedStake', { + id: 'delegatedStake', + header: `Current Stake (${ticker})`, + sortDescFirst: true, + cell: ({ row }) => { + return `${new mIOToken(row.original.delegatedStake).toIO().valueOf()}`; + }, + }), + columnHelper.accessor('streak', { + id: 'streak', + header: 'Streak', + sortDescFirst: true, + cell: ({ row }) => , + }), + columnHelper.accessor('pendingWithdrawals', { + id: 'pendingWithdrawals', + header: 'Pending Withdrawals', + sortDescFirst: true, + cell: ({ row }) => ( +
0 ? 'text-high' : 'text-low' + } + > + {`${row.original.pendingWithdrawals}`} +
+ ), + }), + columnHelper.display({ + id: 'action', + header: '', + cell: ({ row }) => { + return ( +
+
+ ); + }, + }), + ]; + + const hasDelegatedStake = + activeStakes?.some((v) => v.delegatedStake > 0) ?? false; + + // Define columns for the pending withdrawals table + const pendingWithdrawalsColumns: ColumnDef< + PendingWithdrawalsTableData, + any + >[] = [ + columnHelperWithdrawals.accessor('gateway.settings.label', { + id: 'label', + header: 'Label', + sortDescFirst: false, + }), + columnHelperWithdrawals.accessor('gateway.settings.fqdn', { + id: 'domain', + header: 'Domain', + sortDescFirst: false, + cell: ({ row }) => ( + + ), + }), + columnHelperWithdrawals.accessor('owner', { + id: 'owner', + header: 'Address', + sortDescFirst: false, + cell: ({ row }) => , + }), + columnHelperWithdrawals.accessor('withdrawal.balance', { + id: 'withdrawal', + header: `Stake Withdrawing (${ticker})`, + sortDescFirst: true, + cell: ({ row }) => { + return `${new mIOToken(row.original.withdrawal.balance).toIO().valueOf()}`; + }, + }), + columnHelperWithdrawals.accessor((row) => row.withdrawal.endTimestamp, { + id: 'endDate', + header: `Date Returning`, + sortDescFirst: true, + cell: ({ row }) => { + return `${dayjs(new Date(row.original.withdrawal.endTimestamp)).format('YYYY-MM-DD')}`; + }, + }), + columnHelperWithdrawals.display({ + id: 'actions', + cell: ({ row }) => { + return ( +
+
+ ); + }, + }), + ]; + + return ( +
+
+
+ { + setTableMode(e.target.value as TableMode); + }} + value={tableMode} + tightPadding={true} + /> +
+ + {tableMode == 'activeStakes' && ( + <> + {hasDelegatedStake && ( +
+ {tableMode === 'activeStakes' ? ( + { + navigate(`/gateways/${row.owner}`); + }} + /> + ) : ( + { + navigate(`/gateways/${row.owner}`); + }} + /> + )} + {showUnstakeAllModal && ( + setShowUnstakeAllModal(false)} + /> + )} + {(stakingModalWalletAddress || showQuickStake) && ( + { + setStakingModalWalletAddress(undefined); + setShowQuickStake(false); + }} + ownerWallet={stakingModalWalletAddress} + /> + )} + {confirmCancelWithdrawal && ( + setConfirmCancelWithdrawal(undefined)} + /> + )} +
+ ); +}; + +export default MyStakesTable; From 42ed694e4f4b8de446251540f3a9c330c5a91a09 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 28 Oct 2024 15:46:46 -0400 Subject: [PATCH 12/36] fix: explicitly check undefined vs. falsey to ensure placeholders show correctly --- src/pages/Staking/ConnectedLandingPage.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pages/Staking/ConnectedLandingPage.tsx b/src/pages/Staking/ConnectedLandingPage.tsx index 13b8b08..ff64ace 100644 --- a/src/pages/Staking/ConnectedLandingPage.tsx +++ b/src/pages/Staking/ConnectedLandingPage.tsx @@ -6,8 +6,8 @@ import useRewardsEarned from '@src/hooks/useRewardsEarned'; import { useGlobalState } from '@src/store'; import { formatWithCommas } from '@src/utils'; import { useEffect, useState } from 'react'; -import MyStakesTable from './MyStakesTable'; import DelegateStake from './DelegateStakeTable'; +import MyStakesTable from './MyStakesTable'; const TopPanel = ({ title, @@ -107,9 +107,15 @@ const ConnectedLandingPage = () => { }, { title: 'Rewards Earned', - balance: `${formatWithCommas(rewardsEarned?.totalForPastAvailableEpochs || 0)} ${ticker}`, + balance: + rewardsEarned?.totalForPastAvailableEpochs !== undefined + ? `${formatWithCommas(rewardsEarned.totalForPastAvailableEpochs)} ${ticker}` + : undefined, leftTitle: 'LAST EPOCH', - leftValue: `${formatWithCommas(rewardsEarned?.previousEpoch || 0)} ${ticker}`, + leftValue: + rewardsEarned?.previousEpoch !== undefined + ? `${formatWithCommas(rewardsEarned.previousEpoch)} ${ticker}` + : undefined, }, ]; From 4a9084bef55a531cd0fc1286027e2f62b3e9c0ba Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 28 Oct 2024 15:47:43 -0400 Subject: [PATCH 13/36] fix: refetches were showing stale data due to difference between when isFetching returned false and useEffect finishing processing --- src/pages/Staking/MyStakesTable.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/Staking/MyStakesTable.tsx b/src/pages/Staking/MyStakesTable.tsx index 1319c48..dcb54f9 100644 --- a/src/pages/Staking/MyStakesTable.tsx +++ b/src/pages/Staking/MyStakesTable.tsx @@ -47,10 +47,10 @@ const MyStakesTable = () => { const { isFetching, data: gateways } = useGateways(); const [activeStakes, setActiveStakes] = useState< Array - >([]); + >(); const [pendingWithdrawals, setPendingWithdrawals] = useState< Array - >([]); + >(); const [tableMode, setTableMode] = useState('activeStakes'); @@ -122,8 +122,8 @@ const MyStakesTable = () => { useEffect(() => { if (isFetching) { - setActiveStakes([]); - setPendingWithdrawals([]); + setActiveStakes(undefined); + setPendingWithdrawals(undefined); } }, [isFetching]); @@ -338,8 +338,8 @@ const MyStakesTable = () => { {tableMode === 'activeStakes' ? ( { ) : ( { }} /> )} - {showUnstakeAllModal && ( + {showUnstakeAllModal && activeStakes !== undefined && ( setShowUnstakeAllModal(false)} From cbb2706bcd5c5163ec80927ad9ac599402295b9b Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 28 Oct 2024 17:41:17 -0400 Subject: [PATCH 14/36] fix: issue with tables not being treated as different fixed by using keys --- src/pages/Staking/MyStakesTable.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/Staking/MyStakesTable.tsx b/src/pages/Staking/MyStakesTable.tsx index dcb54f9..6da3fa0 100644 --- a/src/pages/Staking/MyStakesTable.tsx +++ b/src/pages/Staking/MyStakesTable.tsx @@ -45,12 +45,10 @@ const MyStakesTable = () => { const ticker = useGlobalState((state) => state.ticker); const { isFetching, data: gateways } = useGateways(); - const [activeStakes, setActiveStakes] = useState< - Array - >(); - const [pendingWithdrawals, setPendingWithdrawals] = useState< - Array - >(); + const [activeStakes, setActiveStakes] = + useState>(); + const [pendingWithdrawals, setPendingWithdrawals] = + useState>(); const [tableMode, setTableMode] = useState('activeStakes'); @@ -337,6 +335,7 @@ const MyStakesTable = () => {
{tableMode === 'activeStakes' ? ( { /> ) : ( Date: Sun, 3 Nov 2024 19:37:09 -0600 Subject: [PATCH 15/36] fix(observe): set `Accept-Encoding` header when fetching arns data Gateways may use CDNs to serve data. Those CDN's may return compressed data depending on their configuration. To avoid discrpencies, the observations should request the raw data using the `Accept-Encoding: identity` header. Related PR: https://github.com/ar-io/ar-io-observer/pull/53 --- src/utils/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/index.ts b/src/utils/index.ts index 78c13b2..a755c96 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -104,6 +104,10 @@ export const fetchWithTimeout = async (resource:string, options?:RequestInit, ti const response = await fetch(resource, { ...options, + headers: { + ...options?.headers, + 'Accept-Encoding': 'identity' + }, signal: controller.signal }); clearTimeout(id); From 6837330db74c73eb48000cb6a6f2033361a2bcdc Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sun, 3 Nov 2024 19:49:16 -0600 Subject: [PATCH 16/36] chore(arns): change reference gateway to arweave.net This is the production arns resolver and what observers validate against - https://github.com/ar-io/ar-io-observer/blob/develop/src/config.ts#L62-L65. --- src/utils/observations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/observations.ts b/src/utils/observations.ts index 8545497..e3167f6 100644 --- a/src/utils/observations.ts +++ b/src/utils/observations.ts @@ -6,7 +6,7 @@ import { ArNSAssessment, Assessment, OwnershipAssessment } from '@src/types'; import { arrayBufferToBase64Url, fetchWithTimeout } from '.'; const NAME_PASS_THRESHOLD = 0.8; -const REFERENCE_GATEWAY_FQDN = 'ar-io.dev'; +const REFERENCE_GATEWAY_FQDN = 'arweave.net'; export const assessOwnership = async ( gateway: AoGatewayWithAddress, From 4de54351e2b6daa723585785f6bee80986b7c6ef Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 4 Nov 2024 11:47:12 -0500 Subject: [PATCH 17/36] chore: move constants to constants.ts and add env var override for REFERENCE_GATEWAY_FQDN --- CHANGELOG.md | 1 + src/constants.ts | 5 +++++ src/utils/observations.ts | 5 +---- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca3d1c1..6aeadc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * Updated header style of cards +* Observations: Updated to use arweave.net for reference domain when generating observation report ## [1.3.0] - 2024-10-21 diff --git a/src/constants.ts b/src/constants.ts index 34820e0..a35791e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -54,3 +54,8 @@ export const EAY_TOOLTIP_FORMULA = export const OPERATOR_EAY_TOOLTIP_FORMULA = '\\(EAY = \\frac{OperatorRewardsPerEpoch}{OperatorStake} * EpochsPerYear\\)'; + +// OBSERVATION ASSESSMENT CONSTANTS +export const NAME_PASS_THRESHOLD = 0.8; +export const REFERENCE_GATEWAY_FQDN = + process.env.VITE_REFERENCE_GATEWAY_FQDN ?? 'arweave.net'; diff --git a/src/utils/observations.ts b/src/utils/observations.ts index e3167f6..d5c4836 100644 --- a/src/utils/observations.ts +++ b/src/utils/observations.ts @@ -1,13 +1,10 @@ /* Based on code by elliotsayes from https://github.com/elliotsayes/gateway-explorer */ import { AoGatewayWithAddress } from '@ar.io/sdk/web'; -import { log } from '@src/constants'; +import { log, NAME_PASS_THRESHOLD, REFERENCE_GATEWAY_FQDN } from '@src/constants'; import { ArNSAssessment, Assessment, OwnershipAssessment } from '@src/types'; import { arrayBufferToBase64Url, fetchWithTimeout } from '.'; -const NAME_PASS_THRESHOLD = 0.8; -const REFERENCE_GATEWAY_FQDN = 'arweave.net'; - export const assessOwnership = async ( gateway: AoGatewayWithAddress, ): Promise => { From 88141dfe536e0396695891823a3653b4985f846d Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 5 Nov 2024 13:43:03 -0500 Subject: [PATCH 18/36] fix: make link to "reported on by" use gateway address for given observer --- src/hooks/useObserverToGatewayMap.ts | 21 +++++++++++++++++++++ src/pages/Gateway/SnitchRow.tsx | 10 +++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useObserverToGatewayMap.ts diff --git a/src/hooks/useObserverToGatewayMap.ts b/src/hooks/useObserverToGatewayMap.ts new file mode 100644 index 0000000..868a7a5 --- /dev/null +++ b/src/hooks/useObserverToGatewayMap.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; +import useGateways from './useGateways'; + +const useObserverToGatewayMap = () => { + const { data: gateways } = useGateways(); + const [observerToGatewayMap, setObserverToGatewayMap] = + useState>(); + + useEffect(() => { + if (gateways) { + const results: Record = {}; + Object.entries(gateways).forEach(([gatewayAddress, gateway]) => { + results[gateway.observerAddress] = gatewayAddress; + }); + setObserverToGatewayMap(results); + } + }, [gateways]); + + return observerToGatewayMap; +}; +export default useObserverToGatewayMap; diff --git a/src/pages/Gateway/SnitchRow.tsx b/src/pages/Gateway/SnitchRow.tsx index 173127b..802e1e7 100644 --- a/src/pages/Gateway/SnitchRow.tsx +++ b/src/pages/Gateway/SnitchRow.tsx @@ -3,6 +3,7 @@ import Dropdown from '@src/components/Dropdown'; import { StatsArrowIcon } from '@src/components/icons'; import Placeholder from '@src/components/Placeholder'; import useEpochs from '@src/hooks/useEpochs'; +import useObserverToGatewayMap from '@src/hooks/useObserverToGatewayMap'; import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; @@ -10,6 +11,7 @@ const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { const { data: epochs } = useEpochs(); const [selectedEpochIndex, setSelectedEpochIndex] = useState(0); const [failureObservers, setFailureObservers] = useState([]); + const observerToGatewayMap = useObserverToGatewayMap(); useEffect(() => { if (epochs) { @@ -73,7 +75,13 @@ const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { >
- {observer}{' '} + {observerToGatewayMap ? ( + + {observer} + + ) : ( + observer + )}
))} From 715e1a390107bf87b387c512ecfe91c35624929b Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 5 Nov 2024 14:00:03 -0500 Subject: [PATCH 19/36] feat: add observers button to dashboard observers card --- .../Dashboard/ObserverPerformancePanel.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pages/Dashboard/ObserverPerformancePanel.tsx b/src/pages/Dashboard/ObserverPerformancePanel.tsx index 5dbe6f4..410a031 100644 --- a/src/pages/Dashboard/ObserverPerformancePanel.tsx +++ b/src/pages/Dashboard/ObserverPerformancePanel.tsx @@ -1,7 +1,11 @@ +import Button from '@src/components/Button'; +import { BinocularsIcon } from '@src/components/icons'; import Placeholder from '@src/components/Placeholder'; import { useGlobalState } from '@src/store'; +import { useNavigate } from 'react-router-dom'; const ObserverPerformancePanel = () => { + const navigate = useNavigate(); const currentEpoch = useGlobalState((state) => state.currentEpoch); const reportsCount = currentEpoch @@ -10,7 +14,19 @@ const ObserverPerformancePanel = () => { return (
-
Observer Performance
+
+
Observer Performance
+
@@ -25,7 +41,7 @@ const ObserverPerformancePanel = () => {
- {reportsCount !== undefined? ( + {reportsCount !== undefined ? ( <>
{reportsCount}/50
observations submitted
From a2384f86ce7023ffbfbf124ae48d300c6615bdcf Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Thu, 7 Nov 2024 14:25:33 -0500 Subject: [PATCH 20/36] fix: use startEpoch and sdk as part of query key to ensure retriggering --- src/hooks/useEpochs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useEpochs.ts b/src/hooks/useEpochs.ts index faa4afe..943e0eb 100644 --- a/src/hooks/useEpochs.ts +++ b/src/hooks/useEpochs.ts @@ -8,7 +8,7 @@ const useEpochs = () => { const startEpoch = useGlobalState((state) => state.currentEpoch); const queryResults = useQuery({ - queryKey: ['epochs'], + queryKey: ['epochs', arIOReadSDK, startEpoch], queryFn: async () => { if (!arIOReadSDK || startEpoch === undefined) { throw new Error('arIOReadSDK or startEpoch not available'); From eef2a60c72e318bf5439ea80dd6f0d42086645d3 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Thu, 7 Nov 2024 15:04:21 -0500 Subject: [PATCH 21/36] fix: adjusted title text and fixed duplicate tIO --- src/pages/Staking/ConnectedLandingPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Staking/ConnectedLandingPage.tsx b/src/pages/Staking/ConnectedLandingPage.tsx index ff64ace..d9780b0 100644 --- a/src/pages/Staking/ConnectedLandingPage.tsx +++ b/src/pages/Staking/ConnectedLandingPage.tsx @@ -106,10 +106,10 @@ const ConnectedLandingPage = () => { : undefined, }, { - title: 'Rewards Earned', + title: 'Total Rewards Earned (Last 14 Epochs)', balance: rewardsEarned?.totalForPastAvailableEpochs !== undefined - ? `${formatWithCommas(rewardsEarned.totalForPastAvailableEpochs)} ${ticker}` + ? formatWithCommas(rewardsEarned.totalForPastAvailableEpochs) : undefined, leftTitle: 'LAST EPOCH', leftValue: From 3fb54102fd28faf21fc601033457298f537838fc Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Thu, 7 Nov 2024 15:41:42 -0500 Subject: [PATCH 22/36] fix: data did not show up correctly when refetched and app appeared hung on showing stakes --- src/pages/Staking/MyStakesTable.tsx | 58 ++++++++++++++--------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/src/pages/Staking/MyStakesTable.tsx b/src/pages/Staking/MyStakesTable.tsx index 6da3fa0..5a1dff4 100644 --- a/src/pages/Staking/MyStakesTable.tsx +++ b/src/pages/Staking/MyStakesTable.tsx @@ -64,8 +64,9 @@ const MyStakesTable = () => { const navigate = useNavigate(); useEffect(() => { - const activeStakes: Array = - !walletAddress || !gateways + const activeStakes: Array | undefined = isFetching + ? undefined + : !walletAddress || !gateways ? [] : Object.keys(gateways).reduce((acc, key) => { const gateway = gateways[key]; @@ -91,39 +92,34 @@ const MyStakesTable = () => { return acc; }, [] as Array); - const pendingWithdrawals: Array = - !walletAddress || !gateways - ? [] - : Object.keys(gateways).reduce((acc, key) => { - const gateway = gateways[key]; - const delegate = gateway.delegates[walletAddress?.toString()]; + const pendingWithdrawals: Array | undefined = + isFetching + ? undefined + : !walletAddress || !gateways + ? [] + : Object.keys(gateways).reduce((acc, key) => { + const gateway = gateways[key]; + const delegate = gateway.delegates[walletAddress?.toString()]; - if (delegate?.vaults) { - const withdrawals = Object.entries(delegate.vaults).map( - ([withdrawalId, withdrawal]) => { - return { - owner: key, - gateway, - withdrawal, - withdrawalId, - }; - }, - ); + if (delegate?.vaults) { + const withdrawals = Object.entries(delegate.vaults).map( + ([withdrawalId, withdrawal]) => { + return { + owner: key, + gateway, + withdrawal, + withdrawalId, + }; + }, + ); - return [...acc, ...withdrawals]; - } - return acc; - }, [] as Array); + return [...acc, ...withdrawals]; + } + return acc; + }, [] as Array); setActiveStakes(activeStakes); setPendingWithdrawals(pendingWithdrawals); - }, [gateways, walletAddress]); - - useEffect(() => { - if (isFetching) { - setActiveStakes(undefined); - setPendingWithdrawals(undefined); - } - }, [isFetching]); + }, [gateways, walletAddress, isFetching]); // Define columns for the active stakes table const activeStakesColumns: ColumnDef[] = [ From 6787bc91d3d9cc52b4419d1e20f4f66971cb4751 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 8 Nov 2024 09:00:39 -0600 Subject: [PATCH 23/36] feat(observe): use current prescribed names in obesre defaults --- src/hooks/usePrescribedNames.ts | 32 +++++++++++++++++++++++++++++ src/pages/Observe/ObserveHeader.tsx | 24 ++++++++++++++++------ 2 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 src/hooks/usePrescribedNames.ts diff --git a/src/hooks/usePrescribedNames.ts b/src/hooks/usePrescribedNames.ts new file mode 100644 index 0000000..477737d --- /dev/null +++ b/src/hooks/usePrescribedNames.ts @@ -0,0 +1,32 @@ +import { useGlobalState } from '@src/store'; +import { useQuery } from '@tanstack/react-query'; + +const DEFAULT_NAMES = ['dapp_ardrive', 'arns']; + +const usePrescribedNames = () => { + const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); + + const currentEpoch = useGlobalState((state) => state.currentEpoch); + + const queryResults = useQuery({ + queryKey: ['prescribedNames', currentEpoch?.epochIndex || -1], + queryFn: () => { + if (arIOReadSDK && currentEpoch) { + return arIOReadSDK.getPrescribedNames(currentEpoch).catch((e) => { + // log error + console.error('Failed to fetch prescribed names', { + message: e.message, + }); + // fallback to defaults + return DEFAULT_NAMES; + }); + } + // log error + throw new Error('arIOReadSDK or currentEpoch not available'); + }, + }); + + return queryResults; +}; + +export default usePrescribedNames; diff --git a/src/pages/Observe/ObserveHeader.tsx b/src/pages/Observe/ObserveHeader.tsx index 9ab2b55..bf8504c 100644 --- a/src/pages/Observe/ObserveHeader.tsx +++ b/src/pages/Observe/ObserveHeader.tsx @@ -8,10 +8,11 @@ import { HeaderSeparatorIcon, } from '@src/components/icons'; import { log } from '@src/constants'; +import usePrescribedNames from '@src/hooks/usePrescribedNames'; import db from '@src/store/db'; import { Assessment } from '@src/types'; import { performAssessment } from '@src/utils/observations'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Link, useParams } from 'react-router-dom'; const isSearchStringValid = (searchString: string) => { @@ -32,11 +33,17 @@ const ObserveHeader = ({ }) => { const params = useParams(); const ownerId = params?.ownerId; - - const [arnsNamesToSearch, setArnsNamesToSearch] = - useState('dapp_ardrive, arns'); + const [runningObservation, setRunningObservation] = useState(false); + // fetch current prescribed names, fallback to defaults + const { data: prescribedNames = [], isLoading: loadingPrescribedNames } = + usePrescribedNames(); + + const [arnsNamesToSearch, setArnsNamesToSearch] = useState( + prescribedNames.join(', '), + ); + const runObservation = async () => { setRunningObservation(true); const assessment = await performAssessment( @@ -61,6 +68,11 @@ const ObserveHeader = ({ setRunningObservation(false); }; + // update prescribed names after loading + useEffect(() => { + setArnsNamesToSearch(prescribedNames.join(',')); + }, [prescribedNames]); + return (
@@ -94,8 +106,8 @@ const ObserveHeader = ({ 'h-7 w-[12.5rem] rounded-md border border-grey-700 bg-grey-1000 p-3 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' } type="text" - disabled={!gateway} - readOnly={!gateway} + disabled={!gateway || loadingPrescribedNames} + readOnly={!gateway || loadingPrescribedNames} value={arnsNamesToSearch} onChange={(e) => { setArnsNamesToSearch(e.target.value); From 4a2573e2e7c39884d0156f4b2de17fc38c646bfd Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 8 Nov 2024 13:46:22 -0500 Subject: [PATCH 24/36] feat: implement Expedited WIthdrawal flow for delegates stakes --- package.json | 2 +- src/components/LabelValueRow.tsx | 37 +++ .../modals/InstantWithdrawalModal.tsx | 244 ++++++++++++++++++ src/components/modals/StakingModal.tsx | 69 ++--- src/components/modals/UnstakeWarning.tsx | 2 +- src/pages/Staking/ConnectedLandingPage.tsx | 5 +- src/pages/Staking/MyStakesTable.tsx | 35 ++- src/utils/stake.ts | 20 ++ yarn.lock | 8 +- 9 files changed, 358 insertions(+), 64 deletions(-) create mode 100644 src/components/LabelValueRow.tsx create mode 100644 src/components/modals/InstantWithdrawalModal.tsx create mode 100644 src/utils/stake.ts diff --git a/package.json b/package.json index 5413017..7ff8d72 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "deploy": "yarn build && permaweb-deploy --ant-process ${DEPLOY_ANT_PROCESS_ID}" }, "dependencies": { - "@ar.io/sdk": "2.3.2", + "@ar.io/sdk": "2.4.0-alpha.12", "@fontsource/rubik": "^5.0.19", "@headlessui/react": "^1.7.19", "@radix-ui/react-tooltip": "^1.0.7", diff --git a/src/components/LabelValueRow.tsx b/src/components/LabelValueRow.tsx new file mode 100644 index 0000000..9faa3f6 --- /dev/null +++ b/src/components/LabelValueRow.tsx @@ -0,0 +1,37 @@ +const LabelValueRow = ({ + label, + value, + className, + isLink = false, + rightIcon, +}: { + label: string; + value: string; + isLink?: boolean; + className?: string; + rightIcon?: React.ReactNode; +}) => { + return ( +
+
{label}
+
+ {isLink && value !== '-' ? ( + + {value} + + ) : ( +
+ {value} + {rightIcon} +
+ )} +
+ ); +}; + +export default LabelValueRow; diff --git a/src/components/modals/InstantWithdrawalModal.tsx b/src/components/modals/InstantWithdrawalModal.tsx new file mode 100644 index 0000000..4a116a1 --- /dev/null +++ b/src/components/modals/InstantWithdrawalModal.tsx @@ -0,0 +1,244 @@ +import { AoGateway, AoVaultData, mIOToken } from '@ar.io/sdk/web'; +import { WRITE_OPTIONS } from '@src/constants'; +import { useGlobalState } from '@src/store'; +import { formatAddress, formatDateTime, formatWithCommas } from '@src/utils'; +import { calculateInstantWithdrawalPenaltyRate } from '@src/utils/stake'; +import { showErrorToast } from '@src/utils/toast'; +import { useQueryClient } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import Button, { ButtonType } from '../Button'; +import LabelValueRow from '../LabelValueRow'; +import Tooltip from '../Tooltip'; +import { InfoIcon, LinkArrowIcon } from '../icons'; +import BaseModal from './BaseModal'; +import BlockingMessageModal from './BlockingMessageModal'; +import SuccessModal from './SuccessModal'; + +const InstantWithdrawalModal = ({ + gateway, + gatewayAddress, + vaultId, + vault, + onClose, +}: { + gateway: AoGateway; + gatewayAddress: string; + vaultId: string; + vault: AoVaultData; + onClose: () => void; +}) => { + const queryClient = useQueryClient(); + + const walletAddress = useGlobalState((state) => state.walletAddress); + const arIOWriteableSDK = useGlobalState((state) => state.arIOWriteableSDK); + const ticker = useGlobalState((state) => state.ticker); + + const [showBlockingMessageModal, setShowBlockingMessageModal] = + useState(false); + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [txid, setTxid] = useState(); + + const [confirmText, setConfirmText] = useState(''); + + const [calculatedFeeAndAmountReturning, setCalculatedFeeAndAmountReturning] = + useState<{ penaltyRate: number; fee: number; amountReturning: number }>(); + + useEffect(() => { + const penaltyRate = calculateInstantWithdrawalPenaltyRate( + vault, + new Date(), + ); + + const fee = Math.floor(penaltyRate * vault.balance); + const amountReturning = Math.round(vault.balance - fee); + + setCalculatedFeeAndAmountReturning({ + penaltyRate, + fee: new mIOToken(fee).toIO().valueOf(), + amountReturning: new mIOToken(amountReturning).toIO().valueOf(), + }); + }, [setCalculatedFeeAndAmountReturning, vault]); + + const termsAccepted = confirmText === 'WITHDRAW'; + + const processInstantWithdrawal = async () => { + if (walletAddress && arIOWriteableSDK) { + setShowBlockingMessageModal(true); + + try { + const { id: txID } = await arIOWriteableSDK.instantWithdrawal( + { gatewayAddress: gatewayAddress, vaultId: vaultId }, + WRITE_OPTIONS, + ); + setTxid(txID); + + queryClient.invalidateQueries({ + queryKey: ['gateways'], + refetchType: 'all', + }); + + setShowSuccessModal(true); + // onClose(); + } catch (e: any) { + showErrorToast(`${e}`); + } finally { + setShowBlockingMessageModal(false); + } + } + }; + + return ( + <> + +
+
+
Expedited Withdrawal
+
+ +
+ + + + +
+ +
+ {/*
+ +
Warning: Expedited Withdrawal Fee
+
*/} +
+ You are about to expedite your withdrawal, subject to a dynamic + fee. Please note: +
+
    +
  • + A fee of{' '} + {calculatedFeeAndAmountReturning + ? (calculatedFeeAndAmountReturning.penaltyRate * 100).toFixed( + 2, + ) + : ''} + % will be applied to your withdrawal based on the current time + remaining until your original return date. +
  • +
  • This action is irreversible once confirmed.
  • +
  • + Your staked tokens will return immediately to your wallet. +
  • +
+
+ +
+ + + +

+ Expedited withdrawal fee starts at 50% and declines + linearly to 10% over the withdrawal period. +

+
+ } + > + + + } + /> + +
+ +
+
+
+ Please type "WITHDRAW" in the text box to proceed. +
+ setConfirmText(e.target.value)} + className={ + 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-3 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' + } + value={confirmText} + /> +
+ +
+
} + className={`w-full ${!termsAccepted && 'pointer-events-none opacity-30'}`} + /> +
+
+
+ + {showBlockingMessageModal && ( + setShowBlockingMessageModal(false)} + message="Sign the following data with your wallet to proceed." + > + )} + {showSuccessModal && ( + { + setShowSuccessModal(false); + onClose(); + }} + title="Confirmed" + bodyText={ +
+
You have successfully canceled the withdrawal.
+
+
Transaction ID:
+ +
+
+ } + /> + )} + + ); +}; + +export default InstantWithdrawalModal; diff --git a/src/components/modals/StakingModal.tsx b/src/components/modals/StakingModal.tsx index a22fc90..3f4c9e9 100644 --- a/src/components/modals/StakingModal.tsx +++ b/src/components/modals/StakingModal.tsx @@ -1,4 +1,4 @@ -import { IOToken, mIOToken } from '@ar.io/sdk/web'; +import { AoGatewayDelegate, IOToken, mIOToken } from '@ar.io/sdk/web'; import { EAY_TOOLTIP_FORMULA, EAY_TOOLTIP_TEXT, @@ -14,6 +14,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { MathJax } from 'better-react-mathjax'; import { useState } from 'react'; import Button, { ButtonType } from '../Button'; +import LabelValueRow from '../LabelValueRow'; import Tooltip from '../Tooltip'; import ErrorMessageIcon from '../forms/ErrorMessageIcon'; import { @@ -27,42 +28,6 @@ import BlockingMessageModal from './BlockingMessageModal'; import SuccessModal from './SuccessModal'; import UnstakeWarning from './UnstakeWarning'; -const DisplayRow = ({ - label, - value, - className, - isLink = false, - rightIcon, -}: { - label: string; - value: string; - isLink?: boolean; - className?: string; - rightIcon?: React.ReactNode; -}) => { - return ( -
-
{label}
-
- {isLink && value !== '-' ? ( - - {value} - - ) : ( -
- {value} - {rightIcon} -
- )} -
- ); -}; - const StakingModal = ({ onClose, ownerWallet, @@ -99,8 +64,9 @@ const StakingModal = ({ const allowDelegatedStaking = gateway?.settings.allowDelegatedStaking ?? false; - const delegateData = walletAddress - ? gateway?.delegates[walletAddress?.toString()] + const delegateData: AoGatewayDelegate | undefined = walletAddress + ? // @ts-expect-error - delegates is currently available on the gateway + gateway?.delegates[walletAddress?.toString()] : undefined; const currentStake = new mIOToken(delegateData?.delegatedStake ?? 0) .toIO() @@ -110,7 +76,8 @@ const StakingModal = ({ tab == 0 ? currentStake + parseFloat(amountToStake) : currentStake - parseFloat(amountToUnstake); - const newStake = tab == 0 ? parseFloat(amountToStake) : -parseFloat(amountToUnstake); + const newStake = + tab == 0 ? parseFloat(amountToStake) : -parseFloat(amountToUnstake); const rewardsInfo = useRewardsInfo(gateway, newStake); const EAY = rewardsInfo && newTotalStake > 0 @@ -253,9 +220,7 @@ const StakingModal = ({
Gateway Owner:
{ownerWallet ? ( -
- {ownerWallet} -
+
{ownerWallet}
) : ( )} {tab == 1 && @@ -322,7 +287,7 @@ const StakingModal = ({ errorMessages.unstakeAmount && ( )}
); }; diff --git a/src/utils/stake.ts b/src/utils/stake.ts new file mode 100644 index 0000000..eda83b5 --- /dev/null +++ b/src/utils/stake.ts @@ -0,0 +1,20 @@ +import { AoVaultData } from '@ar.io/sdk/web'; + +const MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE = 0.5; +const MIN_EXPEDITED_WITHDRAWAL_PENALTY_RATE = 0.1; + +export const calculateInstantWithdrawalPenaltyRate = ( + vault: AoVaultData, + date: Date, +) => { + const elapsedTimeMs = Math.max(0, date.getTime() - vault.startTimestamp); + const totalWithdrawalTimeMs = vault.endTimestamp - vault.startTimestamp; + + const penaltyRate = + MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE - + (MAX_EXPEDITED_WITHDRAWAL_PENALTY_RATE - + MIN_EXPEDITED_WITHDRAWAL_PENALTY_RATE) * + (elapsedTimeMs / totalWithdrawalTimeMs); + + return penaltyRate; +}; diff --git a/yarn.lock b/yarn.lock index 96fb098..15a610b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,10 +35,10 @@ plimit-lit "^3.0.1" warp-contracts "1.4.45" -"@ar.io/sdk@2.3.2": - version "2.3.2" - resolved "https://registry.npmjs.org/@ar.io/sdk/-/sdk-2.3.2.tgz" - integrity sha512-O1BX951DzwRB3/9hc8O8PulxE84qe6wSN3ADqlJT4W0k9RcWLN/rbGMdSPaoN8dMgnxwtnIkXkHw6CG9Fu+V3g== +"@ar.io/sdk@2.4.0-alpha.12": + version "2.4.0-alpha.12" + resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.4.0-alpha.12.tgz#bdf956bc863038cc7e26c58fd82fa99f2559d593" + integrity sha512-UTZ02I7fUkSvFrS4eTqJJNrhgywjygU2ImQRyKOJpR9DEHM7nsEohQzxHKbELl4LE+mnFubmG+3qe2gUNm7UYA== dependencies: "@dha-team/arbundles" "^1.0.1" "@permaweb/aoconnect" "^0.0.57" From a30bb7a8b3a46258abee3be6a4653354ef67a2f9 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 8 Nov 2024 14:42:42 -0500 Subject: [PATCH 25/36] chore: remove balances from global state and move to hook so that it can be easily invalidated --- CHANGELOG.md | 2 + src/components/Profile.tsx | 8 ++-- src/components/WalletProvider.tsx | 33 +--------------- .../modals/CancelWithdrawalModal.tsx | 4 +- .../modals/InstantWithdrawalModal.tsx | 4 ++ src/components/modals/StakingModal.tsx | 23 ++++++----- src/constants.ts | 3 ++ src/hooks/useBalances.ts | 39 +++++++++++++++++++ src/hooks/useGateways.ts | 2 +- src/pages/Gateway/index.tsx | 7 +++- src/pages/Staking/ConnectedLandingPage.tsx | 5 ++- src/store/index.ts | 13 ------- 12 files changed, 79 insertions(+), 64 deletions(-) create mode 100644 src/hooks/useBalances.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aeadc8..2de025d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* View Pending Withdrawals on Staking page and support cancelling pending withdrawals as well as performing expedited withdrawals. * View Changelog in app by clicking version number in sidebar ### Changed * Updated header style of cards * Observations: Updated to use arweave.net for reference domain when generating observation report +* Observe: Default to using prescribed names ## [1.3.0] - 2024-10-21 diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index 5af7412..69d31ce 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -14,6 +14,8 @@ import { WalletIcon, } from './icons'; import ConnectModal from './modals/ConnectModal'; +import useBalances from '@src/hooks/useBalances'; +import Placeholder from './Placeholder'; // eslint-disable-next-line react/display-name const CustomPopoverButton = forwardRef((props, ref) => { @@ -36,9 +38,9 @@ const Profile = () => { ); const wallet = useGlobalState((state) => state.wallet); - const balances = useGlobalState((state) => state.balances); const updateWallet = useGlobalState((state) => state.updateWallet); const walletAddress = useGlobalState((state) => state.walletAddress); + const { data:balances } = useBalances(walletAddress); const ticker = useGlobalState((state) => state.ticker); return walletAddress ? ( @@ -73,11 +75,11 @@ const Profile = () => {
{ticker} Balance
- {formatBalance(balances.io)} + {balances ? formatBalance(balances.io) : }
AR Balance
- {formatBalance(balances.ar)} + {balances ? formatBalance(balances.ar) : }
diff --git a/src/components/WalletProvider.tsx b/src/components/WalletProvider.tsx index 56a3912..0335dc9 100644 --- a/src/components/WalletProvider.tsx +++ b/src/components/WalletProvider.tsx @@ -1,4 +1,4 @@ -import { AOProcess, IO, mIOToken } from '@ar.io/sdk/web'; +import { AOProcess, IO } from '@ar.io/sdk/web'; import { connect } from '@permaweb/aoconnect'; import { AO_CU_URL, IO_PROCESS_ID } from '@src/constants'; import { useEffectOnce } from '@src/hooks/useEffectOnce'; @@ -6,25 +6,16 @@ import { ArConnectWalletConnector } from '@src/services/wallets/ArConnectWalletC import { useGlobalState } from '@src/store'; import { KEY_WALLET_TYPE } from '@src/store/persistent'; import { WALLET_TYPES } from '@src/types'; -import { ArweaveTransactionID } from '@src/utils/ArweaveTransactionId'; import { showErrorToast } from '@src/utils/toast'; -import Ar from 'arweave/web/ar'; import { ReactElement, useEffect } from 'react'; -const AR = new Ar(); - const WalletProvider = ({ children }: { children: ReactElement }) => { - const blockHeight = useGlobalState((state) => state.blockHeight); - const walletAddress = useGlobalState((state) => state.walletAddress); const setWalletStateInitialized = useGlobalState( (state) => state.setWalletStateInitialized, ); const wallet = useGlobalState((state) => state.wallet); const updateWallet = useGlobalState((state) => state.updateWallet); - const setBalances = useGlobalState((state) => state.setBalances); - const arweave = useGlobalState((state) => state.arweave); - const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); const setArIOWriteableSDK = useGlobalState( (state) => state.setArIOWriteableSDK, ); @@ -43,28 +34,6 @@ const WalletProvider = ({ children }: { children: ReactElement }) => { }, 5000); }); - useEffect(() => { - if (walletAddress) { - const updateBalances = async (address: ArweaveTransactionID) => { - try { - const [mioBalance, winstonBalance] = await Promise.all([ - arIOReadSDK.getBalance({ address: address.toString() }), - arweave.wallets.getBalance(address.toString()), - ]); - - const arBalance = +AR.winstonToAr(winstonBalance); - const ioBalance = new mIOToken(mioBalance).toIO().valueOf(); - - setBalances(arBalance, ioBalance); - } catch (error) { - showErrorToast(`${error}`); - } - }; - - updateBalances(walletAddress); - } - }, [walletAddress, blockHeight, arIOReadSDK, arweave, setBalances]); - useEffect(() => { if (wallet) { const signer = wallet.signer; diff --git a/src/components/modals/CancelWithdrawalModal.tsx b/src/components/modals/CancelWithdrawalModal.tsx index 0f45350..60d8e9e 100644 --- a/src/components/modals/CancelWithdrawalModal.tsx +++ b/src/components/modals/CancelWithdrawalModal.tsx @@ -37,8 +37,8 @@ const CancelWithdrawalModal = ({ setShowBlockingMessageModal(true); try { - const { id: txID } = await arIOWriteableSDK.cancelDelegateWithdrawal( - { address: gatewayAddress, vaultId: vaultId }, + const { id: txID } = await arIOWriteableSDK.cancelWithdrawal( + { gatewayAddress: gatewayAddress, vaultId: vaultId }, WRITE_OPTIONS, ); setTxid(txID); diff --git a/src/components/modals/InstantWithdrawalModal.tsx b/src/components/modals/InstantWithdrawalModal.tsx index 4a116a1..5c52c48 100644 --- a/src/components/modals/InstantWithdrawalModal.tsx +++ b/src/components/modals/InstantWithdrawalModal.tsx @@ -76,6 +76,10 @@ const InstantWithdrawalModal = ({ queryKey: ['gateways'], refetchType: 'all', }); + queryClient.invalidateQueries({ + queryKey: ['balances'], + refetchType: 'all', + }); setShowSuccessModal(true); // onClose(); diff --git a/src/components/modals/StakingModal.tsx b/src/components/modals/StakingModal.tsx index 3f4c9e9..ae75d84 100644 --- a/src/components/modals/StakingModal.tsx +++ b/src/components/modals/StakingModal.tsx @@ -5,6 +5,7 @@ import { WRITE_OPTIONS, log, } from '@src/constants'; +import useBalances from '@src/hooks/useBalances'; import useGateway from '@src/hooks/useGateway'; import useRewardsInfo from '@src/hooks/useRewardsInfo'; import { useGlobalState } from '@src/store'; @@ -38,8 +39,8 @@ const StakingModal = ({ }) => { const queryClient = useQueryClient(); - const balances = useGlobalState((state) => state.balances); const walletAddress = useGlobalState((state) => state.walletAddress); + const { data: balances } = useBalances(walletAddress); const arIOWriteableSDK = useGlobalState((state) => state.arIOWriteableSDK); const ticker = useGlobalState((state) => state.ticker); @@ -100,7 +101,7 @@ const StakingModal = ({ 'Stake Amount', ticker, minRequiredStakeToAdd, - balances.io, + balances?.io, ), unstakeAmount: validateUnstakeAmount( 'Unstake Amount', @@ -121,9 +122,8 @@ const StakingModal = ({ } }; - const remainingBalance = isFormValid() - ? balances.io - parseFloat(amountToStake) - : '-'; + const remainingBalance = + isFormValid() && balances ? balances.io - parseFloat(amountToStake) : '-'; const baseTabClassName = 'text-center py-3'; const selectedTabClassNames = `${baseTabClassName} bg-grey-700 border-b border-red-400`; @@ -131,7 +131,7 @@ const StakingModal = ({ const setMaxAmount = () => { if (tab == 0) { - setAmountToStake(balances.io + ''); + setAmountToStake((balances?.io || 0) + ''); } else { setAmountToUnstake(currentStake + ''); } @@ -140,7 +140,8 @@ const StakingModal = ({ const disableInput = !gateway || (tab == 0 && - (balances.io < minRequiredStakeToAdd || !allowDelegatedStaking)) || + ((balances?.io || 0) < minRequiredStakeToAdd || + !allowDelegatedStaking)) || (tab == 1 && currentStake <= 0); const submitForm = async () => { @@ -178,6 +179,10 @@ const StakingModal = ({ queryKey: ['gateways'], refetchType: 'all', }); + queryClient.invalidateQueries({ + queryKey: ['balances'], + refetchType: 'all', + }); setShowSuccessModal(true); } catch (e: any) { @@ -193,7 +198,7 @@ const StakingModal = ({ stakeAmount: validators.stakeAmount(amountToStake), unstakeAmount: validators.unstakeAmount(amountToUnstake), cannotStake: - balances.io < minRequiredStakeToAdd + (balances?.io || 0) < minRequiredStakeToAdd ? `Insufficient balance, at least ${minRequiredStakeToAdd} IO required.` : !allowDelegatedStaking ? 'Gateway does not allow delegated staking.' @@ -272,7 +277,7 @@ const StakingModal = ({ {tab == 0 && gateway && (amountToStake?.length > 0 || - balances.io < minRequiredStakeToAdd || + (balances?.io || 0) < minRequiredStakeToAdd || !allowDelegatedStaking) && (errorMessages.cannotStake || errorMessages.stakeAmount) && ( { + const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); + const arweave = useGlobalState((state) => state.arweave); + const blockHeight = useGlobalState((state) => state.blockHeight); + + const res = useQuery({ + queryKey: ['balances', arIOReadSDK, arweave, blockHeight, walletAddress], + queryFn: async () => { + if (!walletAddress || !arweave || !arIOReadSDK) { + throw new Error( + 'Error: Wallet Address, arweave, or arIOReadSDK is not initialized', + ); + } + + const [mioBalance, winstonBalance] = await Promise.all([ + arIOReadSDK.getBalance({ address: walletAddress.toString() }), + arweave.wallets.getBalance(walletAddress.toString()), + ]); + + const arBalance = +AR.winstonToAr(winstonBalance); + const ioBalance = new mIOToken(mioBalance).toIO().valueOf(); + + return { ar: arBalance, io: ioBalance }; + }, + staleTime: 5 * 60 * 1000, + }); + + return res; +}; + +export default useBalances; diff --git a/src/hooks/useGateways.ts b/src/hooks/useGateways.ts index 6bd5fbf..42722b2 100644 --- a/src/hooks/useGateways.ts +++ b/src/hooks/useGateways.ts @@ -23,7 +23,7 @@ const useGateways = () => { }; const queryResults = useQuery({ - queryKey: ['gateways'], + queryKey: ['gateways', arIOReadSDK], queryFn: () => { if (arIOReadSDK) { return fetchAllGateways(arIOReadSDK); diff --git a/src/pages/Gateway/index.tsx b/src/pages/Gateway/index.tsx index f29ec61..1228c84 100644 --- a/src/pages/Gateway/index.tsx +++ b/src/pages/Gateway/index.tsx @@ -29,6 +29,7 @@ import { WRITE_OPTIONS, log, } from '@src/constants'; +import useBalances from '@src/hooks/useBalances'; import useGateway from '@src/hooks/useGateway'; import useGateways from '@src/hooks/useGateways'; import useHealthcheck from '@src/hooks/useHealthCheck'; @@ -61,10 +62,10 @@ const Gateway = () => { const walletAddress = useGlobalState((state) => state.walletAddress); const arIOWriteableSDK = useGlobalState((state) => state.arIOWriteableSDK); - const balances = useGlobalState((state) => state.balances); const ticker = useGlobalState((state) => state.ticker); const { data: protocolBalance } = useProtocolBalance(); const { data: gateways } = useGateways(); + const { data: balances } = useBalances(walletAddress); const params = useParams(); @@ -427,8 +428,10 @@ const Gateway = () => { diff --git a/src/pages/Staking/ConnectedLandingPage.tsx b/src/pages/Staking/ConnectedLandingPage.tsx index ed33bd5..8b8e247 100644 --- a/src/pages/Staking/ConnectedLandingPage.tsx +++ b/src/pages/Staking/ConnectedLandingPage.tsx @@ -8,6 +8,7 @@ import { formatWithCommas } from '@src/utils'; import { useEffect, useState } from 'react'; import DelegateStake from './DelegateStakeTable'; import MyStakesTable from './MyStakesTable'; +import useBalances from '@src/hooks/useBalances'; const TopPanel = ({ title, @@ -73,7 +74,7 @@ const ConnectedLandingPage = () => { const [isStakingModalOpen, setIsStakingModalOpen] = useState(false); const { data: gateways } = useGateways(); - const balances = useGlobalState((state) => state.balances); + const { data: balances } = useBalances(walletAddress); const rewardsEarned = useRewardsEarned(walletAddress?.toString()); useEffect(() => { @@ -97,7 +98,7 @@ const ConnectedLandingPage = () => { const topPanels = [ { title: 'Your Balance', - balance: formatWithCommas(balances.io), + balance: balances ? formatWithCommas(balances.io) : undefined, }, { title: 'Amount Staking + Pending Withdrawals', diff --git a/src/store/index.ts b/src/store/index.ts index b1629ab..ae2aebe 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -30,10 +30,6 @@ export type GlobalState = { currentEpoch?: AoEpochData; walletAddress?: ArweaveTransactionID; wallet?: ArweaveWalletConnector; - balances: { - ar: number; - io: number; - }; walletStateInitialized: boolean; ticker: string; aoCongested: boolean; @@ -48,7 +44,6 @@ export type GlobalStateActions = { wallet?: ArweaveWalletConnector, ) => void; setArIOWriteableSDK: (arIOWriteableSDK?: AoIOWrite) => void; - setBalances(ar: number, io: number): void; setWalletStateInitialized: (initialized: boolean) => void; setTicker: (ticker: string) => void; setAoCongested: (congested: boolean) => void; @@ -69,10 +64,6 @@ export const initialGlobalState: GlobalState = { }), }), }), - balances: { - ar: 0, - io: 0, - }, walletStateInitialized: false, ticker: 'tIO', aoCongested: false, @@ -107,10 +98,6 @@ export class GlobalStateActionBase implements GlobalStateActions { this.set({ arIOWriteableSDK }); }; - setBalances = (ar: number, io: number) => { - this.set({ balances: { ar, io } }); - }; - setWalletStateInitialized = (initialized: boolean) => { this.set({ walletStateInitialized: initialized }); }; From 0423a0efdf45dfca36bdc74a6d20e5ff8edb9268 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 8 Nov 2024 14:49:34 -0500 Subject: [PATCH 26/36] fix: update typescript as earlier version was reporting false errors --- .gitignore | 2 ++ package.json | 2 +- yarn.lock | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7d8d0b5..49639eb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ dist-id.txt dist-manifest.csv dist-manifest.json package-lock.json +*.tsbuildinfo + diff --git a/package.json b/package.json index 7ff8d72..7927799 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "tailwindcss-animate": "^1.0.7", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", - "typescript": "^5.2.2", + "typescript": "^5.6.3", "vite": "^5.1.0", "vite-bundle-visualizer": "^1.0.1", "vite-plugin-node-polyfills": "^0.21.0", diff --git a/yarn.lock b/yarn.lock index 15a610b..92a3481 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10827,11 +10827,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -"typescript@^4.6.4 || ^5.2.2", typescript@^5.2.2: +"typescript@^4.6.4 || ^5.2.2": version "5.4.5" resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +typescript@^5.6.3: + version "5.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" + integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" From 490273ff313934b2a4f5c479f62ce01037bb33f9 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 8 Nov 2024 15:33:56 -0500 Subject: [PATCH 27/36] fix: update imports to use @ar.io/sdk/web --- src/hooks/useGateways.ts | 2 +- src/hooks/useReports.ts | 2 +- src/hooks/useRewardsEarned.ts | 2 +- src/main.tsx | 2 +- src/pages/Dashboard/IOTokenDistributionPanel.tsx | 2 +- src/pages/Dashboard/RewardsDistributionPanel.tsx | 2 +- src/pages/Reports/ReportsTable.tsx | 2 +- src/store/db.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/hooks/useGateways.ts b/src/hooks/useGateways.ts index 42722b2..fcde5be 100644 --- a/src/hooks/useGateways.ts +++ b/src/hooks/useGateways.ts @@ -1,4 +1,4 @@ -import { AoGateway, AoIORead } from '@ar.io/sdk'; +import { AoGateway, AoIORead } from '@ar.io/sdk/web'; import { useGlobalState } from '@src/store'; import { useQuery } from '@tanstack/react-query'; diff --git a/src/hooks/useReports.ts b/src/hooks/useReports.ts index 5271914..29e98f7 100644 --- a/src/hooks/useReports.ts +++ b/src/hooks/useReports.ts @@ -1,4 +1,4 @@ -import { AoGateway } from '@ar.io/sdk'; +import { AoGateway } from '@ar.io/sdk/web'; import { DEFAULT_ARWEAVE_HOST } from '@src/constants'; import { useGlobalState } from '@src/store'; import { useQuery } from '@tanstack/react-query'; diff --git a/src/hooks/useRewardsEarned.ts b/src/hooks/useRewardsEarned.ts index 3be2ec4..83ecdb6 100644 --- a/src/hooks/useRewardsEarned.ts +++ b/src/hooks/useRewardsEarned.ts @@ -1,4 +1,4 @@ -import { mIOToken } from '@ar.io/sdk'; +import { mIOToken } from '@ar.io/sdk/web'; import { useEffect, useState } from 'react'; import useEpochs from './useEpochs'; diff --git a/src/main.tsx b/src/main.tsx index 46d86e9..9fe1985 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -5,7 +5,7 @@ import App from './App.tsx'; import './index.css'; // setup sentry import './services/sentry.ts'; -import { Logger } from '@ar.io/sdk'; +import { Logger } from '@ar.io/sdk/web'; Logger.default.setLogLevel('none'); diff --git a/src/pages/Dashboard/IOTokenDistributionPanel.tsx b/src/pages/Dashboard/IOTokenDistributionPanel.tsx index d46fb88..1efce07 100644 --- a/src/pages/Dashboard/IOTokenDistributionPanel.tsx +++ b/src/pages/Dashboard/IOTokenDistributionPanel.tsx @@ -1,4 +1,4 @@ -import { AoTokenSupplyData, mIOToken } from '@ar.io/sdk'; +import { AoTokenSupplyData, mIOToken } from '@ar.io/sdk/web'; import Placeholder from '@src/components/Placeholder'; import useTokenSupply from '@src/hooks/useTokenSupply'; import { useGlobalState } from '@src/store'; diff --git a/src/pages/Dashboard/RewardsDistributionPanel.tsx b/src/pages/Dashboard/RewardsDistributionPanel.tsx index 893ea9b..37675ed 100644 --- a/src/pages/Dashboard/RewardsDistributionPanel.tsx +++ b/src/pages/Dashboard/RewardsDistributionPanel.tsx @@ -1,4 +1,4 @@ -import { mIOToken } from '@ar.io/sdk'; +import { mIOToken } from '@ar.io/sdk/web'; import Placeholder from '@src/components/Placeholder'; import useEpochs from '@src/hooks/useEpochs'; import { useGlobalState } from '@src/store'; diff --git a/src/pages/Reports/ReportsTable.tsx b/src/pages/Reports/ReportsTable.tsx index a86e682..54fa925 100644 --- a/src/pages/Reports/ReportsTable.tsx +++ b/src/pages/Reports/ReportsTable.tsx @@ -1,4 +1,4 @@ -import { AoGateway } from '@ar.io/sdk'; +import { AoGateway } from '@ar.io/sdk/web'; import TableView from '@src/components/TableView'; import useReports, { ReportTransactionData } from '@src/hooks/useReports'; import { formatDateTime } from '@src/utils'; diff --git a/src/store/db.ts b/src/store/db.ts index 975ddfd..392920c 100644 --- a/src/store/db.ts +++ b/src/store/db.ts @@ -1,4 +1,4 @@ -import { AoEpochData, AoIORead } from '@ar.io/sdk'; +import { AoEpochData, AoIORead } from '@ar.io/sdk/web'; import { IO_PROCESS_ID } from '@src/constants'; import { Assessment } from '@src/types'; import Dexie, { type EntityTable } from 'dexie'; From 1ead0032032dbacabae83e0147f8a9fe174fb69e Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 8 Nov 2024 15:52:50 -0500 Subject: [PATCH 28/36] fix: switch to babel-jest for transform to handle import issue for tests --- jest.config.json | 2 +- package.json | 2 +- yarn.lock | 66 +++++++++++++++++++++++++++++++++++------------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/jest.config.json b/jest.config.json index 08982ef..89d79d9 100644 --- a/jest.config.json +++ b/jest.config.json @@ -10,7 +10,7 @@ "^@src/(.*)$": "/src/$1" }, "transform": { - "^.+\\.(ts|tsx|js|jsx|mjs)$": ["ts-jest", { "useESM": true }] + "^.+\\.(ts|tsx|js|jsx|mjs)$": ["babel-jest"] }, "transformIgnorePatterns": [ "/node_modules/(?!arbundles|arweave-wallet-connector|@permaweb|@dha-team|@ar.io).+\\.js$" diff --git a/package.json b/package.json index 7927799..c921691 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "tailwind-scrollbar": "^3.1.0", "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.7", - "ts-jest": "^29.1.2", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "^5.6.3", "vite": "^5.1.0", diff --git a/yarn.lock b/yarn.lock index 92a3481..5e5bf71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4573,9 +4573,9 @@ browserslist@^4.22.2, browserslist@^4.23.0: node-releases "^2.0.14" update-browserslist-db "^1.0.13" -bs-logger@0.x: +bs-logger@^0.2.6: version "0.2.6" - resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" @@ -4755,7 +4755,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -5635,6 +5635,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + electron-to-chromium@^1.4.668: version "1.4.777" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.777.tgz" @@ -6335,6 +6342,13 @@ file-uri-to-path@1.0.0: resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" @@ -7428,6 +7442,16 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + jayson@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/jayson/-/jayson-4.1.1.tgz" @@ -8200,9 +8224,9 @@ lodash.kebabcase@^4.1.1: resolved "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz" integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== -lodash.memoize@4.x: +lodash.memoize@^4.1.2: version "4.1.2" - resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.6.2: @@ -8359,7 +8383,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -8521,7 +8545,7 @@ minimatch@9.0.3: dependencies: brace-expansion "^2.0.1" -minimatch@^5.1.0: +minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -10007,6 +10031,11 @@ semver@^7.3.4, semver@^7.5.3, semver@^7.5.4: resolved "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== +semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" @@ -10676,19 +10705,20 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -ts-jest@^29.1.2: - version "29.1.3" - resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.3.tgz" - integrity sha512-6L9qz3ginTd1NKhOxmkP0qU3FyKjj5CPoY+anszfVn6Pmv/RIKzhiMCsH7Yb7UvJR9I2A64rm4zQl531s2F1iw== +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" jest-util "^29.0.0" json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "^7.5.3" - yargs-parser "^21.0.1" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" ts-node@^10.8.1, ts-node@^10.9.2: version "10.9.2" @@ -11444,7 +11474,7 @@ yargs-parser@^20.2.3: resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.0.1, yargs-parser@^21.1.1: +yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== From 6b1c5a8102c7d4f9e4f8c63dcf3687e74da720a9 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Sat, 9 Nov 2024 09:15:17 -0600 Subject: [PATCH 29/36] fix(report): link to the report for the epoch --- src/pages/Gateway/SnitchRow.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/Gateway/SnitchRow.tsx b/src/pages/Gateway/SnitchRow.tsx index 802e1e7..ab3d85f 100644 --- a/src/pages/Gateway/SnitchRow.tsx +++ b/src/pages/Gateway/SnitchRow.tsx @@ -75,12 +75,14 @@ const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { >
- {observerToGatewayMap ? ( - + {observerToGatewayMap && epochs ? ( + {observer} ) : ( - observer + )}
From ea2d68309cc5fb639c1e3cb7d53c205e9743f526 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 11 Nov 2024 11:00:42 -0500 Subject: [PATCH 30/36] chore: tidying unused code --- src/components/modals/CancelWithdrawalModal.tsx | 1 - src/components/modals/InstantWithdrawalModal.tsx | 1 - src/components/modals/LeaveNetworkModal.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/src/components/modals/CancelWithdrawalModal.tsx b/src/components/modals/CancelWithdrawalModal.tsx index 60d8e9e..49da4c7 100644 --- a/src/components/modals/CancelWithdrawalModal.tsx +++ b/src/components/modals/CancelWithdrawalModal.tsx @@ -49,7 +49,6 @@ const CancelWithdrawalModal = ({ }); setShowSuccessModal(true); - // onClose(); } catch (e: any) { showErrorToast(`${e}`); } finally { diff --git a/src/components/modals/InstantWithdrawalModal.tsx b/src/components/modals/InstantWithdrawalModal.tsx index 5c52c48..cdad294 100644 --- a/src/components/modals/InstantWithdrawalModal.tsx +++ b/src/components/modals/InstantWithdrawalModal.tsx @@ -82,7 +82,6 @@ const InstantWithdrawalModal = ({ }); setShowSuccessModal(true); - // onClose(); } catch (e: any) { showErrorToast(`${e}`); } finally { diff --git a/src/components/modals/LeaveNetworkModal.tsx b/src/components/modals/LeaveNetworkModal.tsx index 5bc4dcf..5605a9e 100644 --- a/src/components/modals/LeaveNetworkModal.tsx +++ b/src/components/modals/LeaveNetworkModal.tsx @@ -47,7 +47,6 @@ const LeaveNetworkModal = ({ onClose }: { onClose: () => void }) => { }); setShowSuccessModal(true); - // onClose(); } catch (e: any) { showErrorToast(`${e}`); } finally { From 86cafec12d214a48c138515a8c2ed727f7f44e41 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 11 Nov 2024 11:47:21 -0500 Subject: [PATCH 31/36] chore: add comment to investigate link/button issue --- src/components/modals/CancelWithdrawalModal.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/modals/CancelWithdrawalModal.tsx b/src/components/modals/CancelWithdrawalModal.tsx index 49da4c7..4e05ebf 100644 --- a/src/components/modals/CancelWithdrawalModal.tsx +++ b/src/components/modals/CancelWithdrawalModal.tsx @@ -1,3 +1,4 @@ +import { WRITE_OPTIONS } from '@src/constants'; import { useGlobalState } from '@src/store'; import { showErrorToast } from '@src/utils/toast'; import { useQueryClient } from '@tanstack/react-query'; @@ -7,14 +8,13 @@ import { LinkArrowIcon } from '../icons'; import BaseModal from './BaseModal'; import BlockingMessageModal from './BlockingMessageModal'; import SuccessModal from './SuccessModal'; -import { WRITE_OPTIONS } from '@src/constants'; const CancelWithdrawalModal = ({ gatewayAddress, vaultId, onClose, }: { - gatewayAddress:string; + gatewayAddress: string; vaultId: string; onClose: () => void; }) => { @@ -112,6 +112,7 @@ const CancelWithdrawalModal = ({ onClose(); }} title="Confirmed" + // FIXME: This uses a button as using a standard tag does not work. Needs further investigation. bodyText={
You have successfully canceled the withdrawal.
From 2da372ef0a21185009edf2d0b0e09a40959ecb96 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 11 Nov 2024 11:48:37 -0500 Subject: [PATCH 32/36] chore: remove unused code --- src/components/modals/InstantWithdrawalModal.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/modals/InstantWithdrawalModal.tsx b/src/components/modals/InstantWithdrawalModal.tsx index cdad294..fe30dc2 100644 --- a/src/components/modals/InstantWithdrawalModal.tsx +++ b/src/components/modals/InstantWithdrawalModal.tsx @@ -116,10 +116,6 @@ const InstantWithdrawalModal = ({
- {/*
- -
Warning: Expedited Withdrawal Fee
-
*/}
You are about to expedite your withdrawal, subject to a dynamic fee. Please note: From aa51f5939b6ef0c7b0416f288be824d0023f16a6 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 11 Nov 2024 14:16:57 -0500 Subject: [PATCH 33/36] fix: protect for distributions without walletAddress found leading to undefined results --- src/hooks/useRewardsEarned.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hooks/useRewardsEarned.ts b/src/hooks/useRewardsEarned.ts index 83ecdb6..addf2da 100644 --- a/src/hooks/useRewardsEarned.ts +++ b/src/hooks/useRewardsEarned.ts @@ -18,13 +18,14 @@ const useRewardsEarned = (walletAddress?: string) => { const previousEpochDistributed = previousEpoch.distributions.rewards.distributed; const previousEpochRewards = previousEpochDistributed - ? previousEpochDistributed[walletAddress] + ? previousEpochDistributed[walletAddress] || 0 : 0; const totalForPastAvailableEpochs = epochs.reduce((acc, epoch) => { const distributed = epoch.distributions.rewards.distributed; - return acc + (distributed ? distributed[walletAddress] : 0); + return acc + (distributed ? distributed[walletAddress] || 0 : 0); }, 0); + setRewardsEarned({ previousEpoch: new mIOToken(previousEpochRewards).toIO().valueOf(), totalForPastAvailableEpochs: new mIOToken(totalForPastAvailableEpochs) From 7c2d0245f5ff46a8832a8f01d4ce42da83f7b3fb Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 12 Nov 2024 13:38:14 -0500 Subject: [PATCH 34/36] chore: update to alpha.17 of SDK and remove ts-expect-error's that were used to bypass temporary SDK/process differences --- package.json | 2 +- src/components/modals/StakingModal.tsx | 3 +-- src/pages/Gateway/index.tsx | 4 +--- src/pages/Staking/ConnectedLandingPage.tsx | 1 - src/pages/Staking/MyStakesTable.tsx | 17 +++++++++++------ yarn.lock | 8 ++++---- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index c921691..8ca706d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "deploy": "yarn build && permaweb-deploy --ant-process ${DEPLOY_ANT_PROCESS_ID}" }, "dependencies": { - "@ar.io/sdk": "2.4.0-alpha.12", + "@ar.io/sdk": "2.4.0-alpha.16", "@fontsource/rubik": "^5.0.19", "@headlessui/react": "^1.7.19", "@radix-ui/react-tooltip": "^1.0.7", diff --git a/src/components/modals/StakingModal.tsx b/src/components/modals/StakingModal.tsx index ae75d84..5dc3889 100644 --- a/src/components/modals/StakingModal.tsx +++ b/src/components/modals/StakingModal.tsx @@ -66,8 +66,7 @@ const StakingModal = ({ gateway?.settings.allowDelegatedStaking ?? false; const delegateData: AoGatewayDelegate | undefined = walletAddress - ? // @ts-expect-error - delegates is currently available on the gateway - gateway?.delegates[walletAddress?.toString()] + ? gateway?.delegates[walletAddress?.toString()] : undefined; const currentStake = new mIOToken(delegateData?.delegatedStake ?? 0) .toIO() diff --git a/src/pages/Gateway/index.tsx b/src/pages/Gateway/index.tsx index 1228c84..b5a9bac 100644 --- a/src/pages/Gateway/index.tsx +++ b/src/pages/Gateway/index.tsx @@ -428,10 +428,8 @@ const Gateway = () => { diff --git a/src/pages/Staking/ConnectedLandingPage.tsx b/src/pages/Staking/ConnectedLandingPage.tsx index 8b8e247..3393025 100644 --- a/src/pages/Staking/ConnectedLandingPage.tsx +++ b/src/pages/Staking/ConnectedLandingPage.tsx @@ -80,7 +80,6 @@ const ConnectedLandingPage = () => { useEffect(() => { if (gateways && walletAddress) { const amountStaking = Object.values(gateways).reduce((acc, gateway) => { - // @ts-expect-error - delegates is currently available on the gateway const userDelegate:AoGatewayDelegate = gateway.delegates[walletAddress.toString()]; const delegatedStake = userDelegate?.delegatedStake ?? 0; const withdrawn = userDelegate?.vaults diff --git a/src/pages/Staking/MyStakesTable.tsx b/src/pages/Staking/MyStakesTable.tsx index b056d02..2181b43 100644 --- a/src/pages/Staking/MyStakesTable.tsx +++ b/src/pages/Staking/MyStakesTable.tsx @@ -1,4 +1,9 @@ -import { AoGateway, AoGatewayDelegate, AoVaultData, mIOToken } from '@ar.io/sdk/web'; +import { + AoGateway, + AoGatewayDelegate, + AoVaultData, + mIOToken, +} from '@ar.io/sdk/web'; import AddressCell from '@src/components/AddressCell'; import Button, { ButtonType } from '@src/components/Button'; import Dropdown from '@src/components/Dropdown'; @@ -78,9 +83,9 @@ const MyStakesTable = () => { ? [] : Object.keys(gateways).reduce((acc, key) => { const gateway = gateways[key]; - - // @ts-expect-error - delegates is currently available on the gateway object - const delegate:AoGatewayDelegate = gateway.delegates[walletAddress?.toString()]; + + const delegate: AoGatewayDelegate = + gateway.delegates[walletAddress?.toString()]; if (delegate) { return [ @@ -110,8 +115,8 @@ const MyStakesTable = () => { : Object.keys(gateways).reduce((acc, key) => { const gateway = gateways[key]; - // @ts-expect-error - delegates is currently available on the gateway object - const delegate:AoGatewayDelegate = gateway.delegates[walletAddress?.toString()]; + const delegate: AoGatewayDelegate = + gateway.delegates[walletAddress?.toString()]; if (delegate?.vaults) { const withdrawals = Object.entries(delegate.vaults).map( diff --git a/yarn.lock b/yarn.lock index 5e5bf71..a57392c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,10 +35,10 @@ plimit-lit "^3.0.1" warp-contracts "1.4.45" -"@ar.io/sdk@2.4.0-alpha.12": - version "2.4.0-alpha.12" - resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.4.0-alpha.12.tgz#bdf956bc863038cc7e26c58fd82fa99f2559d593" - integrity sha512-UTZ02I7fUkSvFrS4eTqJJNrhgywjygU2ImQRyKOJpR9DEHM7nsEohQzxHKbELl4LE+mnFubmG+3qe2gUNm7UYA== +"@ar.io/sdk@2.4.0-alpha.16": + version "2.4.0-alpha.16" + resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.4.0-alpha.16.tgz#001cf4a9cf7e4abee7c7652e8ead88fd09d42a84" + integrity sha512-9dwNo+ZpSnpZzQ0/5qA92v6CFCMyZrgO09AY7z5Di2uKTTS12Yh+AlAjD5Hq1TsEHpSpsGkjZ2wS+A4gMWBi9w== dependencies: "@dha-team/arbundles" "^1.0.1" "@permaweb/aoconnect" "^0.0.57" From 6874cc2be98a0aa28644519bf3a566562e9c9e68 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 12 Nov 2024 14:06:00 -0500 Subject: [PATCH 35/36] chore: update to v2.4.0 for sdk --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8ca706d..9062a8e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "deploy": "yarn build && permaweb-deploy --ant-process ${DEPLOY_ANT_PROCESS_ID}" }, "dependencies": { - "@ar.io/sdk": "2.4.0-alpha.16", + "@ar.io/sdk": "2.4.0", "@fontsource/rubik": "^5.0.19", "@headlessui/react": "^1.7.19", "@radix-ui/react-tooltip": "^1.0.7", diff --git a/yarn.lock b/yarn.lock index a57392c..2f371b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,10 +35,10 @@ plimit-lit "^3.0.1" warp-contracts "1.4.45" -"@ar.io/sdk@2.4.0-alpha.16": - version "2.4.0-alpha.16" - resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.4.0-alpha.16.tgz#001cf4a9cf7e4abee7c7652e8ead88fd09d42a84" - integrity sha512-9dwNo+ZpSnpZzQ0/5qA92v6CFCMyZrgO09AY7z5Di2uKTTS12Yh+AlAjD5Hq1TsEHpSpsGkjZ2wS+A4gMWBi9w== +"@ar.io/sdk@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.4.0.tgz#4ea0c0ffa629a4be0dac837d30e81d67452e64e3" + integrity sha512-IvletHvtz4gzlY8OQysHqt9fecftsqXZ8TTDvGRW1OkEHlb5AnOCrhoscYT0qDFA6LXo1nNcmDSucXfRemNCXA== dependencies: "@dha-team/arbundles" "^1.0.1" "@permaweb/aoconnect" "^0.0.57" From ce13d8d95e5dd25d38723b1ff79b71f5e65cb5f4 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Thu, 14 Nov 2024 10:07:15 -0500 Subject: [PATCH 36/36] chore: update changelog and version number for app release --- CHANGELOG.md | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de025d..fb1b632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.4.0] - 2024-11-14 + ### Added -* View Pending Withdrawals on Staking page and support cancelling pending withdrawals as well as performing expedited withdrawals. +* View Pending Withdrawals on Staking page and support cancelling pending withdrawals as well as performing expedited withdrawals * View Changelog in app by clicking version number in sidebar +### Updated + +* Staking page top cards now show balance, amount staking + pending withdrawals, and rewards earned last 14 epochs and last epoch + ### Changed * Updated header style of cards diff --git a/package.json b/package.json index 9062a8e..092e03a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@ar-io/network-portal", "private": true, - "version": "1.3.0", + "version": "1.4.0", "type": "module", "scripts": { "build": "yarn clean && tsc --build tsconfig.build.json && NODE_OPTIONS=--max-old-space-size=32768 vite build",