diff --git a/src/app/state/index.tsx b/src/app/state/index.tsx
index d1898a2e..15da12d1 100644
--- a/src/app/state/index.tsx
+++ b/src/app/state/index.tsx
@@ -21,6 +21,7 @@ const STATE_LIST = [DelegationState, DelegationV2State];
export interface AppState {
availableUTXOs?: UTXO[];
+ allUTXOs?: UTXO[];
totalBalance: number;
networkInfo?: NetworkInfo;
currentHeight?: number;
@@ -116,6 +117,7 @@ export function AppState({ children }: PropsWithChildren) {
const context = useMemo(
() => ({
availableUTXOs,
+ allUTXOs: utxos,
currentHeight: height,
totalBalance,
networkInfo,
@@ -127,6 +129,7 @@ export function AppState({ children }: PropsWithChildren) {
}),
[
availableUTXOs,
+ utxos,
height,
totalBalance,
networkInfo,
diff --git a/src/components/delegations/DelegationList/components/ActionButton.tsx b/src/components/delegations/DelegationList/components/ActionButton.tsx
index 0c3098f9..079ab307 100644
--- a/src/components/delegations/DelegationList/components/ActionButton.tsx
+++ b/src/components/delegations/DelegationList/components/ActionButton.tsx
@@ -1,9 +1,14 @@
+import { Transaction } from "bitcoinjs-lib";
+import { Tooltip } from "react-tooltip";
+
import { DELEGATION_ACTIONS as ACTIONS } from "@/app/constants";
import { ActionType } from "@/app/hooks/services/useDelegationService";
+import { useAppState } from "@/app/state";
import {
DelegationV2,
DelegationV2StakingState as State,
} from "@/app/types/delegationsV2";
+import { isTransactionInputAvailable } from "@/utils/delegations";
interface ActionButtonProps {
delegation: DelegationV2;
@@ -42,16 +47,43 @@ const ACTION_BUTTON_PROPS: Record<
};
export function ActionButton(props: ActionButtonProps) {
+ const { allUTXOs } = useAppState();
const buttonProps = ACTION_BUTTON_PROPS[props.state];
- if (!buttonProps) return null;
+ if (!buttonProps || !allUTXOs) return null;
+ const delegation = props.delegation;
+
+ // For verifed delegation where user will perform stake action,
+ // we need to verify that the UTXOs are still available
+ // to avoid the user to perform the action on a spent UTXO
+ let isButtonDisabled = false;
+ if (buttonProps.action === ACTIONS.STAKE) {
+ isButtonDisabled = !isTransactionInputAvailable(
+ Transaction.fromHex(delegation.stakingTxHex),
+ allUTXOs,
+ );
+ }
return (
-
+
+
+
);
}
diff --git a/src/utils/delegations/index.ts b/src/utils/delegations/index.ts
index 46a432fb..49d6eb30 100644
--- a/src/utils/delegations/index.ts
+++ b/src/utils/delegations/index.ts
@@ -1,3 +1,4 @@
+import { UTXO } from "@babylonlabs-io/btc-staking-ts";
import { Transaction } from "bitcoinjs-lib";
import { BtcStakingInputs } from "@/app/hooks/services/useTransactionService";
@@ -58,3 +59,23 @@ export const validateStakingInput = (stakingInput: BtcStakingInputs) => {
if (!stakingInput.stakingAmountSat) throw new Error("Staking amount not set");
if (!stakingInput.stakingTimelock) throw new Error("Staking time not set");
};
+
+/**
+ * Verifies that the transaction inputs are still available from the UTXOs set.
+ * @param tx - The transaction to verify.
+ * @param allUTXOs - The UTXOs set.
+ * @returns True if the transaction inputs are still available, false otherwise.
+ */
+export const isTransactionInputAvailable = (
+ tx: Transaction,
+ allUTXOs: UTXO[],
+) => {
+ return tx.ins.every((input) => {
+ return allUTXOs.find((utxo) => {
+ return (
+ utxo.txid === Buffer.from(input.hash).reverse().toString("hex") &&
+ utxo.vout === input.index
+ );
+ });
+ });
+};