diff --git a/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx b/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx
index 32e5714b4..a45857513 100644
--- a/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx
+++ b/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx
@@ -3,6 +3,7 @@ import {
AssetsSection,
TransactionsSection,
SpaceTabAccessWrapper,
+ SpacePendingRewardsSection,
} from '@hypha-platform/epics';
import { getDhoPathTreasury } from './constants';
import { findSpaceBySlug } from '@hypha-platform/core/server';
@@ -26,6 +27,9 @@ export default async function TreasuryPage(props: PageProps) {
spaceSlug={id}
>
+
= ({
pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0;
const hyphaTokenAsset =
- originalAsset && pendingRewards !== undefined
- ? {
- ...originalAsset,
- value: parsedRewardValue,
- }
+ pendingRewards !== undefined
+ ? originalAsset
+ ? { ...originalAsset, value: parsedRewardValue }
+ : { ...HYPHA_REWARDS_FALLBACK, value: parsedRewardValue }
: undefined;
useEffect(() => {
if (parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) {
@@ -67,6 +74,7 @@ export const PendingRewardsSection: FC = ({
!(parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) ||
isClaiming ||
pendingRewards === undefined;
+
const onHandleClaim = useCallback(async () => {
try {
const txHash = await claim();
@@ -99,17 +107,24 @@ export const PendingRewardsSection: FC = ({
- {!isAuthenticated ? (
+ {isLoading ? (
+
+ ) : !isAuthenticated ? (
No rewards found for this user
) : (
- <>
-
- {isLoading &&
}
- >
+
)}
diff --git a/packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx b/packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx
new file mode 100644
index 000000000..aca3b9af8
--- /dev/null
+++ b/packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx
@@ -0,0 +1,168 @@
+'use client';
+
+import { FC, useCallback, useEffect, useState } from 'react';
+import { SectionFilter } from '@hypha-platform/ui/server';
+import {
+ usePendingRewards,
+ useSpaceDetailsWeb3Rpc,
+} from '@hypha-platform/core/client';
+import { AssetCard } from './asset-card';
+import { useAssetsSection, useTokenSupply } from '../../hooks';
+import { Button } from '@hypha-platform/ui';
+import { Loader2 } from 'lucide-react';
+import { useAuthentication } from '@hypha-platform/authentication';
+import { Empty } from '../../../common';
+import { useSpaceMember } from '../../../spaces';
+import { useParams } from 'next/navigation';
+import { useSWRConfig } from 'swr';
+
+const HYPHA_TOKEN_ADDRESS = '0x8b93862835C36e9689E9bb1Ab21De3982e266CD3';
+const MIN_REWARD_CLAIM_VALUE = 0.01;
+
+const HYPHA_REWARDS_FALLBACK = {
+ icon: '/placeholder/hypha-token-icon.svg',
+ name: 'Hypha',
+ symbol: 'HYPHA',
+ value: 0,
+ address: HYPHA_TOKEN_ADDRESS,
+};
+
+type SpacePendingRewardsSectionProps = {
+ web3SpaceId: number;
+};
+
+export const SpacePendingRewardsSection: FC<
+ SpacePendingRewardsSectionProps
+> = ({ web3SpaceId }) => {
+ const { id: spaceSlug } = useParams<{ id: string }>();
+ const { mutate } = useSWRConfig();
+ const { isAuthenticated } = useAuthentication();
+ const { spaceDetails } = useSpaceDetailsWeb3Rpc({ spaceId: web3SpaceId });
+ const { isMember } = useSpaceMember({ spaceId: web3SpaceId });
+ const executor = spaceDetails?.executor as `0x${string}` | undefined;
+
+ const { filteredAssets, isLoading: isLoadingAssets } = useAssetsSection();
+ const { supply: hyphaTotalSupply } = useTokenSupply(
+ HYPHA_TOKEN_ADDRESS as `0x${string}`,
+ );
+
+ const {
+ pendingRewards,
+ isLoading,
+ claim,
+ waitForClaimReceipt,
+ isClaiming,
+ updatePendingRewards,
+ } = usePendingRewards({ user: executor });
+
+ const [hasClaimed, setHasClaimed] = useState(false);
+
+ const originalAsset = filteredAssets?.find(
+ (a) => a.address === HYPHA_TOKEN_ADDRESS,
+ );
+
+ const parsedRewardValue =
+ pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0;
+
+ const baseHyphaAsset = originalAsset
+ ? { ...originalAsset, value: parsedRewardValue }
+ : { ...HYPHA_REWARDS_FALLBACK, value: parsedRewardValue };
+
+ const supplyFromAsset = (originalAsset as { supply?: { total: number } })
+ ?.supply;
+ const supply =
+ supplyFromAsset ??
+ (hyphaTotalSupply !== undefined ? { total: hyphaTotalSupply } : undefined);
+
+ const hyphaTokenAsset =
+ pendingRewards !== undefined ? { ...baseHyphaAsset, supply } : undefined;
+
+ useEffect(() => {
+ if (parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) {
+ setHasClaimed(false);
+ }
+ }, [parsedRewardValue]);
+
+ const updateSpaceAssets = useCallback(() => {
+ if (spaceSlug) {
+ mutate([`/api/v1/spaces/${spaceSlug}/assets`]);
+ }
+ }, [mutate, spaceSlug]);
+
+ const disableClaimButton =
+ hasClaimed ||
+ !(parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) ||
+ isClaiming ||
+ pendingRewards === undefined;
+
+ const canClaim = isAuthenticated && isMember;
+
+ const onHandleClaim = useCallback(async () => {
+ if (!canClaim || !executor) return;
+ try {
+ const txHash = await claim();
+ await waitForClaimReceipt(txHash as `0x${string}`);
+ await updatePendingRewards();
+ await updateSpaceAssets();
+ setHasClaimed(true);
+ } catch (error) {
+ console.error('Claim failed:', error);
+ }
+ }, [
+ canClaim,
+ executor,
+ claim,
+ waitForClaimReceipt,
+ updatePendingRewards,
+ updateSpaceAssets,
+ ]);
+
+ return (
+
+
+
+
+
+
+ {isLoading || !executor ? (
+
+ ) : !isAuthenticated ? (
+
+ Sign in to view space rewards
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/packages/epics/src/treasury/components/index.ts b/packages/epics/src/treasury/components/index.ts
index fab042313..494acafe8 100644
--- a/packages/epics/src/treasury/components/index.ts
+++ b/packages/epics/src/treasury/components/index.ts
@@ -12,6 +12,7 @@ export * from './common/token-max-supply-field';
export * from './assets/user-assets-section';
export * from './requests/user-transactions-section';
export * from './assets/pending-rewards-section';
+export * from './assets/space-pending-rewards-section';
export * from './common/token-max-supply-type-field';
export * from './common/transfer-whitelist-field-array';
export * from './common/general-token-settings';