From 9460b8ea6d09580c182b8868beaf1dbb52a19171 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:32:57 +0100 Subject: [PATCH 1/8] Add dep --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index ec9f2b0..d27bcd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@cosmjs/math": "^0.32.2", "@cosmjs/proto-signing": "^0.32.2", "@cosmjs/stargate": "^0.32.2", + "@cosmjs/tendermint-rpc": "^0.32.2", "@cosmjs/utils": "^0.32.2", "@hookform/resolvers": "^3.3.1", "@keplr-wallet/types": "^0.12.23", diff --git a/package.json b/package.json index d17c38e..f39b7fc 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@cosmjs/math": "^0.32.2", "@cosmjs/proto-signing": "^0.32.2", "@cosmjs/stargate": "^0.32.2", + "@cosmjs/tendermint-rpc": "^0.32.2", "@cosmjs/utils": "^0.32.2", "@hookform/resolvers": "^3.3.1", "@keplr-wallet/types": "^0.12.23", From fcf59e8aa4d05929bb18668308c8ac07de4fa990 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:33:14 +0100 Subject: [PATCH 2/8] Add get validators helper --- lib/staking.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 lib/staking.ts diff --git a/lib/staking.ts b/lib/staking.ts new file mode 100644 index 0000000..e2c864c --- /dev/null +++ b/lib/staking.ts @@ -0,0 +1,25 @@ +import { QueryClient, StakingExtension, setupStakingExtension } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { Validator } from "cosmjs-types/cosmos/staking/v1beta1/staking"; + +const getValidatorsPage = ( + queryClient: QueryClient & StakingExtension, + paginationKey: Uint8Array | undefined, +) => queryClient.staking.validators("BOND_STATUS_BONDED", paginationKey); + +export const getAllValidators = async (rpcUrl: string): Promise => { + const validators: Validator[] = []; + + const tmClient = await Tendermint34Client.connect(rpcUrl); + const queryClient = QueryClient.withExtensions(tmClient, setupStakingExtension); + + let paginationKey: Uint8Array | undefined = undefined; + + do { + const response = await getValidatorsPage(queryClient, paginationKey); + validators.push(...response.validators); + paginationKey = response.pagination?.nextKey; + } while (paginationKey?.length); + + return validators; +}; From 718d151aad402141cbfa955d6b7da1fc853e9992 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:33:46 +0100 Subject: [PATCH 3/8] Add validatorState to ChainsContext --- context/ChainsContext/helpers.tsx | 4 ++++ context/ChainsContext/index.tsx | 33 +++++++++++++++++++++++++++++-- context/ChainsContext/types.tsx | 14 +++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/context/ChainsContext/helpers.tsx b/context/ChainsContext/helpers.tsx index 0d0cf53..0ac88bd 100644 --- a/context/ChainsContext/helpers.tsx +++ b/context/ChainsContext/helpers.tsx @@ -41,6 +41,10 @@ export const setChain = (dispatch: Dispatch, chain: ChainInfo) => { dispatch({ type: "setChain", payload: chain }); }; +export const loadValidators = (dispatch: Dispatch) => { + dispatch({ type: "loadValidators" }); +}; + export const setNewConnection = (dispatch: Dispatch, newConnection: NewConnection) => { dispatch({ type: "setNewConnection", payload: newConnection }); }; diff --git a/context/ChainsContext/index.tsx b/context/ChainsContext/index.tsx index bbfef9b..5233027 100644 --- a/context/ChainsContext/index.tsx +++ b/context/ChainsContext/index.tsx @@ -1,3 +1,4 @@ +import { getAllValidators } from "@/lib/staking"; import { ReactNode, createContext, useContext, useEffect, useReducer } from "react"; import { emptyChain, isChainInfoFilled, setChain, setChains, setChainsError } from "./helpers"; import { getChain, getNodeFromArray, useChainsFromRegistry } from "./service"; @@ -6,7 +7,7 @@ import { Action, ChainsContextType, State } from "./types"; const ChainsContext = createContext(undefined); -const chainsReducer = (state: State, action: Action) => { +const chainsReducer = (state: State, action: Action): State => { switch (action.type) { case "setChains": { return { ...state, chains: action.payload }; @@ -26,11 +27,23 @@ const chainsReducer = (state: State, action: Action) => { addRecentChainNameInStorage(action.payload.registryName); setChainInUrl(action.payload, state.chains); - return { ...state, chain: action.payload }; + return { + ...state, + chain: action.payload, + validatorState: { validators: [], status: "initial" }, + }; } case "addNodeAddress": { return { ...state, chain: { ...state.chain, nodeAddress: action.payload } }; } + case "loadValidators": { + return state.validatorState.status === "initial" + ? { ...state, validatorState: { ...state.validatorState, status: "loading" } } + : state; + } + case "setValidatorState": { + return { ...state, validatorState: action.payload }; + } case "setNewConnection": { return { ...state, newConnection: action.payload }; } @@ -52,6 +65,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" }, }); const { chainItems, chainItemsError } = useChainsFromRegistry(); @@ -78,6 +92,21 @@ export const ChainsProvider = ({ children }: ChainsProviderProps) => { })(); }, [state.chain]); + useEffect(() => { + (async function loadValidators() { + if (state.validatorState.status === "loading" && state.chain.nodeAddress) { + try { + const validators = await getAllValidators(state.chain.nodeAddress); + console.log({ validators }); + dispatch({ type: "setValidatorState", payload: { validators, status: "done" } }); + } catch (e) { + console.error(e instanceof Error ? e.message : "Failed to load validators"); + dispatch({ type: "setValidatorState", payload: { validators: [], status: "error" } }); + } + } + })(); + }, [state.chain.nodeAddress, state.validatorState.status]); + return {children}; }; diff --git a/context/ChainsContext/types.tsx b/context/ChainsContext/types.tsx index 5c8c650..20bf452 100644 --- a/context/ChainsContext/types.tsx +++ b/context/ChainsContext/types.tsx @@ -1,3 +1,4 @@ +import { Validator } from "cosmjs-types/cosmos/staking/v1beta1/staking"; import { RegistryAsset } from "../../types/chainRegistry"; export interface ChainsContextType { @@ -8,6 +9,7 @@ export interface ChainsContextType { export interface State { readonly chains: ChainItems; readonly chain: ChainInfo; + readonly validatorState: ValidatorState; readonly newConnection: NewConnection; readonly chainsError?: string | null; } @@ -36,6 +38,11 @@ export interface ChainInfo { readonly explorerLinks: ExplorerLinks; } +export interface ValidatorState { + readonly validators: readonly Validator[]; + readonly status: "initial" | "loading" | "done" | "error"; +} + export type ExplorerLinks = { readonly tx: string; readonly account: string; @@ -64,6 +71,13 @@ export type Action = readonly type: "addNodeAddress"; readonly payload: string; } + | { + readonly type: "loadValidators"; + } + | { + readonly type: "setValidatorState"; + readonly payload: ValidatorState; + } | { readonly type: "setNewConnection"; readonly payload: NewConnection; From 845a142be113ca05619a9a79be8bc6ea586bbe7d Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:35:30 +0100 Subject: [PATCH 4/8] Load validators with buttons --- components/forms/CreateTxForm/index.tsx | 26 ++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/components/forms/CreateTxForm/index.tsx b/components/forms/CreateTxForm/index.tsx index 1fee651..388c30b 100644 --- a/components/forms/CreateTxForm/index.tsx +++ b/components/forms/CreateTxForm/index.tsx @@ -1,3 +1,4 @@ +import { loadValidators } from "@/context/ChainsContext/helpers"; import { EncodeObject } from "@cosmjs/proto-signing"; import { Account, calculateFee } from "@cosmjs/stargate"; import { assert } from "@cosmjs/utils"; @@ -25,7 +26,11 @@ interface CreateTxFormProps { } const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormProps) => { - const { chain } = useChains(); + const { + chain, + validatorState: { validators }, + chainsDispatch, + } = useChains(); const [processing, setProcessing] = useState(false); const [msgTypes, setMsgTypes] = useState([]); @@ -45,6 +50,14 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro }); }; + const addMsgWithValidator = (newMsgType: MsgTypeUrl) => { + if (!validators.length) { + loadValidators(chainsDispatch); + } + + addMsgType(newMsgType); + }; + const createTx = async () => { try { setShowTxError(false); @@ -185,15 +198,18 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
  • -
  • -
@@ -201,7 +217,7 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
  • From ded8b665e658b3843e7fdc6a5b28832553e419bd Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:35:45 +0100 Subject: [PATCH 5/8] Add SelectValidator component --- components/SelectValidator.tsx | 83 ++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 components/SelectValidator.tsx diff --git a/components/SelectValidator.tsx b/components/SelectValidator.tsx new file mode 100644 index 0000000..13d3bc2 --- /dev/null +++ b/components/SelectValidator.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { useChains } from "@/context/ChainsContext"; +import { cn } from "@/lib/utils"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { useState } from "react"; + +interface SelectValidatorProps { + readonly validatorAddress: string; + readonly setValidatorAddress: (validatorAddress: string) => void; +} + +export default function SelectValidator({ + validatorAddress, + setValidatorAddress, +}: SelectValidatorProps) { + const { + validatorState: { validators }, + } = useChains(); + const [open, setOpen] = useState(false); + const [searchText, setSearchText] = useState(""); + + return ( + + + + + + + + No framework found. + + {validators?.map((validatorItem) => ( + { + setValidatorAddress(validatorItem.operatorAddress); + setOpen(false); + }} + > + + {validatorItem.description.moniker} + + ))} + + + + + ); +} From c644e2dff926e751c46dd94f203638100be433e7 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:35:58 +0100 Subject: [PATCH 6/8] Add SelectValidator to forms --- .../forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx | 5 +++++ .../forms/CreateTxForm/MsgForm/MsgDelegateForm.tsx | 5 +++++ .../forms/CreateTxForm/MsgForm/MsgRedelegateForm.tsx | 9 +++++++++ .../forms/CreateTxForm/MsgForm/MsgUndelegateForm.tsx | 5 +++++ 4 files changed, 24 insertions(+) diff --git a/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx index 46f6dc4..64eb507 100644 --- a/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx +++ b/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx @@ -1,3 +1,4 @@ +import SelectValidator from "@/components/SelectValidator"; import { MsgWithdrawDelegatorRewardEncodeObject } from "@cosmjs/stargate"; import { useEffect, useState } from "react"; import { MsgGetter } from ".."; @@ -63,6 +64,10 @@ const MsgClaimRewardsForm = ({

    MsgWithdrawDelegatorReward

    +

    MsgDelegate

    +

    MsgBeginRedelegate

    +
    +

    MsgUndelegate

    + Date: Thu, 8 Feb 2024 11:51:22 +0100 Subject: [PATCH 7/8] Use connectComet --- lib/staking.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/staking.ts b/lib/staking.ts index e2c864c..217dc56 100644 --- a/lib/staking.ts +++ b/lib/staking.ts @@ -1,5 +1,5 @@ import { QueryClient, StakingExtension, setupStakingExtension } from "@cosmjs/stargate"; -import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { connectComet } from "@cosmjs/tendermint-rpc"; import { Validator } from "cosmjs-types/cosmos/staking/v1beta1/staking"; const getValidatorsPage = ( @@ -10,8 +10,8 @@ const getValidatorsPage = ( export const getAllValidators = async (rpcUrl: string): Promise => { const validators: Validator[] = []; - const tmClient = await Tendermint34Client.connect(rpcUrl); - const queryClient = QueryClient.withExtensions(tmClient, setupStakingExtension); + const cometClient = await connectComet(rpcUrl); + const queryClient = QueryClient.withExtensions(cometClient, setupStakingExtension); let paginationKey: Uint8Array | undefined = undefined; From 73902f6decad59b3c583d9daee9bf0f6d1ee2531 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:52:37 +0100 Subject: [PATCH 8/8] Remove console log --- context/ChainsContext/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/context/ChainsContext/index.tsx b/context/ChainsContext/index.tsx index 5233027..38fc67e 100644 --- a/context/ChainsContext/index.tsx +++ b/context/ChainsContext/index.tsx @@ -97,7 +97,6 @@ export const ChainsProvider = ({ children }: ChainsProviderProps) => { if (state.validatorState.status === "loading" && state.chain.nodeAddress) { try { const validators = await getAllValidators(state.chain.nodeAddress); - console.log({ validators }); dispatch({ type: "setValidatorState", payload: { validators, status: "done" } }); } catch (e) { console.error(e instanceof Error ? e.message : "Failed to load validators");