Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 120 additions & 20 deletions src/ui/common/components/Rewards/Rewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import babyTokenIcon from "@/ui/common/assets/baby-token.svg";
import { AuthGuard } from "@/ui/common/components/Common/AuthGuard";
import { Section } from "@/ui/common/components/Section/Section";
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
import { useBbnQuery } from "@/ui/common/hooks/client/rpc/queries/useBbnQuery";
import { useRewardsService } from "@/ui/common/hooks/services/useRewardsService";
import { useRewardsState } from "@/ui/common/state/RewardState";
import { ubbnToBaby } from "@/ui/common/utils/bbn";
Expand Down Expand Up @@ -54,39 +55,136 @@ export function Rewards() {
} = useRewardsState();

const { showPreview, claimRewards } = useRewardsService();
const { rewardCoinsQuery } = useBbnQuery();

const { networkName: bbnNetworkName, coinSymbol: bbnCoinSymbol } =
getNetworkConfigBBN();

// BABY / tBABY reward
// Build rewards list from per-denom rewards; fallback to BABY only if empty
const formattedRewardBaby = rewardBalance
? ubbnToBaby(rewardBalance).toString()
: "0";
const babyIcon = /BABY$/i.test(bbnCoinSymbol)
? babyTokenIcon
: generatePlaceholder(bbnCoinSymbol.charAt(0));
const rewards: RewardItem[] = [
{
amount: formattedRewardBaby,
currencyIcon: babyIcon,
chainName: bbnNetworkName,
currencyName: bbnCoinSymbol,
placeholder: "0",
displayBalance: true,
balanceDetails: {
balance: formattedRewardBaby,
symbol: bbnCoinSymbol,
price: 0,
displayUSD: false,
decimals: 6,
},
},
];

const rewards: RewardItem[] = (() => {
const coins = rewardCoinsQuery.data ?? [];
console.log("[RewardsUI] rewardCoinsQuery", {
loading: rewardCoinsQuery.isLoading,
error: rewardCoinsQuery.error,
data: coins,
});
if (!coins.length) {
return [
{
amount: formattedRewardBaby,
currencyIcon: babyIcon,
chainName: bbnNetworkName,
currencyName: bbnCoinSymbol,
placeholder: "0",
displayBalance: true,
balanceDetails: {
balance: formattedRewardBaby,
symbol: bbnCoinSymbol,
price: 0,
displayUSD: false,
decimals: 6,
},
},
];
}

return coins.map(({ denom, amount }) => {
console.log("[RewardsUI] processing denom", { denom, amount });
if (denom === "ubbn") {
const amt = ubbnToBaby(amount).toString();
console.log("[RewardsUI] ubbn decoded", { amount, baby: amt });
return {
amount: amt,
currencyIcon: babyIcon,
chainName: bbnNetworkName,
currencyName: bbnCoinSymbol,
placeholder: "0",
displayBalance: true,
balanceDetails: {
balance: amt,
symbol: bbnCoinSymbol,
price: 0,
displayUSD: false,
decimals: 6,
},
} as RewardItem;
}

if (denom.startsWith("factory/")) {
const parts = denom.split("/");
const subdenom = parts[parts.length - 1] || denom;
console.log("[RewardsUI] factory token", { denom, subdenom });
return {
amount: String(amount),
currencyIcon: generatePlaceholder(subdenom.charAt(0).toUpperCase()),
chainName: bbnNetworkName,
currencyName: subdenom,
placeholder: "0",
displayBalance: true,
balanceDetails: {
balance: String(amount),
symbol: subdenom,
price: 0,
displayUSD: false,
decimals: 0,
},
} as RewardItem;
}

// IBC token: show denom text for now (hash)
if (denom.startsWith("ibc/")) {
const symbol = denom; // minimal: show denom text
console.log("[RewardsUI] ibc token", { denom });
return {
amount: String(amount),
currencyIcon: generatePlaceholder("I"),
chainName: bbnNetworkName,
currencyName: symbol,
placeholder: "0",
displayBalance: true,
balanceDetails: {
balance: String(amount),
symbol,
price: 0,
displayUSD: false,
decimals: 0,
},
} as RewardItem;
}

// Fallback: show raw denom
const symbol = denom;
console.log("[RewardsUI] unknown denom fallback", { denom });
return {
amount: String(amount),
currencyIcon: generatePlaceholder(symbol.charAt(0).toUpperCase()),
chainName: bbnNetworkName,
currencyName: symbol,
placeholder: "0",
displayBalance: true,
balanceDetails: {
balance: String(amount),
symbol,
price: 0,
displayUSD: false,
decimals: 0,
},
} as RewardItem;
});
})();

const [previewOpen, setPreviewOpen] = useState(false);

const handleClick = async () => {
if (!rewardBalance || processing) return;
const hasAnyRewards = (rewardCoinsQuery.data?.length ?? 0) > 0;
if ((!hasAnyRewards && !rewardBalance) || processing) return;
await showPreview();
setPreviewOpen(true);
};
Expand All @@ -100,7 +198,9 @@ export function Rewards() {
setPreviewOpen(false);
};

const claimDisabled = !rewardBalance || processing;
const claimDisabled =
processing ||
((rewardCoinsQuery.data?.length ?? 0) === 0 && !rewardBalance);

return (
<AuthGuard>
Expand Down
101 changes: 98 additions & 3 deletions src/ui/common/hooks/client/rpc/queries/useBbnQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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_REWARDS_COINS_KEY = "BBN_REWARDS_COINS";
const BBN_HEIGHT_KEY = "BBN_HEIGHT";
const REWARD_GAUGE_KEY_BTC_DELEGATION = "BTC_STAKER";

Expand Down Expand Up @@ -73,15 +74,18 @@ export const useBbnQuery = () => {
return 0;
}

const coins =
rewards.rewardGauges[REWARD_GAUGE_KEY_BTC_DELEGATION]?.coins;
const coins = rewards.rewardGauges[
REWARD_GAUGE_KEY_BTC_DELEGATION
]?.coins?.filter((c) => c.denom === "ubbn");
if (!coins) {
return 0;
}

const withdrawnCoins = rewards.rewardGauges[
REWARD_GAUGE_KEY_BTC_DELEGATION
]?.withdrawnCoins.reduce((acc, coin) => acc + Number(coin.amount), 0);
]?.withdrawnCoins
.filter((c) => c.denom === "ubbn")
.reduce((acc, coin) => acc + Number(coin.amount), 0);

return (
coins.reduce((acc, coin) => acc + Number(coin.amount), 0) -
Expand All @@ -99,6 +103,96 @@ export const useBbnQuery = () => {
refetchInterval: ONE_MINUTE,
});

/**
* Gets per-denom rewards coins for BTC_STAKER gauge.
* Returns an array of { denom: string; amount: number } with withdrawn amounts subtracted.
*/
const rewardCoinsQuery = useClientQuery({
queryKey: [BBN_REWARDS_COINS_KEY, bech32Address, connected],
queryFn: async () => {
if (!connected || !queryClient || !bech32Address) {
return [] as Array<{ denom: string; amount: number }>;
}
console.log("[BbnQuery] Fetching per-denom reward coins", {
address: bech32Address,
});
const { incentive } = setupIncentiveExtension(queryClient);
const req: incentivequery.QueryRewardGaugesRequest =
incentivequery.QueryRewardGaugesRequest.fromPartial({
address: bech32Address,
});

let rewards: incentivequery.QueryRewardGaugesResponse;
try {
rewards = await incentive.RewardGauges(req);
console.log("[BbnQuery] Raw RewardGauges response", rewards);
} catch (error) {
if (
error instanceof Error &&
error.message.includes("reward gauge not found")
) {
console.log(
"[BbnQuery] Reward gauge not found; returning empty rewards",
);
return [] as Array<{ denom: string; amount: number }>;
}
throw new ClientError(
ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE,
"Error getting rewards",
{ cause: error as Error },
);
}

const gauge = rewards.rewardGauges[REWARD_GAUGE_KEY_BTC_DELEGATION];
if (!gauge) {
console.log(
"[BbnQuery] BTC_STAKER gauge missing in response; returning empty",
);
return [] as Array<{ denom: string; amount: number }>;
}
console.log("[BbnQuery] Gauge coins", gauge.coins);
console.log("[BbnQuery] Gauge withdrawnCoins", gauge.withdrawnCoins);
const totalByDenom = new Map<string, number>();
const withdrawnByDenom = new Map<string, number>();

for (const coin of gauge.coins ?? []) {
const prev = totalByDenom.get(coin.denom) ?? 0;
totalByDenom.set(coin.denom, prev + Number(coin.amount));
}
for (const coin of gauge.withdrawnCoins ?? []) {
const prev = withdrawnByDenom.get(coin.denom) ?? 0;
withdrawnByDenom.set(coin.denom, prev + Number(coin.amount));
}
console.log(
"[BbnQuery] totalByDenom",
Array.from(totalByDenom.entries()),
);
console.log(
"[BbnQuery] withdrawnByDenom",
Array.from(withdrawnByDenom.entries()),
);
const results: Array<{ denom: string; amount: number }> = [];
for (const [denom, total] of totalByDenom.entries()) {
const withdrawn = withdrawnByDenom.get(denom) ?? 0;
const net = Math.max(0, total - withdrawn);
if (net > 0) {
results.push({ denom, amount: net });
}
}
console.log("[BbnQuery] Net per-denom rewards", results);
return results;
},
enabled: Boolean(
queryClient &&
connected &&
bech32Address &&
!isGeoBlocked &&
!isHealthcheckLoading,
),
staleTime: ONE_MINUTE,
refetchInterval: ONE_MINUTE,
});

/**
* Gets the balance of the user's account.
* @returns {Promise<Object>} - The balance of the user's account.
Expand Down Expand Up @@ -172,6 +266,7 @@ export const useBbnQuery = () => {

return {
rewardsQuery,
rewardCoinsQuery,
balanceQuery,
btcTipQuery,
babyTipQuery,
Expand Down