Skip to content

Commit

Permalink
Merge pull request #113 from ar-io/develop
Browse files Browse the repository at this point in the history
Release v1.4.1
  • Loading branch information
kunstmusik authored Nov 18, 2024
2 parents ccdc6fd + 0908a85 commit 128ce20
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 102 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.4.1] - 2024-11-18

### 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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ar-io/network-portal",
"private": true,
"version": "1.4.0",
"version": "1.4.1",
"type": "module",
"scripts": {
"build": "yarn clean && tsc --build tsconfig.build.json && NODE_OPTIONS=--max-old-space-size=32768 vite build",
Expand All @@ -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
([_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,
});

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

0 comments on commit 128ce20

Please sign in to comment.