From c37c09622f3aa5a4cf624304f1fe14be636f79d6 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 25 Oct 2024 10:08:22 -0400 Subject: [PATCH 01/22] 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 00000000..3be2ec4e --- /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 741fefad..59239ff6 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 02/22] 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 8ea627d9..0186b391 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 ae16fb01..6bd5fbf1 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 5e4166b9..00000000 --- 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 59239ff6..13b8b08e 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 03/22] 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 13b8b08e..ff64ace2 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 04/22] 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 1319c482..dcb54f9e 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 05/22] 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 dcb54f9e..6da3fa04 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: Tue, 5 Nov 2024 13:43:03 -0500 Subject: [PATCH 06/22] 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 00000000..868a7a5d --- /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 173127ba..802e1e73 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 07/22] 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 5dbe6f47..410a0319 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 08/22] 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 faa4afe4..943e0eb7 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 09/22] 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 ff64ace2..d9780b0e 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 10/22] 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 6da3fa04..5a1dff4d 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 4a2573e2e7c39884d0156f4b2de17fc38c646bfd Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 8 Nov 2024 13:46:22 -0500 Subject: [PATCH 11/22] 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 54130172..7ff8d727 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 00000000..9faa3f66 --- /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 00000000..4a116a11 --- /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 a22fc90a..3f4c9e9e 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 00000000..eda83b58 --- /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 96fb0988..15a610b0 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 12/22] 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 6aeadc89..2de025db 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 5af7412f..69d31ce3 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 56a3912f..0335dc9a 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 0f453506..60d8e9e1 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 4a116a11..5c52c486 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 3f4c9e9e..ae75d84d 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 6bd5fbf1..42722b20 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 f29ec61d..1228c849 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 ed33bd58..8b8e247f 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 b1629ab8..ae2aebe8 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 13/22] 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 7d8d0b5c..49639eb6 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 7ff8d727..7927799f 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 15a610b0..92a3481b 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 14/22] 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 42722b20..fcde5be2 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 5271914b..29e98f7c 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 3be2ec4e..83ecdb63 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 46d86e9d..9fe1985a 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 d46fb889..1efce076 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 893ea9b1..37675edd 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 a86e6821..54fa9254 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 975ddfd8..392920c7 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 15/22] 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 08982efd..89d79d92 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 7927799f..c9216916 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 92a3481b..5e5bf71f 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 16/22] 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 802e1e73..ab3d85f3 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 17/22] 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 60d8e9e1..49da4c74 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 5c52c486..cdad2941 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 5bc4dcff..5605a9e1 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 18/22] 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 49da4c74..4e05ebf1 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 19/22] 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 cdad2941..fe30dc2a 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 20/22] 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 83ecdb63..addf2da4 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 21/22] 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 c9216916..8ca706d1 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 ae75d84d..5dc38897 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 1228c849..b5a9bac1 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 8b8e247f..33930252 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 b056d028..2181b432 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 5e5bf71f..a57392ca 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 22/22] 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 8ca706d1..9062a8e3 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 a57392ca..2f371b11 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"