Compare commits
3 Commits
develop
...
fix/public
Author | SHA1 | Date | |
---|---|---|---|
|
bfaaedcfc5 | ||
|
424afb60a7 | ||
|
92552580f6 |
@ -4,5 +4,6 @@ tmp/*
|
||||
.dockerignore
|
||||
dockerfiles
|
||||
node_modules
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
|
@ -38,7 +38,6 @@ import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
|
||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
import { Indicator } from '../proposal/indicator';
|
||||
import { type ProposalNode } from '../proposal/proposal-utils';
|
||||
|
||||
const ProposalTypeTags = ({
|
||||
proposal,
|
||||
@ -541,12 +540,10 @@ const BatchProposalStateText = ({
|
||||
|
||||
export const ProposalHeader = ({
|
||||
proposal,
|
||||
restData,
|
||||
isListItem = true,
|
||||
voteState,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
isListItem?: boolean;
|
||||
voteState?: VoteState | null;
|
||||
}) => {
|
||||
@ -598,7 +595,7 @@ export const ProposalHeader = ({
|
||||
)}
|
||||
</div>
|
||||
<ProposalDetails proposal={proposal} />
|
||||
<VoteBreakdown proposal={proposal} restData={restData} />
|
||||
<VoteBreakdown proposal={proposal} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -91,28 +91,6 @@ export type ProposalNode = {
|
||||
proposal: ProposalData;
|
||||
proposalType: ProposalNodeType;
|
||||
proposals: SubProposalData[];
|
||||
yes?: [
|
||||
{
|
||||
partyId: string;
|
||||
elsPerMarket?: [
|
||||
{
|
||||
marketId: string;
|
||||
els: string;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
no?: [
|
||||
{
|
||||
partyId: string;
|
||||
elsPerMarket?: [
|
||||
{
|
||||
marketId: string;
|
||||
els: string;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
type SingleProposalNode = ProposalNode & {
|
||||
|
@ -48,7 +48,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
|
||||
<ProposalHeader
|
||||
proposal={proposal}
|
||||
restData={restData}
|
||||
isListItem={false}
|
||||
voteState={voteState}
|
||||
/>
|
||||
|
@ -17,7 +17,6 @@ import {
|
||||
import { useBatchVoteInformation } from '../../hooks/use-vote-information';
|
||||
import { MarketName } from '../proposal/market-name';
|
||||
import { Indicator } from '../proposal/indicator';
|
||||
import { type ProposalNode } from '../proposal/proposal-utils';
|
||||
|
||||
export const CompactVotes = ({ number }: { number: BigNumber }) => (
|
||||
<CompactNumber
|
||||
@ -111,64 +110,24 @@ const Status = ({ reached, threshold, text, testId }: StatusProps) => {
|
||||
|
||||
export const VoteBreakdown = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: Proposal | BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
if (proposal.__typename === 'Proposal') {
|
||||
return <VoteBreakdownNormal proposal={proposal} />;
|
||||
}
|
||||
|
||||
if (proposal.__typename === 'BatchProposal') {
|
||||
return <VoteBreakdownBatch proposal={proposal} restData={restData} />;
|
||||
return <VoteBreakdownBatch proposal={proposal} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const VoteBreakdownBatch = ({
|
||||
proposal,
|
||||
restData,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
restData?: ProposalNode | null;
|
||||
}) => {
|
||||
const VoteBreakdownBatch = ({ proposal }: { proposal: BatchProposal }) => {
|
||||
const [fullBreakdown, setFullBreakdown] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const yesELS =
|
||||
restData?.yes?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const noELS =
|
||||
restData?.no?.reduce((all, y) => {
|
||||
if (y.elsPerMarket) {
|
||||
y.elsPerMarket.forEach((item) => {
|
||||
const share = Number(item.els);
|
||||
if (all[item.marketId]) {
|
||||
all[item.marketId].push(share);
|
||||
} else {
|
||||
all[item.marketId] = [share];
|
||||
}
|
||||
return all;
|
||||
});
|
||||
}
|
||||
return all;
|
||||
}, {} as Record<string, number[]>) || {};
|
||||
|
||||
const voteInfo = useBatchVoteInformation({
|
||||
terms: compact(
|
||||
proposal.subProposals ? proposal.subProposals.map((p) => p?.terms) : []
|
||||
@ -235,8 +194,6 @@ const VoteBreakdownBatch = ({
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -297,8 +254,6 @@ const VoteBreakdownBatch = ({
|
||||
proposal={proposal}
|
||||
votes={proposal.votes}
|
||||
terms={p.terms}
|
||||
yesELS={yesELS}
|
||||
noELS={noELS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -316,17 +271,17 @@ const VoteBreakdownBatchSubProposal = ({
|
||||
votes,
|
||||
terms,
|
||||
indicator,
|
||||
yesELS,
|
||||
noELS,
|
||||
}: {
|
||||
proposal: BatchProposal;
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
indicator?: number;
|
||||
yesELS: Record<string, number[]>;
|
||||
noELS: Record<string, number[]>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const voteInfo = useVoteInformation({
|
||||
votes,
|
||||
terms,
|
||||
});
|
||||
|
||||
const isProposalOpen = proposal?.state === ProposalState.STATE_OPEN;
|
||||
const isUpdateMarket = terms?.change?.__typename === 'UpdateMarket';
|
||||
@ -339,15 +294,6 @@ const VoteBreakdownBatchSubProposal = ({
|
||||
marketId = terms.change.market.id;
|
||||
}
|
||||
|
||||
const voteInfo = useVoteInformation({
|
||||
votes,
|
||||
terms,
|
||||
// yes votes ELS for this specific proposal (market)
|
||||
yesELS: marketId ? yesELS[marketId] : undefined,
|
||||
// no votes ELS for this specific proposal (market)
|
||||
noELS: marketId ? noELS[marketId] : undefined,
|
||||
});
|
||||
|
||||
const marketName = marketId ? (
|
||||
<>
|
||||
: <MarketName marketId={marketId} />
|
||||
|
@ -8,18 +8,13 @@ import {
|
||||
type VoteFieldsFragment,
|
||||
} from '../__generated__/Proposals';
|
||||
import { type ProposalChangeType } from '../types';
|
||||
import sum from 'lodash/sum';
|
||||
|
||||
export const useVoteInformation = ({
|
||||
votes,
|
||||
terms,
|
||||
yesELS,
|
||||
noELS,
|
||||
}: {
|
||||
votes: VoteFieldsFragment;
|
||||
terms: ProposalTermsFieldsFragment;
|
||||
yesELS?: number[];
|
||||
noELS?: number[];
|
||||
}) => {
|
||||
const {
|
||||
appState: { totalSupply, decimals },
|
||||
@ -36,9 +31,7 @@ export const useVoteInformation = ({
|
||||
paramsForChange,
|
||||
votes,
|
||||
totalSupply,
|
||||
decimals,
|
||||
yesELS,
|
||||
noELS
|
||||
decimals
|
||||
);
|
||||
};
|
||||
|
||||
@ -79,11 +72,7 @@ const getVoteData = (
|
||||
},
|
||||
votes: ProposalFieldsFragment['votes'],
|
||||
totalSupply: BigNumber,
|
||||
decimals: number,
|
||||
/** A list of ELS yes votes */
|
||||
yesELS?: number[],
|
||||
/** A list if ELS no votes */
|
||||
noELS?: number[]
|
||||
decimals: number
|
||||
) => {
|
||||
const requiredMajorityPercentage = params.requiredMajority
|
||||
? new BigNumber(params.requiredMajority).times(100)
|
||||
@ -97,31 +86,17 @@ const getVoteData = (
|
||||
addDecimal(votes.no.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
let noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
|
||||
const noEquityLikeShareWeight = !votes.no.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.no.totalEquityLikeShareWeight).times(100);
|
||||
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
|
||||
// it has to be deduced from `elsPerMarket` of `no` votes of given proposal
|
||||
// data. (by REST DATA)
|
||||
if (noELS != null) {
|
||||
const noTotalELS = sum(noELS);
|
||||
noEquityLikeShareWeight = new BigNumber(noTotalELS).times(100);
|
||||
}
|
||||
|
||||
const yesTokens = new BigNumber(
|
||||
addDecimal(votes.yes.totalTokens ?? 0, decimals)
|
||||
);
|
||||
|
||||
let yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
|
||||
const yesEquityLikeShareWeight = !votes.yes.totalEquityLikeShareWeight
|
||||
? new BigNumber(0)
|
||||
: new BigNumber(votes.yes.totalEquityLikeShareWeight).times(100);
|
||||
// there's no meaningful `totalEquityLikeShareWeight` in batch proposals,
|
||||
// it has to be deduced from `elsPerMarket` of `yes` votes of given proposal
|
||||
// data. (by REST DATA)
|
||||
if (noELS != null) {
|
||||
const yesTotalELS = sum(yesELS);
|
||||
yesEquityLikeShareWeight = new BigNumber(yesTotalELS).times(100);
|
||||
}
|
||||
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
|
||||
|
@ -1,126 +1,63 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
AppFailure,
|
||||
AppLoader,
|
||||
NetworkLoader,
|
||||
NodeFailure,
|
||||
NodeGuard,
|
||||
useEnvironment,
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { useEffect, type ReactNode, useState } from 'react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { Web3Provider } from './web3-provider';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { DataLoader } from './data-loader';
|
||||
import { WalletProvider } from '@vegaprotocol/wallet-react';
|
||||
import { useVegaWalletConfig } from '../../lib/hooks/use-vega-wallet-config';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { Button, Loader, Splash, VLogo } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const Failure = ({ reason }: { reason?: ReactNode }) => {
|
||||
const t = useT();
|
||||
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
|
||||
return (
|
||||
<Splash>
|
||||
<div className="border border-vega-red m-10 mx-auto w-4/5 max-w-3xl rounded-lg overflow-hidden animate-shake">
|
||||
<div className="bg-vega-red text-white px-2 py-2 flex gap-1 items-center font-alpha calt uppercase">
|
||||
<VLogo className="h-4" />
|
||||
<span className="text-lg">{t('Failed to initialize the app')}</span>
|
||||
</div>
|
||||
<div className="p-4 text-left text-sm">
|
||||
<p className="mb-4">{reason}</p>
|
||||
<div className="text-center">
|
||||
<Button className="border-2" onClick={() => setNodeSwitcher(true)}>
|
||||
{t('Change node')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
};
|
||||
|
||||
const Loading = () => {
|
||||
const [showSlowNotification, setShowSlowNotification] = useState(false);
|
||||
const t = useT();
|
||||
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
|
||||
useEffect(() => {
|
||||
const to = setTimeout(() => {
|
||||
if (!showSlowNotification) setShowSlowNotification(true);
|
||||
}, 5000);
|
||||
return () => {
|
||||
clearTimeout(to);
|
||||
};
|
||||
}, [showSlowNotification]);
|
||||
return (
|
||||
<Splash>
|
||||
<div className="border border-transparent m-10 mx-auto w-4/5 max-w-3xl rounded-lg overflow-hidden">
|
||||
<div className="mt-11 p-4 text-left text-sm">
|
||||
<Loader />
|
||||
{showSlowNotification && (
|
||||
<>
|
||||
<p className="mt-4 text-center">
|
||||
{t(
|
||||
"It looks like you're connection is slow, try switching to another node."
|
||||
)}
|
||||
</p>
|
||||
<div className="mt-4 text-center">
|
||||
<Button
|
||||
className="border-2"
|
||||
onClick={() => setNodeSwitcher(true)}
|
||||
>
|
||||
{t('Change node')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
};
|
||||
|
||||
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||
const t = useT();
|
||||
|
||||
const { error, VEGA_URL } = useEnvironment((state) => ({
|
||||
error: state.error,
|
||||
VEGA_URL: state.VEGA_URL,
|
||||
}));
|
||||
const { error, VEGA_URL } = useEnvironment();
|
||||
const config = useVegaWalletConfig();
|
||||
|
||||
if (!config) {
|
||||
return <AppLoader />;
|
||||
}
|
||||
|
||||
const ERR_DATA_LOADER = (
|
||||
<Trans
|
||||
i18nKey="It appears that the connection to the node <0>{{VEGA_URL}}</0> does not return necessary data, try switching to another node."
|
||||
components={[
|
||||
<span key="vega" className="text-muted">
|
||||
{VEGA_URL}
|
||||
</span>,
|
||||
]}
|
||||
values={{
|
||||
VEGA_URL,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<NetworkLoader
|
||||
cache={cacheConfig}
|
||||
skeleton={<Loading />}
|
||||
failure={<Failure reason={error} />}
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure title={t('Could not initialize app')} error={error} />
|
||||
}
|
||||
>
|
||||
<DataLoader
|
||||
skeleton={<Loading />}
|
||||
failure={<Failure reason={ERR_DATA_LOADER} />}
|
||||
<NodeGuard
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<NodeFailure
|
||||
title={t('Node: {{VEGA_URL}} is unsuitable', { VEGA_URL })}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Web3Provider
|
||||
skeleton={<Loading />}
|
||||
failure={<Failure reason={t('Could not configure web3 provider')} />}
|
||||
<DataLoader
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure
|
||||
title={t('Could not load market data or asset data')}
|
||||
error={error}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<WalletProvider config={config}>{children}</WalletProvider>
|
||||
</Web3Provider>
|
||||
</DataLoader>
|
||||
<Web3Provider
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure title={t('Could not configure web3 provider')} />
|
||||
}
|
||||
>
|
||||
<WalletProvider config={config}>{children}</WalletProvider>
|
||||
</Web3Provider>
|
||||
</DataLoader>
|
||||
</NodeGuard>
|
||||
</NetworkLoader>
|
||||
);
|
||||
};
|
||||
@ -170,9 +107,6 @@ const cacheConfig: InMemoryCacheConfig = {
|
||||
Fees: {
|
||||
keyFields: false,
|
||||
},
|
||||
PartyProfile: {
|
||||
keyFields: ['partyId'],
|
||||
},
|
||||
// The folling types are cached by the data provider and not by apollo
|
||||
PositionUpdate: {
|
||||
keyFields: false,
|
||||
|
@ -8,12 +8,6 @@ import {
|
||||
mockConfig,
|
||||
MockedWalletProvider,
|
||||
} from '@vegaprotocol/wallet-react/testing';
|
||||
import { MockedProvider, type MockedResponse } from '@apollo/react-testing';
|
||||
import {
|
||||
PartyProfilesDocument,
|
||||
type PartyProfilesQuery,
|
||||
type PartyProfilesQueryVariables,
|
||||
} from '../vega-wallet-connect-button/__generated__/PartyProfiles';
|
||||
|
||||
jest.mock('@vegaprotocol/proposals', () => ({
|
||||
ProtocolUpgradeCountdown: () => null,
|
||||
@ -30,45 +24,15 @@ describe('Navbar', () => {
|
||||
publicKey: '2'.repeat(64),
|
||||
},
|
||||
];
|
||||
const key1Alias = 'key 1 alias';
|
||||
const marketId = 'abc';
|
||||
const navbarContent = 'navbar-menu-content';
|
||||
|
||||
const partyProfilesMock: MockedResponse<
|
||||
PartyProfilesQuery,
|
||||
PartyProfilesQueryVariables
|
||||
> = {
|
||||
request: {
|
||||
query: PartyProfilesDocument,
|
||||
variables: {
|
||||
partyIds: mockKeys.map((k) => k.publicKey),
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
partiesProfilesConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
partyId: mockKeys[0].publicKey,
|
||||
alias: key1Alias,
|
||||
metadata: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = (initialEntries?: string[]) => {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<MockedProvider mocks={[partyProfilesMock]}>
|
||||
<MockedWalletProvider>
|
||||
<Navbar />
|
||||
</MockedWalletProvider>
|
||||
</MockedProvider>
|
||||
<MockedWalletProvider>
|
||||
<Navbar />
|
||||
</MockedWalletProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
@ -176,7 +140,6 @@ describe('Navbar', () => {
|
||||
const activeKey = within(menu.getByTestId(/key-1+-mobile/));
|
||||
expect(activeKey.getByText(mockKeys[0].name)).toBeInTheDocument();
|
||||
expect(activeKey.getByTestId('icon-tick')).toBeInTheDocument();
|
||||
expect(screen.getByText(key1Alias)).toBeInTheDocument();
|
||||
|
||||
const inactiveKey = within(menu.getByTestId(/key-2+-mobile/));
|
||||
await userEvent.click(inactiveKey.getByText(mockKeys[1].name));
|
||||
|
@ -31,27 +31,39 @@ export const ProfileDialog = () => {
|
||||
const pubKey = useProfileDialogStore((store) => store.pubKey);
|
||||
const setOpen = useProfileDialogStore((store) => store.setOpen);
|
||||
|
||||
const { send, status, error, reset } = useSimpleTransaction({
|
||||
onSuccess: () => {
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
const profileEdge = data?.partiesProfilesConnection?.edges.find(
|
||||
(e) => e.node.partyId === pubKey
|
||||
);
|
||||
|
||||
const sendTx = (field: FormFields) => {
|
||||
send({
|
||||
updatePartyProfile: {
|
||||
alias: field.alias,
|
||||
metadata: [],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onChange={() => {
|
||||
setOpen(undefined);
|
||||
reset();
|
||||
}}
|
||||
title={t('Edit profile')}
|
||||
>
|
||||
<ProfileFormContainer
|
||||
<ProfileForm
|
||||
profile={profileEdge?.node}
|
||||
onSuccess={() => {
|
||||
refetch();
|
||||
|
||||
setTimeout(() => {
|
||||
setOpen(undefined);
|
||||
}, 1000);
|
||||
}}
|
||||
status={status}
|
||||
error={error}
|
||||
onSubmit={sendTx}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
@ -65,32 +77,6 @@ type Profile = NonNullable<
|
||||
PartyProfilesQuery['partiesProfilesConnection']
|
||||
>['edges'][number]['node'];
|
||||
|
||||
const ProfileFormContainer = ({
|
||||
profile,
|
||||
onSuccess,
|
||||
}: {
|
||||
profile: Profile | undefined;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { send, status, error } = useSimpleTransaction({ onSuccess });
|
||||
const sendTx = (field: FormFields) => {
|
||||
send({
|
||||
updatePartyProfile: {
|
||||
alias: field.alias,
|
||||
metadata: [],
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<ProfileForm
|
||||
profile={profile}
|
||||
status={status}
|
||||
error={error}
|
||||
onSubmit={sendTx}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileForm = ({
|
||||
profile,
|
||||
onSubmit,
|
||||
@ -128,14 +114,6 @@ const ProfileForm = ({
|
||||
|
||||
const errorMessage = errors.alias?.message || error;
|
||||
|
||||
if (status === 'confirmed') {
|
||||
return (
|
||||
<p className="mt-2 mb-4 text-sm text-vega-green-600 dark:text-vega-green">
|
||||
{t('Profile updated')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="mt-3">
|
||||
<FormGroup label="Alias" labelFor="alias">
|
||||
@ -153,6 +131,12 @@ const ProfileForm = ({
|
||||
</p>
|
||||
</InputError>
|
||||
)}
|
||||
|
||||
{status === 'confirmed' && (
|
||||
<p className="mt-2 mb-4 text-sm text-success">
|
||||
{t('Profile updated')}
|
||||
</p>
|
||||
)}
|
||||
</FormGroup>
|
||||
<TradingButton
|
||||
type="submit"
|
||||
|
@ -94,7 +94,6 @@ export const VegaWalletConnectButton = ({
|
||||
<KeypairRadioGroup
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys}
|
||||
activeKey={activeKey?.publicKey}
|
||||
onSelect={selectPubKey}
|
||||
/>
|
||||
<TradingDropdownSeparator />
|
||||
@ -139,18 +138,15 @@ export const VegaWalletConnectButton = ({
|
||||
const KeypairRadioGroup = ({
|
||||
pubKey,
|
||||
pubKeys,
|
||||
activeKey,
|
||||
onSelect,
|
||||
}: {
|
||||
pubKey: string | undefined;
|
||||
pubKeys: Key[];
|
||||
activeKey: string | undefined;
|
||||
onSelect: (pubKey: string) => void;
|
||||
}) => {
|
||||
const { data } = usePartyProfilesQuery({
|
||||
variables: { partyIds: pubKeys.map((pk) => pk.publicKey) },
|
||||
skip: pubKeys.length <= 0,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
return (
|
||||
@ -160,27 +156,14 @@ const KeypairRadioGroup = ({
|
||||
(e) => e.node.partyId === pk.publicKey
|
||||
);
|
||||
return (
|
||||
<KeypairItem
|
||||
key={pk.publicKey}
|
||||
pk={pk}
|
||||
isActive={activeKey === pk.publicKey}
|
||||
alias={profile?.node.alias}
|
||||
/>
|
||||
<KeypairItem key={pk.publicKey} pk={pk} alias={profile?.node.alias} />
|
||||
);
|
||||
})}
|
||||
</TradingDropdownRadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const KeypairItem = ({
|
||||
pk,
|
||||
isActive,
|
||||
alias,
|
||||
}: {
|
||||
pk: Key;
|
||||
alias: string | undefined;
|
||||
isActive: boolean;
|
||||
}) => {
|
||||
const KeypairItem = ({ pk, alias }: { pk: Key; alias: string | undefined }) => {
|
||||
const t = useT();
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
const setOpen = useProfileDialogStore((store) => store.setOpen);
|
||||
@ -211,13 +194,8 @@ const KeypairItem = ({
|
||||
data-testid={`key-${pk.publicKey}`}
|
||||
>
|
||||
<Tooltip description={t('Public facing key alias. Click to edit')}>
|
||||
<button
|
||||
data-testid="alias"
|
||||
onClick={() => setOpen(pk.publicKey)}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<button data-testid="alias" onClick={() => setOpen(pk.publicKey)}>
|
||||
{alias ? alias : t('No alias')}
|
||||
{isActive && <VegaIcon name={VegaIconNames.EDIT} />}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
@ -12,8 +12,6 @@ import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { usePartyProfilesQuery } from '../vega-wallet-connect-button/__generated__/PartyProfiles';
|
||||
import { useProfileDialogStore } from '../../stores/profile-dialog-store';
|
||||
|
||||
export const VegaWalletMenu = ({
|
||||
setMenu,
|
||||
@ -25,12 +23,6 @@ export const VegaWalletMenu = ({
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const setViews = useSidebar((store) => store.setViews);
|
||||
|
||||
const { data } = usePartyProfilesQuery({
|
||||
variables: { partyIds: pubKeys.map((pk) => pk.publicKey) },
|
||||
skip: pubKeys.length <= 0,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
const activeKey = useMemo(() => {
|
||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||
}, [pubKey, pubKeys]);
|
||||
@ -45,21 +37,14 @@ export const VegaWalletMenu = ({
|
||||
return (
|
||||
<div>
|
||||
<div className="grow my-4" role="list">
|
||||
{(pubKeys || []).map((pk) => {
|
||||
const profile = data?.partiesProfilesConnection?.edges.find(
|
||||
(e) => e.node.partyId === pk.publicKey
|
||||
);
|
||||
return (
|
||||
<KeypairListItem
|
||||
key={pk.publicKey}
|
||||
pk={pk}
|
||||
isActive={activeKey?.publicKey === pk.publicKey}
|
||||
onSelectItem={onSelectItem}
|
||||
alias={profile?.node.alias}
|
||||
setMenu={setMenu}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{(pubKeys || []).map((pk) => (
|
||||
<KeypairListItem
|
||||
key={pk.publicKey}
|
||||
pk={pk}
|
||||
isActive={activeKey?.publicKey === pk.publicKey}
|
||||
onSelectItem={onSelectItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 m-4">
|
||||
@ -87,23 +72,18 @@ export const VegaWalletMenu = ({
|
||||
const KeypairListItem = ({
|
||||
pk,
|
||||
isActive,
|
||||
alias,
|
||||
onSelectItem,
|
||||
setMenu,
|
||||
}: {
|
||||
pk: Key;
|
||||
isActive: boolean;
|
||||
alias: string | undefined;
|
||||
onSelectItem: (pk: string) => void;
|
||||
setMenu: (open: 'nav' | 'wallet' | null) => void;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const [copied, setCopied] = useCopyTimeout();
|
||||
const setOpen = useProfileDialogStore((store) => store.setOpen);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col w-full px-4 mb-4"
|
||||
className="flex flex-col w-full ml-4 mr-2 mb-4"
|
||||
data-testid={`key-${pk.publicKey}-mobile`}
|
||||
>
|
||||
<span className="flex gap-2 items-center mr-2">
|
||||
@ -126,24 +106,6 @@ const KeypairListItem = ({
|
||||
</CopyToClipboard>
|
||||
{copied && <span className="text-xs">{t('Copied')}</span>}
|
||||
</span>
|
||||
<span
|
||||
className="flex gap-2 items-center"
|
||||
data-testid={`key-${pk.publicKey}`}
|
||||
>
|
||||
<span className="truncate">{alias ? alias : t('No alias')}</span>
|
||||
{isActive && (
|
||||
<button
|
||||
data-testid="alias"
|
||||
onClick={() => {
|
||||
setOpen(pk.publicKey);
|
||||
setMenu(null);
|
||||
}}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.EDIT} />
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -9,14 +9,12 @@ from actions.utils import next_epoch, change_keys
|
||||
from wallet_config import MM_WALLET
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def setup_environment(request, browser) -> Generator[Tuple[Page, str, str], None, None]:
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance))
|
||||
|
||||
tDAI_market, tDAI_asset_id = setup_market_with_reward_program(
|
||||
vega_instance)
|
||||
tDAI_market, tDAI_asset_id = setup_market_with_reward_program(vega_instance)
|
||||
|
||||
with init_page(vega_instance, browser, request) as page:
|
||||
risk_accepted_setup(page)
|
||||
@ -149,12 +147,14 @@ def setup_market_with_reward_program(vega: VegaServiceNull):
|
||||
return tDAI_market, tDAI_asset_id
|
||||
|
||||
|
||||
def test_network_reward_pot(setup_environment: Tuple[Page, str, str],
|
||||
) -> None:
|
||||
|
||||
def test_network_reward_pot( setup_environment: Tuple[Page, str, str],
|
||||
) -> None:
|
||||
page, tDAI_market, tDAI_asset_id = setup_environment
|
||||
expect(page.get_by_test_id(TOTAL_REWARDS)).to_have_text("183.33333 tDAI")
|
||||
|
||||
|
||||
|
||||
def test_reward_multiplier(
|
||||
setup_environment: Tuple[Page, str, str],
|
||||
) -> None:
|
||||
@ -168,6 +168,7 @@ def test_reward_multiplier(
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_reward_history(
|
||||
setup_environment: Tuple[Page, str, str],
|
||||
) -> None:
|
||||
@ -176,37 +177,26 @@ def test_reward_history(
|
||||
expect((page.get_by_role(ROW).locator(PRICE_TAKING_COL_ID)).nth(1)).to_have_text(
|
||||
"299.99999100.00%"
|
||||
)
|
||||
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(
|
||||
1)).to_have_text("299.99999")
|
||||
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text("299.99999")
|
||||
page.get_by_test_id(EARNED_BY_ME_BUTTON).click()
|
||||
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text(
|
||||
"183.33333"
|
||||
)
|
||||
|
||||
|
||||
def test_staking_reward(
|
||||
setup_environment: Tuple[Page, str, str],
|
||||
page: Page,
|
||||
):
|
||||
page, tDAI_market, tDAI_asset_id = setup_environment
|
||||
expect(page.get_by_test_id("active-rewards-card")).to_have_count(2)
|
||||
staking_reward_card = page.get_by_test_id("active-rewards-card").nth(1)
|
||||
expect(staking_reward_card).to_be_visible()
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"entity-scope")).to_have_text("Individual")
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"locked-for")).to_have_text("0 epochs")
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"reward-value")).to_have_text("100.00")
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"distribution-strategy")).to_have_text("Pro rata")
|
||||
expect(staking_reward_card.get_by_test_id("entity-scope")).to_have_text("Individual")
|
||||
expect(staking_reward_card.get_by_test_id("locked-for")).to_have_text("0 epochs")
|
||||
expect(staking_reward_card.get_by_test_id("reward-value")).to_have_text("100.00")
|
||||
expect(staking_reward_card.get_by_test_id("distribution-strategy")).to_have_text("Pro rata")
|
||||
expect(staking_reward_card.get_by_test_id("dispatch-metric-info")).to_have_text(
|
||||
"Staking rewards"
|
||||
)
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"assessed-over")).to_have_text("1 epoch")
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"scope")).to_have_text("Individual")
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"staking-requirement")).to_have_text("1.00")
|
||||
expect(staking_reward_card.get_by_test_id(
|
||||
"average-position")).to_have_text("0.00")
|
||||
expect(staking_reward_card.get_by_test_id("assessed-over")).to_have_text("1 epoch")
|
||||
expect(staking_reward_card.get_by_test_id("scope")).to_have_text("Individual")
|
||||
expect(staking_reward_card.get_by_test_id("staking-requirement")).to_have_text("1.00")
|
||||
expect(staking_reward_card.get_by_test_id("average-position")).to_have_text("0.00")
|
@ -39,8 +39,6 @@ const nextConfig = {
|
||||
GIT_COMMIT: commitHash,
|
||||
GIT_TAG: tag,
|
||||
},
|
||||
basePath: '/apps/vegas', // Set the base path
|
||||
assetPrefix: '/apps/vegas', // Set the asset prefix
|
||||
};
|
||||
|
||||
module.exports = SENTRY_AUTH_TOKEN
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Controller, type Control } from 'react-hook-form';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||
import { determinePriceStep, useValidateAmount } from '@vegaprotocol/utils';
|
||||
import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
|
||||
import {
|
||||
TradingFormGroup,
|
||||
TradingInputError,
|
||||
Tooltip,
|
||||
FormGroup,
|
||||
Input,
|
||||
@ -29,7 +30,31 @@ export const DealTicketPriceTakeProfitStopLoss = ({
|
||||
}: DealTicketPriceTakeProfitStopLossProps) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
const priceStep = determinePriceStep(market);
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
|
||||
const renderTakeProfitError = () => {
|
||||
if (takeProfitError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-take-profit-error-message">
|
||||
{takeProfitError}
|
||||
</TradingInputError>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderStopLossError = () => {
|
||||
if (stopLossError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-stop-loss-error-message">
|
||||
{stopLossError}
|
||||
</TradingInputError>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
@ -79,9 +104,9 @@ export const DealTicketPriceTakeProfitStopLoss = ({
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
{(fieldState.error || takeProfitError) && (
|
||||
{fieldState.error && (
|
||||
<InputError testId="deal-ticket-error-message-price-take-profit">
|
||||
{fieldState.error?.message || takeProfitError}
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
@ -129,9 +154,9 @@ export const DealTicketPriceTakeProfitStopLoss = ({
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
{(fieldState.error || stopLossError) && (
|
||||
{fieldState.error && (
|
||||
<InputError testId="deal-ticket-error-message-price-stop-loss">
|
||||
{fieldState.error?.message || stopLossError}
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
@ -140,6 +165,8 @@ export const DealTicketPriceTakeProfitStopLoss = ({
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
{renderTakeProfitError()}
|
||||
{renderStopLossError()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Controller, type Control } from 'react-hook-form';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||
import { useValidateAmount } from '@vegaprotocol/utils';
|
||||
import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
|
||||
import {
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
@ -9,7 +9,6 @@ import {
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from '../../use-t';
|
||||
import { determineSizeStep } from '@vegaprotocol/utils';
|
||||
|
||||
export interface DealTicketSizeIcebergProps {
|
||||
control: Control<OrderFormValues>;
|
||||
@ -30,7 +29,7 @@ export const DealTicketSizeIceberg = ({
|
||||
}: DealTicketSizeIcebergProps) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
const sizeStep = determineSizeStep(market);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
|
||||
const renderPeakSizeError = () => {
|
||||
if (peakSizeError) {
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
formatForInput,
|
||||
formatValue,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
useValidateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { type Control, type UseFormWatch } from 'react-hook-form';
|
||||
@ -58,7 +59,6 @@ import { KeyValue } from './key-value';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { stopOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useT } from '../../use-t';
|
||||
import { determinePriceStep, determineSizeStep } from '@vegaprotocol/utils';
|
||||
|
||||
export interface StopOrderProps {
|
||||
market: Market;
|
||||
@ -904,8 +904,8 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
market.tradableInstrument.instrument.metadata.tags
|
||||
);
|
||||
|
||||
const sizeStep = determineSizeStep(market);
|
||||
const priceStep = determinePriceStep(market);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
|
||||
useController({
|
||||
name: 'type',
|
||||
|
@ -883,7 +883,7 @@ describe('DealTicket', () => {
|
||||
'deal-ticket-error-message-price'
|
||||
);
|
||||
expect(errorMessage).toHaveTextContent(
|
||||
'Price must be a multiple of 0.01 for this market'
|
||||
'Price accepts up to 2 decimal places'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
toBigNum,
|
||||
removeDecimal,
|
||||
useValidateAmount,
|
||||
toDecimal,
|
||||
formatForInput,
|
||||
formatValue,
|
||||
} from '@vegaprotocol/utils';
|
||||
@ -81,7 +82,6 @@ import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { useT } from '../../use-t';
|
||||
import { DealTicketPriceTakeProfitStopLoss } from './deal-ticket-price-tp-sl';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
import { determinePriceStep, determineSizeStep } from '@vegaprotocol/utils';
|
||||
|
||||
export const REDUCE_ONLY_TOOLTIP =
|
||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
||||
@ -419,12 +419,11 @@ export const DealTicket = ({
|
||||
},
|
||||
});
|
||||
|
||||
const sizeStep = determineSizeStep(market);
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = getQuoteName(market);
|
||||
const isLimitType = type === Schema.OrderType.TYPE_LIMIT;
|
||||
|
||||
const priceStep = determinePriceStep(market);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={
|
||||
|
@ -11,7 +11,6 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
|
||||
positionDecimalPlaces: 1,
|
||||
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
state: Schema.MarketState.STATE_ACTIVE,
|
||||
tickSize: '1',
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
close: '',
|
||||
|
@ -20,11 +20,5 @@
|
||||
"jest.config.ts",
|
||||
"__mocks__"
|
||||
],
|
||||
"include": [
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"../utils/src/lib/step.ts"
|
||||
]
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
16
libs/environment/src/components/app-failure/app-failure.tsx
Normal file
16
libs/environment/src/components/app-failure/app-failure.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const AppFailure = ({
|
||||
title,
|
||||
error,
|
||||
}: {
|
||||
title: string;
|
||||
error?: string | null;
|
||||
}) => {
|
||||
return (
|
||||
<Splash>
|
||||
<p className="mb-4 text-xl">{title}</p>
|
||||
{error && <p className="mb-8 text-sm">{error}</p>}
|
||||
</Splash>
|
||||
);
|
||||
};
|
1
libs/environment/src/components/app-failure/index.ts
Normal file
1
libs/environment/src/components/app-failure/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './app-failure';
|
@ -1,3 +1,4 @@
|
||||
export * from './app-failure';
|
||||
export * from './app-loader';
|
||||
export * from './network-loader';
|
||||
export * from './network-switcher';
|
||||
|
@ -40,16 +40,6 @@ const mockStatsQuery = (
|
||||
vegaTime: new Date().toISOString(),
|
||||
chainId: 'test-chain-id',
|
||||
},
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'a',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -345,16 +335,6 @@ describe('RowData', () => {
|
||||
vegaTime: new Date().toISOString(),
|
||||
chainId: 'test-chain-id',
|
||||
},
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'a',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,9 +1,4 @@
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
TradingRadio,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingRadio } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CUSTOM_NODE_KEY } from '../../types';
|
||||
import {
|
||||
@ -132,17 +127,8 @@ export const RowData = ({
|
||||
return (
|
||||
<>
|
||||
{id !== CUSTOM_NODE_KEY && (
|
||||
<div className="break-all flex gap-2" data-testid="node">
|
||||
<div className="break-all" data-testid="node">
|
||||
<TradingRadio id={`node-url-${id}`} value={url} label={url} />
|
||||
{url.length > 0 && url !== 'custom' && (
|
||||
<span className="text-muted">
|
||||
<CopyWithTooltip text={url}>
|
||||
<button>
|
||||
<VegaIcon name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<LayoutCell
|
||||
|
@ -39,19 +39,6 @@ export const getMockStatisticsResult = (
|
||||
blockHeight: '11',
|
||||
vegaTime: new Date().toISOString(),
|
||||
},
|
||||
networkParametersConnection: {
|
||||
__typename: 'NetworkParametersConnection',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'NetworkParameterEdge',
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'a',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const getMockQueryResult = (env: Networks): NodeCheckQuery => ({
|
||||
@ -61,19 +48,6 @@ export const getMockQueryResult = (env: Networks): NodeCheckQuery => ({
|
||||
blockHeight: '11',
|
||||
vegaTime: new Date().toISOString(),
|
||||
},
|
||||
networkParametersConnection: {
|
||||
__typename: 'NetworkParametersConnection',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'NetworkParameterEdge',
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'a',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
function getHandler<T>(
|
||||
|
@ -34,16 +34,6 @@ const createDefaultMockClient = () => {
|
||||
blockHeight: '100',
|
||||
vegaTime: new Date().toISOString(),
|
||||
},
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'something',
|
||||
value: 123,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
subscribe: () => ({
|
||||
@ -193,16 +183,6 @@ describe('useEnvironment', () => {
|
||||
blockHeight: '100',
|
||||
vegaTime: new Date(1).toISOString(),
|
||||
},
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'something',
|
||||
value: 123,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}, wait);
|
||||
@ -264,16 +244,6 @@ describe('useEnvironment', () => {
|
||||
blockHeight: '100',
|
||||
vegaTime: new Date().toISOString(),
|
||||
},
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'something',
|
||||
value: 123,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
subscribe: () => ({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { parse as tomlParse } from 'toml';
|
||||
import { LocalStorage, isValidUrl } from '@vegaprotocol/utils';
|
||||
import { isValidUrl, LocalStorage } from '@vegaprotocol/utils';
|
||||
import { useEffect } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import { createClient } from '@vegaprotocol/apollo-client';
|
||||
@ -22,7 +22,6 @@ import uniq from 'lodash/uniq';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import first from 'lodash/first';
|
||||
import { canMeasureResponseTime, measureResponseTime } from '../utils/time';
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
type Client = ReturnType<typeof createClient>;
|
||||
type ClientCollection = {
|
||||
@ -71,19 +70,10 @@ const fetchConfig = async (url?: string) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Find a suitable nodes by running a test query and test
|
||||
* Find a suitable node by running a test query and test
|
||||
* subscription, against a list of clients, first to resolve wins
|
||||
*/
|
||||
const findHealthyNodes = async (nodes: string[]) => {
|
||||
const clients: ClientCollection = {};
|
||||
nodes.forEach((url) => {
|
||||
clients[url] = createClient({
|
||||
url,
|
||||
cacheConfig: undefined,
|
||||
retry: false,
|
||||
connectToDevTools: false,
|
||||
});
|
||||
});
|
||||
const findNode = async (clients: ClientCollection): Promise<string | null> => {
|
||||
const tests = Object.entries(clients).map((args) => testNode(...args));
|
||||
try {
|
||||
const nodes = await Promise.all(tests);
|
||||
@ -103,10 +93,11 @@ const findHealthyNodes = async (nodes: string[]) => {
|
||||
['desc', 'desc', 'asc']
|
||||
);
|
||||
|
||||
return ordered;
|
||||
const best = first(ordered);
|
||||
return best ? best.url : null;
|
||||
} catch (err) {
|
||||
// All tests rejected, no suitable node found
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@ -151,15 +142,6 @@ const testQuery = (
|
||||
})
|
||||
.then((result) => {
|
||||
if (result && !result.error) {
|
||||
const netParams = compact(
|
||||
result.data.networkParametersConnection.edges?.map((n) => n?.node)
|
||||
);
|
||||
if (netParams.length === 0) {
|
||||
// any node that doesn't return the network parameters is considered
|
||||
// failed
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const res = {
|
||||
blockHeight: Number(result.data.statistics.blockHeight),
|
||||
vegaTime: new Date(result.data.statistics.vegaTime),
|
||||
@ -618,34 +600,32 @@ export const useEnvironment = create<EnvStore>()((set, get) => ({
|
||||
}
|
||||
|
||||
const state = get();
|
||||
|
||||
let storedUrl = LocalStorage.getItem(STORAGE_KEY);
|
||||
if (!isValidUrl(storedUrl)) {
|
||||
// remove invalid data from local storage
|
||||
LocalStorage.removeItem(STORAGE_KEY);
|
||||
storedUrl = null;
|
||||
}
|
||||
const storedUrl = LocalStorage.getItem(STORAGE_KEY);
|
||||
|
||||
let nodes: string[] | undefined;
|
||||
try {
|
||||
nodes = uniq(
|
||||
compact([
|
||||
// url from local storage
|
||||
storedUrl,
|
||||
// url from state (if set via env var)
|
||||
state.VEGA_URL,
|
||||
// urls from network configuration
|
||||
...(await fetchConfig(state.VEGA_CONFIG_URL)),
|
||||
])
|
||||
nodes = await fetchConfig(state.VEGA_CONFIG_URL);
|
||||
const enrichedNodes = uniq(
|
||||
[...nodes, state.VEGA_URL, storedUrl].filter(Boolean) as string[]
|
||||
);
|
||||
set({ nodes });
|
||||
set({ nodes: enrichedNodes });
|
||||
} catch (err) {
|
||||
console.warn(`Could not fetch node config from ${state.VEGA_CONFIG_URL}`);
|
||||
}
|
||||
|
||||
// skip picking up the best node if VEGA_URL env variable is set
|
||||
if (state.VEGA_URL && isValidUrl(state.VEGA_URL)) {
|
||||
state.setUrl(state.VEGA_URL);
|
||||
// Node url found in localStorage, if its valid attempt to connect
|
||||
if (storedUrl) {
|
||||
if (isValidUrl(storedUrl)) {
|
||||
set({ VEGA_URL: storedUrl, status: 'success' });
|
||||
return;
|
||||
} else {
|
||||
LocalStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
// VEGA_URL env var is set and is a valid url no need to proceed
|
||||
if (state.VEGA_URL) {
|
||||
set({ status: 'success' });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -659,35 +639,37 @@ export const useEnvironment = create<EnvStore>()((set, get) => ({
|
||||
return;
|
||||
}
|
||||
|
||||
const healthyNodes = await findHealthyNodes(nodes);
|
||||
|
||||
// A requested node is a node to which the app was previously connected
|
||||
// or the one set via env variable.
|
||||
const requestedNodeUrl = storedUrl || state.VEGA_URL;
|
||||
|
||||
const bestNode = first(healthyNodes);
|
||||
const requestedNode = healthyNodes.find(
|
||||
(n) => requestedNodeUrl && n.url === requestedNodeUrl
|
||||
);
|
||||
if (!requestedNode) {
|
||||
// remove unhealthy node url from local storage
|
||||
LocalStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
// A node's url (VEGA_URL) is either the requested node (previously
|
||||
// connected or taken form env variable) or the currently best available
|
||||
// node.
|
||||
const url = requestedNode?.url || bestNode?.url;
|
||||
|
||||
if (url != null) {
|
||||
state.setUrl(url);
|
||||
return;
|
||||
}
|
||||
|
||||
set({
|
||||
status: 'failed',
|
||||
error: 'No suitable node found',
|
||||
// Create a map of node urls to client instances
|
||||
const clients: ClientCollection = {};
|
||||
nodes.forEach((url) => {
|
||||
clients[url] = createClient({
|
||||
url,
|
||||
cacheConfig: undefined,
|
||||
retry: false,
|
||||
connectToDevTools: false,
|
||||
});
|
||||
});
|
||||
console.warn('No suitable node was found');
|
||||
|
||||
// Find a suitable node to connect to by attempting a query and a
|
||||
// subscription, first to fulfill both will be the resulting url.
|
||||
const url = await findNode(clients);
|
||||
|
||||
if (url !== null) {
|
||||
set({
|
||||
status: 'success',
|
||||
VEGA_URL: url,
|
||||
});
|
||||
LocalStorage.setItem(STORAGE_KEY, url);
|
||||
}
|
||||
// Every node failed either to make a query or retrieve data from
|
||||
// a subscription
|
||||
else {
|
||||
set({
|
||||
status: 'failed',
|
||||
error: 'No node found',
|
||||
});
|
||||
console.warn('No suitable vega node was found');
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -28,16 +28,6 @@ const createStatsMock = (
|
||||
blockHeight: blockHeight.toString(),
|
||||
vegaTime: '12345',
|
||||
},
|
||||
networkParametersConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
key: 'a',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,19 +1,9 @@
|
||||
query NodeCheck {
|
||||
# statistics needed to get the most recent node in terms of block height
|
||||
statistics {
|
||||
chainId
|
||||
blockHeight
|
||||
vegaTime
|
||||
}
|
||||
# net params needed to filter out the nodes that are not suitable
|
||||
networkParametersConnection {
|
||||
edges {
|
||||
node {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscription NodeCheckTimeUpdate {
|
||||
|
@ -6,7 +6,7 @@ const defaultOptions = {} as const;
|
||||
export type NodeCheckQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type NodeCheckQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string, vegaTime: any }, networkParametersConnection: { __typename?: 'NetworkParametersConnection', edges?: Array<{ __typename?: 'NetworkParameterEdge', node: { __typename?: 'NetworkParameter', key: string, value: string } } | null> | null } };
|
||||
export type NodeCheckQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string, vegaTime: any } };
|
||||
|
||||
export type NodeCheckTimeUpdateSubscriptionVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
@ -21,14 +21,6 @@ export const NodeCheckDocument = gql`
|
||||
blockHeight
|
||||
vegaTime
|
||||
}
|
||||
networkParametersConnection {
|
||||
edges {
|
||||
node {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -12,19 +12,6 @@ export const statisticsQuery = (
|
||||
blockHeight: '11',
|
||||
vegaTime: new Date().toISOString(),
|
||||
},
|
||||
networkParametersConnection: {
|
||||
__typename: 'NetworkParametersConnection',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'NetworkParameterEdge',
|
||||
node: {
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'a',
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
return merge(defaultResult, override);
|
||||
|
@ -54,7 +54,6 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
|
||||
decimalPlaces: 5,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
tickSize: '1',
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
@ -22,7 +22,7 @@ export const generateFundingPayment = (
|
||||
decimalPlaces: 5,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
tickSize: '1',
|
||||
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
@ -485,7 +485,5 @@
|
||||
"See all the live games on the cards below. Every on-chain game is community funded and designed. <0>Find out how to create one</0>.": "See all the live games on the cards below. Every on-chain game is community funded and designed. <0>Find out how to create one</0>.",
|
||||
"Teams can earn rewards if they meet the goals set in the on-chain trading competitions. Track your earned rewards here, and see which teams are top of the leaderboard this month.": "Teams can earn rewards if they meet the goals set in the on-chain trading competitions. Track your earned rewards here, and see which teams are top of the leaderboard this month.",
|
||||
"Currently no active games on the network.": "Currently no active games on the network.",
|
||||
"Currently no active teams on the network.": "Currently no active teams on the network.",
|
||||
"It looks like you're connection is slow, try switching to another node.": "It looks like you're connection is slow, try switching to another node.",
|
||||
"It appears that the connection to the node <0>{{VEGA_URL}}</0> does not return necessary data, try switching to another node.": "It appears that the connection to the node <0>{{VEGA_URL}}</0> does not return necessary data, try switching to another node."
|
||||
"Currently no active teams on the network.": "Currently no active teams on the network."
|
||||
}
|
||||
|
5
libs/markets/src/lib/__generated__/markets.ts
generated
5
libs/markets/src/lib/__generated__/markets.ts
generated
File diff suppressed because one or more lines are too long
@ -139,7 +139,6 @@ query MarketInfo($marketId: ID!) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tickSize
|
||||
state
|
||||
tradingMode
|
||||
linearSlippageFactor
|
||||
|
File diff suppressed because one or more lines are too long
@ -23,7 +23,6 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
determinePriceStep,
|
||||
formatNumber,
|
||||
formatNumberPercentage,
|
||||
getDateTimeFormat,
|
||||
@ -321,7 +320,6 @@ export const KeyDetailsInfoPanel = ({
|
||||
marketDecimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
settlementAssetDecimalPlaces: assetDecimals,
|
||||
tickSize: determinePriceStep(market),
|
||||
}
|
||||
: {
|
||||
name: market.tradableInstrument.instrument.name,
|
||||
@ -332,7 +330,6 @@ export const KeyDetailsInfoPanel = ({
|
||||
marketDecimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
settlementAssetDecimalPlaces: assetDecimals,
|
||||
tickSize: determinePriceStep(market),
|
||||
}
|
||||
}
|
||||
parentData={
|
||||
|
@ -14,7 +14,6 @@ export const marketInfoQuery = (
|
||||
positionDecimalPlaces: 0,
|
||||
state: Schema.MarketState.STATE_ACTIVE,
|
||||
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
tickSize: '1',
|
||||
proposal: {
|
||||
__typename: 'Proposal',
|
||||
id: 'market-0',
|
||||
|
@ -2,7 +2,6 @@ fragment MarketFields on Market {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tickSize
|
||||
state
|
||||
tradingMode
|
||||
parentMarketID
|
||||
|
@ -37,7 +37,6 @@ export const createMarketFragment = (
|
||||
positionDecimalPlaces: 0,
|
||||
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
state: Schema.MarketState.STATE_ACTIVE,
|
||||
tickSize: '1',
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
close: null,
|
||||
|
@ -11,7 +11,6 @@ export const generateOrder = (partialOrder?: PartialDeep<Order>) => {
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 1,
|
||||
tickSize: '1',
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
@ -21,7 +21,6 @@ export const generateStopOrder = (
|
||||
__typename: 'Market',
|
||||
id: 'market-id',
|
||||
decimalPlaces: 1,
|
||||
tickSize: '1',
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
@ -17,7 +17,6 @@ describe('OrderEditDialog', () => {
|
||||
);
|
||||
const editOrder = await screen.findByTestId('edit-order');
|
||||
const limitPrice = within(editOrder).getByLabelText('Price');
|
||||
await userEvent.clear(limitPrice);
|
||||
await userEvent.type(limitPrice, '0.111111');
|
||||
const submitButton = within(editOrder).getByRole('button', {
|
||||
name: 'Update',
|
||||
@ -25,7 +24,7 @@ describe('OrderEditDialog', () => {
|
||||
await userEvent.click(submitButton);
|
||||
const inputErrorText = within(editOrder).getByTestId('input-error-text');
|
||||
expect(inputErrorText).toHaveTextContent(
|
||||
'Price must be a multiple of 0.1 for this market'
|
||||
'Price accepts up to 1 decimal places'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -4,8 +4,6 @@ import {
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
useValidateAmount,
|
||||
determinePriceStep,
|
||||
determineSizeStep,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { Size } from '@vegaprotocol/datagrid';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
@ -54,10 +52,8 @@ export const OrderEditDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const step = order.market ? determinePriceStep(order.market) : toDecimal(0);
|
||||
const stepSize = order.market
|
||||
? determineSizeStep(order.market)
|
||||
: toDecimal(0);
|
||||
const step = toDecimal(order.market?.decimalPlaces ?? 0);
|
||||
const stepSize = toDecimal(order.market?.positionDecimalPlaces ?? 0);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@ -121,11 +117,9 @@ export const OrderEditDialog = ({
|
||||
required: t('You need to provide a price'),
|
||||
validate: {
|
||||
min: (value) =>
|
||||
Number(value) >= Number(step)
|
||||
Number(value) > 0
|
||||
? true
|
||||
: t('Price cannot be lower than {{step}}', {
|
||||
step,
|
||||
}),
|
||||
: t('The price cannot be negative'),
|
||||
validate: validateAmount(step, t('Price')),
|
||||
},
|
||||
})}
|
||||
@ -145,11 +139,7 @@ export const OrderEditDialog = ({
|
||||
required: t('You need to provide a size'),
|
||||
validate: {
|
||||
min: (value) =>
|
||||
Number(value) >= Number(stepSize)
|
||||
? true
|
||||
: t('Size cannot be lower than {{stepSize}}', {
|
||||
stepSize,
|
||||
}),
|
||||
Number(value) > 0 ? true : t('The size cannot be negative'),
|
||||
validate: validateAmount(stepSize, t('Size')),
|
||||
},
|
||||
})}
|
||||
|
@ -22,7 +22,6 @@ describe('OrderViewDialog', () => {
|
||||
positionDecimalPlaces: 3,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
tickSize: '1',
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
@ -34,21 +34,6 @@
|
||||
"options": {
|
||||
"jestConfig": "libs/types/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"generate": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": ["npx graphql-codegen --config=libs/types/codegen.yml"],
|
||||
"parallel": false
|
||||
}
|
||||
},
|
||||
"local-registry": {
|
||||
"executor": "@nx/js:verdaccio",
|
||||
"options": {
|
||||
"port": 4873,
|
||||
"config": ".verdaccio/config.yml",
|
||||
"storage": "tmp/local-registry/storage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
|
@ -35,14 +35,6 @@
|
||||
"options": {
|
||||
"jestConfig": "libs/utils/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"local-registry": {
|
||||
"executor": "@nx/js:verdaccio",
|
||||
"options": {
|
||||
"port": 4873,
|
||||
"config": ".verdaccio/config.yml",
|
||||
"storage": "tmp/local-registry/storage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
|
@ -16,4 +16,3 @@ export * from './lib/validate';
|
||||
export * from './lib/resolve-network-name';
|
||||
export * from './lib/is-test-env';
|
||||
export * from './lib/constants';
|
||||
export * from './lib/step';
|
||||
|
@ -18,42 +18,18 @@ export const getUnlimitedThreshold = (decimalPlaces: number) =>
|
||||
const MIN_FRACTION_DIGITS = 2;
|
||||
const MAX_FRACTION_DIGITS = 20;
|
||||
|
||||
/**
|
||||
* Converts "raw" value to a decimal representation as a `BigNumber`
|
||||
*
|
||||
* Example:
|
||||
* `toBigNum(1, 3)` -> 0.001
|
||||
* `toBigNum(1234, 2)` -> 12.34
|
||||
*
|
||||
* @param rawValue The "raw" value
|
||||
* @param decimals The number of decimal places
|
||||
*/
|
||||
export function toDecimal(numberOfDecimals: number) {
|
||||
return new BigNumber(1)
|
||||
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
|
||||
.toString(10);
|
||||
}
|
||||
|
||||
export function toBigNum(
|
||||
rawValue: string | number | BigNumber,
|
||||
decimals: number
|
||||
): BigNumber {
|
||||
const d = new BigNumber(10).exponentiatedBy(decimals);
|
||||
return new BigNumber(rawValue || 0).dividedBy(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses `toBigNum` - converts given decimal representation
|
||||
* as a "raw" value (`BigNumber`)
|
||||
*
|
||||
* Example:
|
||||
* `formBigNum(5.4321, 4)` -> 54321
|
||||
* `formBigNum(112.34, 2)` -> 11234
|
||||
*/
|
||||
export const fromBigNum = (
|
||||
input: string | number | BigNumber,
|
||||
decimals: number
|
||||
) => {
|
||||
const d = new BigNumber(10).exponentiatedBy(decimals);
|
||||
return new BigNumber(input).times(d);
|
||||
};
|
||||
|
||||
export function toDecimal(numberOfDecimals: number) {
|
||||
return toBigNum(1, numberOfDecimals).toString(10);
|
||||
const divides = new BigNumber(10).exponentiatedBy(decimals);
|
||||
return new BigNumber(rawValue || 0).dividedBy(divides);
|
||||
}
|
||||
|
||||
export function addDecimal(
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const isValidUrl = (url?: string | null) => {
|
||||
export const isValidUrl = (url?: string) => {
|
||||
if (!url) return false;
|
||||
try {
|
||||
new URL(url);
|
||||
|
@ -1,21 +0,0 @@
|
||||
import type { Market } from '@vegaprotocol/types';
|
||||
import { toBigNum, toDecimal } from './format';
|
||||
|
||||
export const determinePriceStep = (
|
||||
market: Pick<Market, 'decimalPlaces' | 'tickSize'>
|
||||
) => {
|
||||
let priceStep = toDecimal(market.decimalPlaces);
|
||||
|
||||
const scaledTickSize = toBigNum(market.tickSize, market.decimalPlaces);
|
||||
if (scaledTickSize.isGreaterThan(0)) {
|
||||
priceStep = scaledTickSize.toString();
|
||||
}
|
||||
|
||||
return priceStep;
|
||||
};
|
||||
|
||||
export const determineSizeStep = (
|
||||
market: Pick<Market, 'positionDecimalPlaces'>
|
||||
) => {
|
||||
return toDecimal(market.positionDecimalPlaces);
|
||||
};
|
@ -1,38 +0,0 @@
|
||||
import { validateAgainstStep } from './validate-amount';
|
||||
|
||||
describe('validateAgainstStep', () => {
|
||||
it('fails when step is an empty string', () => {
|
||||
expect(validateAgainstStep('', '1234')).toEqual(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[0, 0],
|
||||
[1234567890, 0],
|
||||
[0.03, 0.03],
|
||||
[0.09, 0.03],
|
||||
[0.27, 0.03],
|
||||
[1, 1],
|
||||
[123, 1],
|
||||
[4, 2],
|
||||
[8, 2],
|
||||
])(
|
||||
'checks whether given value (%s) IS a multiple of given step (%s)',
|
||||
(value, step) => {
|
||||
expect(validateAgainstStep(step, value)).toEqual(true);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
[1, 2],
|
||||
[0.1, 0.003],
|
||||
[1.11, 0.1],
|
||||
[123.1, 1],
|
||||
[222, 221],
|
||||
[NaN, 1],
|
||||
])(
|
||||
'checks whether given value (%s) IS NOT a multiple of given step (%s)',
|
||||
(value, step) => {
|
||||
expect(validateAgainstStep(step, value)).toEqual(false);
|
||||
}
|
||||
);
|
||||
});
|
@ -1,24 +1,35 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useT } from '../use-t';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export const useValidateAmount = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(step: number | string, field: string) => {
|
||||
const [, stepDecimals = ''] = String(step).split('.');
|
||||
|
||||
return (value?: string) => {
|
||||
const isValid = validateAgainstStep(step, value);
|
||||
if (!isValid) {
|
||||
if (new BigNumber(step).isEqualTo(1)) {
|
||||
if (Number(step) > 1) {
|
||||
if (Number(value) % Number(step) > 0) {
|
||||
return t(
|
||||
'{{field}} must be a multiple of {{step}} for this market',
|
||||
{
|
||||
field,
|
||||
step,
|
||||
}
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const [, valueDecimals = ''] = (value || '').split('.');
|
||||
if (stepDecimals.length < valueDecimals.length) {
|
||||
if (stepDecimals === '') {
|
||||
return t('{{field}} must be whole numbers for this market', {
|
||||
field,
|
||||
step,
|
||||
});
|
||||
}
|
||||
|
||||
return t('{{field}} must be a multiple of {{step}} for this market', {
|
||||
return t('{{field}} accepts up to {{decimals}} decimal places', {
|
||||
field,
|
||||
step,
|
||||
decimals: stepDecimals.length,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
@ -27,24 +38,3 @@ export const useValidateAmount = () => {
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
const isMultipleOf = (value: BigNumber, multipleOf: BigNumber) =>
|
||||
value.modulo(multipleOf).isZero();
|
||||
|
||||
export const validateAgainstStep = (
|
||||
step: string | number,
|
||||
input?: string | number
|
||||
) => {
|
||||
const stepValue = new BigNumber(step);
|
||||
if (stepValue.isNaN()) {
|
||||
// unable to check if step is not a number
|
||||
return false;
|
||||
}
|
||||
if (stepValue.isZero()) {
|
||||
// every number is valid when step is zero
|
||||
return true;
|
||||
}
|
||||
|
||||
const value = new BigNumber(input || '');
|
||||
return isMultipleOf(value, stepValue);
|
||||
};
|
||||
|
@ -35,14 +35,6 @@
|
||||
"options": {
|
||||
"jestConfig": "libs/wallet/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"local-registry": {
|
||||
"executor": "@nx/js:verdaccio",
|
||||
"options": {
|
||||
"port": 4873,
|
||||
"config": ".verdaccio/config.yml",
|
||||
"storage": "tmp/local-registry/storage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
|
@ -140,7 +140,6 @@ describe('WithdrawFormContainer', () => {
|
||||
positionDecimalPlaces: 0,
|
||||
state: Types.MarketState.STATE_SUSPENDED,
|
||||
tradingMode: Types.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
tickSize: '1',
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
|
@ -241,5 +241,8 @@
|
||||
"graphql": "15.8.0",
|
||||
"//": "workaround storybook issue: https://github.com/storybookjs/storybook/issues/21642",
|
||||
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0"
|
||||
},
|
||||
"nx": {
|
||||
"includedScripts": []
|
||||
}
|
||||
}
|
||||
|
14
project.json
Normal file
14
project.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "nx-monorepo",
|
||||
"$schema": "node_modules/nx/schemas/project-schema.json",
|
||||
"targets": {
|
||||
"local-registry": {
|
||||
"executor": "@nx/js:verdaccio",
|
||||
"options": {
|
||||
"port": 4873,
|
||||
"config": ".verdaccio/config.yml",
|
||||
"storage": "tmp/local-registry/storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user