Skip to content

Commit

Permalink
refactor: moving withdraw and unbonding to MyValidator correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrorezende committed Aug 21, 2024
1 parent 6ece054 commit 5212342
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 244 deletions.
23 changes: 10 additions & 13 deletions apps/namadillo/src/App/Staking/StakingOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import { PageWithSidebar } from "App/Common/PageWithSidebar";
import { ValidatorDiversification } from "App/Sidebars/ValidatorDiversification";
import { YourStakingDistribution } from "App/Sidebars/YourStakingDistribution";
import { namadaExtensionConnectedAtom } from "atoms/settings";
import {
myValidatorsAtom,
stakedAmountByAddressAtom,
unbondedAmountByAddressAtom,
withdrawableAmountByAddressAtom,
} from "atoms/validators";
import { myValidatorsAtom } from "atoms/validators";
import { useAtomValue } from "jotai";
import { AllValidatorsTable } from "./AllValidatorsTable";
import { MyValidatorsTable } from "./MyValidatorsTable";
Expand All @@ -25,16 +20,18 @@ import { UnbondingAmountsTable } from "./UnbondingAmountsTable";
export const StakingOverview = (): JSX.Element => {
const isConnected = useAtomValue(namadaExtensionConnectedAtom);
const myValidators = useAtomValue(myValidatorsAtom);
const unbondedAmounts = useAtomValue(unbondedAmountByAddressAtom);
const withdrawableAmounts = useAtomValue(withdrawableAmountByAddressAtom);
const stakedByAddress = useAtomValue(stakedAmountByAddressAtom);

const hasStaking =
stakedByAddress.isSuccess && Object.keys(stakedByAddress.data).length > 0;
myValidators.isSuccess &&
myValidators.data.some((v) => v.stakedAmount?.gt(0));

const hasUnbonded =
unbondedAmounts.isSuccess && Object.keys(unbondedAmounts.data).length > 0;
myValidators.isSuccess &&
myValidators.data.some((v) => v.unbondedAmount?.gt(0));

const hasWithdraws =
withdrawableAmounts.isSuccess &&
Object.keys(withdrawableAmounts.data).length > 0;
myValidators.isSuccess &&
myValidators.data.some((v) => v.withdrawableAmount?.gt(0));

return (
<PageWithSidebar>
Expand Down
5 changes: 4 additions & 1 deletion apps/namadillo/src/App/Staking/StakingSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export const StakingSummary = (): JSX.Element => {
return [
{ value: balance, color: "#ffffff" },
{ value: totalStaked.totalBonded, color: "#00ffff" },
{ value: totalStaked.totalUnbonded, color: "#DD1599" },
{
value: totalStaked.totalUnbonded.plus(totalStaked.totalWithdrawable),
color: "#DD1599",
},
];
};

Expand Down
33 changes: 19 additions & 14 deletions apps/namadillo/src/App/Staking/UnbondingAmountsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { StyledTable, TableRow } from "@namada/components";
import { AtomErrorBoundary } from "App/Common/AtomErrorBoundary";
import { NamCurrency } from "App/Common/NamCurrency";
import { WalletAddress } from "App/Common/WalletAddress";
import { myUnbondsAtom } from "atoms/validators";
import { myValidatorsAtom } from "atoms/validators";
import BigNumber from "bignumber.js";
import { useAtomValue } from "jotai";
import { useMemo } from "react";
import { twMerge } from "tailwind-merge";
import { UnbondingValidator } from "types";
import { ValidatorCard } from "./ValidatorCard";
import { WithdrawalButton } from "./WithdrawalButton";

export const UnbondingAmountsTable = (): JSX.Element => {
const myUnbonds = useAtomValue(myUnbondsAtom);
const myValidators = useAtomValue(myValidatorsAtom);
const headers = [
"Validator",
"Address",
Expand All @@ -20,15 +21,14 @@ export const UnbondingAmountsTable = (): JSX.Element => {
];

const rows = useMemo(() => {
if (!myUnbonds.isSuccess) return [];
if (!myValidators.isSuccess) return [];

const rowsList: TableRow[] = [];
for (const myValidator of myUnbonds.data) {
const { validator, unbondedAmount, withdrawableAmount } = myValidator;
for (const myValidator of myValidators.data) {
const { validator } = myValidator;
const unbonding = myValidator.unbonding.concat(myValidator.withdrawable);

const amount = new BigNumber(unbondedAmount || withdrawableAmount || 0);

if (amount.gt(0)) {
unbonding.forEach((entry: UnbondingValidator) => {
rowsList.push({
cells: [
<ValidatorCard
Expand All @@ -44,30 +44,35 @@ export const UnbondingAmountsTable = (): JSX.Element => {
key={`my-validator-currency-${validator.address}`}
className="text-right leading-tight"
>
<NamCurrency amount={amount || new BigNumber(0)} />
<NamCurrency
amount={BigNumber(entry.amount) || new BigNumber(0)}
/>
</div>,
<div
key={`commission-${validator.address}`}
className="text-right leading-tight text-sm"
>
{myValidator.timeLeft}
{entry.timeLeft}
</div>,
<div
key={`withdraw-${validator.address}`}
className="ml-4 relative z-0"
>
<WithdrawalButton myValidator={myValidator} />
<WithdrawalButton
myValidator={myValidator}
unbondingStatus={entry}
/>
</div>,
],
});
}
});
}
return rowsList;
}, [myUnbonds]);
}, [myValidators]);

return (
<AtomErrorBoundary
result={myUnbonds}
result={myValidators}
niceError="Unable to load unbonding list"
>
<StyledTable
Expand Down
2 changes: 1 addition & 1 deletion apps/namadillo/src/App/Staking/UnstakeBondingTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const UnstakeBondingTable = ({

return (
<ValidatorsTable
id="increment-bonding-table"
id="increment-unbonding-table"
tableClassName="mt-2"
validatorList={sortedValidators}
headers={headers}
Expand Down
53 changes: 26 additions & 27 deletions apps/namadillo/src/App/Staking/WithdrawalButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ import invariant from "invariant";
import { useAtomValue, useSetAtom } from "jotai";
import { TransactionPair, broadcastTx } from "lib/query";
import { useCallback, useEffect } from "react";
import { MyValidator } from "types";
import { MyValidator, UnbondingValidator } from "types";

type WithdrawalButtonProps = {
myValidator: MyValidator;
unbondingStatus: UnbondingValidator;
};

export const WithdrawalButton = ({
myValidator,
unbondingStatus,
}: WithdrawalButtonProps): JSX.Element => {
const change = {
validatorId: myValidator.validator.address,
amount: myValidator.withdrawableAmount!,
amount: unbondingStatus.amount,
};

const { gasPrice } = useGasEstimate();
Expand All @@ -49,29 +51,26 @@ export const WithdrawalButton = ({
};
}, []);

const onWithdraw = useCallback(
async (myValidator: MyValidator) => {
invariant(
account,
"Extension is not connected or you don't have an account"
);
invariant(gasPrice, "Gas price loading is still pending");
invariant(gasLimits.isSuccess, "Gas limit loading is still pending");
invariant(
myValidator.withdrawableAmount,
"Validator doesn't have amounts available for withdrawal"
);
createWithdrawTx({
changes: [change],
gasConfig: {
gasPrice: gasPrice!,
gasLimit: gasLimits.data!.Withdraw.native,
},
account: account!,
});
},
[myValidator.withdrawableAmount, gasPrice, gasLimits.isSuccess]
);
const onWithdraw = useCallback(async () => {
invariant(
account,
"Extension is not connected or you don't have an account"
);
invariant(gasPrice, "Gas price loading is still pending");
invariant(gasLimits.isSuccess, "Gas limit loading is still pending");
invariant(
unbondingStatus.amount,
"Validator doesn't have amounts available for withdrawal"
);
createWithdrawTx({
changes: [change],
gasConfig: {
gasPrice: gasPrice!,
gasLimit: gasLimits.data!.Withdraw.native,
},
account: account!,
});
}, [unbondingStatus.amount, change, gasPrice, gasLimits.isSuccess]);

const dispatchNotification = useSetAtom(dispatchToastNotificationAtom);

Expand Down Expand Up @@ -133,8 +132,8 @@ export const WithdrawalButton = ({
<ActionButton
size="xs"
outlineColor="white"
disabled={!myValidator.withdrawableAmount || isPending || isSuccess}
onClick={() => onWithdraw(myValidator)}
disabled={!unbondingStatus.canWithdraw || isPending || isSuccess}
onClick={() => onWithdraw()}
>
{isSuccess && "Claimed"}
{isPending && "Processing"}
Expand Down
7 changes: 1 addition & 6 deletions apps/namadillo/src/atoms/syncStatus/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { accountBalanceAtom } from "atoms/accounts/atoms";
import { allProposalsAtom, votedProposalIdsAtom } from "atoms/proposals/atoms";
import { indexerHeartbeatAtom, rpcHeartbeatAtom } from "atoms/settings/atoms";
import {
allValidatorsAtom,
myUnbondsAtom,
myValidatorsAtom,
} from "atoms/validators/atoms";
import { allValidatorsAtom, myValidatorsAtom } from "atoms/validators/atoms";
import { atom } from "jotai";

export const syncStatusAtom = atom((get) => {
Expand All @@ -17,7 +13,6 @@ export const syncStatusAtom = atom((get) => {
// Staking
get(accountBalanceAtom),
get(myValidatorsAtom),
get(myUnbondsAtom),
get(allValidatorsAtom),

// Governance
Expand Down
109 changes: 20 additions & 89 deletions apps/namadillo/src/atoms/validators/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@ import { indexerApiAtom } from "atoms/api";
import { chainParametersAtom } from "atoms/chain";
import { shouldUpdateBalanceAtom } from "atoms/etc";
import { queryDependentFn } from "atoms/utils";
import BigNumber from "bignumber.js";
import {
AtomWithQueryResult,
UndefinedInitialDataOptions,
atomWithQuery,
} from "jotai-tanstack-query";
import { MyUnbondingValidator, MyValidator, Validator } from "types";
import { atomWithQuery } from "jotai-tanstack-query";
import { MyValidator, Validator } from "types";
import { toMyValidators } from "./functions";
import {
fetchAllValidators,
fetchMyUnbonds,
fetchMyValidators,
fetchMyBondedAmounts,
fetchMyUnbondedAmounts,
fetchVotingPower,
} from "./services";

Expand Down Expand Up @@ -51,85 +47,20 @@ export const myValidatorsAtom = atomWithQuery((get) => {
return {
queryKey: ["my-validators", account.data?.address],
refetchInterval: enablePolling ? 1000 : false,
...queryDependentFn(
async (): Promise<MyValidator[]> =>
fetchMyValidators(
api,
account.data!,
chainParameters.data!,
votingPower.data!
),
[account, chainParameters, votingPower]
),
...queryDependentFn(async (): Promise<MyValidator[]> => {
const bondedAmountsQuery = fetchMyBondedAmounts(api, account.data!);
const unbondedAmountsQuery = fetchMyUnbondedAmounts(api, account.data!);
const [unbondedAmounts, bondedAmounts] = await Promise.all([
unbondedAmountsQuery,
bondedAmountsQuery,
]);
return toMyValidators(
bondedAmounts,
unbondedAmounts,
votingPower.data!,
chainParameters.data!.epochInfo,
chainParameters.data!.apr
);
}, [account, chainParameters, votingPower]),
};
});

export const myUnbondsAtom = atomWithQuery<MyUnbondingValidator[]>((get) => {
const chainParameters = get(chainParametersAtom);
const account = get(defaultAccountAtom);
const votingPower = get(votingPowerAtom);
const api = get(indexerApiAtom);

// TODO: Refactor after this event subscription is enabled in the indexer
const enablePolling = get(shouldUpdateBalanceAtom);
return {
queryKey: ["my-unbonds", account.data?.address],
refetchInterval: enablePolling ? 1000 : false,
...queryDependentFn(
async (): Promise<MyUnbondingValidator[]> =>
fetchMyUnbonds(
api,
account.data!,
chainParameters.data!,
votingPower.data!
),
[account, chainParameters, votingPower]
),
};
});

export const unbondedAmountByAddressAtom = atomWithQuery((get) =>
deriveFromMyValidatorsAtom(
"unbonded-amount",
"unbondedAmount",
get(myUnbondsAtom)
)
);

export const withdrawableAmountByAddressAtom = atomWithQuery((get) =>
deriveFromMyValidatorsAtom(
"withdrawable-amount",
"withdrawableAmount",
get(myUnbondsAtom)
)
);

export const stakedAmountByAddressAtom = atomWithQuery((get) =>
deriveFromMyValidatorsAtom(
"staked-amount",
"stakedAmount",
get(myValidatorsAtom)
)
);

const deriveFromMyValidatorsAtom = (
key: string,
property: "stakedAmount" | "unbondedAmount" | "withdrawableAmount",
myValidators: AtomWithQueryResult<
(MyValidator | MyUnbondingValidator)[],
Error
>
): UndefinedInitialDataOptions<Record<string, BigNumber>> => {
return {
queryKey: [key, myValidators.data],
enabled: myValidators.isSuccess,
queryFn: async () => {
return myValidators.data!.reduce((prev, current) => {
if (current[property]?.gt(0)) {
return { ...prev, [current.validator.address]: current[property] };
}
return prev;
}, {});
},
};
};
Loading

0 comments on commit 5212342

Please sign in to comment.