Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staking card backend generation & Fixes #53

Merged
merged 25 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
72bdac6
fix: disable refetchOnWindowFocus on contract queries
Aerilym Sep 25, 2024
6747955
fix: use bigint maths for number sent formatter
Aerilym Sep 25, 2024
e2217a7
feat: add new stakes endpoint to sent staking backend client
Aerilym Sep 25, 2024
6c9588f
chore: update contract abis
Aerilym Sep 25, 2024
8cb7f6d
chore: add new node card strings
Aerilym Sep 25, 2024
f84a0c9
fix: optimise locale client util functions for numbers and useLocale …
Aerilym Sep 25, 2024
8b59d59
feat: create getNodes hook
Aerilym Sep 25, 2024
fd25d57
feat: create useRegisteredNode hook to get nodes from any registered …
Aerilym Sep 25, 2024
392e472
feat: use useRegisteredNode hook for the node not found and registrat…
Aerilym Sep 25, 2024
6178038
feat: create address page to lookup any wallet and update modules to …
Aerilym Sep 25, 2024
1cb185c
fix: update staked balance calcs to use backend values
Aerilym Sep 25, 2024
58e6be6
chore: remove unused address hooks
Aerilym Sep 25, 2024
3f8d368
feat: create useRelativeTime hook
Aerilym Sep 25, 2024
0b3d641
feat: refactor StakedNodeCard to use new backend generated stake data…
Aerilym Sep 25, 2024
0d6eab7
feat: add node exit logic to dev sheet for easy liquidations
Aerilym Sep 25, 2024
2ad5568
fix: pubkey strict expansion
Aerilym Sep 25, 2024
03c0b93
fix: tooltip wrapping and max width on desktop
Aerilym Sep 25, 2024
b5dc820
fix: chrome text opacity rendering issue for stake card info and shor…
Aerilym Sep 25, 2024
df133c9
chore: nextjs update doc link
Aerilym Sep 25, 2024
1449d0b
fix: remove unused open nodes code
Aerilym Sep 25, 2024
a1c6abf
fix: remove unused stake code
Aerilym Sep 25, 2024
f4fcfa3
fix: update test file
Aerilym Sep 26, 2024
02b96bf
chore: add comments to explain edge cases
Aerilym Sep 26, 2024
6ebc6c5
chore: extract node card types
Aerilym Sep 26, 2024
80ffe35
chore: final comment on test file
Aerilym Sep 26, 2024
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
6 changes: 6 additions & 0 deletions apps/staking/app/address/[address]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import ScreenContainer from '@/components/ScreenContainer';
import type { ReactNode } from 'react';

export default async function Layout({ children }: { children: ReactNode }) {
return <ScreenContainer>{children}</ScreenContainer>;
}
22 changes: 22 additions & 0 deletions apps/staking/app/address/[address]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { StakedNodesWithAddress } from '@/app/mystakes/modules/StakedNodesModule';
import { isAddress } from 'viem';
import { notFound } from 'next/navigation';
import { ModuleGrid } from '@session/ui/components/ModuleGrid';
import BalanceModule from '@/app/mystakes/modules/BalanceModule';
import TotalRewardsModule from '@/app/mystakes/modules/TotalRewardsModule';
import UnclaimedTokensModule from '@/app/mystakes/modules/UnclaimedTokensModule';

export default function Page({ params: { address } }: { params: { address: string } }) {
return isAddress(address) ? (
<div className="flex flex-col gap-4">
<ModuleGrid>
<BalanceModule addressOverride={address} />
<TotalRewardsModule addressOverride={address} />
<UnclaimedTokensModule addressOverride={address} />
</ModuleGrid>
<StakedNodesWithAddress address={address} />
</div>
) : (
notFound()
);
}
50 changes: 23 additions & 27 deletions apps/staking/app/mystakes/modules/BalanceModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,41 @@ import {
getVariableFontSizeForLargeModule,
ModuleDynamicQueryText,
} from '@/components/ModuleDynamic';
import { getTotalStakedAmountForAddressBigInt } from '@/components/NodeCard';
import type { ServiceNode } from '@session/sent-staking-js/client';
import type { Stake } from '@session/sent-staking-js/client';
import { Module, ModuleTitle } from '@session/ui/components/Module';
import { useWallet } from '@session/wallet/hooks/wallet-hooks';
import { useTranslations } from 'next-intl';
import { useMemo } from 'react';
import type { Address } from 'viem';
import { useStakingBackendQueryWithParams } from '@/lib/sent-staking-backend-client';
import { getStakedNodes } from '@/lib/queries/getStakedNodes';
import { generateMockNodeData } from '@session/sent-staking-js/test';
import type { QUERY_STATUS } from '@/lib/query';
import { formatSENTBigInt } from '@session/contracts/hooks/SENT';
import { FEATURE_FLAG } from '@/lib/feature-flags';
import { useFeatureFlag } from '@/lib/feature-flags-client';
import { generateMockNodeData } from '@session/sent-staking-js/test';

const getTotalStakedAmount = ({
nodes,
address,
}: {
nodes: Array<ServiceNode>;
address: Address;
}) => {
return formatSENTBigInt(
nodes.reduce(
(acc, node) => acc + getTotalStakedAmountForAddressBigInt(node.contributors, address),
BigInt(0)
)
const getTotalStakedAmount = ({ stakes }: { stakes: Array<Stake> }) =>
formatSENTBigInt(
stakes.reduce((acc, stake) => {
const stakedBalance = stake.staked_balance ?? BigInt(0);
return typeof stakedBalance !== 'bigint' ? acc + BigInt(stakedBalance) : acc + stakedBalance;
}, BigInt(0))
);
};

function useTotalStakedAmount() {
function useTotalStakedAmount(params?: { addressOverride?: Address }) {
const showMockNodes = useFeatureFlag(FEATURE_FLAG.MOCK_STAKED_NODES);
const showNoNodes = useFeatureFlag(FEATURE_FLAG.MOCK_NO_STAKED_NODES);

if (showMockNodes && showNoNodes) {
console.error('Cannot show mock nodes and no nodes at the same time');
}

const { address } = useWallet();
const { address: connectedAddress } = useWallet();
const address = useMemo(
() => params?.addressOverride ?? connectedAddress,
[params?.addressOverride, connectedAddress]
);

const { data, refetch, status } = useStakingBackendQueryWithParams(
getStakedNodes,
Expand All @@ -52,25 +48,25 @@ function useTotalStakedAmount() {
{ enabled: !!address }
);

const nodes = useMemo(() => {
const stakes = useMemo(() => {
if (!address || showNoNodes) {
return [];
} else if (showMockNodes) {
return generateMockNodeData({ userAddress: address }).nodes;
return generateMockNodeData({ userAddress: address }).stakes;
}
return data?.nodes ?? [];
return data?.stakes ?? [];
}, [data, showMockNodes, showNoNodes]);

const totalStakedAmount = useMemo(() => {
if (!address || !nodes.length) return null;
return getTotalStakedAmount({ nodes, address });
}, [nodes.length, address]);
const totalStakedAmount = useMemo(
() => (stakes ? getTotalStakedAmount({ stakes }) : null),
[stakes]
);

return { totalStakedAmount, status, refetch };
}

export default function BalanceModule() {
const { totalStakedAmount, status, refetch } = useTotalStakedAmount();
export default function BalanceModule({ addressOverride }: { addressOverride?: Address }) {
const { totalStakedAmount, status, refetch } = useTotalStakedAmount({ addressOverride });
const dictionary = useTranslations('modules.balance');
const toastDictionary = useTranslations('modules.toast');
const titleFormat = useTranslations('modules.title');
Expand Down
2 changes: 1 addition & 1 deletion apps/staking/app/mystakes/modules/ClaimTokensModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function ClaimTokensModule() {

const [rewards, blsSignature, excludedSigners] = useMemo(() => {
if (!rewardsClaimData) return [null, null, null];
const { amount, signature, non_signer_indices } = rewardsClaimData.bls_rewards_response;
const { amount, signature, non_signer_indices } = rewardsClaimData.result;

return [BigInt(amount), signature, non_signer_indices.map(BigInt)];
}, [rewardsClaimData]);
Expand Down
5 changes: 3 additions & 2 deletions apps/staking/app/mystakes/modules/DailyNodeReward.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { externalLink } from '@/lib/locale-defaults';
import { Module, ModuleTitle, ModuleTooltip } from '@session/ui/components/Module';
import { useTranslations } from 'next-intl';
import { useMemo } from 'react';
import { formatSENTNumber } from '@session/contracts/hooks/SENT';
import { formatSENTBigInt } from '@session/contracts/hooks/SENT';

export default function DailyNodeReward() {
const { dailyNodeReward, status, refetch } = useDailyNodeReward();
Expand All @@ -21,7 +21,8 @@ export default function DailyNodeReward() {
const title = dictionary('title');

const formattedDailyNodeRewardAmount = useMemo(
() => `~ ${formatSENTNumber(dailyNodeReward ?? 0, DYNAMIC_MODULE.SENT_ROUNDED_DECIMALS)}`,
() =>
`~ ${formatSENTBigInt(dailyNodeReward ?? BigInt(0), DYNAMIC_MODULE.SENT_ROUNDED_DECIMALS)}`,
[dailyNodeReward]
);

Expand Down
91 changes: 33 additions & 58 deletions apps/staking/app/mystakes/modules/StakedNodesModule.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'use client';

import Loading from '@/app/loading';
import { GenericStakedNode, StakedNode, StakedNodeCard } from '@/components/StakedNodeCard';
import { generateStakeId, StakedNodeCard } from '@/components/StakedNodeCard';
import { WalletModalButtonWithLocales } from '@/components/WalletModalButtonWithLocales';
import { internalLink } from '@/lib/locale-defaults';
import { ButtonDataTestId } from '@/testing/data-test-ids';
import type { ServiceNode } from '@session/sent-staking-js/client';
import { generateMockNodeData } from '@session/sent-staking-js/test';
import {
ModuleGridContent,
ModuleGridHeader,
Expand All @@ -21,12 +19,12 @@ import Link from 'next/link';
import { useMemo } from 'react';
import { useStakingBackendQueryWithParams } from '@/lib/sent-staking-backend-client';
import { getStakedNodes } from '@/lib/queries/getStakedNodes';
import { getDateFromUnixTimestampSeconds, getUnixTimestampNowSeconds } from '@session/util/date';
import { SESSION_NODE } from '@/lib/constants';
import { EXPERIMENTAL_FEATURE_FLAG, FEATURE_FLAG } from '@/lib/feature-flags';
import { useExperimentalFeatureFlag, useFeatureFlag } from '@/lib/feature-flags-client';
import { Address } from 'viem';
import { generateMockNodeData } from '@session/sent-staking-js/test';

function StakedNodesWithAddress({ address }: { address: string }) {
export function StakedNodesWithAddress({ address }: { address: Address }) {
const showMockNodes = useFeatureFlag(FEATURE_FLAG.MOCK_STAKED_NODES);
const showNoNodes = useFeatureFlag(FEATURE_FLAG.MOCK_NO_STAKED_NODES);

Expand All @@ -38,32 +36,43 @@ function StakedNodesWithAddress({ address }: { address: string }) {
address,
});

const nodes = useMemo(() => {
const [stakes, blockHeight, networkTime] = useMemo(() => {
if (showMockNodes) {
return generateMockNodeData({ userAddress: address }).nodes;
} else if (showNoNodes) {
return [];
const mockResponse = generateMockNodeData({ userAddress: address });
return [
mockResponse.stakes,
mockResponse.network.block_height,
mockResponse.network.block_timestamp,
];
} else if (!data || showNoNodes) {
return [[], null, null];
}
return data?.nodes ?? [];

return [
data.stakes.concat(data.historical_stakes),
data.network.block_height,
data.network.block_timestamp,
];
}, [data, showMockNodes, showNoNodes]);

return (
<ModuleGridContent className="h-full md:overflow-y-auto">
{isLoading ? (
<Loading />
) : nodes.length ? (
nodes.map((node) => (
<StakedNodeCard
key={node.service_node_pubkey}
node={
parseSessionNodeData(
node,
data?.network?.block_height,
data?.network?.block_timestamp
) as StakedNode
}
/>
))
) : stakes?.length && blockHeight && networkTime ? (
stakes.map((node) => {
const key = generateStakeId(node);
return (
<StakedNodeCard
key={key}
uniqueId={key}
node={node}
blockHeight={blockHeight}
networkTime={networkTime}
targetWalletAddress={address}
/>
);
})
) : (
<NoNodes />
)}
Expand Down Expand Up @@ -126,37 +135,3 @@ function NoNodes() {
</ModuleGridInfoContent>
);
}

export const parseSessionNodeData = (
node: ServiceNode,
currentBlock: number = 0,
networkTime: number = getUnixTimestampNowSeconds()
): GenericStakedNode => {
return {
state: node.state,
contributors: node.contributors,
lastRewardHeight: 0,
lastUptime: getDateFromUnixTimestampSeconds(node.last_uptime_proof),
pubKey: node.service_node_pubkey,
balance: node.total_contributed,
operatorFee: node.operator_fee,
operator_address: node.operator_address,
contract_id: node.contract_id,
...(node.awaiting_liquidation ? { awaitingLiquidation: true } : {}),
...(node.decomm_blocks_remaining
? {
deregistrationDate: new Date(
networkTime * 1000 + node.decomm_blocks_remaining * SESSION_NODE.MS_PER_BLOCK
),
}
: {}),
...(node.requested_unlock_height
? {
unlockDate: new Date(
networkTime * 1000 +
(node.requested_unlock_height - currentBlock) * SESSION_NODE.MS_PER_BLOCK
),
}
: {}),
};
};
9 changes: 7 additions & 2 deletions apps/staking/app/mystakes/modules/TotalRewardsModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ import {
import type { QUERY_STATUS } from '@/lib/query';
import { useMemo } from 'react';
import { formatSENTBigInt } from '@session/contracts/hooks/SENT';
import { Address } from 'viem';

export default function TotalRewardsModule() {
export default function TotalRewardsModule(params?: { addressOverride?: Address }) {
const dictionary = useTranslations('modules.totalRewards');
const toastDictionary = useTranslations('modules.toast');
const titleFormat = useTranslations('modules.title');
const title = dictionary('title');

const { address } = useWallet();
const { address: connectedAddress } = useWallet();
const address = useMemo(
() => params?.addressOverride ?? connectedAddress,
[params?.addressOverride, connectedAddress]
);

const { data, status, refetch } = useStakingBackendQueryWithParams(
getStakedNodes,
Expand Down
15 changes: 11 additions & 4 deletions apps/staking/app/mystakes/modules/UnclaimedTokensModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ import {
ModuleDynamicQueryText,
} from '@/components/ModuleDynamic';
import { formatSENTBigInt } from '@session/contracts/hooks/SENT';
import { Address } from 'viem';

export const useUnclaimedTokens = () => {
const { address } = useWallet();
export const useUnclaimedTokens = (params?: { addressOverride?: Address }) => {
const { address: connectedAddress } = useWallet();
const address = useMemo(
() => params?.addressOverride ?? connectedAddress,
[params?.addressOverride, connectedAddress]
);

const { data, status, refetch } = useStakingBackendQueryWithParams(
getStakedNodes,
Expand Down Expand Up @@ -47,13 +52,15 @@ export const useUnclaimedTokens = () => {
return { status, refetch, unclaimedRewards, formattedUnclaimedRewardsAmount, canClaim };
};

export default function UnclaimedTokensModule() {
export default function UnclaimedTokensModule({ addressOverride }: { addressOverride?: Address }) {
const dictionary = useTranslations('modules.unclaimedTokens');
const toastDictionary = useTranslations('modules.toast');
const titleFormat = useTranslations('modules.title');
const title = dictionary('title');

const { formattedUnclaimedRewardsAmount, status, refetch } = useUnclaimedTokens();
const { formattedUnclaimedRewardsAmount, status, refetch } = useUnclaimedTokens({
addressOverride,
});

return (
<Module>
Expand Down
Loading
Loading