* fix: dissasociation without vega wallet (806) * fix: removed unused import * chore: removed redundant func prepend0xIfNeeded * Update apps/token/src/routes/staking/disassociate/disassociate-page.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> * Update apps/token/src/routes/staking/disassociate/disassociate-page.tsx Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com> Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
This commit is contained in:
parent
aea2a85519
commit
db08a177c4
@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
export enum StakingMethod {
|
||||
Contract = 'Contract',
|
||||
Wallet = 'Wallet',
|
||||
Unknown = 'Unknown',
|
||||
}
|
||||
|
||||
export const StakingMethodRadio = ({
|
||||
|
@ -3,25 +3,8 @@ import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useAnimateValue } from '../../hooks/use-animate-value';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { formatNumber } from '../../lib/format-number';
|
||||
|
||||
const useNumberParts = (
|
||||
value: BigNumber | null | undefined,
|
||||
decimals: number
|
||||
) => {
|
||||
return React.useMemo(() => {
|
||||
if (!value) {
|
||||
return ['0', '0'.repeat(decimals)];
|
||||
}
|
||||
// @ts-ignore confident not undefined
|
||||
const separator = BigNumber.config().FORMAT.decimalSeparator as string;
|
||||
const [integers, decimalsPlaces] = formatNumber(value, 18)
|
||||
.toString()
|
||||
.split(separator);
|
||||
return [integers, decimalsPlaces];
|
||||
}, [decimals, value]);
|
||||
};
|
||||
import type { BigNumber } from '../../lib/bignumber';
|
||||
import { useNumberParts } from '../../lib/format-number';
|
||||
|
||||
interface WalletCardProps {
|
||||
children: React.ReactNode;
|
||||
|
33
apps/token/src/lib/format-number.spec.ts
Normal file
33
apps/token/src/lib/format-number.spec.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { BigNumber } from './bignumber';
|
||||
import {
|
||||
formatNumber,
|
||||
formatNumberPercentage,
|
||||
toNumberParts,
|
||||
} from './format-number';
|
||||
|
||||
describe('formatNumber and formatNumberPercentage', () => {
|
||||
it.each([
|
||||
{ v: new BigNumber(123), d: 3, o: '123.000' },
|
||||
{ v: new BigNumber(123.123), d: 3, o: '123.123' },
|
||||
{ v: new BigNumber(123.123), d: 6, o: '123.123000' },
|
||||
{ v: new BigNumber(123.123), d: 0, o: '123' },
|
||||
{ v: new BigNumber(123), d: undefined, o: '123.00' }, // it default to 2 decimal places
|
||||
])('formats given number correctly', ({ v, d, o }) => {
|
||||
expect(formatNumber(v, d)).toStrictEqual(o);
|
||||
expect(formatNumberPercentage(v, d)).toStrictEqual(`${o}%`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNumberParts', () => {
|
||||
it.each([
|
||||
{ v: null, d: 3, o: ['0', '000'] },
|
||||
{ v: undefined, d: 3, o: ['0', '000'] },
|
||||
{ v: new BigNumber(123), d: 3, o: ['123', '000'] },
|
||||
{ v: new BigNumber(123.123), d: 3, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 6, o: ['123', '123000'] },
|
||||
{ v: new BigNumber(123.123), d: 0, o: ['123', ''] },
|
||||
{ v: new BigNumber(123), d: undefined, o: ['123', '000000000000000000'] },
|
||||
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
|
||||
expect(toNumberParts(v, d)).toStrictEqual(o);
|
||||
});
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
import type { BigNumber } from './bignumber';
|
||||
import React from 'react';
|
||||
import { BigNumber } from './bignumber';
|
||||
|
||||
export const formatNumber = (value: BigNumber, decimals?: number) => {
|
||||
const decimalPlaces =
|
||||
@ -6,8 +7,27 @@ export const formatNumber = (value: BigNumber, decimals?: number) => {
|
||||
return value.dp(decimalPlaces).toFormat(decimalPlaces);
|
||||
};
|
||||
|
||||
export const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
|
||||
const decimalPlaces =
|
||||
typeof decimals === 'undefined' ? Math.max(value.dp(), 2) : decimals;
|
||||
return `${value.dp(decimalPlaces).toFormat(decimalPlaces)}%`;
|
||||
export const formatNumberPercentage = (value: BigNumber, decimals?: number) =>
|
||||
`${formatNumber(value, decimals)}%`;
|
||||
|
||||
export const toNumberParts = (
|
||||
value: BigNumber | null | undefined,
|
||||
decimals = 18
|
||||
): [integers: string, decimalPlaces: string] => {
|
||||
if (!value) {
|
||||
return ['0', '0'.repeat(decimals)];
|
||||
}
|
||||
// @ts-ignore confident not undefined
|
||||
const separator = BigNumber.config().FORMAT.decimalSeparator as string;
|
||||
const [integers, decimalsPlaces] = formatNumber(value, decimals)
|
||||
.toString()
|
||||
.split(separator);
|
||||
return [integers, decimalsPlaces || ''];
|
||||
};
|
||||
|
||||
export const useNumberParts = (
|
||||
value: BigNumber | null | undefined,
|
||||
decimals: number
|
||||
): [integers: string, decimalPlaces: string] => {
|
||||
return React.useMemo(() => toNumberParts(value, decimals), [decimals, value]);
|
||||
};
|
||||
|
15
apps/token/src/lib/truncate-middle.spec.ts
Normal file
15
apps/token/src/lib/truncate-middle.spec.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { truncateMiddle } from './truncate-middle';
|
||||
|
||||
describe('truncateMiddle', () => {
|
||||
it.each([
|
||||
{ i: '1234567890134567890', o: '123456\u20267890' },
|
||||
{ i: '12345678901', o: '123456\u20268901' },
|
||||
{ i: '1234567890', o: '1234567890' },
|
||||
{ i: '123456', o: '123456' },
|
||||
])(
|
||||
'truncates the middle section of any long string (address)',
|
||||
({ i, o }) => {
|
||||
expect(truncateMiddle(i)).toStrictEqual(o);
|
||||
}
|
||||
);
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
export function truncateMiddle(address: string) {
|
||||
if (address.length < 11) return address;
|
||||
return (
|
||||
address.slice(0, 6) +
|
||||
'\u2026' +
|
||||
|
@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { TokenInput } from '../../../components/token-input';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
|
||||
export const ContractDisassociate = ({
|
||||
perform,
|
||||
amount,
|
||||
setAmount,
|
||||
}: {
|
||||
perform: () => void;
|
||||
amount: string;
|
||||
setAmount: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) => {
|
||||
const {
|
||||
appState: { lien },
|
||||
} = useAppState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (lien.isEqualTo('0')) {
|
||||
return (
|
||||
<div className="disassociate-page__error">
|
||||
{t(
|
||||
'You have no VEGA tokens currently staked through your connected Eth wallet.'
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TokenInput
|
||||
submitText={t('Disassociate VEGA Tokens from key')}
|
||||
perform={perform}
|
||||
maximum={lien}
|
||||
amount={amount}
|
||||
setAmount={setAmount}
|
||||
currency={t('VEGA Tokens')}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,17 +1,12 @@
|
||||
import { StakingWalletsContainer } from '../staking-wallets-container';
|
||||
import { DisassociatePage } from './disassociate-page';
|
||||
import { DisassociatePageNoVega } from './disassociate-page-no-vega';
|
||||
|
||||
export const DisassociateContainer = () => {
|
||||
return (
|
||||
<StakingWalletsContainer needsEthereum={true} needsVega={false}>
|
||||
{({ address, currVegaKey = null }) =>
|
||||
currVegaKey ? (
|
||||
<DisassociatePage address={address} vegaKey={currVegaKey} />
|
||||
) : (
|
||||
<DisassociatePageNoVega />
|
||||
)
|
||||
}
|
||||
{({ address, currVegaKey = null }) => (
|
||||
<DisassociatePage address={address} vegaKey={currVegaKey?.pub ?? ''} />
|
||||
)}
|
||||
</StakingWalletsContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
StakingMethod,
|
||||
StakingMethodRadio,
|
||||
} from '../../../components/staking-method-radio';
|
||||
import { useSearchParams } from '../../../hooks/use-search-params';
|
||||
import { ConnectToVega } from '../connect-to-vega';
|
||||
import { ContractDisassociate } from './contract-disassociate';
|
||||
|
||||
export const DisassociatePageNoVega = () => {
|
||||
const { t } = useTranslation();
|
||||
const params = useSearchParams();
|
||||
const [amount, setAmount] = React.useState<string>('');
|
||||
const [selectedStakingMethod, setSelectedStakingMethod] =
|
||||
React.useState<StakingMethod | null>(
|
||||
(params.method as StakingMethod) || null
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="disassociate-page" data-testid="disassociate-page">
|
||||
<p>
|
||||
{t(
|
||||
'Use this form to disassociate VEGA tokens with a Vega key. This returns them to either the Ethereum wallet that used the Staking bridge or the vesting contract.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="disassociate-page__error">{t('Warning')}:</span>{' '}
|
||||
{t(
|
||||
'Any Tokens that have been nominated to a node will sacrifice any Rewards they are due for the current epoch. If you do not wish to sacrifices fees you should remove stake from a node at the end of an epoch before disassocation.'
|
||||
)}
|
||||
</p>
|
||||
<h2>{t('What Vega wallet are you removing Tokens from?')}</h2>
|
||||
<ConnectToVega />
|
||||
<h2>{t('What tokens would you like to return?')}</h2>
|
||||
<StakingMethodRadio
|
||||
setSelectedStakingMethod={setSelectedStakingMethod}
|
||||
selectedStakingMethod={selectedStakingMethod}
|
||||
/>
|
||||
{selectedStakingMethod &&
|
||||
(selectedStakingMethod === StakingMethod.Wallet ? (
|
||||
<ConnectToVega />
|
||||
) : (
|
||||
<ContractDisassociate
|
||||
setAmount={setAmount}
|
||||
amount={amount}
|
||||
perform={() => undefined}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
};
|
@ -1,65 +1,147 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ConnectedVegaKey } from '../../../components/connected-vega-key';
|
||||
import {
|
||||
StakingMethod,
|
||||
StakingMethodRadio,
|
||||
} from '../../../components/staking-method-radio';
|
||||
import { TxState } from '../../../hooks/transaction-reducer';
|
||||
import { useSearchParams } from '../../../hooks/use-search-params';
|
||||
import { useRefreshAssociatedBalances } from '../../../hooks/use-refresh-associated-balances';
|
||||
import { ContractDisassociate } from './contract-disassociate';
|
||||
import { DisassociateTransaction } from './disassociate-transaction';
|
||||
import { formatNumber } from '../../../lib/format-number';
|
||||
import { remove0x, toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import { Select } from '@vegaprotocol/ui-toolkit';
|
||||
import { StakingMethod } from '../../../components/staking-method-radio';
|
||||
import { TokenInput } from '../../../components/token-input';
|
||||
import { TxState } from '../../../hooks/transaction-reducer';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import { useRefreshAssociatedBalances } from '../../../hooks/use-refresh-associated-balances';
|
||||
import { useRemoveStake } from './hooks';
|
||||
import { WalletDisassociate } from './wallet-disassociate';
|
||||
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
|
||||
import type { RemoveStakePayload } from './hooks';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { BigNumber } from '../../../lib/bignumber';
|
||||
|
||||
type Association = {
|
||||
key: string;
|
||||
value: BigNumber;
|
||||
stakingMethod: StakingMethod;
|
||||
};
|
||||
|
||||
const toListOfAssociations = (
|
||||
obj: { [vegaKey: string]: BigNumber },
|
||||
stakingMethod: StakingMethod
|
||||
): Association[] =>
|
||||
Object.keys(obj)
|
||||
.map((k) => ({
|
||||
key: remove0x(k),
|
||||
value: obj[k],
|
||||
stakingMethod,
|
||||
}))
|
||||
.filter((k) => k.value.isGreaterThan(0));
|
||||
|
||||
export const DisassociatePage = ({
|
||||
address,
|
||||
vegaKey,
|
||||
}: {
|
||||
address: string;
|
||||
vegaKey: VegaKeyExtended;
|
||||
vegaKey: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const params = useSearchParams();
|
||||
const [amount, setAmount] = React.useState<string>('');
|
||||
const [selectedStakingMethod, setSelectedStakingMethod] =
|
||||
React.useState<StakingMethod | null>(
|
||||
(params.method as StakingMethod) || null
|
||||
);
|
||||
|
||||
const {
|
||||
appState: {
|
||||
associationBreakdown: { stakingAssociations, vestingAssociations },
|
||||
},
|
||||
} = useAppState();
|
||||
|
||||
const associations = useMemo(
|
||||
() => [
|
||||
...toListOfAssociations(stakingAssociations, StakingMethod.Wallet),
|
||||
...toListOfAssociations(vestingAssociations, StakingMethod.Contract),
|
||||
],
|
||||
[stakingAssociations, vestingAssociations]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setChosen(associations.find((k) => k.key === vegaKey) || associations[0]);
|
||||
}, [associations, vegaKey]);
|
||||
|
||||
const [chosen, setChosen] = useState<Association>();
|
||||
|
||||
const maximum = chosen?.value || toBigNum(0, 0);
|
||||
const [amount, setAmount] = useState<string>('');
|
||||
|
||||
const refreshBalances = useRefreshAssociatedBalances();
|
||||
|
||||
// Clear the amount when the staking method changes
|
||||
React.useEffect(() => {
|
||||
setAmount('');
|
||||
}, [selectedStakingMethod]);
|
||||
const payload: RemoveStakePayload = {
|
||||
amount,
|
||||
vegaKey: chosen?.key || '',
|
||||
stakingMethod: chosen?.stakingMethod || StakingMethod.Unknown,
|
||||
};
|
||||
|
||||
const {
|
||||
state: txState,
|
||||
dispatch: txDispatch,
|
||||
perform: txPerform,
|
||||
} = useRemoveStake(address, amount, vegaKey.pub, selectedStakingMethod);
|
||||
} = useRemoveStake(address, payload);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (txState.txState === TxState.Complete) {
|
||||
refreshBalances(address, vegaKey.pub);
|
||||
refreshBalances(address, chosen?.key || '');
|
||||
}
|
||||
}, [txState, refreshBalances, address, vegaKey.pub]);
|
||||
}, [txState, refreshBalances, address, chosen]);
|
||||
|
||||
if (txState.txState !== TxState.Default) {
|
||||
if (txState.txState !== TxState.Default && payload) {
|
||||
return (
|
||||
<DisassociateTransaction
|
||||
state={txState}
|
||||
amount={amount}
|
||||
vegaKey={vegaKey.pub}
|
||||
stakingMethod={selectedStakingMethod as StakingMethod}
|
||||
vegaKey={chosen?.key || ''}
|
||||
stakingMethod={payload.stakingMethod}
|
||||
dispatch={txDispatch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const noKeysMessage = (
|
||||
<div className="disassociate-page__error">
|
||||
{t(
|
||||
'You have no VEGA tokens currently associated through your connected Ethereum wallet.'
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const disassociate = (
|
||||
<>
|
||||
<div className="pb-8">
|
||||
<Select
|
||||
className="font-mono"
|
||||
disabled={associations.length === 1}
|
||||
id="vega-key-selector"
|
||||
onChange={(e) => {
|
||||
if (!e.target.value) return;
|
||||
const chosen = associations.find((k) => k.key === e.target.value);
|
||||
if (chosen) {
|
||||
setChosen(chosen);
|
||||
setAmount('');
|
||||
}
|
||||
}}
|
||||
value={chosen?.key}
|
||||
>
|
||||
{associations.map((k) => (
|
||||
<option
|
||||
key={k.key}
|
||||
value={k.key}
|
||||
title={`${t(k.stakingMethod)}: ${formatNumber(k.value, 18)}`}
|
||||
>
|
||||
{k.key}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<TokenInput
|
||||
submitText={t('Disassociate VEGA Tokens from key')}
|
||||
perform={txPerform}
|
||||
maximum={maximum}
|
||||
amount={amount}
|
||||
setAmount={setAmount}
|
||||
currency={t('VEGA Tokens')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="disassociate-page" data-testid="disassociate-page">
|
||||
<p>
|
||||
@ -70,30 +152,12 @@ export const DisassociatePage = ({
|
||||
<p>
|
||||
<span className="text-vega-red">{t('Warning')}:</span>{' '}
|
||||
{t(
|
||||
'Any Tokens that have been nominated to a node will sacrifice any Rewards they are due for the current epoch. If you do not wish to sacrifices fees you should remove stake from a node at the end of an epoch before disassocation.'
|
||||
'Any tokens that have been nominated to a node will sacrifice rewards they are due for the current epoch. If you do not wish to sacrifice these, you should remove stake from a node at the end of an epoch before disassociation.'
|
||||
)}
|
||||
</p>
|
||||
<h2>{t('What Vega wallet are you removing Tokens from?')}</h2>
|
||||
<ConnectedVegaKey pubKey={vegaKey.pub} />
|
||||
|
||||
<h2>{t('What tokens would you like to return?')}</h2>
|
||||
<StakingMethodRadio
|
||||
setSelectedStakingMethod={setSelectedStakingMethod}
|
||||
selectedStakingMethod={selectedStakingMethod}
|
||||
/>
|
||||
{selectedStakingMethod &&
|
||||
(selectedStakingMethod === StakingMethod.Wallet ? (
|
||||
<WalletDisassociate
|
||||
setAmount={setAmount}
|
||||
amount={amount}
|
||||
perform={txPerform}
|
||||
/>
|
||||
) : (
|
||||
<ContractDisassociate
|
||||
setAmount={setAmount}
|
||||
amount={amount}
|
||||
perform={txPerform}
|
||||
/>
|
||||
))}
|
||||
{associations.length === 0 ? noKeysMessage : disassociate}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -8,12 +8,24 @@ import { TxState } from '../../../hooks/transaction-reducer';
|
||||
import { useGetAssociationBreakdown } from '../../../hooks/use-get-association-breakdown';
|
||||
import { useRefreshBalances } from '../../../hooks/use-refresh-balances';
|
||||
import { useTransaction } from '../../../hooks/use-transaction';
|
||||
import { initialState } from '../../../hooks/transaction-reducer';
|
||||
|
||||
export type RemoveStakePayload = {
|
||||
amount: string;
|
||||
vegaKey: string;
|
||||
stakingMethod: StakingMethod;
|
||||
};
|
||||
|
||||
const EMPTY_REMOVE = {
|
||||
state: initialState,
|
||||
dispatch: () => undefined,
|
||||
perform: () => undefined as void,
|
||||
reset: () => undefined as void,
|
||||
};
|
||||
|
||||
export const useRemoveStake = (
|
||||
address: string,
|
||||
amount: string,
|
||||
vegaKey: string,
|
||||
stakingMethod: StakingMethod | null
|
||||
payload: RemoveStakePayload
|
||||
) => {
|
||||
const { appState } = useAppState();
|
||||
const { staking, vesting } = useContracts();
|
||||
@ -21,11 +33,18 @@ export const useRemoveStake = (
|
||||
// which if staked > wallet balance means you cannot unstaked
|
||||
// even worse if you stake everything then you can't unstake anything!
|
||||
const contractRemove = useTransaction(() =>
|
||||
vesting.remove_stake(removeDecimal(amount, appState.decimals), vegaKey)
|
||||
vesting.remove_stake(
|
||||
removeDecimal(payload.amount, appState.decimals),
|
||||
payload.vegaKey
|
||||
)
|
||||
);
|
||||
const walletRemove = useTransaction(() =>
|
||||
staking.remove_stake(removeDecimal(amount, appState.decimals), vegaKey)
|
||||
staking.remove_stake(
|
||||
removeDecimal(payload.amount, appState.decimals),
|
||||
payload.vegaKey
|
||||
)
|
||||
);
|
||||
|
||||
const refreshBalances = useRefreshBalances(address);
|
||||
const getAssociationBreakdown = useGetAssociationBreakdown(
|
||||
address,
|
||||
@ -49,10 +68,13 @@ export const useRemoveStake = (
|
||||
]);
|
||||
|
||||
return React.useMemo(() => {
|
||||
if (stakingMethod === StakingMethod.Contract) {
|
||||
return contractRemove;
|
||||
} else {
|
||||
return walletRemove;
|
||||
switch (payload.stakingMethod) {
|
||||
case StakingMethod.Contract:
|
||||
return contractRemove;
|
||||
case StakingMethod.Wallet:
|
||||
return walletRemove;
|
||||
default:
|
||||
return EMPTY_REMOVE;
|
||||
}
|
||||
}, [contractRemove, stakingMethod, walletRemove]);
|
||||
}, [contractRemove, payload, walletRemove]);
|
||||
};
|
||||
|
@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { TokenInput } from '../../../components/token-input';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
|
||||
export const WalletDisassociate = ({
|
||||
perform,
|
||||
amount,
|
||||
setAmount,
|
||||
}: {
|
||||
perform: () => void;
|
||||
amount: string;
|
||||
setAmount: React.Dispatch<React.SetStateAction<string>>;
|
||||
}) => {
|
||||
const {
|
||||
appState: { walletAssociatedBalance },
|
||||
} = useAppState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!walletAssociatedBalance || walletAssociatedBalance.isEqualTo('0')) {
|
||||
return (
|
||||
<div className="disassociate-page__error">
|
||||
{t(
|
||||
'You have no VEGA tokens currently staked through your connected Vega wallet.'
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TokenInput
|
||||
submitText={t('Disassociate VEGA Tokens from key')}
|
||||
perform={perform}
|
||||
maximum={walletAssociatedBalance}
|
||||
amount={amount}
|
||||
setAmount={setAmount}
|
||||
currency={t('VEGA Tokens')}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,6 +1,11 @@
|
||||
import { prepend0x } from './prepend-0x';
|
||||
|
||||
test('Prepends strings with 0x', () => {
|
||||
expect(prepend0x('abc')).toEqual('0xabc');
|
||||
expect(prepend0x('123456789')).toEqual('0x123456789');
|
||||
describe('prepend0x', () => {
|
||||
it.each([
|
||||
{ input: 'ABC123', output: '0xABC123' },
|
||||
{ input: '0XABC123', output: '0x0XABC123' },
|
||||
{ input: '0xABC123', output: '0xABC123' },
|
||||
])('prepends strings with 0x only if needed', ({ input, output }) => {
|
||||
expect(prepend0x(input)).toBe(output);
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,3 @@
|
||||
export function prepend0x(str: string) {
|
||||
return `0x${str}`;
|
||||
return !str || str.indexOf('0x') === 0 ? str : `0x${str}`;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user