Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PE-7115: remove use of delegates field on gateways and update app to use getGatewayDelegates() and getDelegations() #111

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Updated

* Optimized loading of user stakes and pending withdrawals.

### Fixed

* Gateways count in site header should only count active gateways.

## [1.4.0] - 2024-11-14

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"deploy": "yarn build && permaweb-deploy --ant-process ${DEPLOY_ANT_PROCESS_ID}"
},
"dependencies": {
"@ar.io/sdk": "2.4.0",
"@ar.io/sdk": "2.5.0-alpha.3",
"@fontsource/rubik": "^5.0.19",
"@headlessui/react": "^1.7.19",
"@radix-ui/react-tooltip": "^1.0.7",
Expand Down
9 changes: 8 additions & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,14 @@ const Header = () => {
loading={!blockHeight}
/>
<HeaderItem
value={gateways ? Object.keys(gateways).length : undefined}
value={
gateways
? Object.entries(gateways).filter(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought maybe the _ would avoid needing this, fine to revert

([_address, gateway]) => gateway.status === 'joined',
).length
: undefined
}
label="GATEWAYS"
loading={gatewaysLoading}
/>
Expand Down
4 changes: 4 additions & 0 deletions src/components/modals/CancelWithdrawalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ const CancelWithdrawalModal = ({
queryKey: ['gateways'],
refetchType: 'all',
});
queryClient.invalidateQueries({
queryKey: ['delegateStakes'],
refetchType: 'all',
});

setShowSuccessModal(true);
} catch (e: any) {
Expand Down
4 changes: 4 additions & 0 deletions src/components/modals/InstantWithdrawalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ const InstantWithdrawalModal = ({
queryKey: ['balances'],
refetchType: 'all',
});
queryClient.invalidateQueries({
queryKey: ['delegateStakes'],
refetchType: 'all',
});

setShowSuccessModal(true);
} catch (e: any) {
Expand Down
38 changes: 23 additions & 15 deletions src/components/modals/StakingModal.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { AoGatewayDelegate, IOToken, mIOToken } from '@ar.io/sdk/web';
import { IOToken, mIOToken } from '@ar.io/sdk/web';
import {
EAY_TOOLTIP_FORMULA,
EAY_TOOLTIP_TEXT,
WRITE_OPTIONS,
log,
} from '@src/constants';
import useBalances from '@src/hooks/useBalances';
import useDelegateStakes from '@src/hooks/useDelegateStakes';
import useGateway from '@src/hooks/useGateway';
import useRewardsInfo from '@src/hooks/useRewardsInfo';
import { useGlobalState } from '@src/store';
import { formatWithCommas } from '@src/utils';
import { showErrorToast } from '@src/utils/toast';
import { useQueryClient } from '@tanstack/react-query';
import { MathJax } from 'better-react-mathjax';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import Button, { ButtonType } from '../Button';
import LabelValueRow from '../LabelValueRow';
import Tooltip from '../Tooltip';
Expand Down Expand Up @@ -48,6 +49,7 @@ const StakingModal = ({
const [userEnteredWalletAddress, setUserEnteredWalletAddress] =
useState<string>('');

const [currentStake, setCurrentStake] = useState<number>(0);
const [amountToStake, setAmountToStake] = useState<string>('');
const [amountToUnstake, setAmountToUnstake] = useState<string>('');

Expand All @@ -62,16 +64,21 @@ const StakingModal = ({
ownerWalletAddress: gatewayOwnerWallet,
});

const { data: delegateStakes } = useDelegateStakes(walletAddress?.toString());

useEffect(() => {
if (!gateway || !delegateStakes) {
return;
}
const stake = delegateStakes.stakes.find(
(stake) => stake.gatewayAddress === gateway.gatewayAddress,
)?.balance;
setCurrentStake(new mIOToken(stake ?? 0).toIO().valueOf());
}, [delegateStakes, gateway]);

const allowDelegatedStaking =
gateway?.settings.allowDelegatedStaking ?? false;

const delegateData: AoGatewayDelegate | undefined = walletAddress
? gateway?.delegates[walletAddress?.toString()]
: undefined;
const currentStake = new mIOToken(delegateData?.delegatedStake ?? 0)
.toIO()
.valueOf();

const newTotalStake =
tab == 0
? currentStake + parseFloat(amountToStake)
Expand All @@ -86,13 +93,10 @@ const StakingModal = ({
}) + '%'
: '-';

const existingStake = new mIOToken(delegateData?.delegatedStake ?? 0)
.toIO()
.valueOf();
const minDelegatedStake = gateway
? new mIOToken(gateway?.settings.minDelegatedStake).toIO().valueOf()
: 500;
const minRequiredStakeToAdd = existingStake > 0 ? 1 : minDelegatedStake;
const minRequiredStakeToAdd = currentStake > 0 ? 1 : minDelegatedStake;

const validators = {
address: validateWalletAddress('Gateway Owner'),
Expand All @@ -105,7 +109,7 @@ const StakingModal = ({
unstakeAmount: validateUnstakeAmount(
'Unstake Amount',
ticker,
existingStake,
currentStake,
minDelegatedStake,
),
};
Expand Down Expand Up @@ -182,6 +186,10 @@ const StakingModal = ({
queryKey: ['balances'],
refetchType: 'all',
});
queryClient.invalidateQueries({
queryKey: ['delegateStakes'],
refetchType: 'all',
});

setShowSuccessModal(true);
} catch (e: any) {
Expand Down Expand Up @@ -308,7 +316,7 @@ const StakingModal = ({
<LabelValueRow
className="border-b border-divider pb-4"
label="Existing Stake:"
value={`${existingStake} ${ticker}`}
value={`${currentStake} ${ticker}`}
/>
)}
<LabelValueRow
Expand Down
4 changes: 4 additions & 0 deletions src/components/modals/UnstakeAllModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ const UnstakeAllModal = ({
queryKey: ['gateways'],
refetchType: 'all',
});
queryClient.invalidateQueries({
queryKey: ['delegateStakes'],
refetchType: 'all',
});

setShowSuccessModal(true);
} catch (e: any) {
Expand Down
51 changes: 51 additions & 0 deletions src/hooks/useDelegateStakes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { AoStakeDelegation, AoVaultDelegation } from '@ar.io/sdk/web';
import { useGlobalState } from '@src/store';
import { useQuery } from '@tanstack/react-query';

type DelegateStakes = {
stakes: Array<AoStakeDelegation>;
withdrawals: Array<AoVaultDelegation>;
};

const useDelegateStakes = (address?: string) => {
const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK);

const res = useQuery<DelegateStakes>({
queryKey: ['delegateStakes', address],
queryFn: async () => {
if (!address) {
throw new Error('Address is not set');
}

const retVal: DelegateStakes = {
stakes: [],
withdrawals: [],
};

let cursor: string | undefined;

do {
const pageResult = await arIOReadSDK.getDelegations({
address,
cursor,
limit: 10,
});
pageResult.items.forEach((d) => {
if (d.type === 'stake') {
retVal.stakes.push(d);
} else {
retVal.withdrawals.push(d);
}
});
cursor = pageResult.nextCursor;
} while (cursor !== undefined);

return retVal;
},
staleTime: Infinity,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any consequence of this stale time?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this in so that the query should return the same value for the duration of the session unless explicitly invalidated by user action (i.e., instant withdrawal, cancelling a stake, adding new stake).

});

return res;
};

export default useDelegateStakes;
23 changes: 15 additions & 8 deletions src/pages/Gateway/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const Gateway = () => {
const queryClient = useQueryClient();

const walletAddress = useGlobalState((state) => state.walletAddress);
const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK);
const arIOWriteableSDK = useGlobalState((state) => state.arIOWriteableSDK);
const ticker = useGlobalState((state) => state.ticker);
const { data: protocolBalance } = useProtocolBalance();
Expand All @@ -83,6 +84,7 @@ const Gateway = () => {
url: gatewayAddress,
});

const [numDelegates, setNumDelegates] = useState<number>();
const [editing, setEditing] = useState(false);

const [initialState, setInitialState] = useState<
Expand Down Expand Up @@ -135,6 +137,18 @@ const Gateway = () => {
});
}, [walletAddress]);

useEffect(() => {
if (!arIOReadSDK || !gateway) return;
const update = async () => {
const res = await arIOReadSDK.getGatewayDelegates({
address: gateway.gatewayAddress,
limit: 1,
});
setNumDelegates(res.totalItems);
};
update();
}, [gateway, arIOReadSDK]);

// This updates the form when the user toggles the delegated staking switch to false to reset the
// form values and error messages back to the initial state.
useEffect(() => {
Expand Down Expand Up @@ -425,14 +439,7 @@ const Gateway = () => {
: formatUptime(healthCheckRes.data?.uptime)
}
/>
<StatsBox
title="Delegates"
value={
gateway?.delegates
? Object.keys(gateway.delegates).length
: undefined
}
/>
<StatsBox title="Delegates" value={numDelegates} />

<StatsBox
title={
Expand Down
38 changes: 20 additions & 18 deletions src/pages/Staking/ConnectedLandingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { AoGatewayDelegate, mIOToken } from '@ar.io/sdk/web';
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 useBalances from '@src/hooks/useBalances';
import useDelegateStakes from '@src/hooks/useDelegateStakes';
import useRewardsEarned from '@src/hooks/useRewardsEarned';
import { useGlobalState } from '@src/store';
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,
Expand Down Expand Up @@ -73,26 +73,28 @@ const ConnectedLandingPage = () => {

const [isStakingModalOpen, setIsStakingModalOpen] = useState<boolean>(false);

const { data: gateways } = useGateways();
const { data: balances } = useBalances(walletAddress);
const { data: balances } = useBalances(walletAddress);
const rewardsEarned = useRewardsEarned(walletAddress?.toString());

useEffect(() => {
if (gateways && walletAddress) {
const amountStaking = Object.values(gateways).reduce((acc, gateway) => {
const userDelegate:AoGatewayDelegate = 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;
const { data: delegatedStakes } = useDelegateStakes(
walletAddress?.toString(),
);

return acc + delegatedStake + withdrawn;
useEffect(() => {
if (delegatedStakes) {
const staked = delegatedStakes.stakes.reduce((acc, stake) => {
return acc + stake.balance;
}, 0);
setAmountStaking(new mIOToken(amountStaking).toIO().valueOf());

const withdrawing = delegatedStakes.withdrawals.reduce(
(acc, withdrawal) => {
return acc + withdrawal.balance;
},
0,
);
setAmountStaking(new mIOToken(staked + withdrawing).toIO().valueOf());
}
}, [gateways, walletAddress]);
}, [delegatedStakes]);

const topPanels = [
{
Expand Down
Loading
Loading