diff --git a/package-lock.json b/package-lock.json index d9f1f0c0..007e92e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "simple-staking", "version": "0.3.9", "dependencies": { - "@babylonlabs-io/babylon-proto-ts": "0.0.3-canary.4", + "@babylonlabs-io/babylon-proto-ts": "0.0.3-canary.5", "@babylonlabs-io/bbn-core-ui": "^0.4.1", "@babylonlabs-io/bbn-wallet-connect": "^0.1.9", "@babylonlabs-io/btc-staking-ts": "0.4.0-canary.3", @@ -2060,9 +2060,9 @@ } }, "node_modules/@babylonlabs-io/babylon-proto-ts": { - "version": "0.0.3-canary.4", - "resolved": "https://registry.npmjs.org/@babylonlabs-io/babylon-proto-ts/-/babylon-proto-ts-0.0.3-canary.4.tgz", - "integrity": "sha512-t+mN1Z/d0NIcreOJkakFY+aHr5yKqNG5G2s4ZqPd2FQgZVLNZOQHrgpgNX1jiH4fOj8D4lj68RRvcqCtgbT6Ew==", + "version": "0.0.3-canary.5", + "resolved": "https://registry.npmjs.org/@babylonlabs-io/babylon-proto-ts/-/babylon-proto-ts-0.0.3-canary.5.tgz", + "integrity": "sha512-NhivyLgSdXqojMg5FdQJPxk5T1uttcMex8jnVTdZFQoIcesWOsCOxZ4V/IBT//8oLoa9K3rkND8aSP30hq7Tkw==", "license": "ISC", "dependencies": { "@bufbuild/protobuf": "^2.2.0" diff --git a/package.json b/package.json index 8e17bd04..a8686dde 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "node": "22.3.0" }, "dependencies": { - "@babylonlabs-io/babylon-proto-ts": "0.0.3-canary.4", + "@babylonlabs-io/babylon-proto-ts": "0.0.3-canary.5", "@babylonlabs-io/bbn-core-ui": "^0.4.1", "@babylonlabs-io/bbn-wallet-connect": "^0.1.9", "@babylonlabs-io/btc-staking-ts": "0.4.0-canary.3", diff --git a/src/app/hooks/client/api/useBTCTipHeight.ts b/src/app/hooks/client/api/useBTCTipHeight.ts deleted file mode 100644 index eafdec87..00000000 --- a/src/app/hooks/client/api/useBTCTipHeight.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useAPIQuery } from "@/app/hooks/client/api/useApi"; -import { getTipHeight } from "@/utils/mempool_api"; - -export const BTC_TIP_HEIGHT_KEY = "BTC_TIP_HEIGHT"; - -export function useBTCTipHeight() { - return useAPIQuery({ - queryKey: [BTC_TIP_HEIGHT_KEY], - queryFn: getTipHeight, - refetchInterval: 60000, // Refetch every 60 seconds - }); -} diff --git a/src/app/hooks/client/query/useBbnQueryClient.ts b/src/app/hooks/client/query/useBbnQueryClient.ts index 92d1b542..755b2d09 100644 --- a/src/app/hooks/client/query/useBbnQueryClient.ts +++ b/src/app/hooks/client/query/useBbnQueryClient.ts @@ -1,4 +1,7 @@ -import { incentivequery } from "@babylonlabs-io/babylon-proto-ts"; +import { + btclightclientquery, + incentivequery, +} from "@babylonlabs-io/babylon-proto-ts"; import { QueryClient, createProtobufRpcClient, @@ -8,8 +11,13 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { useCallback, useEffect, useState } from "react"; import { BBN_RPC_URL } from "@/app/common/rpc"; +import { ONE_MINUTE } from "@/app/constants"; import { useCosmosWallet } from "@/app/context/wallet/CosmosWalletProvider"; +import { useAPIQuery } from "../api/useApi"; + +export const BBN_BTCLIGHTCLIENT_TIP_KEY = "BBN_BTCLIGHTCLIENT_TIP"; + /** * Query service for Babylon which contains all the queries for * interacting with Babylon RPC nodes @@ -58,9 +66,25 @@ export const useBbnQueryClient = () => { return Number(balance?.amount ?? 0); }, [connected, queryClient, bech32Address]); + const btcTipQuery = useAPIQuery({ + queryKey: [BBN_BTCLIGHTCLIENT_TIP_KEY], + queryFn: async () => { + if (!queryClient) { + return undefined; + } + const { btclightQueryClient } = setupBtclightClientExtension(queryClient); + const req = btclightclientquery.QueryTipRequest.fromPartial({}); + const { header } = await btclightQueryClient.Tip(req); + return header; + }, + enabled: Boolean(queryClient), + staleTime: ONE_MINUTE, + }); + return { getRewards, getBalance, + btcTipQuery, }; }; @@ -74,3 +98,13 @@ const setupIncentiveExtension = ( const incentiveQueryClient = new incentivequery.QueryClientImpl(rpc); return { incentive: incentiveQueryClient }; }; + +const setupBtclightClientExtension = ( + base: QueryClient, +): { + btclightQueryClient: btclightclientquery.QueryClientImpl; +} => { + const rpc = createProtobufRpcClient(base); + const btclightQueryClient = new btclightclientquery.QueryClientImpl(rpc); + return { btclightQueryClient }; +}; diff --git a/src/app/hooks/services/useDelegationService.ts b/src/app/hooks/services/useDelegationService.ts index c4a09729..84894747 100644 --- a/src/app/hooks/services/useDelegationService.ts +++ b/src/app/hooks/services/useDelegationService.ts @@ -14,7 +14,6 @@ export type ActionType = keyof typeof ACTIONS; interface TxProps { stakingTxHashHex: string; stakingTxHex: string; - stakingHeight: number; paramsVersion: number; unbondingTxHex: string; covenantUnbondingSignatures?: { @@ -91,7 +90,7 @@ export function useDelegationService() { [ACTIONS.UNBOUND]: async ({ stakingInput, - stakingHeight, + paramsVersion, stakingTxHashHex, stakingTxHex, unbondingTxHex, @@ -103,7 +102,7 @@ export function useDelegationService() { await submitUnbondingTx( stakingInput, - stakingHeight, + paramsVersion, stakingTxHex, unbondingTxHex, covenantUnbondingSignatures.map((sig) => ({ @@ -121,12 +120,12 @@ export function useDelegationService() { [ACTIONS.WITHDRAW_ON_EARLY_UNBOUNDING]: async ({ stakingTxHashHex, stakingInput, - stakingHeight, + paramsVersion, unbondingTxHex, }: TxProps) => { await submitEarlyUnbondedWithdrawalTx( stakingInput, - stakingHeight, + paramsVersion, unbondingTxHex, ); @@ -139,7 +138,7 @@ export function useDelegationService() { [ACTIONS.WITHDRAW_ON_EARLY_UNBOUNDING_SLASHING]: async ({ stakingTxHashHex, stakingInput, - stakingHeight, + paramsVersion, unbondingSlashingTxHex, }) => { if (!unbondingSlashingTxHex) { @@ -150,7 +149,7 @@ export function useDelegationService() { await submitEarlyUnbondedWithdrawalTx( stakingInput, - stakingHeight, + paramsVersion, unbondingSlashingTxHex, ); @@ -162,13 +161,13 @@ export function useDelegationService() { [ACTIONS.WITHDRAW_ON_TIMELOCK]: async ({ stakingInput, - stakingHeight, + paramsVersion, stakingTxHashHex, stakingTxHex, }: TxProps) => { await submitTimelockUnbondedWithdrawalTx( stakingInput, - stakingHeight, + paramsVersion, stakingTxHex, ); @@ -180,7 +179,7 @@ export function useDelegationService() { [ACTIONS.WITHDRAW_ON_TIMELOCK_SLASHING]: async ({ stakingInput, - stakingHeight, + paramsVersion, stakingTxHashHex, slashingTxHex, }) => { @@ -190,7 +189,7 @@ export function useDelegationService() { await submitTimelockUnbondedWithdrawalTx( stakingInput, - stakingHeight, + paramsVersion, slashingTxHex, ); @@ -245,7 +244,6 @@ export function useDelegationService() { state, slashingTxHex, unbondingSlashingTxHex, - startHeight, } = delegation; const finalityProviderPk = finalityProviderBtcPksHex[0]; @@ -263,7 +261,6 @@ export function useDelegationService() { await execute?.({ stakingTxHashHex, stakingTxHex, - stakingHeight: startHeight, paramsVersion, unbondingTxHex, covenantUnbondingSignatures, diff --git a/src/app/hooks/services/useTransactionService.ts b/src/app/hooks/services/useTransactionService.ts index aea765b9..36d4140b 100644 --- a/src/app/hooks/services/useTransactionService.ts +++ b/src/app/hooks/services/useTransactionService.ts @@ -28,11 +28,11 @@ import { } from "@/utils/delegations"; import { getFeeRateFromMempool } from "@/utils/getFeeRateFromMempool"; import { getTxInfo, getTxMerkleProof } from "@/utils/mempool_api"; -import { getBbnParamByBtcHeight } from "@/utils/params"; +import { getBbnParamByBtcHeight, getBbnParamByVersion } from "@/utils/params"; import { BBN_REGISTRY_TYPE_URLS } from "@/utils/wallet/bbnRegistry"; -import { useBTCTipHeight } from "../client/api/useBTCTipHeight"; import { useNetworkFees } from "../client/api/useNetworkFees"; +import { useBbnQueryClient } from "../client/query/useBbnQueryClient"; import { useBbnTransaction } from "../client/query/useBbnTransaction"; export interface BtcStakingInputs { @@ -62,7 +62,9 @@ export const useTransactionService = () => { const { sendBbnTx } = useBbnTransaction(); const { data: networkFees } = useNetworkFees(); const { defaultFeeRate } = getFeeRateFromMempool(networkFees); - const { data: tipHeight } = useBTCTipHeight(); + const { + btcTipQuery: { data: tipHeader }, + } = useBbnQueryClient(); const { connected: cosmosConnected, @@ -79,7 +81,6 @@ export const useTransactionService = () => { pushTx, } = useBTCWallet(); - const latestParam = networkInfo?.params.bbnStakingParams?.latestParam; const versionedParams = networkInfo?.params.bbnStakingParams?.versions; /** @@ -109,16 +110,14 @@ export const useTransactionService = () => { validateStakingInput(stakingInput); - if (!latestParam) throw new Error("Staking params not loaded"); - if (!tipHeight) throw new Error("BTC tip height not loaded"); - // EOI and StakingTx must be created after the latestParam activation height - // This is to ensure that the EOI and StakingTx are valid - if (tipHeight < latestParam.btcActivationHeight - 1) { - throw new Error( - `BTC tip height ${tipHeight} is less than the - activation height ${latestParam.btcActivationHeight - 1}`, - ); - } + if (!tipHeader) throw new Error("BTC tip not loaded from Babylon chain"); + + if (!versionedParams || versionedParams.length == 0) + throw new Error("Staking params not loaded"); + + // Get the param based on the tip height + // EOI should always be created based on the BTC tip height from BBN chain + const p = getBbnParamByBtcHeight(tipHeader.height, versionedParams); const staking = new Staking( btcNetwork!, @@ -126,7 +125,7 @@ export const useTransactionService = () => { address, publicKeyNoCoordHex: publicKeyNoCoord, }, - latestParam, + p, stakingInput.finalityProviderPkNoCoordHex, stakingInput.stakingTimelock, ); @@ -144,7 +143,7 @@ export const useTransactionService = () => { transaction, bech32Address, { address, publicKeyNoCoordHex: publicKeyNoCoord }, - latestParam, + p, { signPsbt, signMessage, signingCallback }, ); await signingCallback(SigningStep.SEND_BBN, EOIStepStatus.PROCESSING); @@ -158,8 +157,8 @@ export const useTransactionService = () => { btcConnected, btcNetwork, signingStargateClient, - latestParam, - tipHeight, + tipHeader, + versionedParams, address, publicKeyNoCoord, inputUTXOs, @@ -186,17 +185,23 @@ export const useTransactionService = () => { btcNetwork, signingStargateClient, ); - if (!latestParam) throw new Error("Staking params not loaded"); + if (!tipHeader) throw new Error("BTC tip not loaded from Babylon chain"); validateStakingInput(stakingInput); + if (!versionedParams || versionedParams.length == 0) + throw new Error("Staking params not loaded"); + + // Get the param based on the tip height + const p = getBbnParamByBtcHeight(tipHeader.height, versionedParams); + const staking = new Staking( btcNetwork!, { address, publicKeyNoCoordHex: publicKeyNoCoord, }, - latestParam, + p, stakingInput.finalityProviderPkNoCoordHex, stakingInput.stakingTimelock, ); @@ -213,7 +218,8 @@ export const useTransactionService = () => { btcConnected, btcNetwork, signingStargateClient, - latestParam, + tipHeader, + versionedParams, address, publicKeyNoCoord, inputUTXOs, @@ -224,7 +230,7 @@ export const useTransactionService = () => { * Transition the delegation to phase 1 * * @param stakingTxHex - The staking transaction hex - * @param stakingHeight - The staking height + * @param stakingHeight - The staking height of the phase-1 delegation * @param stakingInput - The staking inputs * @param signingCallback - The signing callback */ @@ -319,20 +325,15 @@ export const useTransactionService = () => { stakingTxHex: string, ) => { // Perform checks - if (!latestParam) { + if (!versionedParams || versionedParams?.length === 0) { throw new Error("Staking parameters not loaded"); } if (!btcConnected || !btcNetwork) throw new Error("BTC Wallet not connected"); - validateStakingInput(stakingInput); - // Here we utilise the param version to check if the EOI is outdated - if (latestParam.version != paramVersion) { - throw new Error( - "The EOI is outdated due to there is a newer version of staking params", - ); - } + // Get the param based on version from the EOI + const p = getBbnParamByVersion(paramVersion, versionedParams); const staking = new Staking( btcNetwork!, @@ -340,7 +341,7 @@ export const useTransactionService = () => { address, publicKeyNoCoordHex: publicKeyNoCoord, }, - latestParam, + p, stakingInput.finalityProviderPkNoCoordHex, stakingInput.stakingTimelock, ); @@ -360,7 +361,7 @@ export const useTransactionService = () => { await pushTx(signedStakingTx.toHex()); }, [ - latestParam, + versionedParams, btcConnected, btcNetwork, address, @@ -371,10 +372,19 @@ export const useTransactionService = () => { ], ); + /** + * Submit the unbonding transaction + * + * @param stakingInput - The staking inputs + * @param paramVersion - The param version of the EOI + * @param stakingTxHex - The staking transaction hex + * @param unbondingTxHex - The unbonding transaction hex + * @param covenantUnbondingSignatures - The covenant unbonding signatures + */ const submitUnbondingTx = useCallback( async ( stakingInput: BtcStakingInputs, - stakingHeight: number, + paramVersion: number, stakingTxHex: string, unbondingTxHex: string, covenantUnbondingSignatures: { @@ -394,10 +404,10 @@ export const useTransactionService = () => { const stakingTx = Transaction.fromHex(stakingTxHex); // Get the staking params at the time of the staking transaction - const p = getBbnParamByBtcHeight(stakingHeight, versionedParams); + const p = getBbnParamByVersion(paramVersion, versionedParams); if (!p) throw new Error( - `Unable to find staking params for height ${stakingHeight}`, + `Unable to find staking params for version ${paramVersion}`, ); const staking = new Staking( @@ -457,13 +467,13 @@ export const useTransactionService = () => { * Withdraw from the early unbonding transaction which is now unbonded * * @param stakingInput - The staking inputs - * @param paramVersion - The param version - * @param unbondingTxHex - The unbonding transaction hex + * @param paramVersion - The param version of the EOI + * @param earlyUnbondingTxHex - The early unbonding transaction hex */ const submitEarlyUnbondedWithdrawalTx = useCallback( async ( stakingInput: BtcStakingInputs, - stakingHeight: number, + paramVersion: number, earlyUnbondingTxHex: string, ) => { // Perform checks @@ -475,10 +485,10 @@ export const useTransactionService = () => { validateStakingInput(stakingInput); - const p = getBbnParamByBtcHeight(stakingHeight, versionedParams); + const p = getBbnParamByVersion(paramVersion, versionedParams); if (!p) throw new Error( - `Unable to find staking params for height ${stakingHeight}`, + `Unable to find staking params for version ${paramVersion}`, ); const staking = new Staking( @@ -520,13 +530,13 @@ export const useTransactionService = () => { * Submit the timelock unbonded withdrawal transaction * * @param stakingInput - The staking inputs - * @param paramVersion - The param version + * @param paramVersion - The param version of the EOI * @param stakingTxHex - The staking transaction hex */ const submitTimelockUnbondedWithdrawalTx = useCallback( async ( stakingInput: BtcStakingInputs, - stakingHeight: number, + paramVersion: number, stakingTxHex: string, ) => { // Perform checks @@ -538,10 +548,10 @@ export const useTransactionService = () => { validateStakingInput(stakingInput); - const p = getBbnParamByBtcHeight(stakingHeight, versionedParams); + const p = getBbnParamByVersion(paramVersion, versionedParams); if (!p) throw new Error( - `Unable to find staking params for height ${stakingHeight}`, + `Unable to find staking params for version ${paramVersion}`, ); const staking = new Staking( diff --git a/src/app/state/index.tsx b/src/app/state/index.tsx index d1898a2e..8bea3135 100644 --- a/src/app/state/index.tsx +++ b/src/app/state/index.tsx @@ -1,7 +1,6 @@ import { useInscriptionProvider } from "@babylonlabs-io/bbn-wallet-connect"; import { useCallback, useMemo, type PropsWithChildren } from "react"; -import { useBTCTipHeight } from "@/app/hooks/client/api/useBTCTipHeight"; import { useOrdinals } from "@/app/hooks/client/api/useOrdinals"; import { useUTXOs } from "@/app/hooks/client/api/useUTXOs"; import { createStateUtils } from "@/utils/createStateUtils"; @@ -23,7 +22,6 @@ export interface AppState { availableUTXOs?: UTXO[]; totalBalance: number; networkInfo?: NetworkInfo; - currentHeight?: number; isError: boolean; isLoading: boolean; ordinalsExcluded: boolean; @@ -64,20 +62,9 @@ export function AppState({ children }: PropsWithChildren) { isError: isNetworkInfoError, } = useNetworkInfo(); - const { - data: height, - isLoading: isHeightLoading, - isError: isHeightError, - } = useBTCTipHeight(); - // Computed - const isLoading = - isHeightLoading || - isUTXOLoading || - isOrdinalLoading || - isNetworkInfoLoading; - const isError = - isHeightError || isUTXOError || isOrdinalError || isNetworkInfoError; + const isLoading = isUTXOLoading || isOrdinalLoading || isNetworkInfoLoading; + const isError = isUTXOError || isOrdinalError || isNetworkInfoError; const ordinalMap: Record = useMemo( () => @@ -116,7 +103,6 @@ export function AppState({ children }: PropsWithChildren) { const context = useMemo( () => ({ availableUTXOs, - currentHeight: height, totalBalance, networkInfo, isError, @@ -127,7 +113,6 @@ export function AppState({ children }: PropsWithChildren) { }), [ availableUTXOs, - height, totalBalance, networkInfo, isError, diff --git a/src/utils/params/index.ts b/src/utils/params/index.ts index 28a061c8..8c29e9e0 100644 --- a/src/utils/params/index.ts +++ b/src/utils/params/index.ts @@ -1,5 +1,11 @@ import { BbnStakingParamsVersion } from "@/app/types/networkInfo"; +/* + Get the BBN param by BTC height + @param height - The BTC height + @param bbnParams - The BBN params + @returns The BBN param +*/ export const getBbnParamByBtcHeight = ( height: number, bbnParams: BbnStakingParamsVersion[], @@ -10,5 +16,24 @@ export const getBbnParamByBtcHeight = ( ); // Find first param where height is >= btcActivationHeight - return sortedParams.find((param) => height >= param.btcActivationHeight); + const param = sortedParams.find( + (param) => height >= param.btcActivationHeight, + ); + if (!param) throw new Error(`BBN param not found for height ${height}`); + return param; +}; + +/* + Get the BBN param by version + @param version - The BBN param version + @param bbnParams - The BBN params + @returns The BBN param +*/ +export const getBbnParamByVersion = ( + version: number, + bbnParams: BbnStakingParamsVersion[], +): BbnStakingParamsVersion => { + const param = bbnParams.find((param) => param.version === version); + if (!param) throw new Error(`BBN param not found for version ${version}`); + return param; };