Merge pull request #255 from cosmos/load-all-validators
Load unbonded and unbonding validators too
This commit is contained in:
commit
ea6a6c904d
@ -12,24 +12,43 @@ import {
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { useChains } from "@/context/ChainsContext";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Validator } from "cosmjs-types/cosmos/staking/v1beta1/staking";
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface SelectValidatorProps {
|
||||
readonly validatorAddress: string;
|
||||
readonly selectedValidatorAddress: string;
|
||||
readonly setValidatorAddress: (validatorAddress: string) => void;
|
||||
}
|
||||
|
||||
export default function SelectValidator({
|
||||
validatorAddress,
|
||||
selectedValidatorAddress,
|
||||
setValidatorAddress,
|
||||
}: SelectValidatorProps) {
|
||||
const {
|
||||
validatorState: { validators },
|
||||
validatorState: {
|
||||
validators: { bonded, unbonding, unbonded },
|
||||
},
|
||||
} = useChains();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
// The list of validators includes unbonding and unbonded validators in order to
|
||||
// be able to do undelegates and redelegates from jailed validators as well as delegate
|
||||
// to validators who are not yet active.
|
||||
//
|
||||
// If this list becomes too long due to spam registrations, we can try to do some
|
||||
// reasonable filtering here.
|
||||
const validators = [...bonded, ...unbonding, ...unbonded];
|
||||
|
||||
function displayValidator(val: Validator): string {
|
||||
return val.description.moniker + (val.jailed ? " (jailed)" : "");
|
||||
}
|
||||
|
||||
const selectedValidator = validators.find(
|
||||
(validatorItem) => selectedValidatorAddress === validatorItem.operatorAddress,
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
@ -39,9 +58,10 @@ export default function SelectValidator({
|
||||
aria-expanded={open}
|
||||
className="mb-4 w-full max-w-[300px] justify-between border-white bg-fuchsia-900 hover:bg-fuchsia-900"
|
||||
>
|
||||
{validatorAddress
|
||||
? validators.find((validatorItem) => validatorAddress === validatorItem.operatorAddress)
|
||||
?.description.moniker || "Unknown validator"
|
||||
{selectedValidatorAddress
|
||||
? selectedValidator
|
||||
? displayValidator(selectedValidator)
|
||||
: "Unknown validator"
|
||||
: "Select validator…"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
@ -68,12 +88,12 @@ export default function SelectValidator({
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
validatorAddress === validatorItem.operatorAddress
|
||||
selectedValidatorAddress === validatorItem.operatorAddress
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{validatorItem.description.moniker}
|
||||
{validatorItem.description.moniker + (validatorItem.jailed ? " (jailed)" : "")}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
||||
@ -111,7 +111,7 @@ const MsgBeginRedelegateForm = ({
|
||||
<h2>MsgBeginRedelegate</h2>
|
||||
<div className="form-item">
|
||||
<SelectValidator
|
||||
validatorAddress={validatorSrcAddress}
|
||||
selectedValidatorAddress={validatorSrcAddress}
|
||||
setValidatorAddress={setValidatorSrcAddress}
|
||||
/>
|
||||
<Input
|
||||
@ -128,7 +128,7 @@ const MsgBeginRedelegateForm = ({
|
||||
</div>
|
||||
<div className="form-item">
|
||||
<SelectValidator
|
||||
validatorAddress={validatorDstAddress}
|
||||
selectedValidatorAddress={validatorDstAddress}
|
||||
setValidatorAddress={setValidatorDstAddress}
|
||||
/>
|
||||
<Input
|
||||
|
||||
@ -92,7 +92,7 @@ const MsgDelegateForm = ({ senderAddress, setMsgGetter, deleteMsg }: MsgDelegate
|
||||
<h2>MsgDelegate</h2>
|
||||
<div className="form-item">
|
||||
<SelectValidator
|
||||
validatorAddress={validatorAddress}
|
||||
selectedValidatorAddress={validatorAddress}
|
||||
setValidatorAddress={setValidatorAddress}
|
||||
/>
|
||||
<Input
|
||||
|
||||
@ -92,7 +92,7 @@ const MsgUndelegateForm = ({ senderAddress, setMsgGetter, deleteMsg }: MsgUndele
|
||||
<h2>MsgUndelegate</h2>
|
||||
<div className="form-item">
|
||||
<SelectValidator
|
||||
validatorAddress={validatorAddress}
|
||||
selectedValidatorAddress={validatorAddress}
|
||||
setValidatorAddress={setValidatorAddress}
|
||||
/>
|
||||
<Input
|
||||
|
||||
@ -65,7 +65,7 @@ const MsgWithdrawDelegatorRewardForm = ({
|
||||
<h2>MsgWithdrawDelegatorReward</h2>
|
||||
<div className="form-item">
|
||||
<SelectValidator
|
||||
validatorAddress={validatorAddress}
|
||||
selectedValidatorAddress={validatorAddress}
|
||||
setValidatorAddress={setValidatorAddress}
|
||||
/>
|
||||
<Input
|
||||
|
||||
@ -52,7 +52,8 @@ const OldCreateTxForm = ({ router, senderAddress, accountOnChain }: OldCreateTxF
|
||||
};
|
||||
|
||||
const addMsgWithValidator = (newMsgType: MsgTypeUrl) => {
|
||||
if (!validators.length) {
|
||||
const validatorsLoaded = !!validators.bonded.length;
|
||||
if (!validatorsLoaded) {
|
||||
loadValidators(chainsDispatch);
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { getAllValidators } from "@/lib/staking";
|
||||
import { emptyAllValidatorsEmpty, getAllValidators } from "@/lib/staking";
|
||||
import { toastError } from "@/lib/utils";
|
||||
import { ReactNode, createContext, useContext, useEffect, useReducer } from "react";
|
||||
import { emptyChain, isChainInfoFilled, setChain, setChains, setChainsError } from "./helpers";
|
||||
import { getChain, getNodeFromArray, useChainsFromRegistry } from "./service";
|
||||
import { addLocalChainInStorage, addRecentChainNameInStorage, setChainInUrl } from "./storage";
|
||||
import { Action, ChainsContextType, State } from "./types";
|
||||
import { Action, ChainsContextType, Dispatch, State } from "./types";
|
||||
|
||||
const ChainsContext = createContext<ChainsContextType | undefined>(undefined);
|
||||
|
||||
@ -31,7 +31,7 @@ const chainsReducer = (state: State, action: Action): State => {
|
||||
return {
|
||||
...state,
|
||||
chain: action.payload,
|
||||
validatorState: { validators: [], status: "initial" },
|
||||
validatorState: { validators: emptyAllValidatorsEmpty(), status: "initial" },
|
||||
};
|
||||
}
|
||||
case "addNodeAddress": {
|
||||
@ -66,7 +66,7 @@ export const ChainsProvider = ({ children }: ChainsProviderProps) => {
|
||||
chain: emptyChain,
|
||||
chains: { mainnets: new Map(), testnets: new Map(), localnets: new Map() },
|
||||
newConnection: { action: "edit" },
|
||||
validatorState: { validators: [], status: "initial" },
|
||||
validatorState: { validators: emptyAllValidatorsEmpty(), status: "initial" },
|
||||
});
|
||||
|
||||
const { chainItems, chainItemsError } = useChainsFromRegistry();
|
||||
@ -105,7 +105,10 @@ export const ChainsProvider = ({ children }: ChainsProviderProps) => {
|
||||
description: "Failed to load validators",
|
||||
fullError: e instanceof Error ? e : undefined,
|
||||
});
|
||||
dispatch({ type: "setValidatorState", payload: { validators: [], status: "error" } });
|
||||
dispatch({
|
||||
type: "setValidatorState",
|
||||
payload: { validators: emptyAllValidatorsEmpty(), status: "error" },
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
@ -114,7 +117,7 @@ export const ChainsProvider = ({ children }: ChainsProviderProps) => {
|
||||
return <ChainsContext.Provider value={{ state, dispatch }}>{children}</ChainsContext.Provider>;
|
||||
};
|
||||
|
||||
export const useChains = () => {
|
||||
export const useChains = (): State & { chainsDispatch: Dispatch } => {
|
||||
const context = useContext(ChainsContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useChains must be used within a ChainsProvider");
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Validator } from "cosmjs-types/cosmos/staking/v1beta1/staking";
|
||||
import { RegistryAsset } from "../../types/chainRegistry";
|
||||
import { AllValidators } from "@/lib/staking";
|
||||
|
||||
export interface ChainsContextType {
|
||||
readonly state: State;
|
||||
@ -39,7 +39,7 @@ export interface ChainInfo {
|
||||
}
|
||||
|
||||
export interface ValidatorState {
|
||||
readonly validators: readonly Validator[];
|
||||
readonly validators: AllValidators;
|
||||
readonly status: "initial" | "loading" | "done" | "error";
|
||||
}
|
||||
|
||||
|
||||
@ -2,24 +2,68 @@ import { QueryClient, StakingExtension, setupStakingExtension } from "@cosmjs/st
|
||||
import { connectComet } from "@cosmjs/tendermint-rpc";
|
||||
import { Validator } from "cosmjs-types/cosmos/staking/v1beta1/staking";
|
||||
|
||||
const getValidatorsPage = (
|
||||
const getBondedValidatorsPage = (
|
||||
queryClient: QueryClient & StakingExtension,
|
||||
paginationKey: Uint8Array | undefined,
|
||||
) => queryClient.staking.validators("BOND_STATUS_BONDED", paginationKey);
|
||||
|
||||
export const getAllValidators = async (rpcUrl: string): Promise<readonly Validator[]> => {
|
||||
const validators: Validator[] = [];
|
||||
const getUnbondingValidatorsPage = (
|
||||
queryClient: QueryClient & StakingExtension,
|
||||
paginationKey: Uint8Array | undefined,
|
||||
) => queryClient.staking.validators("BOND_STATUS_UNBONDING", paginationKey);
|
||||
|
||||
const getUnbondedValidatorsPage = (
|
||||
queryClient: QueryClient & StakingExtension,
|
||||
paginationKey: Uint8Array | undefined,
|
||||
) => queryClient.staking.validators("BOND_STATUS_UNBONDED", paginationKey);
|
||||
|
||||
export interface AllValidators {
|
||||
bonded: readonly Validator[];
|
||||
unbonding: readonly Validator[];
|
||||
unbonded: readonly Validator[];
|
||||
}
|
||||
|
||||
export function emptyAllValidatorsEmpty(): AllValidators {
|
||||
return { bonded: [], unbonding: [], unbonded: [] };
|
||||
}
|
||||
|
||||
export const getAllValidators = async (rpcUrl: string): Promise<AllValidators> => {
|
||||
const bondedValidators: Validator[] = [];
|
||||
const unbondingValidators: Validator[] = [];
|
||||
const unbondedValidators: Validator[] = [];
|
||||
|
||||
const cometClient = await connectComet(rpcUrl);
|
||||
const queryClient = QueryClient.withExtensions(cometClient, setupStakingExtension);
|
||||
|
||||
let paginationKey: Uint8Array | undefined = undefined;
|
||||
let paginationKey: Uint8Array | undefined;
|
||||
|
||||
// Bonded
|
||||
paginationKey = undefined;
|
||||
do {
|
||||
const response = await getValidatorsPage(queryClient, paginationKey);
|
||||
validators.push(...response.validators);
|
||||
const response = await getBondedValidatorsPage(queryClient, paginationKey);
|
||||
bondedValidators.push(...response.validators);
|
||||
paginationKey = response.pagination?.nextKey;
|
||||
} while (paginationKey?.length);
|
||||
|
||||
return validators;
|
||||
// Unbonding
|
||||
paginationKey = undefined;
|
||||
do {
|
||||
const response = await getUnbondingValidatorsPage(queryClient, paginationKey);
|
||||
unbondingValidators.push(...response.validators);
|
||||
paginationKey = response.pagination?.nextKey;
|
||||
} while (paginationKey?.length);
|
||||
|
||||
// Unbonded
|
||||
paginationKey = undefined;
|
||||
do {
|
||||
const response = await getUnbondedValidatorsPage(queryClient, paginationKey);
|
||||
unbondedValidators.push(...response.validators);
|
||||
paginationKey = response.pagination?.nextKey;
|
||||
} while (paginationKey?.length);
|
||||
|
||||
return {
|
||||
bonded: bondedValidators,
|
||||
unbonding: unbondingValidators,
|
||||
unbonded: unbondedValidators,
|
||||
};
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user