Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ad32c2c96 | ||
|
|
0d850bd8b9 | ||
|
|
44189591fc | ||
|
|
3ed2ec88d7 | ||
|
|
a21feea699 | ||
|
|
41fd14dd00 | ||
|
|
c5a27dc6a2 | ||
|
|
0a3b1cadba | ||
|
|
b953de953a | ||
|
|
5ddcb613e2 | ||
|
|
c31a1d57cf | ||
|
|
50208ae222 |
@@ -0,0 +1,46 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TxDetailsShared } from '../shared/tx-details-shared';
|
||||
import { TableWithTbody } from '../../../table';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
|
||||
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TableCell, TableRow } from '../../../table';
|
||||
|
||||
type Update = components['schemas']['v1UpdatePartyProfile'];
|
||||
|
||||
interface TxDetailsUpdatePartyProfileProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Party profiles can be an alias and arbitrary key/values pairs.
|
||||
* This component displays the alias, if any, but not the metadata. When there is
|
||||
* some wider usage, we can decide how to render it. For now, it's available in the
|
||||
* full TX details.
|
||||
*/
|
||||
export const TxDetailsUpdatePartyProfile = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsUpdatePartyProfileProps) => {
|
||||
if (!txData?.command.updatePartyProfile) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const update: Update = txData.command.updatePartyProfile;
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{update.alias && (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('New alias')}</TableCell>
|
||||
<TableCell>{update.alias}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
@@ -34,6 +34,7 @@ import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
|
||||
import { TxDetailsJoinTeam } from './tx-join-team';
|
||||
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
|
||||
import { TxBatchProposal } from './tx-batch-proposal';
|
||||
import { TxDetailsUpdatePartyProfile } from './proposal/tx-update-party-profile';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -139,6 +140,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsUpdateMarginMode;
|
||||
case 'Batch Proposal':
|
||||
return TxBatchProposal;
|
||||
case 'Update Party Profile':
|
||||
return TxDetailsUpdatePartyProfile;
|
||||
default:
|
||||
return TxDetailsGeneric;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export type FilterOption =
|
||||
| 'Submit Order'
|
||||
| 'Transfer Funds'
|
||||
| 'Undelegate'
|
||||
| 'Update Party Profile'
|
||||
| 'Update Referral Set'
|
||||
| 'Update Margin Mode'
|
||||
| 'Validator Heartbeat'
|
||||
@@ -79,6 +80,7 @@ export const filterOptions: Record<string, FilterOption[]> = {
|
||||
'Apply Referral Code',
|
||||
'Create Referral Set',
|
||||
'Join Team',
|
||||
'Update Party Profile',
|
||||
'Update Referral Set',
|
||||
],
|
||||
'External Data': ['Chain Event', 'Submit Oracle Data'],
|
||||
|
||||
@@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
import type { BigNumber } from '../../../lib/bignumber';
|
||||
import type { UndelegateSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
UndelegateSubmissionMethod,
|
||||
useVegaWallet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { removeDecimal } from '@vegaprotocol/utils';
|
||||
|
||||
interface PendingStakeProps {
|
||||
@@ -38,7 +41,7 @@ export const PendingStake = ({
|
||||
undelegateSubmission: {
|
||||
nodeId,
|
||||
amount: removeDecimal(pendingAmount.toString(), appState.decimals),
|
||||
method: 'METHOD_NOW',
|
||||
method: UndelegateSubmissionMethod.METHOD_NOW,
|
||||
},
|
||||
};
|
||||
await sendTx(pubKey, command);
|
||||
|
||||
@@ -28,12 +28,16 @@ import {
|
||||
NetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useBalances } from '../../../lib/balances/balances-store';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { SubHeading } from '../../../components/heading';
|
||||
import type {
|
||||
DelegateSubmissionBody,
|
||||
UndelegateSubmissionBody,
|
||||
import {
|
||||
UndelegateSubmissionMethod,
|
||||
useVegaWallet,
|
||||
type DelegateSubmissionBody,
|
||||
type UndelegateSubmissionBody,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
import { SubHeading } from '../../../components/heading';
|
||||
import type {} from '@vegaprotocol/wallet';
|
||||
|
||||
import Routes from '../../routes';
|
||||
|
||||
export enum FormState {
|
||||
@@ -128,8 +132,8 @@ export const StakingForm = ({
|
||||
amount: removeDecimal(amount, appState.decimals),
|
||||
method:
|
||||
removeType === RemoveType.Now
|
||||
? 'METHOD_NOW'
|
||||
: 'METHOD_AT_END_OF_EPOCH',
|
||||
? UndelegateSubmissionMethod.METHOD_NOW
|
||||
: UndelegateSubmissionMethod.METHOD_AT_END_OF_EPOCH,
|
||||
},
|
||||
};
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.gateway.pokt.network/v1/lb/af6a2d529a11f8158bc8ca2a
|
||||
NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.rpc.grove.city/v1/af6a2d529a11f8158bc8ca2a
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Intent, TradingAnchorButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
Intent,
|
||||
TradingAnchorButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { useT } from '../../lib/use-t';
|
||||
@@ -30,6 +35,19 @@ export const CompetitionsCreateTeam = () => {
|
||||
<LayoutWithGradient>
|
||||
<div className="mx-auto md:w-2/3 max-w-xl">
|
||||
<Box className="flex flex-col gap-4">
|
||||
<Link
|
||||
to={Links.COMPETITIONS()}
|
||||
className="text-xs inline-flex items-center gap-1 group"
|
||||
>
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CHEVRON_LEFT}
|
||||
size={12}
|
||||
className="text-vega-clight-100 dark:text-vega-cdark-100"
|
||||
/>{' '}
|
||||
<span className="group-hover:underline">
|
||||
{t('Go back to the competitions')}
|
||||
</span>
|
||||
</Link>
|
||||
<h1 className="calt text-2xl lg:text-3xl xl:text-4xl">
|
||||
{isSolo ? t('Create solo team') : t('Create a team')}
|
||||
</h1>
|
||||
@@ -78,15 +96,17 @@ const CreateTeamFormContainer = ({ isSolo }: { isSolo: boolean }) => {
|
||||
<p className="text-sm">{t('Team creation transaction successful')}</p>
|
||||
{code && (
|
||||
<>
|
||||
<p className="text-sm">
|
||||
Your team ID is:{' '}
|
||||
<span
|
||||
className="font-mono break-all"
|
||||
data-testid="team-id-display"
|
||||
>
|
||||
{code}
|
||||
</span>
|
||||
</p>
|
||||
<dl>
|
||||
<dt className="text-sm">{t('Your team ID:')}</dt>
|
||||
<dl>
|
||||
<span
|
||||
className="font-mono break-all bg-rainbow bg-clip-text text-transparent text-2xl"
|
||||
data-testid="team-id-display"
|
||||
>
|
||||
{code}
|
||||
</span>
|
||||
</dl>
|
||||
</dl>
|
||||
<TradingAnchorButton
|
||||
href={Links.COMPETITIONS_TEAM(code)}
|
||||
intent={Intent.Info}
|
||||
|
||||
@@ -3,7 +3,14 @@ import { usePageTitle } from '../../lib/hooks/use-page-title';
|
||||
import { Box } from '../../components/competitions/box';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Intent,
|
||||
Loader,
|
||||
Splash,
|
||||
TradingAnchorButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { RainbowButton } from '../../components/rainbow-button';
|
||||
import { Link, Navigate, useParams } from 'react-router-dom';
|
||||
import { Links } from '../../lib/links';
|
||||
@@ -11,6 +18,7 @@ import { useReferralSetTransaction } from '../../lib/hooks/use-referral-set-tran
|
||||
import { type FormFields, TeamForm, TransactionType } from './team-form';
|
||||
import { useTeam } from '../../lib/hooks/use-team';
|
||||
import { LayoutWithGradient } from '../../components/layouts-inner';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const CompetitionsUpdateTeam = () => {
|
||||
const t = useT();
|
||||
@@ -29,6 +37,19 @@ export const CompetitionsUpdateTeam = () => {
|
||||
<LayoutWithGradient>
|
||||
<div className="mx-auto md:w-2/3 max-w-xl">
|
||||
<Box className="flex flex-col gap-4">
|
||||
<Link
|
||||
to={Links.COMPETITIONS_TEAM(teamId)}
|
||||
className="text-xs inline-flex items-center gap-1 group"
|
||||
>
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CHEVRON_LEFT}
|
||||
size={12}
|
||||
className="text-vega-clight-100 dark:text-vega-cdark-100"
|
||||
/>{' '}
|
||||
<span className="group-hover:underline">
|
||||
{t('Go back to the team profile')}
|
||||
</span>
|
||||
</Link>
|
||||
<h1 className="calt text-2xl lg:text-3xl xl:text-5xl">
|
||||
{t('Update a team')}
|
||||
</h1>
|
||||
@@ -57,7 +78,8 @@ const UpdateTeamFormContainer = ({
|
||||
pubKey: string;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { team, loading, error } = useTeam(teamId, pubKey);
|
||||
const [refetching, setRefetching] = useState<boolean>(false);
|
||||
const { team, loading, error, refetch } = useTeam(teamId, pubKey);
|
||||
|
||||
const { err, status, onSubmit } = useReferralSetTransaction({
|
||||
onSuccess: () => {
|
||||
@@ -65,7 +87,15 @@ const UpdateTeamFormContainer = ({
|
||||
},
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
// refetch when saved
|
||||
useEffect(() => {
|
||||
if (refetch && status === 'confirmed') {
|
||||
refetch();
|
||||
setRefetching(true);
|
||||
}
|
||||
}, [refetch, status]);
|
||||
|
||||
if (loading && !refetching) {
|
||||
return <Loader size="small" />;
|
||||
}
|
||||
if (error) {
|
||||
@@ -84,6 +114,33 @@ const UpdateTeamFormContainer = ({
|
||||
return <Navigate to={Links.COMPETITIONS_TEAM(teamId)} />;
|
||||
}
|
||||
|
||||
if (status === 'confirmed') {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col items-start gap-2"
|
||||
data-testid="team-creation-success-message"
|
||||
>
|
||||
<p className="text-sm">
|
||||
<VegaIcon
|
||||
name={VegaIconNames.TICK}
|
||||
size={18}
|
||||
className="text-vega-green-500"
|
||||
/>{' '}
|
||||
{t('Changes successfully saved to your team.')}
|
||||
</p>
|
||||
|
||||
<TradingAnchorButton
|
||||
href={Links.COMPETITIONS_TEAM(teamId)}
|
||||
intent={Intent.Info}
|
||||
size="small"
|
||||
data-testid="view-team-button"
|
||||
>
|
||||
{t('View team')}
|
||||
</TradingAnchorButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const defaultValues: FormFields = {
|
||||
id: team.teamId,
|
||||
name: team.name,
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import {
|
||||
Intent,
|
||||
MobileActionsDropdown,
|
||||
Tooltip,
|
||||
TradingButton,
|
||||
TradingDropdownItem,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { type BarView, ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
|
||||
const ViewInitializer = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { setViews, getView } = useSidebar();
|
||||
const view = getView(currentRouteId);
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
useEffect(() => {
|
||||
if (largeScreen && view === undefined) {
|
||||
setViews({ type: ViewType.Order }, currentRouteId);
|
||||
}
|
||||
}, [setViews, view, currentRouteId, largeScreen]);
|
||||
return null;
|
||||
};
|
||||
|
||||
export const MarketsMobileSidebar = () => {
|
||||
const t = useT();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { pubKeys, isReadOnly } = useVegaWallet();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path=":marketId"
|
||||
element={
|
||||
<>
|
||||
<ViewInitializer />
|
||||
<div className="grid grid-cols-3 grow md:grow-0 md:flex lg:flex-col items-center gap-2 lg:gap-4 p-1">
|
||||
{!pubKeys || isReadOnly ? (
|
||||
<>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
openVegaWalletDialog();
|
||||
}}
|
||||
>
|
||||
{t('Connect')}
|
||||
</TradingButton>
|
||||
<MobileButton
|
||||
view={ViewType.Order}
|
||||
tooltip={t('Trade')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileBarActionsDropdown currentRouteId={currentRouteId} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MobileButton
|
||||
view={ViewType.Order}
|
||||
tooltip={t('Trade')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileButton
|
||||
view={ViewType.Deposit}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileBarActionsDropdown currentRouteId={currentRouteId} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileButton = ({
|
||||
view,
|
||||
tooltip: label,
|
||||
disabled = false,
|
||||
onClick,
|
||||
routeId,
|
||||
}: {
|
||||
view?: ViewType;
|
||||
tooltip: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
routeId: string;
|
||||
}) => {
|
||||
const { setViews, getView } = useSidebar((store) => ({
|
||||
setViews: store.setViews,
|
||||
getView: store.getView,
|
||||
}));
|
||||
const currView = getView(routeId);
|
||||
const onSelect = (view: BarView['type']) => {
|
||||
if (view === currView?.type) {
|
||||
setViews(null, routeId);
|
||||
} else {
|
||||
setViews({ type: view }, routeId);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonClasses = classNames(
|
||||
'flex items-center p-1 rounded',
|
||||
'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500',
|
||||
{
|
||||
'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500':
|
||||
!view || view !== currView?.type,
|
||||
'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black':
|
||||
view && view === currView?.type,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip description={label} align="center" side="right" sideOffset={10}>
|
||||
<TradingButton
|
||||
className={buttonClasses}
|
||||
data-testid={view}
|
||||
onClick={onClick || (() => onSelect(view as BarView['type']))}
|
||||
disabled={disabled}
|
||||
>
|
||||
{label}
|
||||
</TradingButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileDropdownItem = ({
|
||||
view,
|
||||
icon,
|
||||
tooltip,
|
||||
disabled = false,
|
||||
onClick,
|
||||
routeId,
|
||||
}: {
|
||||
view?: ViewType;
|
||||
icon: VegaIconNames;
|
||||
tooltip: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
routeId: string;
|
||||
}) => {
|
||||
const { setViews, getView } = useSidebar((store) => ({
|
||||
setViews: store.setViews,
|
||||
getView: store.getView,
|
||||
}));
|
||||
const currView = getView(routeId);
|
||||
const onSelect = (view: BarView['type']) => {
|
||||
if (view === currView?.type) {
|
||||
setViews(null, routeId);
|
||||
} else {
|
||||
setViews({ type: view }, routeId);
|
||||
}
|
||||
};
|
||||
|
||||
const buttonClasses = classNames(
|
||||
'flex items-center p-1 rounded',
|
||||
'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500',
|
||||
{
|
||||
'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500':
|
||||
!view || view !== currView?.type,
|
||||
'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black':
|
||||
view && view === currView?.type,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip description={tooltip} align="center" side="right" sideOffset={10}>
|
||||
<TradingDropdownItem
|
||||
className={buttonClasses}
|
||||
data-testid={view}
|
||||
onClick={onClick || (() => onSelect(view as BarView['type']))}
|
||||
disabled={disabled}
|
||||
>
|
||||
<VegaIcon name={icon} size={20} />
|
||||
{tooltip}
|
||||
</TradingDropdownItem>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileBarActionsDropdown = ({
|
||||
currentRouteId,
|
||||
}: {
|
||||
currentRouteId: string;
|
||||
}) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<MobileActionsDropdown>
|
||||
<MobileDropdownItem
|
||||
view={ViewType.Deposit}
|
||||
icon={VegaIconNames.DEPOSIT}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileDropdownItem
|
||||
view={ViewType.Withdraw}
|
||||
icon={VegaIconNames.WITHDRAW}
|
||||
tooltip={t('Withdraw')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileDropdownItem
|
||||
view={ViewType.Transfer}
|
||||
icon={VegaIconNames.TRANSFER}
|
||||
tooltip={t('Transfer')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileDropdownItem
|
||||
view={ViewType.Info}
|
||||
icon={VegaIconNames.BREAKDOWN}
|
||||
tooltip={t('Market specification')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileDropdownItem
|
||||
view={ViewType.Settings}
|
||||
icon={VegaIconNames.COG}
|
||||
tooltip={t('Settings')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</MobileActionsDropdown>
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { SidebarButton, ViewType } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { MobileButton } from '../markets/mobile-buttons';
|
||||
|
||||
export const PortfolioSidebar = () => {
|
||||
const t = useT();
|
||||
@@ -30,3 +31,28 @@ export const PortfolioSidebar = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const PortfolioMobileSidebar = () => {
|
||||
const t = useT();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 grow md:grow-0 md:flex lg:flex-col items-center gap-2 lg:gap-4 p-1">
|
||||
<MobileButton
|
||||
view={ViewType.Deposit}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileButton
|
||||
view={ViewType.Withdraw}
|
||||
tooltip={t('Withdraw')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<MobileButton
|
||||
view={ViewType.Transfer}
|
||||
tooltip={t('Transfer')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getNumberFormat } from '@vegaprotocol/utils';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import omit from 'lodash/omit';
|
||||
import { useReferralProgramQuery } from './__generated__/CurrentReferralProgram';
|
||||
@@ -107,9 +107,7 @@ export const useReferralProgram = () => {
|
||||
discountFactor: Number(t.referralDiscountFactor),
|
||||
discount: BigNumber(t.referralDiscountFactor).times(100).toFixed(2) + '%',
|
||||
minimumVolume: Number(t.minimumRunningNotionalTakerVolume),
|
||||
volume: getNumberFormat(0).format(
|
||||
Number(t.minimumRunningNotionalTakerVolume)
|
||||
),
|
||||
volume: formatNumber(t.minimumRunningNotionalTakerVolume, 0),
|
||||
epochs: Number(t.minimumEpochs),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
getDateFormat,
|
||||
getDateTimeFormat,
|
||||
getNumberFormat,
|
||||
getUserLocale,
|
||||
removePaginationWrapper,
|
||||
} from '@vegaprotocol/utils';
|
||||
@@ -323,7 +323,7 @@ export const Statistics = ({
|
||||
}
|
||||
description={<QUSDTooltip />}
|
||||
>
|
||||
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
||||
{formatNumber(totalCommissionValue, 0)}
|
||||
</StatTile>
|
||||
);
|
||||
|
||||
@@ -563,8 +563,8 @@ export const RefereesTable = ({
|
||||
)
|
||||
.map((r) => ({
|
||||
...r,
|
||||
volume: getNumberFormat(0).format(r.volume),
|
||||
commission: getNumberFormat(0).format(r.commission),
|
||||
volume: formatNumber(r.volume, 0),
|
||||
commission: formatNumber(r.commission, 0),
|
||||
}))
|
||||
.reverse()}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { getNumberFormat } from '@vegaprotocol/utils';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { type useTeams } from '../../lib/hooks/use-teams';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { Table } from '../table';
|
||||
@@ -15,8 +15,7 @@ export const CompetitionsLeaderboard = ({
|
||||
}) => {
|
||||
const t = useT();
|
||||
|
||||
const num = (n?: number | string) =>
|
||||
!n ? '-' : getNumberFormat(0).format(Number(n));
|
||||
const num = (n?: number | string) => (!n ? '-' : formatNumber(n, 0));
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return <Splash>{t('Could not find any teams')}</Splash>;
|
||||
@@ -33,9 +32,9 @@ export const CompetitionsLeaderboard = ({
|
||||
{ name: 'status', displayName: t('Status') },
|
||||
{ name: 'volume', displayName: t('Volume') },
|
||||
]}
|
||||
data={data.map((td, i) => {
|
||||
data={data.map((td) => {
|
||||
// leaderboard place or medal
|
||||
let rank: number | React.ReactNode = i + 1;
|
||||
let rank: number | React.ReactNode = td.rank;
|
||||
if (rank === 1) rank = <Rank variant="gold" />;
|
||||
if (rank === 2) rank = <Rank variant="silver" />;
|
||||
if (rank === 3) rank = <Rank variant="bronze" />;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { type TransferNode } from '@vegaprotocol/types';
|
||||
import { ActiveRewardCard } from '../rewards-container/active-rewards';
|
||||
import {
|
||||
ActiveRewardCard,
|
||||
isActiveReward,
|
||||
} from '../rewards-container/active-rewards';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
||||
import { useMarketsMapProvider } from '@vegaprotocol/markets';
|
||||
|
||||
export const GamesContainer = ({
|
||||
data,
|
||||
@@ -10,8 +15,35 @@ export const GamesContainer = ({
|
||||
currentEpoch: number;
|
||||
}) => {
|
||||
const t = useT();
|
||||
// Re-load markets and assets in the games container to ensure that the
|
||||
// the cards are updated (not grayed out) when the user navigates to the games page
|
||||
const { data: assets } = useAssetsMapProvider();
|
||||
const { data: markets } = useMarketsMapProvider();
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
const enrichedTransfers = data
|
||||
.filter((node) => isActiveReward(node, currentEpoch))
|
||||
.map((node) => {
|
||||
if (node.transfer.kind.__typename !== 'RecurringTransfer') {
|
||||
return node;
|
||||
}
|
||||
|
||||
const asset =
|
||||
assets &&
|
||||
assets[
|
||||
node.transfer.kind.dispatchStrategy?.dispatchMetricAssetId || ''
|
||||
];
|
||||
|
||||
const marketsInScope =
|
||||
node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map(
|
||||
(id) => markets && markets[id]
|
||||
);
|
||||
|
||||
return { ...node, asset, markets: marketsInScope };
|
||||
});
|
||||
|
||||
if (!enrichedTransfers || !enrichedTransfers.length) return null;
|
||||
|
||||
if (!enrichedTransfers || enrichedTransfers.length === 0) {
|
||||
return (
|
||||
<p className="mb-6 text-muted">
|
||||
{t('There are currently no games available.')}
|
||||
@@ -21,7 +53,7 @@ export const GamesContainer = ({
|
||||
|
||||
return (
|
||||
<div className="mb-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{data.map((game, i) => {
|
||||
{enrichedTransfers.map((game, i) => {
|
||||
// TODO: Remove `kind` prop from ActiveRewardCard
|
||||
const { transfer } = game;
|
||||
if (
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { isValidUrl } from '@vegaprotocol/utils';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const NUM_AVATARS = 20;
|
||||
const AVATAR_PATHNAME_PATTERN = '/team-avatars/{id}.png';
|
||||
@@ -11,6 +13,26 @@ export const getFallbackAvatar = (teamId: string) => {
|
||||
return AVATAR_PATHNAME_PATTERN.replace('{id}', avatarId);
|
||||
};
|
||||
|
||||
const useAvatar = (teamId: string, url: string) => {
|
||||
const fallback = getFallbackAvatar(teamId);
|
||||
const [avatar, setAvatar] = useState<string>(fallback);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isValidUrl(url)) return;
|
||||
fetch(url, { cache: 'force-cache' })
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
setAvatar(url);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/** noop */
|
||||
});
|
||||
});
|
||||
|
||||
return avatar;
|
||||
};
|
||||
|
||||
export const TeamAvatar = ({
|
||||
teamId,
|
||||
imgUrl,
|
||||
@@ -22,7 +44,7 @@ export const TeamAvatar = ({
|
||||
alt?: string;
|
||||
size?: 'large' | 'small';
|
||||
}) => {
|
||||
const img = imgUrl && imgUrl.length > 0 ? imgUrl : getFallbackAvatar(teamId);
|
||||
const img = useAvatar(teamId, imgUrl);
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
|
||||
@@ -16,7 +16,7 @@ export const LayoutWithSidebar = ({
|
||||
const sidebarOpen = sidebarView !== null;
|
||||
const gridClasses = classNames(
|
||||
'h-full relative z-0 grid',
|
||||
'grid-rows-[min-content_1fr_40px]',
|
||||
'grid-rows-[min-content_1fr_50px]',
|
||||
'lg:grid-rows-[min-content_1fr]',
|
||||
'lg:grid-cols-[1fr_280px_40px]',
|
||||
'xxxl:grid-cols-[1fr_320px_40px]'
|
||||
|
||||
@@ -468,7 +468,7 @@ export const ActiveRewardCard = ({
|
||||
}
|
||||
</div>
|
||||
{dispatchStrategy?.dispatchMetric && (
|
||||
<span className="text-muted text-sm h-[2rem]">
|
||||
<span className="text-muted text-sm h-[3rem]">
|
||||
{t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { ErrorBoundary } from '../error-boundary';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export enum ViewType {
|
||||
Order = 'Order',
|
||||
@@ -26,9 +27,10 @@ export enum ViewType {
|
||||
Transfer = 'Transfer',
|
||||
Settings = 'Settings',
|
||||
ViewAs = 'ViewAs',
|
||||
Close = 'Close',
|
||||
}
|
||||
|
||||
type SidebarView =
|
||||
export type BarView =
|
||||
| {
|
||||
type: ViewType.Deposit;
|
||||
assetId?: string;
|
||||
@@ -49,6 +51,9 @@ type SidebarView =
|
||||
}
|
||||
| {
|
||||
type: ViewType.Settings;
|
||||
}
|
||||
| {
|
||||
type: ViewType.Close;
|
||||
};
|
||||
|
||||
export const Sidebar = ({ options }: { options?: ReactNode }) => {
|
||||
@@ -57,26 +62,52 @@ export const Sidebar = ({ options }: { options?: ReactNode }) => {
|
||||
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
|
||||
const { pubKeys } = useVegaWallet();
|
||||
const { isMobile } = useScreenDimensions();
|
||||
const { getView } = useSidebar((store) => ({
|
||||
setViews: store.setViews,
|
||||
getView: store.getView,
|
||||
}));
|
||||
const currView = getView(currentRouteId);
|
||||
return (
|
||||
<div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar">
|
||||
{options && <nav className={navClasses}>{options}</nav>}
|
||||
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
||||
<SidebarButton
|
||||
view={ViewType.ViewAs}
|
||||
onClick={() => {
|
||||
setViewAsDialogOpen(true);
|
||||
}}
|
||||
icon={VegaIconNames.EYE}
|
||||
tooltip={t('View as party')}
|
||||
disabled={Boolean(pubKeys)}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Settings}
|
||||
icon={VegaIconNames.COG}
|
||||
tooltip={t('Settings')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<div className="flex h-full lg:flex-col gap-1" data-testid="sidebar">
|
||||
{options && (
|
||||
<nav className={classNames(navClasses, 'flex grow')}>{options}</nav>
|
||||
)}
|
||||
<nav
|
||||
className={classNames(
|
||||
navClasses,
|
||||
'ml-auto lg:mt-auto lg:ml-0 shrink-0'
|
||||
)}
|
||||
>
|
||||
{!isMobile ? (
|
||||
<>
|
||||
<SidebarButton
|
||||
view={ViewType.ViewAs}
|
||||
onClick={() => {
|
||||
setViewAsDialogOpen(true);
|
||||
}}
|
||||
icon={VegaIconNames.EYE}
|
||||
tooltip={t('View as party')}
|
||||
disabled={Boolean(pubKeys)}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Settings}
|
||||
icon={VegaIconNames.COG}
|
||||
tooltip={t('Settings')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
currView && (
|
||||
<SidebarButton
|
||||
view={ViewType.Close}
|
||||
icon={VegaIconNames.ARROW_LEFT}
|
||||
tooltip={t('Back')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<NodeHealthContainer />
|
||||
</nav>
|
||||
</div>
|
||||
@@ -103,7 +134,7 @@ export const SidebarButton = ({
|
||||
getView: store.getView,
|
||||
}));
|
||||
const currView = getView(routeId);
|
||||
const onSelect = (view: SidebarView['type']) => {
|
||||
const onSelect = (view: BarView['type']) => {
|
||||
if (view === currView?.type) {
|
||||
setViews(null, routeId);
|
||||
} else {
|
||||
@@ -133,7 +164,7 @@ export const SidebarButton = ({
|
||||
<button
|
||||
className={buttonClasses}
|
||||
data-testid={view}
|
||||
onClick={onClick || (() => onSelect(view as SidebarView['type']))}
|
||||
onClick={onClick || (() => onSelect(view as BarView['type']))}
|
||||
disabled={disabled}
|
||||
>
|
||||
<VegaIcon name={icon} size={20} />
|
||||
@@ -180,6 +211,10 @@ export const SidebarContent = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (view.type === ViewType.Close) {
|
||||
return <CloseSidebar />;
|
||||
}
|
||||
|
||||
if (view.type === ViewType.Info) {
|
||||
if (params.marketId) {
|
||||
return (
|
||||
@@ -267,9 +302,9 @@ const CloseSidebar = () => {
|
||||
};
|
||||
|
||||
export const useSidebar = create<{
|
||||
views: { [key: string]: SidebarView | null };
|
||||
setViews: (view: SidebarView | null, routeId: string) => void;
|
||||
getView: (routeId: string) => SidebarView | null | undefined;
|
||||
views: { [key: string]: BarView | null };
|
||||
setViews: (view: BarView | null, routeId: string) => void;
|
||||
getView: (routeId: string) => BarView | null | undefined;
|
||||
}>()((set, get) => ({
|
||||
views: {},
|
||||
setViews: (x, routeId) =>
|
||||
|
||||
@@ -288,7 +288,7 @@ def test_game_card(competitions_page: Page):
|
||||
expect(game_1.get_by_test_id("distribution-strategy")
|
||||
).to_have_text("Pro rata")
|
||||
expect(game_1.get_by_test_id("dispatch-metric-info")
|
||||
).to_have_text("Price maker fees paid • ")
|
||||
).to_have_text("Price maker fees paid • tDAI")
|
||||
expect(game_1.get_by_test_id("assessed-over")).to_have_text("15 epochs")
|
||||
expect(game_1.get_by_test_id("scope")).to_have_text("In team")
|
||||
expect(game_1.get_by_test_id("staking-requirement")).to_have_text("0.00")
|
||||
|
||||
@@ -36,7 +36,9 @@ export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => {
|
||||
...stats.find((s) => s.teamId === t.teamId),
|
||||
}));
|
||||
|
||||
return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc');
|
||||
return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc').map(
|
||||
(d, i) => ({ ...d, rank: i + 1 })
|
||||
);
|
||||
}, [teams, stats]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,10 @@ import { compact } from 'lodash';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { LiquidityHeader } from '../components/liquidity-header';
|
||||
import { MarketHeader, MobileMarketHeader } from '../components/market-header';
|
||||
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
|
||||
import {
|
||||
PortfolioMobileSidebar,
|
||||
PortfolioSidebar,
|
||||
} from '../client-pages/portfolio/portfolio-sidebar';
|
||||
import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar';
|
||||
import { MarketsSidebar } from '../client-pages/markets/markets-sidebar';
|
||||
import { useT } from '../lib/use-t';
|
||||
@@ -33,9 +36,10 @@ import { CompetitionsTeams } from '../client-pages/competitions/competitions-tea
|
||||
import { CompetitionsTeam } from '../client-pages/competitions/competitions-team';
|
||||
import { CompetitionsCreateTeam } from '../client-pages/competitions/competitions-create-team';
|
||||
import { CompetitionsUpdateTeam } from '../client-pages/competitions/competitions-update-team';
|
||||
import { MarketsMobileSidebar } from '../client-pages/markets/mobile-buttons';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
|
||||
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
|
||||
// These must remain dynamically imported as pennant cannot be compiled by Next.js due to ESM
|
||||
// Using dynamic imports is a workaround for this until pennant is published as ESM
|
||||
const MarketPage = lazy(() => import('../client-pages/market'));
|
||||
const Portfolio = lazy(() => import('../client-pages/portfolio'));
|
||||
@@ -54,6 +58,17 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
|
||||
const marketHeader = largeScreen ? <MarketHeader /> : <MobileMarketHeader />;
|
||||
const marketsSidebar = largeScreen ? (
|
||||
<MarketsSidebar />
|
||||
) : (
|
||||
<MarketsMobileSidebar />
|
||||
);
|
||||
const portfolioSidebar = largeScreen ? (
|
||||
<PortfolioSidebar />
|
||||
) : (
|
||||
<PortfolioMobileSidebar />
|
||||
);
|
||||
|
||||
const routeConfig = compact([
|
||||
{
|
||||
index: true,
|
||||
@@ -70,7 +85,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
featureFlags.REFERRALS
|
||||
? {
|
||||
path: AppRoutes.REFERRALS,
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
element: (
|
||||
@@ -103,7 +118,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
featureFlags.TEAM_COMPETITION
|
||||
? {
|
||||
path: AppRoutes.COMPETITIONS,
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
// pages with planets and stars
|
||||
{
|
||||
@@ -134,7 +149,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
: undefined,
|
||||
{
|
||||
path: 'fees/*',
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -144,7 +159,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
},
|
||||
{
|
||||
path: 'rewards/*',
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -155,7 +170,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
{
|
||||
path: 'markets/*',
|
||||
element: (
|
||||
<LayoutWithSidebar header={marketHeader} sidebar={<MarketsSidebar />} />
|
||||
<LayoutWithSidebar header={marketHeader} sidebar={marketsSidebar} />
|
||||
),
|
||||
children: [
|
||||
{
|
||||
@@ -176,7 +191,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
},
|
||||
{
|
||||
path: 'portfolio/*',
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
query TransferFee(
|
||||
$fromAccount: ID!
|
||||
$fromAccountType: AccountType!
|
||||
$toAccount: ID!
|
||||
$amount: String!
|
||||
$assetId: String!
|
||||
) {
|
||||
estimateTransferFee(
|
||||
fromAccount: $fromAccount
|
||||
fromAccountType: $fromAccountType
|
||||
toAccount: $toAccount
|
||||
amount: $amount
|
||||
assetId: $assetId
|
||||
) {
|
||||
fee
|
||||
discount
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type TransferFeeQueryVariables = Types.Exact<{
|
||||
fromAccount: Types.Scalars['ID'];
|
||||
fromAccountType: Types.AccountType;
|
||||
toAccount: Types.Scalars['ID'];
|
||||
amount: Types.Scalars['String'];
|
||||
assetId: Types.Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type TransferFeeQuery = { __typename?: 'Query', estimateTransferFee?: { __typename?: 'EstimatedTransferFee', fee: string, discount: string } | null };
|
||||
|
||||
|
||||
export const TransferFeeDocument = gql`
|
||||
query TransferFee($fromAccount: ID!, $fromAccountType: AccountType!, $toAccount: ID!, $amount: String!, $assetId: String!) {
|
||||
estimateTransferFee(
|
||||
fromAccount: $fromAccount
|
||||
fromAccountType: $fromAccountType
|
||||
toAccount: $toAccount
|
||||
amount: $amount
|
||||
assetId: $assetId
|
||||
) {
|
||||
fee
|
||||
discount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useTransferFeeQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useTransferFeeQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useTransferFeeQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useTransferFeeQuery({
|
||||
* variables: {
|
||||
* fromAccount: // value for 'fromAccount'
|
||||
* fromAccountType: // value for 'fromAccountType'
|
||||
* toAccount: // value for 'toAccount'
|
||||
* amount: // value for 'amount'
|
||||
* assetId: // value for 'assetId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useTransferFeeQuery(baseOptions: Apollo.QueryHookOptions<TransferFeeQuery, TransferFeeQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<TransferFeeQuery, TransferFeeQueryVariables>(TransferFeeDocument, options);
|
||||
}
|
||||
export function useTransferFeeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TransferFeeQuery, TransferFeeQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<TransferFeeQuery, TransferFeeQueryVariables>(TransferFeeDocument, options);
|
||||
}
|
||||
export type TransferFeeQueryHookResult = ReturnType<typeof useTransferFeeQuery>;
|
||||
export type TransferFeeLazyQueryHookResult = ReturnType<typeof useTransferFeeLazyQuery>;
|
||||
export type TransferFeeQueryResult = Apollo.QueryResult<TransferFeeQuery, TransferFeeQueryVariables>;
|
||||
@@ -25,7 +25,6 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const t = useT();
|
||||
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.transfer_fee_factor,
|
||||
NetworkParams.transfer_minTransferQuantumMultiple,
|
||||
]);
|
||||
|
||||
@@ -72,7 +71,6 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
|
||||
isReadOnly={isReadOnly}
|
||||
assetId={assetId}
|
||||
feeFactor={params.transfer_fee_factor}
|
||||
minQuantumMultiple={params.transfer_minTransferQuantumMultiple}
|
||||
submitTransfer={transfer}
|
||||
accounts={sortedAccounts}
|
||||
|
||||
@@ -15,6 +15,30 @@ import {
|
||||
} from './transfer-form';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { removeDecimal } from '@vegaprotocol/utils';
|
||||
import type { TransferFeeQuery } from './__generated__/TransferFee';
|
||||
|
||||
const feeFactor = 0.001;
|
||||
const mockUseTransferFeeQuery = jest.fn(
|
||||
({
|
||||
variables: { amount },
|
||||
}: {
|
||||
variables: { amount: string };
|
||||
}): { data: TransferFeeQuery } => {
|
||||
return {
|
||||
data: {
|
||||
estimateTransferFee: {
|
||||
discount: '0',
|
||||
fee: (Number(amount) * feeFactor).toFixed(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock('./__generated__/TransferFee', () => ({
|
||||
useTransferFeeQuery: (props: { variables: { amount: string } }) =>
|
||||
mockUseTransferFeeQuery(props),
|
||||
}));
|
||||
|
||||
describe('TransferForm', () => {
|
||||
const renderComponent = (props: TransferFormProps) => {
|
||||
@@ -56,7 +80,6 @@ describe('TransferForm', () => {
|
||||
const props = {
|
||||
pubKey,
|
||||
pubKeys: [pubKey, '2'.repeat(64)],
|
||||
feeFactor: '0.001',
|
||||
submitTransfer: jest.fn(),
|
||||
accounts: [
|
||||
{
|
||||
@@ -79,7 +102,6 @@ describe('TransferForm', () => {
|
||||
pubKey,
|
||||
'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce',
|
||||
],
|
||||
feeFactor: '0.001',
|
||||
submitTransfer: jest.fn(),
|
||||
accounts: [],
|
||||
minQuantumMultiple: '1',
|
||||
@@ -96,10 +118,6 @@ describe('TransferForm', () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
targetText: 'Transfer fee',
|
||||
tooltipText: /transfer\.fee\.factor/,
|
||||
},
|
||||
{
|
||||
targetText: 'Amount to be transferred',
|
||||
tooltipText: /without the fee/,
|
||||
@@ -109,9 +127,6 @@ describe('TransferForm', () => {
|
||||
tooltipText: /total amount taken from your account/,
|
||||
},
|
||||
])('Tooltip for "$targetText" shows', async (o) => {
|
||||
// 1003-TRAN-015
|
||||
// 1003-TRAN-016
|
||||
// 1003-TRAN-017
|
||||
// 1003-TRAN-018
|
||||
// 1003-TRAN-019
|
||||
renderComponent(props);
|
||||
@@ -124,6 +139,10 @@ describe('TransferForm', () => {
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
// set valid amount
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
await userEvent.type(amountInput, amount);
|
||||
@@ -214,9 +233,7 @@ describe('TransferForm', () => {
|
||||
// set valid amount
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
|
||||
new BigNumber(props.feeFactor).times(amount).toFixed()
|
||||
);
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent('1');
|
||||
|
||||
await submit();
|
||||
|
||||
@@ -385,47 +402,44 @@ describe('TransferForm', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('IncludeFeesCheckbox', () => {
|
||||
it('validates fields when checkbox is not checked', async () => {
|
||||
renderComponent(props);
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
it('validates fields', async () => {
|
||||
renderComponent(props);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('To Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('To Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
await userEvent.type(amountInput, amount);
|
||||
const expectedFee = new BigNumber(amount)
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||
// 1003-TRAN-021
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||
});
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
await userEvent.type(amountInput, amount);
|
||||
const expectedFee = new BigNumber(amount).times(feeFactor).toFixed();
|
||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||
// 1003-TRAN-021
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||
});
|
||||
|
||||
describe('AddressField', () => {
|
||||
@@ -457,24 +471,29 @@ describe('TransferForm', () => {
|
||||
|
||||
describe('TransferFee', () => {
|
||||
const props = {
|
||||
amount: '200',
|
||||
feeFactor: '0.001',
|
||||
fee: '0.2',
|
||||
transferAmount: '200',
|
||||
decimals: 8,
|
||||
amount: '20000',
|
||||
discount: '0',
|
||||
fee: '20',
|
||||
decimals: 2,
|
||||
};
|
||||
it('calculates and renders the transfer fee', () => {
|
||||
it('calculates and renders amounts and fee', () => {
|
||||
render(<TransferFee {...props} />);
|
||||
expect(screen.queryByTestId('discount')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent('0.2');
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent('200.00');
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
|
||||
'200.20'
|
||||
);
|
||||
});
|
||||
|
||||
const expected = new BigNumber(props.amount)
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const total = new BigNumber(props.amount).plus(expected).toFixed();
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
||||
props.amount
|
||||
it('calculates and renders amounts, fee and discount', () => {
|
||||
render(<TransferFee {...props} discount="10" />);
|
||||
expect(screen.getByTestId('discount')).toHaveTextContent('0.1');
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent('0.2');
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent('200.00');
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
|
||||
'200.10'
|
||||
);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,8 +4,9 @@ import {
|
||||
useRequired,
|
||||
useVegaPublicKey,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
toBigNum,
|
||||
removeDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { useT } from './use-t';
|
||||
import {
|
||||
@@ -21,10 +22,11 @@ import type { Transfer } from '@vegaprotocol/wallet';
|
||||
import { normalizeTransfer } from '@vegaprotocol/wallet';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { AssetOption, Balance } from '@vegaprotocol/assets';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { useTransferFeeQuery } from './__generated__/TransferFee';
|
||||
|
||||
interface FormFields {
|
||||
toVegaKey: string;
|
||||
@@ -51,7 +53,6 @@ export interface TransferFormProps {
|
||||
asset: Asset;
|
||||
}>;
|
||||
assetId?: string;
|
||||
feeFactor: string | null;
|
||||
minQuantumMultiple: string | null;
|
||||
submitTransfer: (transfer: Transfer) => void;
|
||||
}
|
||||
@@ -61,7 +62,6 @@ export const TransferForm = ({
|
||||
pubKeys,
|
||||
isReadOnly,
|
||||
assetId: initialAssetId,
|
||||
feeFactor,
|
||||
submitTransfer,
|
||||
accounts,
|
||||
minQuantumMultiple,
|
||||
@@ -136,11 +136,22 @@ export const TransferForm = ({
|
||||
|
||||
// Max amount given selected asset and from account
|
||||
const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0);
|
||||
const normalizedAmount =
|
||||
(amount && asset && removeDecimal(amount, asset.decimals)) || '0';
|
||||
|
||||
const fee = useMemo(
|
||||
() => feeFactor && new BigNumber(feeFactor).times(amount).toString(),
|
||||
[amount, feeFactor]
|
||||
);
|
||||
const transferFeeQuery = useTransferFeeQuery({
|
||||
variables: {
|
||||
fromAccount: pubKey || '',
|
||||
fromAccountType: accountType || AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
amount: normalizedAmount,
|
||||
assetId: asset?.id || '',
|
||||
toAccount: selectedPubKey,
|
||||
},
|
||||
skip: !pubKey || !amount || !asset || !selectedPubKey || fromVested,
|
||||
});
|
||||
const transferFee = transferFeeQuery.loading
|
||||
? transferFeeQuery.data || transferFeeQuery.previousData
|
||||
: transferFeeQuery.data;
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(fields: FormFields) => {
|
||||
@@ -432,12 +443,14 @@ export const TransferForm = ({
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
{amount && fee && (
|
||||
{(transferFee?.estimateTransferFee || fromVested) && amount && asset && (
|
||||
<TransferFee
|
||||
amount={amount}
|
||||
feeFactor={feeFactor}
|
||||
fee={fromVested ? '0' : fee}
|
||||
decimals={asset?.decimals}
|
||||
amount={normalizedAmount}
|
||||
fee={fromVested ? '0' : transferFee?.estimateTransferFee?.fee}
|
||||
discount={
|
||||
fromVested ? '0' : transferFee?.estimateTransferFee?.discount
|
||||
}
|
||||
decimals={asset.decimals}
|
||||
/>
|
||||
)}
|
||||
<TradingButton type="submit" fill={true} disabled={isReadOnly}>
|
||||
@@ -449,39 +462,44 @@ export const TransferForm = ({
|
||||
|
||||
export const TransferFee = ({
|
||||
amount,
|
||||
feeFactor,
|
||||
fee,
|
||||
discount,
|
||||
decimals,
|
||||
}: {
|
||||
amount: string;
|
||||
feeFactor: string | null;
|
||||
fee?: string;
|
||||
decimals?: number;
|
||||
discount?: string;
|
||||
decimals: number;
|
||||
}) => {
|
||||
const t = useT();
|
||||
if (!feeFactor || !amount || !fee) return null;
|
||||
if (isNaN(Number(feeFactor)) || isNaN(Number(amount)) || isNaN(Number(fee))) {
|
||||
if (!amount || !fee) return null;
|
||||
if (isNaN(Number(amount)) || isNaN(Number(fee))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalValue = new BigNumber(amount).plus(fee).toString();
|
||||
const totalValue = (
|
||||
BigInt(amount) +
|
||||
BigInt(fee) -
|
||||
BigInt(discount || '0')
|
||||
).toString();
|
||||
|
||||
return (
|
||||
<div className="mb-4 flex flex-col gap-2 text-xs">
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}`,
|
||||
{ feeFactor }
|
||||
)}
|
||||
>
|
||||
<div>{t('Transfer fee')}</div>
|
||||
</Tooltip>
|
||||
|
||||
<div>{t('Transfer fee')}</div>
|
||||
<div data-testid="transfer-fee" className="text-muted">
|
||||
{formatNumber(fee, decimals)}
|
||||
{addDecimalsFormatNumber(fee, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
{discount && discount !== '0' && (
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<div>{t('Discount')}</div>
|
||||
<div data-testid="discount" className="text-muted">
|
||||
{addDecimalsFormatNumber(discount, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<Tooltip
|
||||
description={t(
|
||||
@@ -492,7 +510,7 @@ export const TransferFee = ({
|
||||
</Tooltip>
|
||||
|
||||
<div data-testid="transfer-amount" className="text-muted">
|
||||
{formatNumber(amount, decimals)}
|
||||
{addDecimalsFormatNumber(amount, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
@@ -505,7 +523,7 @@ export const TransferFee = ({
|
||||
</Tooltip>
|
||||
|
||||
<div data-testid="total-transfer-fee" className="text-muted">
|
||||
{formatNumber(totalValue, decimals)}
|
||||
{addDecimalsFormatNumber(totalValue, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,6 +86,7 @@ export const DocsLinks = VEGA_DOCS_URL
|
||||
POST_REDUCE_ONLY: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#conditional-order-parameters`,
|
||||
QUANTUM: `${VEGA_DOCS_URL}/concepts/assets/asset-framework#quantum`,
|
||||
REFERRALS: `${VEGA_DOCS_URL}/tutorials/proposals/referral-program-proposal`,
|
||||
LIQUIDITY_FEE_PERCENTAGE: `${VEGA_DOCS_URL}/concepts/liquidity/rewards-penalties#determining-the-liquidity-fee-percentage`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
"The total amount of each asset on this key. Includes used and available collateral.": "The total amount of each asset on this key. Includes used and available collateral.",
|
||||
"The total amount taken from your account. The amount to be transferred plus the fee.": "The total amount taken from your account. The amount to be transferred plus the fee.",
|
||||
"The total amount to be transferred (without the fee)": "The total amount to be transferred (without the fee)",
|
||||
"The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}": "The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}",
|
||||
"To account": "To account",
|
||||
"To Vega key": "To Vega key",
|
||||
"Total": "Total",
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"Fills": "Fills",
|
||||
"Final commission rate": "Final commission rate",
|
||||
"Find out more": "Find out more",
|
||||
"For more info, visit the documentation": "For more info, visit the documentation",
|
||||
"Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.": "Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.",
|
||||
"From epoch": "From epoch",
|
||||
"Fully decentralised high performance peer-to-network trading.": "Fully decentralised high performance peer-to-network trading.",
|
||||
@@ -446,5 +447,9 @@
|
||||
"Choose a team": "Choose a team",
|
||||
"Join a team": "Join a team",
|
||||
"Solo team / lone wolf": "Solo team / lone wolf",
|
||||
"Choose a team to get involved": "Choose a team to get involved"
|
||||
"Choose a team to get involved": "Choose a team to get involved",
|
||||
"Go back to the team's profile": "Go back to the team's profile",
|
||||
"Go back to the competitions": "Go back to the competitions",
|
||||
"Your team ID:": "Your team ID:",
|
||||
"Changes successfully saved to your team.": "Changes successfully saved to your team."
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export const LiquidityTable = ({
|
||||
return `${addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
quantum ?? 1
|
||||
)}`;
|
||||
};
|
||||
|
||||
@@ -165,7 +165,7 @@ export const LiquidityTable = ({
|
||||
return `${addDecimalsFormatNumberQuantum(
|
||||
newValue,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
quantum ?? 1
|
||||
)}`;
|
||||
};
|
||||
|
||||
@@ -227,7 +227,7 @@ export const LiquidityTable = ({
|
||||
addDecimalsFormatNumberQuantum(
|
||||
pendingCommitmentAmount,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
quantum ?? 1
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -238,7 +238,7 @@ export const LiquidityTable = ({
|
||||
addDecimalsFormatNumberQuantum(
|
||||
currentCommitmentAmount,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
quantum ?? 1
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -286,7 +286,7 @@ export const LiquidityTable = ({
|
||||
addDecimalsFormatNumberQuantum(
|
||||
pendingCommitmentAmount,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
quantum ?? 1
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -297,7 +297,7 @@ export const LiquidityTable = ({
|
||||
addDecimalsFormatNumberQuantum(
|
||||
currentCommitmentAmount,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
quantum ?? 1
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DepthChart } from 'pennant';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { addDecimal, getNumberFormat } from '@vegaprotocol/utils';
|
||||
import { addDecimal, formatNumber } from '@vegaprotocol/utils';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketDepthProvider } from './market-depth-provider';
|
||||
@@ -216,13 +216,12 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
||||
|
||||
const volumeFormat = useCallback(
|
||||
(volume: number) =>
|
||||
getNumberFormat(market?.positionDecimalPlaces || 0).format(volume),
|
||||
formatNumber(volume, market?.positionDecimalPlaces || 0),
|
||||
[market?.positionDecimalPlaces]
|
||||
);
|
||||
|
||||
const priceFormat = useCallback(
|
||||
(price: number) =>
|
||||
getNumberFormat(market?.decimalPlaces || 0).format(price),
|
||||
(price: number) => formatNumber(price, market?.decimalPlaces || 0),
|
||||
[market?.decimalPlaces]
|
||||
);
|
||||
|
||||
|
||||
+6
-2
File diff suppressed because one or more lines are too long
@@ -171,6 +171,10 @@ query MarketInfo($marketId: ID!) {
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
liquidityFeeSettings {
|
||||
feeConstant
|
||||
method
|
||||
}
|
||||
}
|
||||
priceMonitoringSettings {
|
||||
parameters {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -28,6 +28,7 @@ import {
|
||||
InsurancePoolInfoPanel,
|
||||
KeyDetailsInfoPanel,
|
||||
LiquidationStrategyInfoPanel,
|
||||
LiquidityFeesSettings,
|
||||
LiquidityInfoPanel,
|
||||
LiquidityMonitoringParametersInfoPanel,
|
||||
LiquidityPriceRangeInfoPanel,
|
||||
@@ -300,6 +301,11 @@ export const MarketInfoAccordion = ({
|
||||
}
|
||||
content={<LiquiditySLAParametersInfoPanel market={market} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="lp-fee-settings"
|
||||
title={t('Liquidity fee settings')}
|
||||
content={<LiquidityFeesSettings market={market} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="liquidity"
|
||||
title={t('Liquidity')}
|
||||
|
||||
@@ -44,6 +44,8 @@ import type {
|
||||
} from '@vegaprotocol/types';
|
||||
import {
|
||||
ConditionOperatorMapping,
|
||||
LiquidityFeeMethodMapping,
|
||||
LiquidityFeeMethodMappingDescription,
|
||||
MarketStateMapping,
|
||||
MarketTradingModeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
@@ -54,6 +56,7 @@ import {
|
||||
TOKEN_PROPOSAL,
|
||||
useEnvironment,
|
||||
useLinks,
|
||||
DocsLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import type { Provider } from '../../oracle-schema';
|
||||
import { OracleBasicProfile } from '../../components/oracle-basic-profile';
|
||||
@@ -110,6 +113,44 @@ export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const LiquidityFeesSettings = ({ market }: MarketInfoProps) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<>
|
||||
<MarketInfoTable
|
||||
data={{
|
||||
feeConstant: market.fees.liquidityFeeSettings?.feeConstant,
|
||||
method: market.fees.liquidityFeeSettings && (
|
||||
<Tooltip
|
||||
description={
|
||||
LiquidityFeeMethodMappingDescription[
|
||||
market.fees.liquidityFeeSettings?.method
|
||||
]
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{
|
||||
LiquidityFeeMethodMapping[
|
||||
market.fees.liquidityFeeSettings?.method
|
||||
]
|
||||
}
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<p className="text-xs">
|
||||
<ExternalLink
|
||||
href={DocsLinks?.LIQUIDITY_FEE_PERCENTAGE}
|
||||
className="mt-2"
|
||||
>
|
||||
{t('Fore more info, visit the documentation')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => {
|
||||
const t = useT();
|
||||
const assetSymbol = getAsset(market).symbol;
|
||||
|
||||
@@ -16,7 +16,6 @@ export const useTooltipMapping: () => Record<string, ReactNode> = () => {
|
||||
infrastructureFee: t(
|
||||
'Fees paid to validators as a reward for running the infrastructure of the network.'
|
||||
),
|
||||
|
||||
markPrice: t(
|
||||
'A concept derived from traditional markets. It is a calculated value for the ‘current market price’ on a market.'
|
||||
),
|
||||
@@ -154,5 +153,9 @@ export const useTooltipMapping: () => Record<string, ReactNode> = () => {
|
||||
minProbabilityOfTradingLPOrders: t(
|
||||
'The lower bound for the probability of trading calculation, used to measure liquidity available on a market to determine if LPs are meeting their commitment. This is a network parameter.'
|
||||
),
|
||||
method: t(`The method used to calculate the market's liquidity fee.`),
|
||||
feeConstant: t(
|
||||
'The constant liquidity fee used when using the constant fee method .'
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -240,7 +240,7 @@ export const OracleFullProfile = ({
|
||||
<div className="font-alpha calt dark:text-vega-light-300 text-vega-dark-300 mb-2 grid grid-cols-4 gap-1 uppercase">
|
||||
<div className="col-span-1">{t('Market')}</div>
|
||||
<div className="col-span-1">{t('Status')}</div>
|
||||
<div className="col-span-1">{t('Specifications')}</div>
|
||||
<div className="col-span-2">{t('Specifications')}</div>
|
||||
</div>
|
||||
<div className="max-h-60 overflow-auto">
|
||||
{oracleMarkets?.map((market) => (
|
||||
|
||||
@@ -12,6 +12,10 @@ fragment MarketFields on Market {
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
liquidityFeeSettings {
|
||||
feeConstant
|
||||
method
|
||||
}
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
|
||||
@@ -52,6 +52,11 @@ export const createMarketFragment = (
|
||||
infrastructureFee: '',
|
||||
liquidityFee: '',
|
||||
},
|
||||
liquidityFeeSettings: {
|
||||
__typename: 'LiquidityFeeSettings',
|
||||
method: Schema.LiquidityFeeMethod.METHOD_MARGINAL_COST,
|
||||
feeConstant: '',
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
Generated
+58
-6
@@ -14,6 +14,32 @@ export type Scalars = {
|
||||
Timestamp: any;
|
||||
};
|
||||
|
||||
/** Margins for a hypothetical position not related to any existing party */
|
||||
export type AbstractMarginLevels = {
|
||||
__typename?: 'AbstractMarginLevels';
|
||||
/** Asset for the current margins */
|
||||
asset: Asset;
|
||||
/**
|
||||
* If the margin of the party is greater than this level, then collateral will be released from the margin account into
|
||||
* the general account of the party for the given asset.
|
||||
*/
|
||||
collateralReleaseLevel: Scalars['String'];
|
||||
/** This is the minimum margin required for a party to place a new order on the network, expressed as unsigned integer */
|
||||
initialLevel: Scalars['String'];
|
||||
/** Minimal margin for the position to be maintained in the network (unsigned integer) */
|
||||
maintenanceLevel: Scalars['String'];
|
||||
/** Margin factor, only relevant for isolated margin mode, else 0 */
|
||||
marginFactor: Scalars['String'];
|
||||
/** Margin mode of the party, cross margin or isolated margin */
|
||||
marginMode: MarginMode;
|
||||
/** Market in which the margin is required for this party */
|
||||
market: Market;
|
||||
/** When in isolated margin, the required order margin level, otherwise, 0 */
|
||||
orderMarginLevel: Scalars['String'];
|
||||
/** If the margin is between maintenance and search, the network will initiate a collateral search, expressed as unsigned integer */
|
||||
searchLevel: Scalars['String'];
|
||||
};
|
||||
|
||||
/** An account record */
|
||||
export type AccountBalance = {
|
||||
__typename?: 'AccountBalance';
|
||||
@@ -359,6 +385,8 @@ export enum AuctionTrigger {
|
||||
|
||||
export type BatchProposal = {
|
||||
__typename?: 'BatchProposal';
|
||||
/** Terms of all the proposals in the batch */
|
||||
batchTerms?: Maybe<BatchProposalTerms>;
|
||||
/** RFC3339Nano time and date when the proposal reached the network */
|
||||
datetime: Scalars['Timestamp'];
|
||||
/** Details of the rejection reason */
|
||||
@@ -389,10 +417,10 @@ export type BatchProposal = {
|
||||
votes: ProposalVotes;
|
||||
};
|
||||
|
||||
/** The rationale for the proposal */
|
||||
/** The terms for the batch proposal */
|
||||
export type BatchProposalTerms = {
|
||||
__typename?: 'BatchProposalTerms';
|
||||
/** Actual changes being introduced by the proposal - actions the proposal triggers if passed and enacted. */
|
||||
/** Actual changes being introduced by the batch proposal - actions the proposal triggers if passed and enacted. */
|
||||
changes: Array<Maybe<BatchProposalTermsChange>>;
|
||||
/**
|
||||
* RFC3339Nano time and date when voting closes for this proposal.
|
||||
@@ -531,6 +559,22 @@ export type CompositePriceConfiguration = {
|
||||
decayWeight: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CompositePriceSource = {
|
||||
__typename?: 'CompositePriceSource';
|
||||
/** The source of the price */
|
||||
PriceSource: Scalars['String'];
|
||||
/** The last time the price source was updated in RFC3339Nano */
|
||||
lastUpdated: Scalars['Timestamp'];
|
||||
/** The current value of the composite source price */
|
||||
price: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CompositePriceState = {
|
||||
__typename?: 'CompositePriceState';
|
||||
/** Underlying state of the composite price */
|
||||
priceSources?: Maybe<Array<CompositePriceSource>>;
|
||||
};
|
||||
|
||||
export enum CompositePriceType {
|
||||
/** Composite price is set to the last trade (legacy) */
|
||||
COMPOSITE_PRICE_TYPE_LAST_TRADE = 'COMPOSITE_PRICE_TYPE_LAST_TRADE',
|
||||
@@ -2165,9 +2209,9 @@ export type MarginEdge = {
|
||||
export type MarginEstimate = {
|
||||
__typename?: 'MarginEstimate';
|
||||
/** Margin level estimate assuming no slippage */
|
||||
bestCase: MarginLevels;
|
||||
bestCase: AbstractMarginLevels;
|
||||
/** Margin level estimate assuming slippage cap is applied */
|
||||
worstCase: MarginLevels;
|
||||
worstCase: AbstractMarginLevels;
|
||||
};
|
||||
|
||||
/** Margins for a given a party */
|
||||
@@ -2439,6 +2483,8 @@ export type MarketData = {
|
||||
liquidityProviderSla?: Maybe<Array<LiquidityProviderSLA>>;
|
||||
/** The mark price (an unsigned integer) */
|
||||
markPrice: Scalars['String'];
|
||||
/** State of the underlying internal composite price */
|
||||
markPriceState?: Maybe<CompositePriceState>;
|
||||
/** The methodology used for the calculation of the mark price */
|
||||
markPriceType: CompositePriceType;
|
||||
/** Market of the associated mark price */
|
||||
@@ -3053,6 +3099,8 @@ export type ObservableMarketData = {
|
||||
liquidityProviderSla?: Maybe<Array<ObservableLiquidityProviderSLA>>;
|
||||
/** The mark price (an unsigned integer) */
|
||||
markPrice: Scalars['String'];
|
||||
/** State of the underlying internal composite price */
|
||||
markPriceState?: Maybe<CompositePriceState>;
|
||||
/** The methodology used to calculated mark price */
|
||||
markPriceType: CompositePriceType;
|
||||
/** The market growth factor for the last market time window */
|
||||
@@ -4021,6 +4069,8 @@ export type PerpetualData = {
|
||||
fundingRate?: Maybe<Scalars['String']>;
|
||||
/** Internal composite price used as input to the internal VWAP */
|
||||
internalCompositePrice: Scalars['String'];
|
||||
/** The internal state of the underlying internal composite price */
|
||||
internalCompositePriceState?: Maybe<CompositePriceState>;
|
||||
/** The methodology used to calculated internal composite price for perpetual markets */
|
||||
internalCompositePriceType: CompositePriceType;
|
||||
/** Time-weighted average price calculated from data points for this period from the internal data source. */
|
||||
@@ -4031,6 +4081,8 @@ export type PerpetualData = {
|
||||
seqNum: Scalars['Int'];
|
||||
/** Time at which the funding period started */
|
||||
startTime: Scalars['Timestamp'];
|
||||
/** The last value from the external oracle */
|
||||
underlyingIndexPrice: Scalars['String'];
|
||||
};
|
||||
|
||||
export type PerpetualProduct = {
|
||||
@@ -4328,7 +4380,7 @@ export type ProposalDetail = {
|
||||
__typename?: 'ProposalDetail';
|
||||
/** Batch proposal ID that is provided by Vega once proposal reaches the network */
|
||||
batchId?: Maybe<Scalars['ID']>;
|
||||
/** Terms of the proposal for a batch proposal */
|
||||
/** Terms of all the proposals in the batch */
|
||||
batchTerms?: Maybe<BatchProposalTerms>;
|
||||
/** RFC3339Nano time and date when the proposal reached the Vega network */
|
||||
datetime: Scalars['Timestamp'];
|
||||
@@ -4354,7 +4406,7 @@ export type ProposalDetail = {
|
||||
requiredParticipation: Scalars['String'];
|
||||
/** State of the proposal */
|
||||
state: ProposalState;
|
||||
/** Terms of the proposal for proposal */
|
||||
/** Terms of the proposal */
|
||||
terms?: Maybe<ProposalTerms>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type {
|
||||
ConditionOperator,
|
||||
EntityScope,
|
||||
GovernanceTransferKind,
|
||||
GovernanceTransferType,
|
||||
IndividualScope,
|
||||
PeggedReference,
|
||||
ProposalChange,
|
||||
TransferStatus,
|
||||
import {
|
||||
type LiquidityFeeMethod,
|
||||
type ConditionOperator,
|
||||
type EntityScope,
|
||||
type GovernanceTransferKind,
|
||||
type GovernanceTransferType,
|
||||
type IndividualScope,
|
||||
type PeggedReference,
|
||||
type ProposalChange,
|
||||
type TransferStatus,
|
||||
} from './__generated__/types';
|
||||
import type { AccountType } from './__generated__/types';
|
||||
import type {
|
||||
@@ -734,3 +735,23 @@ export const ProposalProductTypeShortName: Record<ProposalProductType, string> =
|
||||
SpotProduct: 'Spot',
|
||||
PerpetualProduct: 'Perp',
|
||||
};
|
||||
|
||||
export const LiquidityFeeMethodMapping: { [e in LiquidityFeeMethod]: string } =
|
||||
{
|
||||
/** Fee is set by the market to a constant value irrespective of any liquidity provider's nominated fee */
|
||||
METHOD_CONSTANT: 'Constant',
|
||||
/** Fee is smallest value of all bids, such that liquidity providers with nominated fees less than or equal to this value still have sufficient commitment to fulfil the market's target stake. */
|
||||
METHOD_MARGINAL_COST: 'Marginal cost',
|
||||
METHOD_UNSPECIFIED: 'Unspecified',
|
||||
/** Fee is the weighted average of all liquidity providers' nominated fees, weighted by their commitment */
|
||||
METHOD_WEIGHTED_AVERAGE: 'Weighted average',
|
||||
};
|
||||
|
||||
export const LiquidityFeeMethodMappingDescription: {
|
||||
[e in LiquidityFeeMethod]: string;
|
||||
} = {
|
||||
METHOD_CONSTANT: `This liquidity fee is a constant value, set in the market parameters, and overrides the liquidity providers' nominated fees.`,
|
||||
METHOD_MARGINAL_COST: `This liquidity fee factor is determined by sorting all LP fee bids from lowest to highest, with LPs' commitments tallied up to the point of fulfilling the market's target stake. The last LP's bid becomes the fee factor.`,
|
||||
METHOD_UNSPECIFIED: 'Unspecified',
|
||||
METHOD_WEIGHTED_AVERAGE: `This liquidity fee is the weighted average of all liquidity providers' nominated fees, weighted by their commitment.`,
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ export function Dialog({
|
||||
);
|
||||
const wrapperClasses = classNames(
|
||||
// Dimensions
|
||||
'w-screen sm:max-w-[90vw] p-4 md:p-8',
|
||||
'max-w-[95vw] sm:max-w-[90vw] p-4 md:p-8',
|
||||
// Need to apply background and text colors again as content is rendered in a portal
|
||||
'dark:bg-black bg-white dark:text-white',
|
||||
getIntentBorder(intent),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
import { TradingButton } from '../trading-button';
|
||||
import {
|
||||
TradingDropdown,
|
||||
TradingDropdownContent,
|
||||
@@ -15,6 +16,16 @@ export const ActionsDropdownTrigger = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileActionsDropdownTrigger = () => {
|
||||
return (
|
||||
<TradingDropdownTrigger data-testid="dropdown-menu">
|
||||
<TradingButton size="medium">
|
||||
<VegaIcon name={VegaIconNames.KEBAB} />
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type ActionMenuContentProps = React.ComponentProps<
|
||||
typeof TradingDropdownContent
|
||||
>;
|
||||
@@ -26,3 +37,11 @@ export const ActionsDropdown = (props: ActionMenuContentProps) => {
|
||||
</TradingDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileActionsDropdown = (props: ActionMenuContentProps) => {
|
||||
return (
|
||||
<TradingDropdown trigger={<MobileActionsDropdownTrigger />}>
|
||||
<TradingDropdownContent {...props} side="bottom" align="end" />
|
||||
</TradingDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './trading-dropdown';
|
||||
export * from './actions-dropdown';
|
||||
export * from './trading-dropdown';
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('number utils', () => {
|
||||
{ v: new BigNumber(123000), d: 1, o: '12,300.0' },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01' },
|
||||
{ v: new BigNumber(123001000), d: 2, o: '1,230,010.00' },
|
||||
{ v: '100000000000000000001', d: 18, o: '100.000000000000000001' },
|
||||
])(
|
||||
'formats with addDecimalsFormatNumber given number correctly',
|
||||
({ v, d, o }) => {
|
||||
@@ -31,27 +32,10 @@ describe('number utils', () => {
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ v: new BigNumber(123000), d: 5, o: '1.23', q: 0.1 },
|
||||
{ v: new BigNumber(123000), d: 3, o: '123.00', q: 0.1 },
|
||||
{ v: new BigNumber(123000), d: 1, o: '12,300.00', q: 0.1 },
|
||||
{ v: new BigNumber(123001000), d: 2, o: '1,230,010.00', q: 0.1 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 100 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 0.1 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 1 },
|
||||
{
|
||||
v: BigNumber('123456789123456789'),
|
||||
d: 10,
|
||||
o: '12,345,678.91234568',
|
||||
q: '0.00003846',
|
||||
},
|
||||
{
|
||||
v: BigNumber('123456789123456789'),
|
||||
d: 10,
|
||||
o: '12,345,678.91234568',
|
||||
q: '1',
|
||||
},
|
||||
// USDT / USDC
|
||||
{ v: new BigNumber(12345678), d: 6, o: '12.35', q: 1000000 },
|
||||
{ v: '1234000000000000000', d: 18, q: '1000000000000000000', o: '1.23' }, //vega
|
||||
{ v: '1235000000000000000', d: 18, q: '1000000000000000000', o: '1.24' }, //vega
|
||||
{ v: '1230012', d: 6, q: '1000000', o: '1.23' }, // USDT
|
||||
{ v: '1234560000000000000', d: 18, q: '500000000000000', o: '1.2346' }, // WEth
|
||||
])(
|
||||
'formats with addDecimalsFormatNumberQuantum given number correctly',
|
||||
({ v, d, o, q }) => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import isNil from 'lodash/isNil';
|
||||
import memoize from 'lodash/memoize';
|
||||
|
||||
import { getUserLocale } from '../get-user-locale';
|
||||
@@ -53,36 +52,36 @@ export function removeDecimal(
|
||||
return new BigNumber(value || 0).times(times).toFixed(0);
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
|
||||
export const getNumberFormat = memoize((digits: number) => {
|
||||
if (isNil(digits) || digits < 0) {
|
||||
return new Intl.NumberFormat(getUserLocale());
|
||||
}
|
||||
return new Intl.NumberFormat(getUserLocale(), {
|
||||
minimumFractionDigits: Math.min(Math.max(0, digits), MIN_FRACTION_DIGITS),
|
||||
maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
|
||||
});
|
||||
});
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
|
||||
export const getFixedNumberFormat = memoize((digits: number) => {
|
||||
if (isNil(digits) || digits < 0) {
|
||||
return new Intl.NumberFormat(getUserLocale());
|
||||
}
|
||||
return new Intl.NumberFormat(getUserLocale(), {
|
||||
minimumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
|
||||
maximumFractionDigits: Math.min(Math.max(0, digits), MAX_FRACTION_DIGITS),
|
||||
});
|
||||
});
|
||||
|
||||
export const getDecimalSeparator = memoize(
|
||||
() =>
|
||||
getNumberFormat(1)
|
||||
new Intl.NumberFormat(getUserLocale())
|
||||
.formatToParts(1.1)
|
||||
.find((part) => part.type === 'decimal')?.value
|
||||
.find((part) => part.type === 'decimal')?.value ?? '.'
|
||||
);
|
||||
|
||||
/** formatNumber will format the number with fixed decimals
|
||||
export const getGroupFormat = memoize(() => {
|
||||
const parts = new Intl.NumberFormat(getUserLocale()).formatToParts(
|
||||
100000000000.1
|
||||
);
|
||||
const groupSeparator = parts.find((part) => part.type === 'group')?.value;
|
||||
const groupSize =
|
||||
(groupSeparator &&
|
||||
parts.reverse().find((part) => part.type === 'integer')?.value.length) ||
|
||||
0;
|
||||
return {
|
||||
groupSize,
|
||||
groupSeparator,
|
||||
};
|
||||
});
|
||||
|
||||
const getFormat = memoize(() => ({
|
||||
decimalSeparator: getDecimalSeparator(),
|
||||
...getGroupFormat(),
|
||||
}));
|
||||
|
||||
/**
|
||||
* formatNumber will format the number with maximum number of decimals
|
||||
* trailing zeros are removed but min(MIN_FRACTION_DIGITS, formatDecimals) decimal places will be kept
|
||||
* @param rawValue - should be a number that is not outside the safe range fail as in https://mikemcl.github.io/bignumber.js/#toN
|
||||
* @param formatDecimals - number of decimals to use
|
||||
*/
|
||||
@@ -90,7 +89,23 @@ export const formatNumber = (
|
||||
rawValue: string | number | BigNumber,
|
||||
formatDecimals = 0
|
||||
) => {
|
||||
return getNumberFormat(formatDecimals).format(Number(rawValue));
|
||||
const decimalPlaces = Math.min(
|
||||
Math.max(0, formatDecimals),
|
||||
MAX_FRACTION_DIGITS
|
||||
);
|
||||
const format = getFormat();
|
||||
const formatted = new BigNumber(rawValue).toFormat(decimalPlaces, format);
|
||||
// if there are no decimal places just return formatted value
|
||||
if (!decimalPlaces) {
|
||||
return formatted;
|
||||
}
|
||||
// minimum number of decimal places to keep when removing trailing zeros
|
||||
const minimumFractionDigits = Math.min(decimalPlaces, MIN_FRACTION_DIGITS);
|
||||
const parts = formatted.split(format.decimalSeparator);
|
||||
parts[1] = (parts[1] || '')
|
||||
.replace(/0+$/, '')
|
||||
.padEnd(minimumFractionDigits, '0');
|
||||
return parts.join(format.decimalSeparator);
|
||||
};
|
||||
|
||||
/** formatNumberFixed will format the number with fixed decimals
|
||||
@@ -101,7 +116,10 @@ export const formatNumberFixed = (
|
||||
rawValue: string | number | BigNumber,
|
||||
formatDecimals = 0
|
||||
) => {
|
||||
return getFixedNumberFormat(formatDecimals).format(Number(rawValue));
|
||||
return new BigNumber(rawValue).toFormat(
|
||||
Math.min(Math.max(0, formatDecimals), MAX_FRACTION_DIGITS),
|
||||
getFormat()
|
||||
);
|
||||
};
|
||||
|
||||
export const quantumDecimalPlaces = (
|
||||
@@ -131,9 +149,14 @@ export const addDecimalsFormatNumberQuantum = (
|
||||
if (isNaN(Number(quantum))) {
|
||||
return addDecimalsFormatNumber(rawValue, decimalPlaces);
|
||||
}
|
||||
const quantumValue = addDecimal(quantum, decimalPlaces);
|
||||
const numberDP = Math.max(0, Math.log10(100 / Number(quantumValue)));
|
||||
return addDecimalsFormatNumber(rawValue, decimalPlaces, Math.ceil(numberDP));
|
||||
const numberDP = Math.ceil(
|
||||
Math.abs(Math.log10(toBigNum(quantum, decimalPlaces).toNumber()))
|
||||
);
|
||||
return addDecimalsFormatNumber(
|
||||
rawValue,
|
||||
decimalPlaces,
|
||||
Math.max(MIN_FRACTION_DIGITS, numberDP)
|
||||
);
|
||||
};
|
||||
|
||||
export const addDecimalsFormatNumber = (
|
||||
@@ -141,9 +164,10 @@ export const addDecimalsFormatNumber = (
|
||||
decimalPlaces: number,
|
||||
formatDecimals: number = decimalPlaces
|
||||
) => {
|
||||
const x = addDecimal(rawValue, decimalPlaces);
|
||||
|
||||
return formatNumber(x, formatDecimals);
|
||||
return formatNumber(
|
||||
new BigNumber(rawValue || 0).dividedBy(Math.pow(10, decimalPlaces)),
|
||||
formatDecimals
|
||||
);
|
||||
};
|
||||
|
||||
export const addDecimalsFixedFormatNumber = (
|
||||
|
||||
@@ -10,24 +10,24 @@ describe('formatValue', () => {
|
||||
{
|
||||
v: '123456789123456789',
|
||||
d: 10,
|
||||
o: '12,345,678.91234568',
|
||||
o: '12,345,678.9123456789',
|
||||
},
|
||||
])('formats values correctly', ({ v, d, o }) => {
|
||||
expect(formatValue(v, d)).toStrictEqual(o);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ v: 123000, d: 5, o: '1.23', q: '0.1' },
|
||||
{ v: 123000, d: 3, o: '123.00', q: '0.1' },
|
||||
{ v: 123000, d: 1, o: '12,300.00', q: '0.1' },
|
||||
{ v: 123001000, d: 2, o: '1,230,010.00', q: '0.1' },
|
||||
{ v: 123000, d: 5, o: '1.23', q: '1' },
|
||||
{ v: 123000, d: 3, o: '123.00', q: '1' },
|
||||
{ v: 123000, d: 1, o: '12,300.00', q: '1' },
|
||||
{ v: 123001000, d: 2, o: '1,230,010.00', q: '1' },
|
||||
{ v: 123001, d: 2, o: '1,230.01', q: '100' },
|
||||
{ v: 123001, d: 2, o: '1,230.01', q: '0.1' },
|
||||
{ v: 123001, d: 2, o: '1,230.01', q: '1' },
|
||||
{
|
||||
v: '123456789123456789',
|
||||
d: 10,
|
||||
o: '12,345,678.91234568',
|
||||
q: '0.00003846',
|
||||
o: '12,345,678.91235',
|
||||
q: '384600',
|
||||
},
|
||||
])(
|
||||
'formats with formatValue with quantum given number correctly',
|
||||
@@ -42,15 +42,15 @@ describe('formatRange', () => {
|
||||
min: 123000,
|
||||
max: 12300011111,
|
||||
d: 5,
|
||||
o: '1.23 - 123,000.11111',
|
||||
q: '0.1',
|
||||
o: '1.23 - 123,000.11',
|
||||
q: '1000',
|
||||
},
|
||||
{
|
||||
min: 123000,
|
||||
max: 12300011111,
|
||||
d: 3,
|
||||
o: '123.00 - 12,300,011.111',
|
||||
q: '0.1',
|
||||
o: '123.00 - 12,300,011.11',
|
||||
q: '100',
|
||||
},
|
||||
{
|
||||
min: 123000,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { WalletClientError } from '@vegaprotocol/wallet-client';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { PeggedReference } from '@vegaprotocol/types';
|
||||
import {
|
||||
vegaMarginMode,
|
||||
v1UndelegateSubmissionMethod,
|
||||
} from '@vegaprotocol/rest-clients/dist/trading-data';
|
||||
|
||||
export interface LiquidityProvisionSubmission {
|
||||
liquidityProvisionSubmission: LiquidityProvisionBody;
|
||||
@@ -32,7 +36,7 @@ export interface UndelegateSubmissionBody {
|
||||
undelegateSubmission: {
|
||||
nodeId: string;
|
||||
amount: string;
|
||||
method: 'METHOD_NOW' | 'METHOD_AT_END_OF_EPOCH';
|
||||
method: v1UndelegateSubmissionMethod;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -472,13 +476,12 @@ export type UpdateReferralSet = {
|
||||
};
|
||||
};
|
||||
|
||||
export enum MarginMode {
|
||||
MARGIN_MODE_CROSS_MARGIN = 1,
|
||||
MARGIN_MODE_ISOLATED_MARGIN,
|
||||
}
|
||||
export { vegaMarginMode as MarginMode };
|
||||
export { v1UndelegateSubmissionMethod as UndelegateSubmissionMethod };
|
||||
|
||||
export interface UpdateMarginMode {
|
||||
marketId: string;
|
||||
mode: MarginMode;
|
||||
mode: vegaMarginMode;
|
||||
marginFactor?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"@sentry/nextjs": "^6.19.3",
|
||||
"@sentry/react": "^6.19.2",
|
||||
"@sentry/tracing": "^6.19.2",
|
||||
"@vegaprotocol/rest-clients": "^0.0.5",
|
||||
"@vegaprotocol/wallet-client": "0.1.9",
|
||||
"@walletconnect/ethereum-provider": "^2.6.0",
|
||||
"@web3-react/coinbase-wallet": "8.1.2-beta.0",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
|
||||
## Transfer
|
||||
|
||||
- **Must** display tooltip for "Transfer fee when hovered over.(<a name="1003-TRAN-017" href="#1003-TRAN-017">1003-TRAN-017</a>)
|
||||
|
||||
- **Must** display tooltip for "Amount to be transferred" when hovered over.(<a name="1003-TRAN-018" href="#1003-TRAN-018">1003-TRAN-018</a>)
|
||||
|
||||
- **Must** display tooltip for "Total amount (with fee)" when hovered over.(<a name="1003-TRAN-019" href="#1003-TRAN-019">1003-TRAN-019</a>)
|
||||
|
||||
@@ -7808,6 +7808,11 @@
|
||||
"@typescript-eslint/types" "6.11.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@vegaprotocol/rest-clients@^0.0.5":
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@vegaprotocol/rest-clients/-/rest-clients-0.0.5.tgz#b75f14fc549e8d170518e973435f2b91ec19f553"
|
||||
integrity sha512-UTsGJKrzMsXHkuAmRxEA3ei9GEGFKwIHPdVl2DHlRMQRnP39atSu4IZsP6kK2HqLgJi8QiEZCyIkXQk+jrNRDA==
|
||||
|
||||
"@vegaprotocol/wallet-client@0.1.9":
|
||||
version "0.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@vegaprotocol/wallet-client/-/wallet-client-0.1.9.tgz#8c6a71c8b2222b3de5d73cade8fc6db57e332de9"
|
||||
|
||||
Reference in New Issue
Block a user