diff --git a/src/ui/baby/index.tsx b/src/ui/baby/index.tsx index 4900d4b71..8c03df268 100644 --- a/src/ui/baby/index.tsx +++ b/src/ui/baby/index.tsx @@ -1,9 +1,366 @@ -import Layout from "@/ui/legacy/layout"; +import { useState } from "react"; + +import { useCosmosWallet } from "@/ui/common/context/wallet/CosmosWalletProvider"; +import { useBbnQuery } from "@/ui/legacy/hooks/client/rpc/queries/useBbnQuery"; +import { useEpochingService } from "@/ui/legacy/hooks/services/useEpochingService"; +import { babyToUbbn, ubbnToBaby } from "@/ui/legacy/utils/bbn"; export default function BabyStaking() { + const cosmosWallet = useCosmosWallet(); + const { bech32Address, connected } = cosmosWallet; + console.log({ cosmosWallet }); + const { stake, unstake, claimRewards } = useEpochingService(); + const { delegationsQuery, delegationRewardsQuery, validatorsQuery } = + useBbnQuery(); + + const [validatorAddress, setValidatorAddress] = useState(""); + const [stakeAmount, setStakeAmount] = useState(""); + const [unstakeAmount, setUnstakeAmount] = useState(""); + const [loading, setLoading] = useState(false); + + const delegations = delegationsQuery.data || []; + const rewards = delegationRewardsQuery.data || []; + const validators = validatorsQuery.data || []; + + // Helper function to get rewards for a specific validator + const getRewardsForValidator = (validatorAddress: string): number => { + const validatorReward = rewards.find( + (reward) => reward.validatorAddress === validatorAddress, + ); + if (!validatorReward?.reward) return 0; + + return validatorReward.reward.reduce((total, coin) => { + if (coin.denom === "ubbn") { + return total + ubbnToBaby(parseFloat(coin.amount)); + } + return total; + }, 0); + }; + + const totalStaked = delegations.reduce((total, delegation) => { + if (delegation.balance?.denom === "ubbn") { + return total + ubbnToBaby(parseFloat(delegation.balance.amount)); + } + return total; + }, 0); + + const totalRewards = rewards.reduce((total, reward) => { + return ( + total + + (reward.reward || []).reduce((acc, coin) => { + if (coin.denom === "ubbn") { + return acc + ubbnToBaby(parseFloat(coin.amount)); + } + return acc; + }, 0) + ); + }, 0); + + const handleStake = async () => { + if (!validatorAddress || !stakeAmount) return; + + const tbabyAmount = parseFloat(stakeAmount); + if (isNaN(tbabyAmount) || tbabyAmount <= 0) { + alert("Please enter a valid amount"); + return; + } + + setLoading(true); + try { + const ubbnAmount = babyToUbbn(tbabyAmount); + await stake(bech32Address, validatorAddress, { + denom: "ubbn", + amount: ubbnAmount.toString(), + }); + alert(`Successfully staked ${tbabyAmount} tBABY!`); + await delegationsQuery.refetch(); + await delegationRewardsQuery.refetch(); + setStakeAmount(""); + } catch (error: any) { + alert(`Staking failed: ${error.message || "Unknown error"}`); + } finally { + setLoading(false); + } + }; + + const handleUnstake = async () => { + if (!validatorAddress || !unstakeAmount) return; + + const tbabyAmount = parseFloat(unstakeAmount); + if (isNaN(tbabyAmount) || tbabyAmount <= 0) { + alert("Please enter a valid amount"); + return; + } + + setLoading(true); + try { + const ubbnAmount = babyToUbbn(tbabyAmount); + await unstake(bech32Address, validatorAddress, { + denom: "ubbn", + amount: ubbnAmount.toString(), + }); + alert(`Successfully unstaked ${tbabyAmount} tBABY!`); + await delegationsQuery.refetch(); + await delegationRewardsQuery.refetch(); + setUnstakeAmount(""); + } catch (error: any) { + alert(`Unstaking failed: ${error.message || "Unknown error"}`); + } finally { + setLoading(false); + } + }; + + const handleClaimRewards = async () => { + if (!validatorAddress) return; + + setLoading(true); + try { + await claimRewards(bech32Address, validatorAddress); + await delegationRewardsQuery.refetch(); + alert("Rewards claimed successfully!"); + } catch (error: any) { + alert(`Failed to claim rewards: ${error.message || "Unknown error"}`); + } finally { + setLoading(false); + } + }; + + if (!connected) { + return ( +
+
+

Connect Your Wallet

+

+ Please connect your Babylon wallet to use staking features. +

+
+
+ ); + } + return ( - -
Baby staking
-
+
+

Baby Staking

+ +
+

Wallet Info

+

Address: {bech32Address}

+
+
+

Active Delegations

+

{delegations.length}

+
+
+

Total Staked

+

+ {totalStaked.toLocaleString()} tBABY +

+
+
+

Total Rewards

+

+ {totalRewards.toLocaleString()} tBABY +

+
+
+

Available Validators

+

{validators.length}

+
+
+
+ + {delegations.length > 0 && ( +
+

My Delegations

+
+ {delegations.map((delegation, index) => { + const validatorAddress = + delegation.delegation?.validatorAddress || ""; + const stakedAmount = ubbnToBaby( + parseFloat(delegation.balance?.amount || "0"), + ); + const rewardsAmount = getRewardsForValidator(validatorAddress); + + return ( +
+
+

+ {validators.find( + (v) => v.operatorAddress === validatorAddress, + )?.description?.moniker || validatorAddress} +

+

+ Staked: {stakedAmount.toLocaleString()} tBABY +

+ {rewardsAmount > 0 && ( +

+ Rewards: {rewardsAmount.toLocaleString()} tBABY +

+ )} +
+
+ + {rewardsAmount > 0 && ( + + )} +
+
+ ); + })} +
+
+ )} + +
+ {/* Stake Section */} +
+

Stake

+
+
+ + +
+
+ + setStakeAmount(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="Enter tBABY amount to stake (e.g., 10)" + /> +
+ +
+
+ + {/* Unstake Section */} +
+

Unstake

+
+
+ + +
+
+ + setUnstakeAmount(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500" + placeholder="Enter tBABY amount to unstake (e.g., 5)" + /> +
+ +
+
+ + {/* Rewards Section */} +
+

Rewards

+
+
+ + +
+ +
+
+
+
); } diff --git a/src/ui/common/components/Header/Header.tsx b/src/ui/common/components/Header/Header.tsx index 8e51bc3f9..bb6b92b95 100644 --- a/src/ui/common/components/Header/Header.tsx +++ b/src/ui/common/components/Header/Header.tsx @@ -1,4 +1,6 @@ import { useWalletConnect } from "@babylonlabs-io/wallet-connector"; +import { Link, useLocation } from "react-router"; +import { twJoin } from "tailwind-merge"; import { Container } from "@/ui/common/components/Container/Container"; import { useAppState } from "@/ui/common/state"; @@ -10,12 +12,41 @@ export const Header = () => { const { open } = useWalletConnect(); const { isLoading: loading } = useAppState(); + const { pathname } = useLocation(); + return (
- - + +
+ +
+ + -
+
diff --git a/src/ui/legacy/hooks/client/rpc/queries/useBbnQuery.ts b/src/ui/legacy/hooks/client/rpc/queries/useBbnQuery.ts index e087f0b35..ac9c403eb 100644 --- a/src/ui/legacy/hooks/client/rpc/queries/useBbnQuery.ts +++ b/src/ui/legacy/hooks/client/rpc/queries/useBbnQuery.ts @@ -6,7 +6,14 @@ import { QueryClient, createProtobufRpcClient, setupBankExtension, + setupDistributionExtension, + setupStakingExtension, } from "@cosmjs/stargate"; +import { QueryDelegationTotalRewardsResponse } from "cosmjs-types/cosmos/distribution/v1beta1/query"; +import { + QueryDelegatorDelegationsResponse, + QueryValidatorsResponse, +} from "cosmjs-types/cosmos/staking/v1beta1/query"; import { ONE_MINUTE } from "@/ui/legacy/constants"; import { useBbnRpc } from "@/ui/legacy/context/rpc/BbnRpcProvider"; @@ -14,6 +21,7 @@ import { useCosmosWallet } from "@/ui/legacy/context/wallet/CosmosWalletProvider import { ClientError } from "@/ui/legacy/errors"; import { ERROR_CODES } from "@/ui/legacy/errors/codes"; import { useHealthCheck } from "@/ui/legacy/hooks/useHealthCheck"; +import { normalizeRewardResponse } from "@/ui/legacy/utils/bbn"; import { useClientQuery } from "../../useClient"; import { useRpcErrorHandler } from "../useRpcErrorHandler"; @@ -21,6 +29,9 @@ import { useRpcErrorHandler } from "../useRpcErrorHandler"; const BBN_BTCLIGHTCLIENT_TIP_KEY = "BBN_BTCLIGHTCLIENT_TIP"; const BBN_BALANCE_KEY = "BBN_BALANCE"; const BBN_REWARDS_KEY = "BBN_REWARDS"; +const BBN_DELEGATIONS_KEY = "BBN_DELEGATIONS"; +const BBN_DELEGATION_REWARDS_KEY = "BBN_DELEGATION_REWARDS"; +const BBN_VALIDATORS_KEY = "BBN_VALIDATORS"; const REWARD_GAUGE_KEY_BTC_DELEGATION = "BTC_STAKER"; /** @@ -34,7 +45,7 @@ export const useBbnQuery = () => { const { hasRpcError, reconnect } = useRpcErrorHandler(); /** - * Gets the rewards from the user's account. + * [BTC Staking] Gets the rewards of the user's account. * @returns {Promise} - The rewards from the user's account. */ const rewardsQuery = useClientQuery({ @@ -143,10 +154,119 @@ export const useBbnQuery = () => { refetchInterval: false, // Disable automatic periodic refetching }); + /** + * [BABY Staking] Gets all delegations of the user's account. + */ + const delegationsQuery = useClientQuery({ + queryKey: [BBN_DELEGATIONS_KEY, bech32Address, connected], + queryFn: async () => { + if (!connected || !queryClient || !bech32Address) { + return undefined; + } + + const { staking } = setupStakingExtension(queryClient); + + try { + const response: QueryDelegatorDelegationsResponse = + await staking.delegatorDelegations(bech32Address); + return response.delegationResponses || []; + } catch (error) { + throw new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Error getting delegations", + { cause: error as Error }, + ); + } + }, + enabled: Boolean( + queryClient && + connected && + bech32Address && + !isGeoBlocked && + !isHealthcheckLoading, + ), + staleTime: ONE_MINUTE, + refetchInterval: ONE_MINUTE, + }); + + /** + * [BABY Staking] Gets all delegation rewards of the user's account. + */ + const delegationRewardsQuery = useClientQuery({ + queryKey: [BBN_DELEGATION_REWARDS_KEY, bech32Address, connected], + queryFn: async () => { + if (!connected || !queryClient || !bech32Address) { + return undefined; + } + + const { distribution } = setupDistributionExtension(queryClient); + + try { + const response: QueryDelegationTotalRewardsResponse = + await distribution.delegationTotalRewards(bech32Address); + + // Need to normalize the response to 6 decimals + const rewards = normalizeRewardResponse(response); + + return rewards || []; + } catch (error) { + // If no rewards found, return empty array + if (error instanceof Error && error.message.includes("no delegation")) { + return []; + } + throw new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Error getting delegation rewards", + { cause: error as Error }, + ); + } + }, + enabled: Boolean( + queryClient && + connected && + bech32Address && + !isGeoBlocked && + !isHealthcheckLoading, + ), + staleTime: ONE_MINUTE, + refetchInterval: ONE_MINUTE, + }); + + /** + * [BABY Staking] Gets all the validators. + */ + const validatorsQuery = useClientQuery({ + queryKey: [BBN_VALIDATORS_KEY], + queryFn: async () => { + if (!queryClient) { + return undefined; + } + + const { staking } = setupStakingExtension(queryClient); + + try { + const response: QueryValidatorsResponse = await staking.validators(""); + return response.validators || []; + } catch (error) { + throw new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Error getting validators", + { cause: error as Error }, + ); + } + }, + enabled: Boolean(queryClient && !isGeoBlocked && !isHealthcheckLoading), + staleTime: ONE_MINUTE, + refetchInterval: ONE_MINUTE, + }); + return { rewardsQuery, balanceQuery, btcTipQuery, + delegationsQuery, + delegationRewardsQuery, + validatorsQuery, hasRpcError, reconnectRpc: reconnect, queryClient, diff --git a/src/ui/legacy/hooks/services/useEpochingService.ts b/src/ui/legacy/hooks/services/useEpochingService.ts new file mode 100644 index 000000000..8416976c2 --- /dev/null +++ b/src/ui/legacy/hooks/services/useEpochingService.ts @@ -0,0 +1,262 @@ +import { epochingtx } from "@babylonlabs-io/babylon-proto-ts"; +import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; +import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; +import { useCallback } from "react"; + +import { useError } from "@/ui/legacy/context/Error/ErrorProvider"; +import { useLogger } from "@/ui/legacy/hooks/useLogger"; +import { BBN_REGISTRY_TYPE_URLS } from "@/ui/legacy/utils/wallet/bbnRegistry"; + +import { useBbnTransaction } from "../client/rpc/mutation/useBbnTransaction"; + +export const useEpochingService = () => { + const { handleError } = useError(); + const logger = useLogger(); + const { estimateBbnGasFee, sendBbnTx, signBbnTx } = useBbnTransaction(); + + /** + * Estimates the gas fee for BABY staking. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @param amount - The amount to stake + * @returns The gas fee for staking + */ + const estimateStakeGas = useCallback( + async ( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, + ): Promise => { + const stakeMsg = createStakeMsg( + delegatorAddress, + validatorAddress, + amount, + ); + const gasFee = await estimateBbnGasFee(stakeMsg); + return gasFee.amount.reduce((acc, coin) => acc + Number(coin.amount), 0); + }, + [estimateBbnGasFee], + ); + + /** + * Estimates the gas fee for unstaking. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @param amount - The amount to unstake + * @returns The gas fee for unstaking + */ + const estimateUnstakeGas = useCallback( + async ( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, + ): Promise => { + const unstakeMsg = createUnstakeMsg( + delegatorAddress, + validatorAddress, + amount, + ); + const gasFee = await estimateBbnGasFee(unstakeMsg); + return gasFee.amount.reduce((acc, coin) => acc + Number(coin.amount), 0); + }, + [estimateBbnGasFee], + ); + + /** + * Estimates the gas fee for claiming rewards. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @returns The gas fee for claiming rewards + */ + const estimateClaimRewardsGas = useCallback( + async ( + delegatorAddress: string, + validatorAddress: string, + ): Promise => { + const claimMsg = createClaimRewardsMsg( + delegatorAddress, + validatorAddress, + ); + const gasFee = await estimateBbnGasFee(claimMsg); + return gasFee.amount.reduce((acc, coin) => acc + Number(coin.amount), 0); + }, + [estimateBbnGasFee], + ); + + /** + * Stakes tokens with a validator. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @param amount - The amount to stake + * @returns The transaction result + */ + const stake = useCallback( + async ( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, + ) => { + try { + const msg = createStakeMsg(delegatorAddress, validatorAddress, amount); + const signedTx = await signBbnTx(msg); + const result = await sendBbnTx(signedTx); + + logger.info("Stake transaction completed", { + txHash: result?.txHash, + }); + + return result; + } catch (error: any) { + logger.error(error); + handleError({ error }); + throw error; + } + }, + [signBbnTx, sendBbnTx, logger, handleError], + ); + + /** + * Unstakes tokens from a validator. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @param amount - The amount to unstake + * @returns The transaction result + */ + const unstake = useCallback( + async ( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, + ) => { + try { + const msg = createUnstakeMsg( + delegatorAddress, + validatorAddress, + amount, + ); + const signedTx = await signBbnTx(msg); + const result = await sendBbnTx(signedTx); + + logger.info("Unstake transaction completed", { + txHash: result?.txHash, + }); + + return result; + } catch (error: any) { + logger.error(error); + handleError({ error }); + throw error; + } + }, + [signBbnTx, sendBbnTx, logger, handleError], + ); + + /** + * Claims rewards from a validator. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @returns The transaction result + */ + const claimRewards = useCallback( + async (delegatorAddress: string, validatorAddress: string) => { + try { + const msg = createClaimRewardsMsg(delegatorAddress, validatorAddress); + const signedTx = await signBbnTx(msg); + const result = await sendBbnTx(signedTx); + + logger.info("Claim rewards transaction completed", { + txHash: result?.txHash, + }); + + return result; + } catch (error: any) { + logger.error(error); + handleError({ error }); + throw error; + } + }, + [signBbnTx, sendBbnTx, logger, handleError], + ); + + return { + stake, + unstake, + claimRewards, + estimateStakeGas, + estimateUnstakeGas, + estimateClaimRewardsGas, + }; +}; + +/** + * Creates a wrapped delegate message for staking. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @param amount - The amount to stake + * @returns The wrapped delegate message + */ +const createStakeMsg = ( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, +) => { + const wrappedDelegateMsg = epochingtx.MsgWrappedDelegate.fromPartial({ + msg: { + delegatorAddress, + validatorAddress, + amount, + }, + }); + + return { + typeUrl: BBN_REGISTRY_TYPE_URLS.MsgWrappedDelegate, + value: wrappedDelegateMsg, + }; +}; + +/** + * Creates a wrapped undelegate message for unstaking. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @param amount - The amount to unstake + * @returns The wrapped undelegate message + */ +const createUnstakeMsg = ( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, +) => { + const wrappedUndelegateMsg = epochingtx.MsgWrappedUndelegate.fromPartial({ + msg: { + delegatorAddress, + validatorAddress, + amount, + }, + }); + + return { + typeUrl: BBN_REGISTRY_TYPE_URLS.MsgWrappedUndelegate, + value: wrappedUndelegateMsg, + }; +}; + +/** + * Creates a withdraw delegator reward message for claiming rewards. + * @param delegatorAddress - The delegator address + * @param validatorAddress - The validator address + * @returns The withdraw delegator reward message + */ +const createClaimRewardsMsg = ( + delegatorAddress: string, + validatorAddress: string, +) => { + const withdrawRewardMsg = MsgWithdrawDelegatorReward.fromPartial({ + delegatorAddress, + validatorAddress, + }); + + return { + typeUrl: BBN_REGISTRY_TYPE_URLS.MsgWithdrawDelegatorReward, + value: withdrawRewardMsg, + }; +}; diff --git a/src/ui/legacy/utils/bbn.ts b/src/ui/legacy/utils/bbn.ts index 735de9c8f..6bfcc8f3b 100644 --- a/src/ui/legacy/utils/bbn.ts +++ b/src/ui/legacy/utils/bbn.ts @@ -1,3 +1,5 @@ +import { QueryDelegationTotalRewardsResponse } from "cosmjs-types/cosmos/distribution/v1beta1/query"; + /** * Converts BABY to uBBN (micro BABY). * should be used internally in the app @@ -17,3 +19,32 @@ export function babyToUbbn(bbn: number): number { export function ubbnToBaby(ubbn: number): number { return ubbn / 1e6; } + +/** + * Normalizes CosmJS amount from 18-decimal precision to standard ubbn format. + * CosmJS returns amounts with 18 decimal places, but standard ubbn format uses fewer decimals. + * @param amount The amount string from CosmJS (with 18 decimal precision). + * @returns The normalized amount as a string in standard ubbn format. + */ +export function normalizeCosmjsAmount(amount: string): string { + const numAmount = parseFloat(amount); + return (numAmount / 1e18).toString(); +} + +/** + * Normalizes reward response from CosmJS to standard ubbn format. + * Applies amount normalization to all coin amounts in the reward structure. + * @param response The QueryDelegationTotalRewardsResponse from CosmJS. + * @returns The normalized reward array with amounts in standard ubbn format. + */ +export function normalizeRewardResponse( + response: QueryDelegationTotalRewardsResponse, +) { + return response.rewards?.map((reward) => ({ + ...reward, + reward: reward.reward?.map((coin) => ({ + ...coin, + amount: normalizeCosmjsAmount(coin.amount), + })), + })); +} diff --git a/src/ui/legacy/utils/wallet/amino.ts b/src/ui/legacy/utils/wallet/amino.ts index d1ef97fc5..9dae4bf8d 100644 --- a/src/ui/legacy/utils/wallet/amino.ts +++ b/src/ui/legacy/utils/wallet/amino.ts @@ -1,10 +1,16 @@ -import { btcstakingtx, incentivetx } from "@babylonlabs-io/babylon-proto-ts"; +import { + btcstakingtx, + epochingtx, + incentivetx, +} from "@babylonlabs-io/babylon-proto-ts"; import { AminoTypes } from "@cosmjs/stargate"; +import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; import { ClientError, ERROR_CODES } from "@/ui/legacy/errors"; import { BBN_REGISTRY_TYPE_URLS } from "./bbnRegistry"; +// BTC Staking const msgCreateBTCDelegationConverter = { [BBN_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation]: { aminoType: BBN_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation, @@ -107,6 +113,7 @@ const msgCreateBTCDelegationConverter = { }, }; +// Incentives - Claiming BABY rewards from BTC Staking const msgWithdrawRewardConverter = { [BBN_REGISTRY_TYPE_URLS.MsgWithdrawReward]: { aminoType: BBN_REGISTRY_TYPE_URLS.MsgWithdrawReward, @@ -125,9 +132,81 @@ const msgWithdrawRewardConverter = { }, }; +// Epoching - Staking BABY +const msgWrappedDelegateConverter = { + [BBN_REGISTRY_TYPE_URLS.MsgWrappedDelegate]: { + aminoType: BBN_REGISTRY_TYPE_URLS.MsgWrappedDelegate, + toAmino: (msg: epochingtx.MsgWrappedDelegate) => { + return { + msg: { + delegator_address: msg.msg?.delegatorAddress, + validator_address: msg.msg?.validatorAddress, + amount: msg.msg?.amount, + }, + }; + }, + fromAmino: (json: any): epochingtx.MsgWrappedDelegate => { + return { + msg: { + delegatorAddress: json.msg.delegator_address, + validatorAddress: json.msg.validator_address, + amount: json.msg.amount, + }, + }; + }, + }, +}; + +// Epoching - Unstaking BABY +const msgWrappedUndelegateConverter = { + [BBN_REGISTRY_TYPE_URLS.MsgWrappedUndelegate]: { + aminoType: BBN_REGISTRY_TYPE_URLS.MsgWrappedUndelegate, + toAmino: (msg: epochingtx.MsgWrappedUndelegate) => { + return { + msg: { + delegator_address: msg.msg?.delegatorAddress, + validator_address: msg.msg?.validatorAddress, + amount: msg.msg?.amount, + }, + }; + }, + fromAmino: (json: any): epochingtx.MsgWrappedUndelegate => { + return { + msg: { + delegatorAddress: json.msg.delegator_address, + validatorAddress: json.msg.validator_address, + amount: json.msg.amount, + }, + }; + }, + }, +}; + +// Cosmos Distribution - Claiming BABY rewards from BABY Staking +const msgWithdrawDelegatorRewardConverter = { + [BBN_REGISTRY_TYPE_URLS.MsgWithdrawDelegatorReward]: { + aminoType: BBN_REGISTRY_TYPE_URLS.MsgWithdrawDelegatorReward, + toAmino: (msg: MsgWithdrawDelegatorReward) => { + return { + delegator_address: msg.delegatorAddress, + validator_address: msg.validatorAddress, + }; + }, + fromAmino: (json: any): MsgWithdrawDelegatorReward => { + return { + delegatorAddress: json.delegator_address, + validatorAddress: json.validator_address, + }; + }, + }, +}; + export const bbnAminoConverters = { ...msgCreateBTCDelegationConverter, ...msgWithdrawRewardConverter, + ...msgWrappedDelegateConverter, + ...msgWrappedUndelegateConverter, + ...msgWithdrawDelegatorRewardConverter, }; export function createBbnAminoTypes(): AminoTypes { diff --git a/src/ui/legacy/utils/wallet/bbnRegistry.ts b/src/ui/legacy/utils/wallet/bbnRegistry.ts index 208475bff..fe2afe5ce 100644 --- a/src/ui/legacy/utils/wallet/bbnRegistry.ts +++ b/src/ui/legacy/utils/wallet/bbnRegistry.ts @@ -1,6 +1,11 @@ -import { btcstakingtx, incentivetx } from "@babylonlabs-io/babylon-proto-ts"; +import { + btcstakingtx, + epochingtx, + incentivetx, +} from "@babylonlabs-io/babylon-proto-ts"; import { MessageFns } from "@babylonlabs-io/babylon-proto-ts/dist/generated/google/protobuf/any"; import { GeneratedType, Registry } from "@cosmjs/proto-signing"; +import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; // Define the structure of each proto to register type ProtoToRegister = { @@ -11,6 +16,10 @@ type ProtoToRegister = { export const BBN_REGISTRY_TYPE_URLS = { MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation", MsgWithdrawReward: "/babylon.incentive.MsgWithdrawReward", + MsgWrappedDelegate: "/babylon.epoching.v1.MsgWrappedDelegate", + MsgWrappedUndelegate: "/babylon.epoching.v1.MsgWrappedUndelegate", + MsgWithdrawDelegatorReward: + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", }; // List of protos to register in the registry @@ -20,11 +29,25 @@ const protosToRegister: ProtoToRegister[] = [ typeUrl: BBN_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation, messageType: btcstakingtx.MsgCreateBTCDelegation, }, - // Incentives + // Incentives - Claiming BABY rewards from BTC Staking { typeUrl: BBN_REGISTRY_TYPE_URLS.MsgWithdrawReward, messageType: incentivetx.MsgWithdrawReward, }, + // Epoching - Staking / Unstaking BABY + { + typeUrl: BBN_REGISTRY_TYPE_URLS.MsgWrappedDelegate, + messageType: epochingtx.MsgWrappedDelegate, + }, + { + typeUrl: BBN_REGISTRY_TYPE_URLS.MsgWrappedUndelegate, + messageType: epochingtx.MsgWrappedUndelegate, + }, + // Cosmos Distribution - Claiming rewards from BABY Staking + { + typeUrl: BBN_REGISTRY_TYPE_URLS.MsgWithdrawDelegatorReward, + messageType: MsgWithdrawDelegatorReward as any, + }, ]; // Utility function to create a `GeneratedType` from `MessageFns`