Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2773d748 | ||
|
|
b2860121e5 | ||
|
|
db5e5ee782 | ||
|
|
0d3bcf05a1 | ||
|
|
c7dd5e846a | ||
|
|
0d850bd8b9 | ||
|
|
44189591fc | ||
|
|
3ed2ec88d7 | ||
|
|
a21feea699 | ||
|
|
41fd14dd00 | ||
|
|
c5a27dc6a2 | ||
|
|
0a3b1cadba | ||
|
|
b953de953a | ||
|
|
5ddcb613e2 | ||
|
|
76c07992d3 | ||
|
|
46e2965fa2 |
@@ -205,7 +205,7 @@ jobs:
|
||||
# run tests
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 1 --dist loadfile --durations=45
|
||||
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v --numprocesses 4 --dist loadfile --durations=45
|
||||
working-directory: apps/trading/e2e
|
||||
#----------------------------------------------
|
||||
# upload traces
|
||||
|
||||
@@ -24,10 +24,6 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
|
||||
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.getByTestId('vote-progress-bar-for')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
@@ -73,10 +69,6 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
'have.text',
|
||||
'Waiting for Node Vote'
|
||||
);
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.getByTestId('vote-progress-bar-against')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { type ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { VoteProgress } from '@vegaprotocol/proposals';
|
||||
import { type AgGridReact } from 'ag-grid-react';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGrid } from '@vegaprotocol/datagrid';
|
||||
@@ -12,12 +11,7 @@ import { type ColDef } from 'ag-grid-community';
|
||||
import type { RowClickedEvent } from 'ag-grid-community';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { ProposalStateMapping } from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
import { JsonViewerDialog } from '../dialogs/json-viewer-dialog';
|
||||
@@ -31,15 +25,7 @@ type ProposalsTableProps = {
|
||||
data: ProposalListFieldsFragment[] | null;
|
||||
};
|
||||
export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
]);
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
const requiredMajorityPercentage = useMemo(() => {
|
||||
const requiredMajority =
|
||||
params?.governance_proposal_market_requiredMajority ?? 1;
|
||||
return new BigNumber(requiredMajority).times(100);
|
||||
}, [params?.governance_proposal_market_requiredMajority]);
|
||||
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
useLayoutEffect(() => {
|
||||
@@ -90,33 +76,6 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
return value ? ProposalStateMapping[value] : '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'voting',
|
||||
maxWidth: 100,
|
||||
hide: window.innerWidth <= BREAKPOINT_MD,
|
||||
headerName: t('Voting'),
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
|
||||
if (data) {
|
||||
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
|
||||
const noTokens = new BigNumber(data.votes.no.totalTokens);
|
||||
const totalTokensVoted = yesTokens.plus(noTokens);
|
||||
const yesPercentage = totalTokensVoted.isZero()
|
||||
? new BigNumber(0)
|
||||
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center pt-2 uppercase">
|
||||
<VoteProgress
|
||||
threshold={requiredMajorityPercentage}
|
||||
progress={yesPercentage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'cDate',
|
||||
maxWidth: 150,
|
||||
@@ -184,7 +143,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
|
||||
},
|
||||
},
|
||||
],
|
||||
[requiredMajorityPercentage, tokenLink]
|
||||
[tokenLink]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"short_name": "Explorer VEGA",
|
||||
"name": "Vega Protocol - Explorer",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"short_name": "Governance VEGA",
|
||||
"name": "Vega Protocol - Governance",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
+10
@@ -241,6 +241,16 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
amount: '5',
|
||||
},
|
||||
},
|
||||
{
|
||||
// This should not be included in the result
|
||||
node: {
|
||||
epoch: 2,
|
||||
assetId: '3',
|
||||
decimals: 18,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY,
|
||||
amount: '5',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
epoch: {
|
||||
|
||||
+6
@@ -83,6 +83,12 @@ export const generateEpochTotalRewardsList = ({
|
||||
(Number(rewardItem?.amount) || 0) + Number(reward.amount)
|
||||
).toString();
|
||||
|
||||
// only RowAccountTypes are relevant for this table, others should
|
||||
// be discarded
|
||||
if (!Object.keys(RowAccountTypes).includes(reward.rewardType)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
rewards?.set(reward.rewardType, {
|
||||
rewardType: reward.rewardType,
|
||||
amount,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Vega Protocol static asseets</title>
|
||||
<title>Vega Protocol static assets</title>
|
||||
<link rel="stylesheet" href="fonts.css" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
|
||||
@@ -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,6 +1,12 @@
|
||||
{
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"name": "Vega Protocol - Trading",
|
||||
"short_name": "Console",
|
||||
"description": "Vega Protocol - Trading dApp",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
@@ -12,9 +18,5 @@
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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,8 +3,8 @@ import { ErrorBoundary } from '@sentry/react';
|
||||
import { CompetitionsHeader } from '../../components/competitions/competitions-header';
|
||||
import { Intent, Loader, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useGames } from '../../lib/hooks/use-games';
|
||||
import { useCurrentEpochInfoQuery } from '../referrals/hooks/__generated__/Epoch';
|
||||
import { useGameCards } from '../../lib/hooks/use-game-cards';
|
||||
import { useCurrentEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Links } from '../../lib/links';
|
||||
import {
|
||||
@@ -28,7 +28,7 @@ export const CompetitionsHome = () => {
|
||||
const { data: epochData } = useCurrentEpochInfoQuery();
|
||||
const currentEpoch = Number(epochData?.epoch.id);
|
||||
|
||||
const { data: gamesData, loading: gamesLoading } = useGames({
|
||||
const { data: gamesData, loading: gamesLoading } = useGameCards({
|
||||
onlyActive: true,
|
||||
currentEpoch,
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
type TeamStats as ITeamStats,
|
||||
type Team as TeamType,
|
||||
type Member,
|
||||
type TeamGame,
|
||||
} from '../../lib/hooks/use-team';
|
||||
import { DApp, EXPLORER_PARTIES, useLinks } from '@vegaprotocol/environment';
|
||||
import { TeamAvatar } from '../../components/competitions/team-avatar';
|
||||
@@ -23,6 +22,11 @@ import { LayoutWithGradient } from '../../components/layouts-inner';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { JoinTeam } from './join-team';
|
||||
import { UpdateTeamButton } from './update-team-button';
|
||||
import {
|
||||
type TeamGame,
|
||||
useGames,
|
||||
areTeamGames,
|
||||
} from '../../lib/hooks/use-games';
|
||||
|
||||
export const CompetitionsTeam = () => {
|
||||
const t = useT();
|
||||
@@ -38,8 +42,12 @@ export const CompetitionsTeam = () => {
|
||||
const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => {
|
||||
const t = useT();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data, team, partyTeam, stats, members, games, loading, refetch } =
|
||||
useTeam(teamId, pubKey || undefined);
|
||||
const { data, team, partyTeam, stats, members, loading, refetch } = useTeam(
|
||||
teamId,
|
||||
pubKey || undefined
|
||||
);
|
||||
|
||||
const { data: games, loading: gamesLoading } = useGames(teamId);
|
||||
|
||||
// only show spinner on first load so when users join teams its smoother
|
||||
if (!data && loading) {
|
||||
@@ -64,7 +72,8 @@ const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => {
|
||||
partyTeam={partyTeam}
|
||||
stats={stats}
|
||||
members={members}
|
||||
games={games}
|
||||
games={areTeamGames(games) ? games : undefined}
|
||||
gamesLoading={gamesLoading}
|
||||
refetch={refetch}
|
||||
/>
|
||||
);
|
||||
@@ -76,6 +85,7 @@ const TeamPage = ({
|
||||
stats,
|
||||
members,
|
||||
games,
|
||||
gamesLoading,
|
||||
refetch,
|
||||
}: {
|
||||
team: TeamType;
|
||||
@@ -83,6 +93,7 @@ const TeamPage = ({
|
||||
stats?: ITeamStats;
|
||||
members?: Member[];
|
||||
games?: TeamGame[];
|
||||
gamesLoading?: boolean;
|
||||
refetch: () => void;
|
||||
}) => {
|
||||
const t = useT();
|
||||
@@ -113,7 +124,11 @@ const TeamPage = ({
|
||||
onClick={() => setShowGames(true)}
|
||||
data-testid="games-toggle"
|
||||
>
|
||||
{t('Games ({{count}})', { count: games ? games.length : 0 })}
|
||||
{t('Games {{games}}', {
|
||||
replace: {
|
||||
games: gamesLoading ? '' : games ? `(${games.length})` : '(0)',
|
||||
},
|
||||
})}
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
active={!showGames}
|
||||
@@ -125,15 +140,33 @@ const TeamPage = ({
|
||||
})}
|
||||
</ToggleButton>
|
||||
</div>
|
||||
{showGames ? <Games games={games} /> : <Members members={members} />}
|
||||
{showGames ? (
|
||||
<Games games={games} gamesLoading={gamesLoading} />
|
||||
) : (
|
||||
<Members members={members} />
|
||||
)}
|
||||
</section>
|
||||
</LayoutWithGradient>
|
||||
);
|
||||
};
|
||||
|
||||
const Games = ({ games }: { games?: TeamGame[] }) => {
|
||||
const Games = ({
|
||||
games,
|
||||
gamesLoading,
|
||||
}: {
|
||||
games?: TeamGame[];
|
||||
gamesLoading?: boolean;
|
||||
}) => {
|
||||
const t = useT();
|
||||
|
||||
if (gamesLoading) {
|
||||
return (
|
||||
<div className="w-[15px]">
|
||||
<Loader size="small" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!games?.length) {
|
||||
return <p>{t('No games')}</p>;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -46,7 +46,7 @@ const prepareTransaction = (
|
||||
createReferralSet: {
|
||||
isTeam: true,
|
||||
team: {
|
||||
name: fields.name,
|
||||
name: fields.name.trim(),
|
||||
teamUrl: fields.url,
|
||||
avatarUrl: fields.avatarUrl,
|
||||
closed: fields.private,
|
||||
@@ -62,7 +62,7 @@ const prepareTransaction = (
|
||||
id: fields.id,
|
||||
isTeam: true,
|
||||
team: {
|
||||
name: fields.name,
|
||||
name: fields.name.trim(),
|
||||
teamUrl: fields.url,
|
||||
avatarUrl: fields.avatarUrl,
|
||||
closed: fields.private,
|
||||
@@ -116,7 +116,17 @@ export const TeamForm = ({
|
||||
<input type="hidden" {...register('id')} />
|
||||
<TradingFormGroup label={t('Team name')} labelFor="name">
|
||||
<TradingInput
|
||||
{...register('name', { required: t('Required') })}
|
||||
{...register('name', {
|
||||
required: t('Required'),
|
||||
validate: {
|
||||
notEmpty: (value) => {
|
||||
if (/^\s*$/.test(value)) {
|
||||
return t('Team name cannot be empty');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
})}
|
||||
data-testid="team-name-input"
|
||||
/>
|
||||
{errors.name?.message && (
|
||||
|
||||
@@ -56,6 +56,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
||||
<Last24hPriceChange
|
||||
marketId={market.id}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
fallback={<span>-</span>}
|
||||
/>
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Volume (24h)')} testId="market-volume">
|
||||
@@ -112,7 +113,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
||||
heading={`${t('Funding Rate')} / ${t('Countdown')}`}
|
||||
testId="market-funding"
|
||||
>
|
||||
<div className="flex justify-between gap-2">
|
||||
<div className="flex gap-2">
|
||||
<FundingRate marketId={market.id} />
|
||||
<FundingCountdown marketId={market.id} />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { type Market } from '@vegaprotocol/markets';
|
||||
// TODO: handle oracle banner
|
||||
// import { OracleBanner } from '@vegaprotocol/markets';
|
||||
import { useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Popover,
|
||||
@@ -12,21 +11,21 @@ import {
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { MarketBanner } from '../../components/market-banner';
|
||||
import { ErrorBoundary } from '../../components/error-boundary';
|
||||
import { type TradingView } from './trade-views';
|
||||
import { TradingViews } from './trade-views';
|
||||
|
||||
interface TradePanelsProps {
|
||||
market: Market;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
const [view, setView] = useState<TradingView>('chart');
|
||||
const viewCfg = TradingViews[view];
|
||||
const [topView, setTopView] = useState<TradingView>('chart');
|
||||
const topViewCfg = TradingViews[topView];
|
||||
const [bottomView, setBottomView] = useState<TradingView>('positions');
|
||||
const bottomViewCfg = TradingViews[bottomView];
|
||||
|
||||
const renderView = () => {
|
||||
const renderView = (view: TradingView) => {
|
||||
const Component = TradingViews[view].component;
|
||||
|
||||
if (!Component) {
|
||||
@@ -39,12 +38,13 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
// so watch out for clashes in props
|
||||
return (
|
||||
<ErrorBoundary feature={view}>
|
||||
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
|
||||
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const renderMenu = (viewCfg: any) => {
|
||||
if ('menu' in viewCfg || 'settings' in viewCfg) {
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
@@ -69,55 +69,80 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||
<div>
|
||||
<MarketBanner market={market} />
|
||||
</div>
|
||||
<div>{renderMenu()}</div>
|
||||
<div className="h-full relative">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<div style={{ width, height }} className="overflow-auto">
|
||||
{renderView()}
|
||||
</div>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
||||
{Object.keys(TradingViews)
|
||||
// filter to control available views for the current market
|
||||
// eg only perps should get the funding views
|
||||
.filter((_key) => {
|
||||
const key = _key as TradingView;
|
||||
const perpOnlyViews = ['funding', 'fundingPayments'];
|
||||
<div className="h-full flex flex-col lg:grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||
<div className="flex flex-col w-full overflow-hidden">
|
||||
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
||||
{['chart', 'orderbook', 'trades', 'liquidity', 'fundingPayments']
|
||||
// filter to control available views for the current market
|
||||
// e.g. only perpetuals should get the funding views
|
||||
.filter((_key) => {
|
||||
const key = _key as TradingView;
|
||||
const perpOnlyViews = ['funding', 'fundingPayments'];
|
||||
|
||||
if (
|
||||
market?.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (perpOnlyViews.includes(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
market?.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((_key) => {
|
||||
const key = _key as TradingView;
|
||||
const isActive = topView === key;
|
||||
return (
|
||||
<ViewButton
|
||||
key={key}
|
||||
view={key}
|
||||
isActive={isActive}
|
||||
onClick={() => {
|
||||
setTopView(key);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="h-[50vh] lg:h-full relative">
|
||||
<div>{renderMenu(topViewCfg)}</div>
|
||||
<div className="overflow-auto h-full">{renderView(topView)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (perpOnlyViews.includes(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((_key) => {
|
||||
<div className="flex flex-col w-full grow">
|
||||
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
||||
{[
|
||||
'positions',
|
||||
'activeOrders',
|
||||
'closedOrders',
|
||||
'rejectedOrders',
|
||||
'orders',
|
||||
'stopOrders',
|
||||
'collateral',
|
||||
'fills',
|
||||
].map((_key) => {
|
||||
const key = _key as TradingView;
|
||||
const isActive = view === key;
|
||||
const isActive = bottomView === key;
|
||||
return (
|
||||
<ViewButton
|
||||
key={key}
|
||||
view={key}
|
||||
isActive={isActive}
|
||||
onClick={() => {
|
||||
setView(key);
|
||||
setBottomView(key);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="relative grow">
|
||||
<div className="flex flex-col">{renderMenu(bottomViewCfg)}</div>
|
||||
<div className="overflow-auto h-full">{renderView(bottomView)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -157,7 +182,7 @@ const useViewLabel = (view: TradingView) => {
|
||||
depth: t('Depth'),
|
||||
liquidity: t('Liquidity'),
|
||||
funding: t('Funding'),
|
||||
fundingPayments: t('Funding Payments'),
|
||||
fundingPayments: t('Funding'),
|
||||
orderbook: t('Orderbook'),
|
||||
trades: t('Trades'),
|
||||
positions: t('Positions'),
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useEffect } from 'react';
|
||||
import { useT } from '../../../lib/use-t';
|
||||
import { matchPath, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Routes } from '../../../lib/links';
|
||||
import { useCurrentEpochInfoQuery } from './__generated__/Epoch';
|
||||
import { useCurrentEpochInfoQuery } from '../../../lib/hooks/__generated__/Epoch';
|
||||
|
||||
const REFETCH_INTERVAL = 60 * 60 * 1000; // 1h
|
||||
const NON_ELIGIBLE_REFERRAL_SET_TOAST_ID = 'non-eligible-referral-set';
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
getDateFormat,
|
||||
getDateTimeFormat,
|
||||
getNumberFormat,
|
||||
getUserLocale,
|
||||
removePaginationWrapper,
|
||||
} from '@vegaprotocol/utils';
|
||||
@@ -34,9 +34,10 @@ import {
|
||||
} from './hooks/use-referral';
|
||||
import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form';
|
||||
import { useReferralProgram } from './hooks/use-referral-program';
|
||||
import { useCurrentEpochInfoQuery } from './hooks/__generated__/Epoch';
|
||||
import { useCurrentEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch';
|
||||
import { QUSDTooltip } from './qusd-tooltip';
|
||||
import { CodeTile, StatTile, Tile } from './tile';
|
||||
import { areTeamGames, useGames } from '../../lib/hooks/use-games';
|
||||
|
||||
export const ReferralStatistics = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
@@ -323,7 +324,7 @@ export const Statistics = ({
|
||||
}
|
||||
description={<QUSDTooltip />}
|
||||
>
|
||||
{getNumberFormat(0).format(Number(totalCommissionValue))}
|
||||
{formatNumber(totalCommissionValue, 0)}
|
||||
</StatTile>
|
||||
);
|
||||
|
||||
@@ -563,8 +564,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()}
|
||||
/>
|
||||
@@ -576,7 +577,8 @@ export const RefereesTable = ({
|
||||
};
|
||||
|
||||
const Team = ({ teamId }: { teamId?: string }) => {
|
||||
const { team, games, members } = useTeam(teamId);
|
||||
const { team, members } = useTeam(teamId);
|
||||
const { data: games } = useGames(teamId);
|
||||
|
||||
if (!team) return null;
|
||||
|
||||
@@ -585,7 +587,10 @@ const Team = ({ teamId }: { teamId?: string }) => {
|
||||
<TeamAvatar teamId={team.teamId} imgUrl={team.avatarUrl} />
|
||||
<div className="flex flex-col items-start gap-1 lg:gap-3">
|
||||
<h1 className="calt text-2xl lg:text-3xl xl:text-5xl">{team.name}</h1>
|
||||
<TeamStats members={members} games={games} />
|
||||
<TeamStats
|
||||
members={members}
|
||||
games={areTeamGames(games) ? games : undefined}
|
||||
/>
|
||||
</div>
|
||||
</Tile>
|
||||
);
|
||||
|
||||
@@ -153,5 +153,8 @@ const cacheConfig: InMemoryCacheConfig = {
|
||||
OrderUpdate: {
|
||||
keyFields: false,
|
||||
},
|
||||
Game: {
|
||||
keyFields: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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" />;
|
||||
@@ -57,7 +56,10 @@ export const CompetitionsLeaderboard = ({
|
||||
className="hover:underline"
|
||||
to={Links.COMPETITIONS_TEAM(td.teamId)}
|
||||
>
|
||||
{td.name}
|
||||
{
|
||||
// Its possible for a tx to be submitted with an empty space as team name
|
||||
td.name.trim() !== '' ? td.name : t('[empty]')
|
||||
}
|
||||
</Link>
|
||||
),
|
||||
earned: num(td.totalQuantumRewards),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type TeamGame, type TeamStats } from '../../lib/hooks/use-team';
|
||||
import { type TeamStats } from '../../lib/hooks/use-team';
|
||||
import { type TeamsFieldsFragment } from '../../lib/hooks/__generated__/Teams';
|
||||
import { TeamAvatar, getFallbackAvatar } from './team-avatar';
|
||||
import { FavoriteGame, Stat } from './team-stats';
|
||||
@@ -13,6 +13,7 @@ import { take } from 'lodash';
|
||||
import { DispatchMetricLabels } from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
import { UpdateTeamButton } from '../../client-pages/competitions/update-team-button';
|
||||
import { type TeamGame } from '../../lib/hooks/use-games';
|
||||
|
||||
export const TeamCard = ({
|
||||
rank,
|
||||
|
||||
@@ -11,11 +11,11 @@ import { formatNumberRounded } from '@vegaprotocol/utils';
|
||||
import {
|
||||
type TeamStats as ITeamStats,
|
||||
type Member,
|
||||
type TeamGame,
|
||||
} from '../../lib/hooks/use-team';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import { DispatchMetricLabels, type DispatchMetric } from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
import { type TeamGame } from '../../lib/hooks/use-games';
|
||||
|
||||
export const TeamStats = ({
|
||||
stats,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Outlet } from 'react-router-dom';
|
||||
import { Sidebar, SidebarContent, useSidebar } from '../sidebar';
|
||||
import classNames from 'classnames';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const LayoutWithSidebar = ({
|
||||
header,
|
||||
sidebar,
|
||||
@@ -17,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]'
|
||||
@@ -27,10 +26,13 @@ export const LayoutWithSidebar = ({
|
||||
<div className={gridClasses}>
|
||||
<div className="col-span-full">{header}</div>
|
||||
<main
|
||||
className={classNames('col-start-1 col-end-1 overflow-y-auto', {
|
||||
'lg:col-end-3': !sidebarOpen,
|
||||
'hidden lg:block lg:col-end-2': sidebarOpen,
|
||||
})}
|
||||
className={classNames(
|
||||
'col-start-1 col-end-1 overflow-hidden lg:overflow-y-auto grow lg:grow-0',
|
||||
{
|
||||
'lg:col-end-3': !sidebarOpen,
|
||||
'hidden lg:block lg:col-end-2': sidebarOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './market-header';
|
||||
export * from './mobile-market-header';
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelector } from '../market-selector';
|
||||
import {
|
||||
Last24hPriceChange,
|
||||
useMarket,
|
||||
useMarketList,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import { useState } from 'react';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import classNames from 'classnames';
|
||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||
import { MarketMarkPrice } from '../market-mark-price';
|
||||
/**
|
||||
* This is only rendered for the mobile navigation
|
||||
*/
|
||||
export const MobileMarketHeader = () => {
|
||||
const t = useT();
|
||||
const { marketId } = useParams();
|
||||
const { data } = useMarket(marketId);
|
||||
const [openMarket, setOpenMarket] = useState(false);
|
||||
const [openPrice, setOpenPrice] = useState(false);
|
||||
|
||||
// Ensure that markets are kept cached so opening the list
|
||||
// shows all markets instantly
|
||||
useMarketList();
|
||||
|
||||
if (!marketId) return null;
|
||||
|
||||
return (
|
||||
<div className="pl-3 pr-2 flex justify-between gap-2 h-10 bg-vega-clight-700 dark:bg-vega-cdark-700">
|
||||
<FullScreenPopover
|
||||
open={openMarket}
|
||||
onOpenChange={(x) => {
|
||||
setOpenMarket(x);
|
||||
}}
|
||||
trigger={
|
||||
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-base leading-3 md:text-lg whitespace-nowrap">
|
||||
{data
|
||||
? data.tradableInstrument.instrument.code
|
||||
: t('Select market')}
|
||||
<span
|
||||
className={classNames(
|
||||
'transition-transform ease-in-out duration-300 flex',
|
||||
{
|
||||
'rotate-180': openMarket,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={16} />
|
||||
</span>
|
||||
</h1>
|
||||
}
|
||||
>
|
||||
<MarketSelector
|
||||
currentMarketId={marketId}
|
||||
onSelect={() => setOpenMarket(false)}
|
||||
/>
|
||||
</FullScreenPopover>
|
||||
<FullScreenPopover
|
||||
open={openPrice}
|
||||
onOpenChange={(x) => {
|
||||
setOpenPrice(x);
|
||||
}}
|
||||
trigger={
|
||||
<span className="flex gap-2 items-end md:text-md whitespace-nowrap leading-3">
|
||||
{data && (
|
||||
<>
|
||||
<span className="text-xs">
|
||||
<Last24hPriceChange
|
||||
marketId={data.id}
|
||||
decimalPlaces={data.decimalPlaces}
|
||||
/>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<MarketMarkPrice
|
||||
marketId={data.id}
|
||||
decimalPlaces={data.decimalPlaces}
|
||||
/>
|
||||
<VegaIcon
|
||||
name={VegaIconNames.CHEVRON_DOWN}
|
||||
size={16}
|
||||
className={classNames(
|
||||
'transition-transform ease-in-out duration-300',
|
||||
{
|
||||
'rotate-180': openPrice,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{data && (
|
||||
<div className="px-3 py-6 text-sm grid grid-cols-2 items-center gap-x-4 gap-y-6">
|
||||
<MarketHeaderStats market={data} />
|
||||
</div>
|
||||
)}
|
||||
</FullScreenPopover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
|
||||
trigger: React.ReactNode | string;
|
||||
}
|
||||
|
||||
export const FullScreenPopover = ({
|
||||
trigger,
|
||||
children,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: PopoverProps) => {
|
||||
return (
|
||||
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverPrimitive.Trigger data-testid="popover-trigger">
|
||||
{trigger}
|
||||
</PopoverPrimitive.Trigger>
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-testid="popover-content"
|
||||
className="w-screen bg-vega-clight-800 dark:bg-vega-cdark-800 border-y border-default"
|
||||
sideOffset={0}
|
||||
>
|
||||
{children}
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
</PopoverPrimitive.Root>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export * from './navbar';
|
||||
export * from './nav-header';
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelector } from '../market-selector';
|
||||
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import { useState } from 'react';
|
||||
import { useT } from '../../lib/use-t';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* This is only rendered for the mobile navigation
|
||||
*/
|
||||
export const NavHeader = () => {
|
||||
const t = useT();
|
||||
const { marketId } = useParams();
|
||||
const { data } = useMarket(marketId);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// Ensure that markets are kept cached so opening the list
|
||||
// shows all markets instantly
|
||||
useMarketList();
|
||||
|
||||
if (!marketId) return null;
|
||||
|
||||
return (
|
||||
<FullScreenPopover
|
||||
open={open}
|
||||
onOpenChange={(x) => {
|
||||
setOpen(x);
|
||||
}}
|
||||
trigger={
|
||||
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-default text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default">
|
||||
{data ? data.tradableInstrument.instrument.code : t('Select market')}
|
||||
<span
|
||||
className={classNames(
|
||||
'transition-transform ease-in-out duration-300',
|
||||
{
|
||||
'rotate-180': open,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
|
||||
</span>
|
||||
</h1>
|
||||
}
|
||||
>
|
||||
<MarketSelector
|
||||
currentMarketId={marketId}
|
||||
onSelect={() => setOpen(false)}
|
||||
/>
|
||||
</FullScreenPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
|
||||
trigger: React.ReactNode | string;
|
||||
}
|
||||
|
||||
export const FullScreenPopover = ({
|
||||
trigger,
|
||||
children,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: PopoverProps) => {
|
||||
return (
|
||||
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverPrimitive.Trigger data-testid="popover-trigger">
|
||||
{trigger}
|
||||
</PopoverPrimitive.Trigger>
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-testid="popover-content"
|
||||
className="w-screen bg-vega-clight-800 dark:bg-vega-cdark-800 text-default border border-default"
|
||||
sideOffset={5}
|
||||
>
|
||||
{children}
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
</PopoverPrimitive.Root>
|
||||
);
|
||||
};
|
||||
@@ -34,13 +34,7 @@ import { supportedLngs } from '../../lib/i18n';
|
||||
type MenuState = 'wallet' | 'nav' | null;
|
||||
type Theme = 'system' | 'yellow';
|
||||
|
||||
export const Navbar = ({
|
||||
children,
|
||||
theme = 'system',
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
theme?: Theme;
|
||||
}) => {
|
||||
export const Navbar = ({ theme = 'system' }: { theme?: Theme }) => {
|
||||
const i18n = useI18n();
|
||||
const t = useT();
|
||||
// menu state for small screens
|
||||
@@ -75,8 +69,6 @@ export const Navbar = ({
|
||||
>
|
||||
<VLogo className="w-4" />
|
||||
</NavLink>
|
||||
{/* Left section */}
|
||||
<div className="flex items-center lg:hidden">{children}</div>
|
||||
{/* Used to show header in nav on mobile */}
|
||||
<div className="hidden lg:block">
|
||||
<NavbarMenu onClick={() => setMenu(null)} />
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
|
||||
VEGA_VERSION=v0.74.0-preview.8
|
||||
VEGA_VERSION=v0.74.0-preview.10
|
||||
LOCAL_SERVER=false
|
||||
|
||||
@@ -111,6 +111,7 @@ def init_vega(request=None):
|
||||
f"Container {container.id} started",
|
||||
extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")},
|
||||
)
|
||||
vega.container = container
|
||||
yield vega
|
||||
except APIError as e:
|
||||
logger.info(f"Container creation failed.")
|
||||
@@ -177,10 +178,34 @@ def init_page(vega: VegaServiceNull, browser: Browser, request: pytest.FixtureRe
|
||||
|
||||
@pytest.fixture
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance))
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
def cleanup_container(vega_instance):
|
||||
try:
|
||||
# Attempt to stop the container if it's still running
|
||||
if vega_instance.container.status == 'running':
|
||||
print(f"Stopping container {vega_instance.container.id}")
|
||||
vega_instance.container.stop()
|
||||
else:
|
||||
print(f"Container {vega_instance.container.id} is not running.")
|
||||
except docker.errors.NotFound:
|
||||
print(f"Container {vega_instance.container.id} not found, may have been stopped and removed.")
|
||||
except Exception as e:
|
||||
print(f"Error during cleanup: {str(e)}")
|
||||
|
||||
try:
|
||||
# Attempt to remove the container
|
||||
vega_instance.container.remove()
|
||||
print(f"Container {vega_instance.container.id} removed.")
|
||||
except docker.errors.NotFound:
|
||||
print(f"Container {vega_instance.container.id} not found, may have been removed.")
|
||||
except Exception as e:
|
||||
print(f"Error during container removal: {str(e)}")
|
||||
|
||||
@pytest.fixture
|
||||
def page(vega, browser, request):
|
||||
with init_page(vega, browser, request) as page_instance:
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from datetime import datetime, timedelta
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_continuous_market
|
||||
from actions.utils import wait_for_toast_confirmation
|
||||
|
||||
@@ -17,8 +17,10 @@ expire = "expire"
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -3,7 +3,7 @@ from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from actions.vega import submit_order
|
||||
from datetime import datetime, timedelta
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_continuous_market
|
||||
|
||||
stop_order_btn = "order-type-Stop"
|
||||
@@ -259,9 +259,10 @@ def test_submit_stop_limit_order_cancel(
|
||||
|
||||
class TestStopOcoValidation:
|
||||
@pytest.fixture(scope="class")
|
||||
def vega(self, request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def continuous_market(self, vega):
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from actions.utils import change_keys
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_continuous_market
|
||||
|
||||
order_size = "order-size"
|
||||
@@ -14,8 +14,9 @@ deal_ticket_deposit_dialog_button = "deal-ticket-deposit-dialog-button"
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -4,7 +4,7 @@ from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from actions.vega import submit_order
|
||||
from wallet_config import MM_WALLET
|
||||
from conftest import init_vega, init_page, auth_setup
|
||||
from conftest import init_vega, init_page, auth_setup, cleanup_container
|
||||
from actions.utils import next_epoch, change_keys, forward_time
|
||||
from fixtures.market import market_exists, setup_continuous_market
|
||||
|
||||
@@ -82,31 +82,36 @@ def market_ids():
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_volume_discount_tier_1(request):
|
||||
with init_vega(request) as vega_volume_discount_tier_1:
|
||||
yield vega_volume_discount_tier_1
|
||||
request.addfinalizer(lambda: cleanup_container(vega_volume_discount_tier_1)) # Register the cleanup function
|
||||
yield vega_volume_discount_tier_1
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_volume_discount_tier_2(request):
|
||||
with init_vega(request) as vega_volume_discount_tier_2:
|
||||
yield vega_volume_discount_tier_2
|
||||
request.addfinalizer(lambda: cleanup_container(vega_volume_discount_tier_2)) # Register the cleanup function
|
||||
yield vega_volume_discount_tier_2
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_referral_discount_tier_1(request):
|
||||
with init_vega(request) as vega_referral_discount_tier_1:
|
||||
yield vega_referral_discount_tier_1
|
||||
request.addfinalizer(lambda: cleanup_container(vega_referral_discount_tier_1)) # Register the cleanup function
|
||||
yield vega_referral_discount_tier_1
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_referral_discount_tier_2(request):
|
||||
with init_vega(request) as vega_referral_discount_tier_2:
|
||||
yield vega_referral_discount_tier_2
|
||||
request.addfinalizer(lambda: cleanup_container(vega_referral_discount_tier_2)) # Register the cleanup function
|
||||
yield vega_referral_discount_tier_2
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_referral_and_volume_discount(request):
|
||||
with init_vega(request) as vega_referral_and_volume_discount:
|
||||
yield vega_referral_and_volume_discount
|
||||
request.addfinalizer(lambda: cleanup_container(vega_referral_and_volume_discount)) # Register the cleanup function
|
||||
yield vega_referral_and_volume_discount
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -3,7 +3,7 @@ from playwright.sync_api import expect, Page
|
||||
import json
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from fixtures.market import setup_simple_market
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from actions.vega import submit_order
|
||||
from wallet_config import MM_WALLET, TERMINATE_WALLET, wallets
|
||||
import logging
|
||||
@@ -12,9 +12,10 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def vega():
|
||||
with init_vega() as vega:
|
||||
yield vega
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
|
||||
@@ -2,8 +2,6 @@ import pytest
|
||||
from playwright.sync_api import expect, Page
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from actions.vega import submit_order
|
||||
from conftest import init_vega
|
||||
from fixtures.market import setup_continuous_market
|
||||
from wallet_config import MM_WALLET2
|
||||
|
||||
def hover_and_assert_tooltip(page: Page, element_text):
|
||||
@@ -11,39 +9,30 @@ def hover_and_assert_tooltip(page: Page, element_text):
|
||||
element.hover()
|
||||
expect(page.get_by_role("tooltip")).to_be_visible()
|
||||
|
||||
class TestIcebergOrdersValidations:
|
||||
@pytest.fixture(scope="class")
|
||||
def vega(self, request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def continuous_market(self, vega):
|
||||
return setup_continuous_market(vega)
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_iceberg_submit(continuous_market, vega: VegaServiceNull, page: Page):
|
||||
page.goto(f"/#/markets/{continuous_market}")
|
||||
page.get_by_test_id("iceberg").click()
|
||||
page.get_by_test_id("order-peak-size").type("2")
|
||||
page.get_by_test_id("order-minimum-size").type("1")
|
||||
page.get_by_test_id("order-size").type("3")
|
||||
page.get_by_test_id("order-price").type("107")
|
||||
page.get_by_test_id("place-order").click()
|
||||
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_iceberg_submit(self, continuous_market, vega: VegaServiceNull, page: Page):
|
||||
page.goto(f"/#/markets/{continuous_market}")
|
||||
page.get_by_test_id("iceberg").click()
|
||||
page.get_by_test_id("order-peak-size").type("2")
|
||||
page.get_by_test_id("order-minimum-size").type("1")
|
||||
page.get_by_test_id("order-size").type("3")
|
||||
page.get_by_test_id("order-price").type("107")
|
||||
page.get_by_test_id("place-order").click()
|
||||
expect(page.get_by_test_id("toast-content")).to_have_text(
|
||||
"Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer"
|
||||
)
|
||||
|
||||
expect(page.get_by_test_id("toast-content")).to_have_text(
|
||||
"Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer"
|
||||
)
|
||||
|
||||
vega.wait_fn(1)
|
||||
vega.wait_for_total_catchup()
|
||||
expect(page.get_by_test_id("toast-content")).to_have_text(
|
||||
"Order filledYour transaction has been confirmedView in block explorerSubmit order - filledBTC:DAI_2023+3 @ 107.00 tDAI"
|
||||
)
|
||||
page.get_by_test_id("All").click()
|
||||
expect(
|
||||
(page.get_by_role("row").locator('[col-id="type"]')).nth(1)
|
||||
).to_have_text("Limit (Iceberg)")
|
||||
vega.wait_fn(1)
|
||||
vega.wait_for_total_catchup()
|
||||
expect(page.get_by_test_id("toast-content")).to_have_text(
|
||||
"Order filledYour transaction has been confirmedView in block explorerSubmit order - filledBTC:DAI_2023+3 @ 107.00 tDAI"
|
||||
)
|
||||
page.get_by_test_id("All").click()
|
||||
expect(
|
||||
(page.get_by_role("row").locator('[col-id="type"]')).nth(1)
|
||||
).to_have_text("Limit (Iceberg)")
|
||||
|
||||
@pytest.mark.usefixtures("auth", "risk_accepted")
|
||||
def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page):
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_continuous_market
|
||||
from actions.utils import next_epoch, truncate_middle, change_keys
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -4,14 +4,15 @@ import vega_sim.api.governance as governance
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from playwright.sync_api import Page, expect
|
||||
from fixtures.market import setup_continuous_market
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from actions.utils import next_epoch
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def vega():
|
||||
with init_vega() as vega:
|
||||
yield vega
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
|
||||
@@ -3,15 +3,16 @@ import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from fixtures.market import setup_continuous_market
|
||||
from conftest import init_page, init_vega, risk_accepted_setup
|
||||
from conftest import init_page, init_vega, risk_accepted_setup, cleanup_container
|
||||
|
||||
market_title_test_id = "accordion-title"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega():
|
||||
with init_vega() as vega:
|
||||
yield vega
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -3,7 +3,7 @@ import vega_sim.api.governance as governance
|
||||
import re
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_simple_market
|
||||
from wallet_config import MM_WALLET
|
||||
|
||||
@@ -13,8 +13,10 @@ col_market_id = '[col-id="market"] [data-testid="stack-cell-primary"]'
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -3,7 +3,7 @@ from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from actions.vega import submit_order
|
||||
from fixtures.market import setup_simple_market
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from actions.utils import wait_for_toast_confirmation, change_keys
|
||||
from wallet_config import MM_WALLET, MM_WALLET2
|
||||
|
||||
@@ -15,8 +15,10 @@ COL_ID_FEE = ".ag-center-cols-container [col-id='fee'] .ag-cell-value"
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect, Locator
|
||||
|
||||
from conftest import init_page, init_vega
|
||||
from conftest import init_page, init_vega, cleanup_container
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega():
|
||||
with init_vega() as vega:
|
||||
yield vega
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
# we can reuse single page instance in all tests
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.service import PeggedOrder
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from conftest import auth_setup, init_page, init_vega, risk_accepted_setup
|
||||
from conftest import auth_setup, init_page, init_vega, risk_accepted_setup, cleanup_container
|
||||
from fixtures.market import setup_continuous_market, setup_simple_market
|
||||
from actions.utils import wait_for_toast_confirmation
|
||||
|
||||
@@ -11,8 +11,9 @@ order_tab = "tab-orders"
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
|
||||
@@ -2,15 +2,16 @@ import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from typing import List
|
||||
from actions.vega import submit_order, submit_liquidity, submit_multiple_orders
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_simple_market
|
||||
from wallet_config import MM_WALLET, MM_WALLET2
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega():
|
||||
with init_vega() as vega:
|
||||
yield vega
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_continuous_market
|
||||
|
||||
TOOLTIP_LABEL = "margin-health-tooltip-label"
|
||||
@@ -11,8 +11,10 @@ COL_ID_USED = ".ag-center-cols-container [col-id='used'] .ag-cell-value"
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from playwright.sync_api import Page
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from fixtures.market import setup_continuous_market, setup_simple_market
|
||||
from actions.utils import change_keys, create_and_faucet_wallet, forward_time, selector_contains_text
|
||||
from actions.vega import submit_order, submit_liquidity
|
||||
@@ -14,8 +14,10 @@ BUY_ORDERS = [[1, 106], [1, 107], [1, 108]]
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
|
||||
import vega_sim.proto.vega as vega_protos
|
||||
from playwright.sync_api import Page, expect
|
||||
from conftest import init_vega, init_page, auth_setup
|
||||
from conftest import init_vega, init_page, auth_setup, cleanup_container
|
||||
from fixtures.market import setup_continuous_market, market_exists
|
||||
from actions.utils import next_epoch, change_keys
|
||||
from wallet_config import MM_WALLET, PARTY_A, PARTY_B, PARTY_C, PARTY_D
|
||||
@@ -46,36 +46,42 @@ def market_ids():
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_activity_tier_0(request):
|
||||
with init_vega(request) as vega_activity_tier_0:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_activity_tier_0)) # Register the cleanup function
|
||||
yield vega_activity_tier_0
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_hoarder_tier_0(request):
|
||||
with init_vega(request) as vega_hoarder_tier_0:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_hoarder_tier_0)) # Register the cleanup function
|
||||
yield vega_hoarder_tier_0
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_combo_tier_0(request):
|
||||
with init_vega(request) as vega_combo_tier_0:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_combo_tier_0)) # Register the cleanup function
|
||||
yield vega_combo_tier_0
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_activity_tier_1(request):
|
||||
with init_vega(request) as vega_activity_tier_1:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_activity_tier_1)) # Register the cleanup function
|
||||
yield vega_activity_tier_1
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_hoarder_tier_1(request):
|
||||
with init_vega(request) as vega_hoarder_tier_1:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_hoarder_tier_1)) # Register the cleanup function
|
||||
yield vega_hoarder_tier_1
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega_combo_tier_1(request):
|
||||
with init_vega(request) as vega_combo_tier_1:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_combo_tier_1)) # Register the cleanup function
|
||||
yield vega_combo_tier_1
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import pytest
|
||||
import vega_sim.proto.vega as vega_protos
|
||||
from playwright.sync_api import Page, expect
|
||||
from conftest import init_vega, init_page, auth_setup, risk_accepted_setup, cleanup_container
|
||||
from fixtures.market import setup_continuous_market
|
||||
from actions.utils import next_epoch, change_keys, create_and_faucet_wallet
|
||||
from wallet_config import MM_WALLET, WalletConfig
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
|
||||
# region Constants
|
||||
ACTIVITY = "activity"
|
||||
HOARDER = "hoarder"
|
||||
COMBO = "combo"
|
||||
|
||||
REWARDS_URL = "/#/rewards"
|
||||
|
||||
# test IDs
|
||||
COMBINED_MULTIPLIERS = "combined-multipliers"
|
||||
TOTAL_REWARDS = "total-rewards"
|
||||
PRICE_TAKING_COL_ID = '[col-id="priceTaking"]'
|
||||
TOTAL_COL_ID = '[col-id="total"]'
|
||||
ROW = "row"
|
||||
STREAK_REWARD_MULTIPLIER_VALUE = "streak-reward-multiplier-value"
|
||||
HOARDER_REWARD_MULTIPLIER_VALUE = "hoarder-reward-multiplier-value"
|
||||
HOARDER_BONUS_TOTAL_HOARDED = "hoarder-bonus-total-hoarded"
|
||||
EARNED_BY_ME_BUTTON = "earned-by-me-button"
|
||||
TRANSFER_AMOUNT = "transfer-amount"
|
||||
EPOCH_STREAK = "epoch-streak"
|
||||
|
||||
# endregion
|
||||
|
||||
# Keys
|
||||
PARTY_A = "PARTY_A"
|
||||
PARTY_B = "PARTY_B"
|
||||
PARTY_C = "PARTY_C"
|
||||
PARTY_D = "PARTY_D"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def page(vega, browser, request):
|
||||
with init_page(vega, browser, request) as page:
|
||||
risk_accepted_setup(page)
|
||||
auth_setup(vega, page)
|
||||
page.goto(REWARDS_URL)
|
||||
change_keys(page, vega, PARTY_B)
|
||||
yield page
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def setup_market_with_reward_program(vega: VegaServiceNull):
|
||||
tDAI_market = setup_continuous_market(vega)
|
||||
PARTY_A, PARTY_B, PARTY_C, PARTY_D = keys(vega)
|
||||
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
|
||||
vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000)
|
||||
vega.mint(key_name=PARTY_C.name, asset=tDAI_asset_id, amount=100000)
|
||||
vega.mint(key_name=PARTY_A.name, asset=tDAI_asset_id, amount=100000)
|
||||
vega.mint(key_name=PARTY_D.name, asset=tDAI_asset_id, amount=100000)
|
||||
next_epoch(vega=vega)
|
||||
|
||||
vega.update_network_parameter(
|
||||
proposal_key=MM_WALLET.name,
|
||||
parameter="rewards.activityStreak.benefitTiers",
|
||||
new_value=ACTIVITY_STREAKS,
|
||||
)
|
||||
print("update_network_parameter activity done")
|
||||
next_epoch(vega=vega)
|
||||
|
||||
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
|
||||
vega.update_network_parameter(
|
||||
MM_WALLET.name, parameter="reward.asset", new_value=tDAI_asset_id
|
||||
)
|
||||
|
||||
next_epoch(vega=vega)
|
||||
vega.recurring_transfer(
|
||||
from_key_name=PARTY_A.name,
|
||||
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
|
||||
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
|
||||
asset=tDAI_asset_id,
|
||||
reference="reward",
|
||||
asset_for_metric=tDAI_asset_id,
|
||||
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
|
||||
amount=100,
|
||||
factor=1.0,
|
||||
)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_B.name,
|
||||
market_id=tDAI_market,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
vega.submit_order(
|
||||
trading_key=PARTY_A.name,
|
||||
market_id=tDAI_market,
|
||||
order_type="TYPE_MARKET",
|
||||
time_in_force="TIME_IN_FORCE_IOC",
|
||||
side="SIDE_BUY",
|
||||
volume=1,
|
||||
)
|
||||
|
||||
vega.wait_for_total_catchup()
|
||||
|
||||
next_epoch(vega=vega)
|
||||
return tDAI_market, tDAI_asset_id
|
||||
|
||||
|
||||
ACTIVITY_STREAKS = """
|
||||
{
|
||||
"tiers": [
|
||||
{
|
||||
"minimum_activity_streak": 2,
|
||||
"reward_multiplier": "2.0",
|
||||
"vesting_multiplier": "1.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def keys(vega):
|
||||
PARTY_A = WalletConfig("PARTY_A", "PARTY_A")
|
||||
create_and_faucet_wallet(vega=vega, wallet=PARTY_A)
|
||||
PARTY_B = WalletConfig("PARTY_B", "PARTY_B")
|
||||
create_and_faucet_wallet(vega=vega, wallet=PARTY_B)
|
||||
PARTY_C = WalletConfig("PARTY_C", "PARTY_C")
|
||||
create_and_faucet_wallet(vega=vega, wallet=PARTY_C)
|
||||
PARTY_D = WalletConfig("PARTY_D", "PARTY_D")
|
||||
create_and_faucet_wallet(vega=vega, wallet=PARTY_D)
|
||||
return PARTY_A, PARTY_B, PARTY_C, PARTY_D
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="test_rewards_activity_tier_0")
|
||||
def test_network_reward_pot(
|
||||
page: Page,
|
||||
):
|
||||
expect(page.get_by_test_id(TOTAL_REWARDS)).to_have_text("50.00 tDAI")
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="test_rewards_activity_tier_0")
|
||||
def test_reward_multiplier(
|
||||
page: Page,
|
||||
):
|
||||
expect(page.get_by_test_id(COMBINED_MULTIPLIERS)).to_have_text("1x")
|
||||
expect(page.get_by_test_id(STREAK_REWARD_MULTIPLIER_VALUE)).to_have_text("1x")
|
||||
expect(page.get_by_test_id(HOARDER_REWARD_MULTIPLIER_VALUE)).to_have_text("1x")
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="test_rewards_activity_tier_0")
|
||||
def test_activity_streak(
|
||||
page: Page,
|
||||
):
|
||||
expect(page.get_by_test_id(EPOCH_STREAK)).to_have_text(
|
||||
"Active trader: 1 epochs so far "
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.xdist_group(name="test_rewards_activity_tier_0")
|
||||
def test_reward_history(
|
||||
page: Page,
|
||||
):
|
||||
page.locator('[name="fromEpoch"]').fill("1")
|
||||
expect((page.get_by_role(ROW).locator(PRICE_TAKING_COL_ID)).nth(1)).to_have_text(
|
||||
"100.00100.00%"
|
||||
)
|
||||
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text("100.00")
|
||||
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("50.00")
|
||||
@@ -1,12 +1,13 @@
|
||||
import pytest
|
||||
from playwright.sync_api import expect, Page
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega():
|
||||
with init_vega() as vega:
|
||||
yield vega
|
||||
def vega(request):
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("risk_accepted")
|
||||
|
||||
@@ -2,17 +2,17 @@ import pytest
|
||||
from playwright.sync_api import expect, Page
|
||||
import vega_sim.proto.vega as vega_protos
|
||||
from vega_sim.null_service import VegaServiceNull
|
||||
from conftest import init_vega
|
||||
from conftest import init_vega, cleanup_container
|
||||
from actions.utils import next_epoch, change_keys
|
||||
from fixtures.market import setup_continuous_market
|
||||
from conftest import auth_setup, init_page, init_vega, risk_accepted_setup
|
||||
from wallet_config import PARTY_A, PARTY_B, PARTY_C, PARTY_D, MM_WALLET
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def vega(request):
|
||||
with init_vega(request) as vega:
|
||||
yield vega
|
||||
with init_vega(request) as vega_instance:
|
||||
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
|
||||
yield vega_instance
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@@ -213,12 +213,12 @@ def create_team(vega: VegaServiceNull):
|
||||
|
||||
def test_team_page_games_table(team_page: Page):
|
||||
team_page.get_by_test_id("games-toggle").click()
|
||||
expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (1)")
|
||||
expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (10)")
|
||||
expect(team_page.get_by_test_id("rank-0")).to_have_text("2")
|
||||
expect(team_page.get_by_test_id("epoch-0")).to_have_text("19")
|
||||
expect(team_page.get_by_test_id("type-0")
|
||||
).to_have_text("Price maker fees paid")
|
||||
expect(team_page.get_by_test_id("amount-0")).to_have_text("74")
|
||||
expect(team_page.get_by_test_id("amount-0")).to_have_text("74") # 7,438,330 on preview.11
|
||||
expect(team_page.get_by_test_id("participatingTeams-0")).to_have_text("2")
|
||||
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text("4")
|
||||
|
||||
@@ -237,12 +237,12 @@ def test_team_page_headline(team_page: Page, setup_teams_and_games):
|
||||
expect(team_page.get_by_test_id("team-name")).to_have_text(team_name)
|
||||
expect(team_page.get_by_test_id("members-count-stat")).to_have_text("4")
|
||||
|
||||
expect(team_page.get_by_test_id("total-games-stat")).to_have_text("2")
|
||||
expect(team_page.get_by_test_id("total-games-stat")).to_have_text("1")
|
||||
|
||||
# TODO this still seems wrong as its always 0
|
||||
expect(team_page.get_by_test_id("total-volume-stat")).to_have_text("0")
|
||||
|
||||
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("214")
|
||||
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("78")
|
||||
|
||||
|
||||
def test_switch_teams(team_page: Page, vega: VegaServiceNull):
|
||||
@@ -271,7 +271,7 @@ def test_leaderboard(competitions_page: Page, setup_teams_and_games):
|
||||
|
||||
# FIXME: the numbers are different we need to clarify this with the backend
|
||||
# expect(competitions_page.get_by_test_id("earned-1")).to_have_text("160")
|
||||
expect(competitions_page.get_by_test_id("games-1")).to_have_text("2")
|
||||
expect(competitions_page.get_by_test_id("games-1")).to_have_text("1")
|
||||
|
||||
# TODO still odd that this is 0
|
||||
expect(competitions_page.get_by_test_id("volume-0")).to_have_text("-")
|
||||
@@ -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")
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
fragment TeamEntity on TeamGameEntity {
|
||||
rank
|
||||
volume
|
||||
rewardMetric
|
||||
rewardEarned
|
||||
totalRewardsEarned
|
||||
team {
|
||||
teamId
|
||||
membersParticipating {
|
||||
individual
|
||||
rank
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment GameFields on Game {
|
||||
id
|
||||
epoch
|
||||
numberOfParticipants
|
||||
entities {
|
||||
... on TeamGameEntity {
|
||||
...TeamEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query Games($epochFrom: Int) {
|
||||
games(epochFrom: $epochFrom, entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
edges {
|
||||
node {
|
||||
...GameFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,28 +29,6 @@ fragment TeamRefereeFields on TeamReferee {
|
||||
joinedAtEpoch
|
||||
}
|
||||
|
||||
fragment TeamEntity on TeamGameEntity {
|
||||
rank
|
||||
volume
|
||||
rewardMetric
|
||||
rewardEarned
|
||||
totalRewardsEarned
|
||||
team {
|
||||
teamId
|
||||
}
|
||||
}
|
||||
|
||||
fragment TeamGameFields on Game {
|
||||
id
|
||||
epoch
|
||||
numberOfParticipants
|
||||
entities {
|
||||
... on TeamGameEntity {
|
||||
...TeamEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment TeamMemberStatsFields on TeamMemberStatistics {
|
||||
partyId
|
||||
totalQuantumVolume
|
||||
@@ -87,13 +65,6 @@ query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
games(entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
edges {
|
||||
node {
|
||||
...TeamGameFields
|
||||
}
|
||||
}
|
||||
}
|
||||
teamMembersStatistics(
|
||||
teamId: $teamId
|
||||
aggregationEpochs: $aggregationEpochs
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type TeamEntityFragment = { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string, membersParticipating: Array<{ __typename?: 'IndividualGameEntity', individual: string, rank: number }> } };
|
||||
|
||||
export type GameFieldsFragment = { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string, membersParticipating: Array<{ __typename?: 'IndividualGameEntity', individual: string, rank: number }> } }> };
|
||||
|
||||
export type GamesQueryVariables = Types.Exact<{
|
||||
epochFrom?: Types.InputMaybe<Types.Scalars['Int']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GamesQuery = { __typename?: 'Query', games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string, membersParticipating: Array<{ __typename?: 'IndividualGameEntity', individual: string, rank: number }> } }> } } | null> | null } };
|
||||
|
||||
export const TeamEntityFragmentDoc = gql`
|
||||
fragment TeamEntity on TeamGameEntity {
|
||||
rank
|
||||
volume
|
||||
rewardMetric
|
||||
rewardEarned
|
||||
totalRewardsEarned
|
||||
team {
|
||||
teamId
|
||||
membersParticipating {
|
||||
individual
|
||||
rank
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const GameFieldsFragmentDoc = gql`
|
||||
fragment GameFields on Game {
|
||||
id
|
||||
epoch
|
||||
numberOfParticipants
|
||||
entities {
|
||||
... on TeamGameEntity {
|
||||
...TeamEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
${TeamEntityFragmentDoc}`;
|
||||
export const GamesDocument = gql`
|
||||
query Games($epochFrom: Int) {
|
||||
games(epochFrom: $epochFrom, entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
edges {
|
||||
node {
|
||||
...GameFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${GameFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGamesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGamesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGamesQuery` 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 } = useGamesQuery({
|
||||
* variables: {
|
||||
* epochFrom: // value for 'epochFrom'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGamesQuery(baseOptions?: Apollo.QueryHookOptions<GamesQuery, GamesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GamesQuery, GamesQueryVariables>(GamesDocument, options);
|
||||
}
|
||||
export function useGamesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GamesQuery, GamesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GamesQuery, GamesQueryVariables>(GamesDocument, options);
|
||||
}
|
||||
export type GamesQueryHookResult = ReturnType<typeof useGamesQuery>;
|
||||
export type GamesLazyQueryHookResult = ReturnType<typeof useGamesLazyQuery>;
|
||||
export type GamesQueryResult = Apollo.QueryResult<GamesQuery, GamesQueryVariables>;
|
||||
+1
-37
@@ -9,10 +9,6 @@ export type TeamStatsFieldsFragment = { __typename?: 'TeamStatistics', teamId: s
|
||||
|
||||
export type TeamRefereeFieldsFragment = { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number };
|
||||
|
||||
export type TeamEntityFragment = { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } };
|
||||
|
||||
export type TeamGameFieldsFragment = { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> };
|
||||
|
||||
export type TeamMemberStatsFieldsFragment = { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number };
|
||||
|
||||
export type TeamQueryVariables = Types.Exact<{
|
||||
@@ -22,7 +18,7 @@ export type TeamQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, games: { __typename?: 'GamesConnection', edges?: Array<{ __typename?: 'GameEdge', node: { __typename?: 'Game', id: string, epoch: number, numberOfParticipants: number, entities: Array<{ __typename?: 'IndividualGameEntity' } | { __typename?: 'TeamGameEntity', rank: number, volume: string, rewardMetric: Types.DispatchMetric, rewardEarned: string, totalRewardsEarned: string, team: { __typename?: 'TeamParticipation', teamId: string } }> } } | null> | null }, teamMembersStatistics?: { __typename?: 'TeamMembersStatisticsConnection', edges: Array<{ __typename?: 'TeamMemberStatisticsEdge', node: { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number } }> } | null };
|
||||
export type TeamQuery = { __typename?: 'Query', teams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, partyTeams?: { __typename?: 'TeamConnection', edges: Array<{ __typename?: 'TeamEdge', node: { __typename?: 'Team', teamId: string, referrer: string, name: string, teamUrl: string, avatarUrl: string, createdAt: any, createdAtEpoch: number, closed: boolean, allowList: Array<string> } }> } | null, teamsStatistics?: { __typename?: 'TeamsStatisticsConnection', edges: Array<{ __typename?: 'TeamStatisticsEdge', node: { __typename?: 'TeamStatistics', teamId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number, gamesPlayed: Array<string>, quantumRewards: Array<{ __typename?: 'QuantumRewardsPerEpoch', epoch: number, totalQuantumRewards: string }> } }> } | null, teamReferees?: { __typename?: 'TeamRefereeConnection', edges: Array<{ __typename?: 'TeamRefereeEdge', node: { __typename?: 'TeamReferee', teamId: string, referee: string, joinedAt: any, joinedAtEpoch: number } }> } | null, teamMembersStatistics?: { __typename?: 'TeamMembersStatisticsConnection', edges: Array<{ __typename?: 'TeamMemberStatisticsEdge', node: { __typename?: 'TeamMemberStatistics', partyId: string, totalQuantumVolume: string, totalQuantumRewards: string, totalGamesPlayed: number } }> } | null };
|
||||
|
||||
export const TeamFieldsFragmentDoc = gql`
|
||||
fragment TeamFields on Team {
|
||||
@@ -58,30 +54,6 @@ export const TeamRefereeFieldsFragmentDoc = gql`
|
||||
joinedAtEpoch
|
||||
}
|
||||
`;
|
||||
export const TeamEntityFragmentDoc = gql`
|
||||
fragment TeamEntity on TeamGameEntity {
|
||||
rank
|
||||
volume
|
||||
rewardMetric
|
||||
rewardEarned
|
||||
totalRewardsEarned
|
||||
team {
|
||||
teamId
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TeamGameFieldsFragmentDoc = gql`
|
||||
fragment TeamGameFields on Game {
|
||||
id
|
||||
epoch
|
||||
numberOfParticipants
|
||||
entities {
|
||||
... on TeamGameEntity {
|
||||
...TeamEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
${TeamEntityFragmentDoc}`;
|
||||
export const TeamMemberStatsFieldsFragmentDoc = gql`
|
||||
fragment TeamMemberStatsFields on TeamMemberStatistics {
|
||||
partyId
|
||||
@@ -120,13 +92,6 @@ export const TeamDocument = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
games(entityScope: ENTITY_SCOPE_TEAMS) {
|
||||
edges {
|
||||
node {
|
||||
...TeamGameFields
|
||||
}
|
||||
}
|
||||
}
|
||||
teamMembersStatistics(teamId: $teamId, aggregationEpochs: $aggregationEpochs) {
|
||||
edges {
|
||||
node {
|
||||
@@ -138,7 +103,6 @@ export const TeamDocument = gql`
|
||||
${TeamFieldsFragmentDoc}
|
||||
${TeamStatsFieldsFragmentDoc}
|
||||
${TeamRefereeFieldsFragmentDoc}
|
||||
${TeamGameFieldsFragmentDoc}
|
||||
${TeamMemberStatsFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import compact from 'lodash/compact';
|
||||
import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards';
|
||||
import { isActiveReward } from '../../components/rewards-container/active-rewards';
|
||||
import {
|
||||
EntityScope,
|
||||
IndividualScope,
|
||||
type TransferNode,
|
||||
} from '@vegaprotocol/types';
|
||||
|
||||
const isScopedToTeams = (node: TransferNode) =>
|
||||
node.transfer.kind.__typename === 'RecurringTransfer' &&
|
||||
// scoped to teams
|
||||
(node.transfer.kind.dispatchStrategy?.entityScope ===
|
||||
EntityScope.ENTITY_SCOPE_TEAMS ||
|
||||
// or to individuals
|
||||
(node.transfer.kind.dispatchStrategy?.entityScope ===
|
||||
EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
|
||||
// but they have to be in a team
|
||||
node.transfer.kind.dispatchStrategy.individualScope ===
|
||||
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM));
|
||||
|
||||
export const useGameCards = ({
|
||||
currentEpoch,
|
||||
onlyActive,
|
||||
}: {
|
||||
currentEpoch: number;
|
||||
onlyActive: boolean;
|
||||
}) => {
|
||||
const { data, loading, error } = useActiveRewardsQuery({
|
||||
variables: {
|
||||
isReward: true,
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
const games = compact(data?.transfersConnection?.edges?.map((n) => n?.node))
|
||||
.map((n) => n as TransferNode)
|
||||
.filter((node) => {
|
||||
const active = onlyActive ? isActiveReward(node, currentEpoch) : true;
|
||||
return active && isScopedToTeams(node);
|
||||
});
|
||||
|
||||
return {
|
||||
data: games,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
@@ -1,48 +1,80 @@
|
||||
import compact from 'lodash/compact';
|
||||
import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards';
|
||||
import { isActiveReward } from '../../components/rewards-container/active-rewards';
|
||||
import {
|
||||
EntityScope,
|
||||
IndividualScope,
|
||||
type TransferNode,
|
||||
} from '@vegaprotocol/types';
|
||||
useGamesQuery,
|
||||
type GameFieldsFragment,
|
||||
type TeamEntityFragment,
|
||||
} from './__generated__/Games';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useCurrentEpochInfoQuery } from './__generated__/Epoch';
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
|
||||
const isScopedToTeams = (node: TransferNode) =>
|
||||
node.transfer.kind.__typename === 'RecurringTransfer' &&
|
||||
// scoped to teams
|
||||
(node.transfer.kind.dispatchStrategy?.entityScope ===
|
||||
EntityScope.ENTITY_SCOPE_TEAMS ||
|
||||
// or to individuals
|
||||
(node.transfer.kind.dispatchStrategy?.entityScope ===
|
||||
EntityScope.ENTITY_SCOPE_INDIVIDUALS &&
|
||||
// but they have to be in a team
|
||||
node.transfer.kind.dispatchStrategy.individualScope ===
|
||||
IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM));
|
||||
const TAKE_EPOCHS = 30; // TODO: should this be DEFAULT_AGGREGATION_EPOCHS?
|
||||
|
||||
export const useGames = ({
|
||||
currentEpoch,
|
||||
onlyActive,
|
||||
}: {
|
||||
currentEpoch: number;
|
||||
onlyActive: boolean;
|
||||
}) => {
|
||||
const { data, loading, error } = useActiveRewardsQuery({
|
||||
variables: {
|
||||
isReward: true,
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
const findTeam = (entities: GameFieldsFragment['entities'], teamId: string) => {
|
||||
const team = entities.find(
|
||||
(ent) => ent.__typename === 'TeamGameEntity' && ent.team.teamId === teamId
|
||||
);
|
||||
if (team?.__typename === 'TeamGameEntity') return team; // drops __typename === 'IndividualGameEntity' from team object
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export type Game = GameFieldsFragment & {
|
||||
/** The team entity data accessible only if scoped to particular team. */
|
||||
team?: TeamEntityFragment;
|
||||
};
|
||||
export type TeamGame = Game & { team: NonNullable<Game['team']> };
|
||||
|
||||
const isTeamGame = (game: Game): game is TeamGame => game.team !== undefined;
|
||||
export const areTeamGames = (games?: Game[]): games is TeamGame[] =>
|
||||
Boolean(games && games.filter((g) => isTeamGame(g)).length > 0);
|
||||
|
||||
type GamesData = {
|
||||
data?: Game[];
|
||||
loading: boolean;
|
||||
error?: ApolloError;
|
||||
};
|
||||
|
||||
export const useGames = (teamId?: string, epochFrom?: number): GamesData => {
|
||||
const {
|
||||
data: epochData,
|
||||
loading: epochLoading,
|
||||
error: epochError,
|
||||
} = useCurrentEpochInfoQuery({
|
||||
skip: Boolean(epochFrom),
|
||||
});
|
||||
|
||||
const games = compact(data?.transfersConnection?.edges?.map((n) => n?.node))
|
||||
.map((n) => n as TransferNode)
|
||||
.filter((node) => {
|
||||
const active = onlyActive ? isActiveReward(node, currentEpoch) : true;
|
||||
return active && isScopedToTeams(node);
|
||||
let from = epochFrom;
|
||||
if (!from && epochData) {
|
||||
from = Number(epochData.epoch.id) - TAKE_EPOCHS;
|
||||
if (from < 1) from = 1; // make sure it's not negative
|
||||
}
|
||||
|
||||
const { data, loading, error } = useGamesQuery({
|
||||
variables: {
|
||||
epochFrom: from,
|
||||
},
|
||||
skip: !from,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
context: { isEnlargedTimeout: true },
|
||||
});
|
||||
|
||||
const allGames = removePaginationWrapper(data?.games.edges);
|
||||
const allOrScoped = allGames
|
||||
.map((g) => ({
|
||||
...g,
|
||||
team: teamId ? findTeam(g.entities, teamId) : undefined,
|
||||
}))
|
||||
.filter((g) => {
|
||||
// passthrough if not scoped to particular team
|
||||
if (!teamId) return true;
|
||||
return isTeamGame(g);
|
||||
});
|
||||
|
||||
const games = orderBy(allOrScoped, 'epoch', 'desc');
|
||||
|
||||
return {
|
||||
data: games,
|
||||
loading,
|
||||
error,
|
||||
loading: loading || epochLoading,
|
||||
error: error || epochError,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import first from 'lodash/first';
|
||||
import { useTeamsQuery } from './__generated__/Teams';
|
||||
import { useTeam } from './use-team';
|
||||
import { useTeams } from './use-teams';
|
||||
import { areTeamGames, useGames } from './use-games';
|
||||
|
||||
export const useMyTeam = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
@@ -19,7 +20,8 @@ export const useMyTeam = () => {
|
||||
|
||||
const team = first(compact(maybeMyTeam?.teams?.edges.map((n) => n.node)));
|
||||
const rank = teams.findIndex((t) => t.teamId === team?.teamId) + 1;
|
||||
const { games, stats } = useTeam(team?.teamId);
|
||||
const { stats } = useTeam(team?.teamId);
|
||||
const { data: games } = useGames(team?.teamId);
|
||||
|
||||
return { team, stats, games, rank };
|
||||
return { team, stats, games: areTeamGames(games) ? games : undefined, rank };
|
||||
};
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import compact from 'lodash/compact';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import {
|
||||
useTeamQuery,
|
||||
type TeamFieldsFragment,
|
||||
type TeamStatsFieldsFragment,
|
||||
type TeamRefereeFieldsFragment,
|
||||
type TeamEntityFragment,
|
||||
type TeamMemberStatsFieldsFragment,
|
||||
} from './__generated__/Team';
|
||||
import { DEFAULT_AGGREGATION_EPOCHS } from './use-teams';
|
||||
@@ -18,8 +15,6 @@ export type Member = TeamRefereeFieldsFragment & {
|
||||
totalQuantumVolume: string;
|
||||
totalQuantumRewards: string;
|
||||
};
|
||||
export type TeamEntity = TeamEntityFragment;
|
||||
export type TeamGame = ReturnType<typeof useTeam>['games'][number];
|
||||
export type MemberStats = TeamMemberStatsFieldsFragment;
|
||||
|
||||
export const useTeam = (teamId?: string, partyId?: string) => {
|
||||
@@ -80,33 +75,11 @@ export const useTeam = (teamId?: string, partyId?: string) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Find games where the current team participated in
|
||||
const gamesWithTeam = compact(data?.games.edges).map((edge) => {
|
||||
const team = edge.node.entities.find((e) => {
|
||||
if (e.__typename !== 'TeamGameEntity') return false;
|
||||
if (e.team.teamId !== teamId) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!team) return null;
|
||||
|
||||
return {
|
||||
id: edge.node.id,
|
||||
epoch: edge.node.epoch,
|
||||
numberOfParticipants: edge.node.numberOfParticipants,
|
||||
entities: edge.node.entities,
|
||||
team: team as TeamEntity, // TS can't infer that all the game entities are teams
|
||||
};
|
||||
});
|
||||
|
||||
const games = orderBy(compact(gamesWithTeam), 'epoch', 'desc');
|
||||
|
||||
return {
|
||||
...queryResult,
|
||||
stats: teamStatsEdge?.node,
|
||||
team,
|
||||
members,
|
||||
games,
|
||||
partyTeam,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -14,7 +14,7 @@ import './styles.css';
|
||||
import { usePageTitleStore } from '../stores';
|
||||
import DialogsContainer from './dialogs-container';
|
||||
import ToastsManager from './toasts-manager';
|
||||
import { HashRouter, useLocation, Route, Routes } from 'react-router-dom';
|
||||
import { HashRouter, useLocation } from 'react-router-dom';
|
||||
import { Bootstrapper } from '../components/bootstrapper';
|
||||
import { AnnouncementBanner } from '../components/banner';
|
||||
import { Navbar } from '../components/navbar';
|
||||
@@ -25,9 +25,7 @@ import {
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { NavHeader } from '../components/navbar/nav-header';
|
||||
import { Telemetry } from '../components/telemetry';
|
||||
import { Routes as AppRoutes } from '../lib/links';
|
||||
import { SSRLoader } from './ssr-loader';
|
||||
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
|
||||
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
|
||||
@@ -73,16 +71,7 @@ function AppBody({ Component }: AppProps) {
|
||||
<Title />
|
||||
<div className={gridClasses}>
|
||||
<AnnouncementBanner />
|
||||
<Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'}>
|
||||
<Routes>
|
||||
<Route
|
||||
path={AppRoutes.MARKETS}
|
||||
// render nothing for markets/all, otherwise markets/:marketId will match with markets/all
|
||||
element={null}
|
||||
/>
|
||||
<Route path={AppRoutes.MARKET} element={<NavHeader />} />
|
||||
</Routes>
|
||||
</Navbar>
|
||||
<Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'} />
|
||||
<div data-testid="banners">
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
|
||||
@@ -24,11 +24,14 @@ export default function Document() {
|
||||
|
||||
{/* scripts */}
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
|
||||
{/* manifest */}
|
||||
<link rel="manifest" href="/apps/trading/public/manifest.json" />
|
||||
</Head>
|
||||
<Html>
|
||||
<body
|
||||
// Nextjs will set body to display none until js runs. Because the entire app is client rendered
|
||||
// and delivered via ipfs we override this to show a server side render loading animation until the
|
||||
// Next.js will set body to display none until js runs. Because the entire app is client rendered
|
||||
// and delivered via IPFS we override this to show a server side render loading animation until the
|
||||
// js is downloaded and react takes over rendering
|
||||
style={{ display: 'block' }}
|
||||
className="bg-white dark:bg-vega-cdark-900 text-default font-alpha"
|
||||
|
||||
@@ -23,8 +23,11 @@ import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-bo
|
||||
import { compact } from 'lodash';
|
||||
import { useFeatureFlags } from '@vegaprotocol/environment';
|
||||
import { LiquidityHeader } from '../components/liquidity-header';
|
||||
import { MarketHeader } from '../components/market-header';
|
||||
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
|
||||
import { MarketHeader, MobileMarketHeader } from '../components/market-header';
|
||||
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,8 +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'));
|
||||
@@ -50,6 +55,20 @@ const NotFound = () => {
|
||||
|
||||
export const useRouterConfig = (): RouteObject[] => {
|
||||
const featureFlags = useFeatureFlags((state) => state.flags);
|
||||
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,
|
||||
@@ -66,7 +85,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
featureFlags.REFERRALS
|
||||
? {
|
||||
path: AppRoutes.REFERRALS,
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
element: (
|
||||
@@ -99,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
|
||||
{
|
||||
@@ -130,7 +149,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
: undefined,
|
||||
{
|
||||
path: 'fees/*',
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -140,7 +159,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
},
|
||||
{
|
||||
path: 'rewards/*',
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -151,10 +170,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
{
|
||||
path: 'markets/*',
|
||||
element: (
|
||||
<LayoutWithSidebar
|
||||
header={<MarketHeader />}
|
||||
sidebar={<MarketsSidebar />}
|
||||
/>
|
||||
<LayoutWithSidebar header={marketHeader} sidebar={marketsSidebar} />
|
||||
),
|
||||
children: [
|
||||
{
|
||||
@@ -175,7 +191,7 @@ export const useRouterConfig = (): RouteObject[] => {
|
||||
},
|
||||
{
|
||||
path: 'portfolio/*',
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
element: <LayoutWithSidebar sidebar={portfolioSidebar} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Vega Protocol - Trading",
|
||||
"short_name": "Console",
|
||||
"description": "Vega Protocol - Trading dApp",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "cover.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -48,5 +48,8 @@ export const DEFAULT_CACHE_CONFIG: InMemoryCacheConfig = {
|
||||
statistics: {
|
||||
keyFields: false,
|
||||
},
|
||||
Game: {
|
||||
keyFields: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"Earned by me": "Earned by me",
|
||||
"Eligible teams": "Eligible teams",
|
||||
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
|
||||
"[empty]": "[empty]",
|
||||
"Ends in": "Ends in",
|
||||
"Entity scope": "Entity scope",
|
||||
"{{entity}} scope": "{{entity}} scope",
|
||||
@@ -110,6 +111,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.",
|
||||
@@ -316,6 +318,7 @@
|
||||
"Target stake": "Target stake",
|
||||
"Team": "Team",
|
||||
"Team name": "Team name",
|
||||
"Team name cannot be empty": "Team name cannot be empty",
|
||||
"Team creation transaction successful": "Team creation transaction successful",
|
||||
"Team joined": "Team joined",
|
||||
"Team switch successful. You will switch team at the end of the epoch.": "Team switch successful. You will switch team at the end of the epoch.",
|
||||
@@ -446,5 +449,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
@@ -18,12 +18,15 @@ interface Props {
|
||||
initialValue?: string[];
|
||||
isHeader?: boolean;
|
||||
noUpdate?: boolean;
|
||||
// render prop for no price change
|
||||
fallback?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Last24hPriceChange = ({
|
||||
marketId,
|
||||
decimalPlaces,
|
||||
initialValue,
|
||||
fallback,
|
||||
}: Props) => {
|
||||
const t = useT();
|
||||
const { oneDayCandles, error, fiveDaysCandles } = useCandles({
|
||||
@@ -48,13 +51,13 @@ export const Last24hPriceChange = ({
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span>-</span>
|
||||
<span>{fallback}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !isNumeric(decimalPlaces)) {
|
||||
return <span>-</span>;
|
||||
return <span>{fallback}</span>;
|
||||
}
|
||||
|
||||
const candles = oneDayCandles?.map((c) => c.close) || initialValue || [];
|
||||
|
||||
@@ -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),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user