Skip to content

Commit 581fe9e

Browse files
refactor(tangle-dapp): fixes for restaking, liquid staking, and migration flows (#3092)
1 parent ed78002 commit 581fe9e

File tree

21 files changed

+545
-235
lines changed

21 files changed

+545
-235
lines changed

apps/tangle-dapp/src/components/tables/OperatorsTable.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { TableVariant } from '@tangle-network/ui-components/components/Table/typ
99
interface Props {
1010
operatorMap: Map<Address, Operator> | null;
1111
isLoading: boolean;
12-
onRestakeClicked: () => void;
12+
onDelegateClicked: (operatorAddress: Address) => void;
1313
}
1414

1515
const formatDelegationCount = (count: bigint | null | undefined): number => {
@@ -20,7 +20,7 @@ const formatDelegationCount = (count: bigint | null | undefined): number => {
2020
export const OperatorsTable: FC<Props> = ({
2121
operatorMap,
2222
isLoading,
23-
onRestakeClicked,
23+
onDelegateClicked,
2424
}) => {
2525
const data = useMemo<RestakeOperator[]>(() => {
2626
if (!operatorMap) return [];
@@ -49,18 +49,16 @@ export const OperatorsTable: FC<Props> = ({
4949
emptyTableProps={{
5050
title: 'No Operators Available',
5151
description: 'Be the first to register as a restaking operator.',
52-
buttonText: 'Register as Operator',
53-
buttonProps: { onClick: onRestakeClicked },
5452
}}
5553
tableProps={{
5654
variant: TableVariant.GLASS_OUTER,
5755
}}
58-
RestakeOperatorAction={({ address: _address }) => (
56+
RestakeOperatorAction={({ address }) => (
5957
<Button
6058
variant="secondary"
6159
size="sm"
6260
onClick={() => {
63-
onRestakeClicked();
61+
onDelegateClicked(address as Address);
6462
}}
6563
className="min-w-24"
6664
>

apps/tangle-dapp/src/containers/restaking/RestakeOverviewTabs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { RestakeAction } from '../../constants';
4040
import BlueprintListing from '../../pages/blueprints/BlueprintListing';
4141
import RestakeDelegateForm from '../../pages/restake/delegate';
4242
import DepositForm from '../../pages/restake/deposit/DepositForm';
43-
import RestakeUnstakeForm from '../../pages/restake/unstake';
43+
import RestakeUndelegateForm from '../../pages/restake/undelegate';
4444
import RestakeWithdrawForm from '../../pages/restake/withdraw';
4545
import { PagePath, QueryParamKey } from '../../types';
4646

@@ -87,7 +87,7 @@ const RestakeOverviewTabs: FC<Props> = ({
8787
) : action === RestakeAction.DELEGATE ? (
8888
<RestakeDelegateForm />
8989
) : action === RestakeAction.UNDELEGATE ? (
90-
<RestakeUnstakeForm />
90+
<RestakeUndelegateForm />
9191
) : null}
9292
</TabContent>
9393

apps/tangle-dapp/src/containers/restaking/RestakeTabContent.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { RestakeAction, RestakeTab } from '../../constants';
44
import DepositForm from '../../pages/restake/deposit/DepositForm';
55
import RestakeWithdrawForm from '../../pages/restake/withdraw';
66
import RestakeDelegateForm from '../../pages/restake/delegate';
7-
import RestakeUnstakeForm from '../../pages/restake/unstake';
7+
import RestakeUndelegateForm from '../../pages/restake/undelegate';
88
import BlueprintListing from '../../pages/blueprints/BlueprintListing';
99
import { useNavigate } from 'react-router';
10-
import { PagePath } from '../../types';
10+
import { PagePath, QueryParamKey } from '../../types';
11+
import type { Address } from 'viem';
1112
import { NetworkGuard } from '../../components/NetworkGuard';
1213
import { RestakingAssetsTable } from '../../components/tables/RestakingAssetsTable';
1314
import { OperatorsTable } from '../../components/tables/OperatorsTable';
@@ -42,9 +43,14 @@ const RestakeTabContentInner: FC<Props> = ({ tab }) => {
4243
const context = useOptionalRestakeContext();
4344

4445
// Must call useCallback before any conditional returns (React hooks rules)
45-
const handleRestakeClicked = useCallback(() => {
46-
navigate(PagePath.RESTAKE_DEPOSIT);
47-
}, [navigate]);
46+
const handleDelegateClicked = useCallback(
47+
(operatorAddress: Address) => {
48+
navigate(
49+
`${PagePath.RESTAKE_DELEGATE}?${QueryParamKey.RESTAKE_OPERATOR}=${operatorAddress}`,
50+
);
51+
},
52+
[navigate],
53+
);
4854

4955
// If context is not available (e.g., during HMR), show loading state
5056
if (!context) {
@@ -76,7 +82,7 @@ const RestakeTabContentInner: FC<Props> = ({ tab }) => {
7682
case RestakeAction.DELEGATE:
7783
return <RestakeDelegateForm />;
7884
case RestakeAction.UNDELEGATE:
79-
return <RestakeUnstakeForm />;
85+
return <RestakeUndelegateForm />;
8086
case RestakeTab.VAULTS:
8187
return (
8288
<RestakingAssetsTable
@@ -93,7 +99,7 @@ const RestakeTabContentInner: FC<Props> = ({ tab }) => {
9399
<OperatorsTable
94100
operatorMap={operatorMap}
95101
isLoading={isLoadingOperators}
96-
onRestakeClicked={handleRestakeClicked}
102+
onDelegateClicked={handleDelegateClicked}
97103
/>
98104
);
99105
case RestakeTab.BLUEPRINTS:

apps/tangle-dapp/src/data/restaking/useUserRestakingStats.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface UserRestakingStats {
1919
totalDelegated: bigint;
2020
withdrawQueueAmount: bigint;
2121
withdrawableAmount: bigint;
22-
pendingUnstakeAmount: bigint;
22+
pendingUndelegateAmount: bigint;
2323

2424
// Claimable Reward Value Card
2525
pendingRewards: bigint;
@@ -31,7 +31,7 @@ export interface UserRestakingStats {
3131
totalDelegated: string;
3232
withdrawQueueAmount: string;
3333
withdrawableAmount: string;
34-
pendingUnstakeAmount: string;
34+
pendingUndelegateAmount: string;
3535
pendingRewards: string;
3636
activeBalance: string;
3737
};
@@ -98,11 +98,11 @@ const useUserRestakingStats = () => {
9898
}
9999
}
100100

101-
// Calculate pending unstake amounts
102-
let pendingUnstakeAmount = BigInt(0);
101+
// Calculate pending undelegate amounts (note: unstakeRequests is the GraphQL field name)
102+
let pendingUndelegateAmount = BigInt(0);
103103
for (const req of delegator.unstakeRequests) {
104104
if (req.status === 'PENDING') {
105-
pendingUnstakeAmount += req.estimatedAmount;
105+
pendingUndelegateAmount += req.estimatedAmount;
106106
}
107107
}
108108

@@ -127,15 +127,15 @@ const useUserRestakingStats = () => {
127127
totalDelegated: delegator.totalDelegated,
128128
withdrawQueueAmount,
129129
withdrawableAmount,
130-
pendingUnstakeAmount,
130+
pendingUndelegateAmount,
131131
pendingRewards,
132132
activeBalance,
133133
formatted: {
134134
totalDeposited: format(delegator.totalDeposited),
135135
totalDelegated: format(delegator.totalDelegated),
136136
withdrawQueueAmount: format(withdrawQueueAmount),
137137
withdrawableAmount: format(withdrawableAmount),
138-
pendingUnstakeAmount: format(pendingUnstakeAmount),
138+
pendingUndelegateAmount: format(pendingUndelegateAmount),
139139
pendingRewards: format(pendingRewards),
140140
activeBalance: format(activeBalance),
141141
},

apps/tangle-dapp/src/pages/claim/migration/index.tsx

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,66 +21,14 @@ import { type Hex, formatUnits, isAddress, keccak256, toHex } from 'viem';
2121
import { twMerge } from 'tailwind-merge';
2222
import type { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';
2323
import { AnimatePresence, motion } from 'framer-motion';
24+
import parseTransactionError from '@tangle-network/tangle-shared-ui/utils/parseTransactionError';
2425
import SubstrateWalletSelector from './components/SubstrateWalletSelector';
2526
import useClaimEligibility, {
2627
generateChallenge,
2728
} from './hooks/useClaimEligibility';
2829
import useGenerateProof from './hooks/useGenerateProof';
2930
import useSubmitClaim from './hooks/useSubmitClaim';
3031

31-
// Parse transaction errors into user-friendly messages
32-
const parseTransactionError = (error: Error): string => {
33-
const message = error.message || String(error);
34-
35-
// Common error patterns
36-
if (message.includes('intrinsic gas too low')) {
37-
return 'Transaction failed: Gas limit too low. Please try again.';
38-
}
39-
if (message.includes('insufficient funds')) {
40-
return 'Insufficient funds to cover gas fees.';
41-
}
42-
if (message.includes('user rejected') || message.includes('User rejected')) {
43-
return 'Transaction was rejected by user.';
44-
}
45-
if (message.includes('Failed to fetch')) {
46-
return 'Network request failed. If you are using the local relayer, ensure `VITE_CLAIM_RELAYER_URL` is reachable from the browser.';
47-
}
48-
if (message.includes('nonce too low')) {
49-
return 'Transaction nonce conflict. Please try again.';
50-
}
51-
if (message.includes('already claimed')) {
52-
return 'This address has already claimed its allocation.';
53-
}
54-
if (message.includes('InvalidMerkleProof')) {
55-
return 'Invalid Merkle proof for this account and amount. Double-check you selected the correct Polkadot account and you are using the correct migration proofs dataset for this network.';
56-
}
57-
if (message.includes('invalid proof')) {
58-
return 'Invalid proof. Please regenerate and try again.';
59-
}
60-
if (message.includes('not in merkle tree')) {
61-
return 'Address not found in the merkle tree.';
62-
}
63-
if (message.includes('claim period ended')) {
64-
return 'The claim period has ended.';
65-
}
66-
if (message.includes('paused')) {
67-
return 'Claims are currently paused.';
68-
}
69-
70-
// Extract "Details:" section if present
71-
const detailsMatch = message.match(/Details:\s*([^V]+?)(?:Version:|$)/);
72-
if (detailsMatch) {
73-
return detailsMatch[1].trim();
74-
}
75-
76-
// Fallback: truncate long messages
77-
if (message.length > 100) {
78-
return 'Transaction failed. Please try again.';
79-
}
80-
81-
return message;
82-
};
83-
8432
enum ClaimStep {
8533
CONNECT_WALLETS = 0,
8634
CHECK_ELIGIBILITY = 1,

apps/tangle-dapp/src/pages/restake/ExpandTableButton.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ enum NotificationVariant {
1616
export type ExpandTableButtonProps = ComponentProps<'button'> & {
1717
notificationVariant?: NotificationVariant;
1818
tooltipContent?: ReactNode;
19+
requestCount?: number;
1920
};
2021

2122
const COLOR_CLASSES = {
@@ -35,15 +36,24 @@ const COLOR_CLASSES = {
3536
export const ExpandTableButton: FC<ExpandTableButtonProps> = ({
3637
notificationVariant,
3738
tooltipContent,
39+
requestCount,
3840
...props
3941
}) => {
42+
const showCount = requestCount !== undefined && requestCount > 0;
43+
4044
return (
4145
<Tooltip>
4246
<TooltipTrigger asChild>
4347
<IconButton {...props}>
4448
<DoubleArrowRightIcon />
4549

46-
{notificationVariant && (
50+
{showCount && (
51+
<span className="absolute -top-1.5 -right-1.5 flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-semibold text-white bg-blue-500 rounded-full">
52+
{requestCount > 99 ? '99+' : requestCount}
53+
</span>
54+
)}
55+
56+
{notificationVariant && !showCount && (
4757
<span className="absolute top-0 right-0 flex w-2 h-2 -mt-0.5 -mr-0.5">
4858
<span
4959
className={twMerge(

apps/tangle-dapp/src/pages/restake/delegate/index.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { Modal } from '@tangle-network/ui-components/components/Modal';
1616
import type { TextFieldInputProps } from '@tangle-network/ui-components/components/TextField/types';
1717
import { TransactionInputCard } from '@tangle-network/ui-components/components/TransactionInputCard';
1818
import { useModal } from '@tangle-network/ui-components/hooks/useModal';
19-
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
19+
import { FC, useCallback, useEffect, useMemo, useRef } from 'react';
2020
import useFormSetValue from '../../../hooks/useFormSetValue';
2121
import { SubmitHandler, useForm } from 'react-hook-form';
2222
import { Address, formatUnits, parseUnits } from 'viem';
@@ -269,10 +269,6 @@ const RestakeDelegateForm: FC = () => {
269269
update: updateOperatorModal,
270270
} = useModal(false);
271271

272-
const [selectedAssetItem, setSelectedAssetItem] = useState<AssetItem | null>(
273-
null,
274-
);
275-
276272
const depositedAssets = useMemo<AssetItem[]>(() => {
277273
if (!restakeAssets || !depositMap) {
278274
return [];
@@ -308,18 +304,27 @@ const RestakeDelegateForm: FC = () => {
308304
.filter((item): item is AssetItem => item !== null);
309305
}, [depositMap, restakeAssets, tokenAddresses]);
310306

307+
// Derive selectedAssetItem from depositedAssets to ensure it updates when metadata loads
308+
const selectedAssetItem = useMemo(() => {
309+
if (!selectedAssetId || depositedAssets.length === 0) {
310+
return null;
311+
}
312+
return (
313+
depositedAssets.find((asset) => asset.id === selectedAssetId) ?? null
314+
);
315+
}, [depositedAssets, selectedAssetId]);
316+
317+
// Auto-select first asset when available and none selected
311318
useEffect(() => {
312-
if (depositedAssets.length > 0 && !selectedAssetItem) {
319+
if (depositedAssets.length > 0 && !selectedAssetId) {
313320
const firstAsset = depositedAssets[0];
314321
setValue('assetId', firstAsset.id);
315-
setSelectedAssetItem(firstAsset);
316322
}
317-
}, [depositedAssets, setValue, selectedAssetItem]);
323+
}, [depositedAssets, setValue, selectedAssetId]);
318324

319325
const handleAssetSelect = useCallback(
320326
(asset: AssetItem) => {
321327
setValue('assetId', asset.id);
322-
setSelectedAssetItem(asset);
323328
closeAssetModal();
324329
},
325330
[closeAssetModal, setValue],
@@ -427,7 +432,6 @@ const RestakeDelegateForm: FC = () => {
427432
setValue('amount', '', { shouldValidate: false });
428433
setValue('assetId', '' as Address, { shouldValidate: false });
429434
setValue('operatorAddress', '' as Address, { shouldValidate: false });
430-
setSelectedAssetItem(null);
431435
}, [setValue]);
432436

433437
const onSubmit = useCallback<SubmitHandler<EvmDelegationFormFields>>(

apps/tangle-dapp/src/pages/restake/unstake/Details.tsx renamed to apps/tangle-dapp/src/pages/restake/undelegate/Details.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import formatMsDuration from '../../../utils/formatMsDuration';
77
const Details: FC = () => {
88
const { data: config } = useProtocolConfig();
99

10-
const unstakePeriod = useMemo(() => {
10+
const undelegatePeriod = useMemo(() => {
1111
if (!config) {
1212
return null;
1313
}
@@ -25,7 +25,7 @@ const Details: FC = () => {
2525
<DetailItem
2626
title="Undelegate period"
2727
tooltip="Waiting time between scheduling and executing an undelegation"
28-
value={unstakePeriod}
28+
value={undelegatePeriod}
2929
/>
3030
</DetailsContainer>
3131
);

0 commit comments

Comments
 (0)