Compare commits

..
69 changed files with 472 additions and 1724 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ on:
inputs:
console-test-branch:
type: choice
description: 'main: v0.73.13, develop: v0.74.0'
description: 'main: v0.73.5, develop: v0.73.5'
options:
- main
- develop
@@ -64,9 +64,7 @@ export const ProposalSummary = ({
return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
{id && <ProposalStatusIcon id={id} />}
{rationale?.title && (
<h1 className="text-xl pb-1 break-all">{rationale.title}</h1>
)}
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
{rationale?.description && (
<div className="pt-2 text-sm leading-tight">
<ReactMarkdown
@@ -1,18 +0,0 @@
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
@@ -1,61 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerTransferStatusQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerTransferStatusQuery = { __typename?: 'Query', transfer?: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', reference?: string | null, timestamp: any, status: Types.TransferStatus, reason?: string | null, fromAccountType: Types.AccountType, from: string, to: string, toAccountType: Types.AccountType, amount: string, asset?: { __typename?: 'Asset', id: string } | null } } | null };
export const ExplorerTransferStatusDocument = gql`
query ExplorerTransferStatus($id: ID!) {
transfer(id: $id) {
transfer {
reference
timestamp
status
reason
fromAccountType
from
to
toAccountType
asset {
id
}
amount
}
}
}
`;
/**
* __useExplorerTransferStatusQuery__
*
* To run a query within a React component, call `useExplorerTransferStatusQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerTransferStatusQuery` 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 } = useExplorerTransferStatusQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerTransferStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export function useExplorerTransferStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>(ExplorerTransferStatusDocument, options);
}
export type ExplorerTransferStatusQueryHookResult = ReturnType<typeof useExplorerTransferStatusQuery>;
export type ExplorerTransferStatusLazyQueryHookResult = ReturnType<typeof useExplorerTransferStatusLazyQuery>;
export type ExplorerTransferStatusQueryResult = Apollo.QueryResult<ExplorerTransferStatusQuery, ExplorerTransferStatusQueryVariables>;
@@ -111,7 +111,7 @@ export function TransferParticipants({
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9"
className="fill-white dark:fill-black"
className="fill-vega-light-100 dark:fill-black"
>
<path d="M0,0L8,9l8,-9Z" />
</svg>
@@ -120,7 +120,7 @@ export function TransferParticipants({
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 9"
className="fill-vega-light-200 dark:fill-vega-dark-200"
className="fill-vega-light-100 dark:fill-vega-dark-200"
>
<path d="M0,0L8,9l8,-9Z" />
</svg>
@@ -1,223 +1,97 @@
import { t } from '@vegaprotocol/i18n';
import { AssetLink, MarketLink } from '../../../../links';
import { headerClasses, wrapperClasses } from '../transfer-details';
import type { components } from '../../../../../../types/explorer';
import type { Recurring } from '../transfer-details';
import {
DispatchMetricLabels,
DistributionStrategy,
} from '@vegaprotocol/types';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { formatNumber } from '@vegaprotocol/utils';
import { DispatchMetricLabels } from '@vegaprotocol/types';
export type Metric = components['schemas']['vegaDispatchMetric'];
export type Strategy = components['schemas']['vegaDispatchStrategy'];
export const wrapperClasses = 'border pv-2 w-full flex-auto basis-full';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 text-center text-xl py-2 font-alpha calt';
const metricLabels: Record<Metric, string> = {
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
...DispatchMetricLabels,
};
// Maps the two (non-null) values of entityScope to the icon that represents it
const entityScopeIcons: Record<
string,
typeof VegaIconNames[keyof typeof VegaIconNames]
> = {
ENTITY_SCOPE_INDIVIDUALS: VegaIconNames.MAN,
ENTITY_SCOPE_TEAMS: VegaIconNames.TEAM,
};
const distributionStrategyLabel: Record<DistributionStrategy, string> = {
[DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA]: 'Pro Rata',
[DistributionStrategy.DISTRIBUTION_STRATEGY_RANK]: 'Ranked',
};
interface TransferRewardsProps {
recurring: Recurring;
}
/**
* Renders recurring transfers/game details in a way that is, perhaps, easy to understand
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
*
* @param transfer A recurring transfer object
*/
export function TransferRewards({ recurring }: TransferRewardsProps) {
const metric =
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
if (!recurring || !recurring.dispatchStrategy) {
return null;
}
// Destructure to make things a bit more readable
const {
entityScope,
individualScope,
teamScope,
distributionStrategy,
lockPeriod,
markets,
stakingRequirement,
windowLength,
notionalTimeWeightedAveragePositionRequirement,
rankTable,
nTopPerformers,
} = recurring.dispatchStrategy;
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{getRewardTitle(entityScope)}</h2>
<ul className="relative block rounded-lg py-6 text-left p-6">
{entityScope && entityScopeIcons[entityScope] ? (
<h2 className={headerClasses}>{t('Reward metrics')}</h2>
<ul className="relative block rounded-lg py-6 text-center p-6">
{recurring.dispatchStrategy.assetForMetric ? (
<li>
<strong>{t('Scope')}</strong>:{' '}
<VegaIcon name={entityScopeIcons[entityScope]} />
&nbsp;
{individualScope ? individualScopeLabels[individualScope] : null}
{getScopeLabel(entityScope, teamScope)}
<strong>{t('Asset')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
) : null}
{recurring.dispatchStrategy &&
recurring.dispatchStrategy.assetForMetric && (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
</li>
)}
{recurring.dispatchStrategy.metric &&
metricLabels[recurring.dispatchStrategy.metric] && (
<li>
<strong>{t('Metric')}</strong>:{' '}
{metricLabels[recurring.dispatchStrategy.metric]}
</li>
)}
{lockPeriod && (
<li>
<strong>{t('Reward lock')}</strong>:&nbsp;
{recurring.dispatchStrategy.lockPeriod}{' '}
{recurring.dispatchStrategy.lockPeriod === '1'
? t('epoch')
: t('epochs')}
</li>
)}
{markets && markets.length > 0 ? (
<li>
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
</li>
{recurring.dispatchStrategy.markets &&
recurring.dispatchStrategy.markets.length > 0 ? (
<li>
<strong>{t('Markets in scope')}</strong>:
<ul className="inline-block ml-1">
{markets.map((m) => (
<li key={m} className="inline-block mr-2">
<ul>
{recurring.dispatchStrategy.markets.map((m) => (
<li key={m}>
<MarketLink id={m} />
</li>
))}
</ul>
</li>
) : null}
{stakingRequirement && stakingRequirement !== '0' ? (
<li>
<strong>{t('Staking requirement')}</strong>: {stakingRequirement}
</li>
) : null}
{windowLength && windowLength !== '0' ? (
<li>
<strong>{t('Window length')}</strong>:{' '}
{recurring.dispatchStrategy.windowLength}{' '}
{recurring.dispatchStrategy.windowLength === '1'
? t('epoch')
: t('epochs')}
</li>
) : null}
{notionalTimeWeightedAveragePositionRequirement &&
notionalTimeWeightedAveragePositionRequirement !== '' ? (
<li>
<strong>{t('Notional TWAP')}</strong>:{' '}
{notionalTimeWeightedAveragePositionRequirement}
</li>
) : null}
{nTopPerformers && (
<li>
<strong>{t('Elligible team members:')}</strong> top{' '}
{`${formatNumber(Number(nTopPerformers) * 100, 0)}%`}
</li>
)}
{distributionStrategy &&
distributionStrategy !== 'DISTRIBUTION_STRATEGY_UNSPECIFIED' && (
<li>
<strong>{t('Distribution strategy')}</strong>:{' '}
{distributionStrategyLabel[distributionStrategy]}
</li>
)}
<li>
<strong>{t('Factor')}</strong>: {recurring.factor}
</li>
</ul>
<div className="px-6 pt-1 pb-5">
{rankTable && rankTable.length > 0 ? (
<table className="border-collapse border border-gray-400 ">
<thead>
<tr>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Start rank')}</strong>
</th>
<th className="border border-gray-300 bg-gray-300 px-3">
<strong>{t('Share of reward pool')}</strong>
</th>
</tr>
</thead>
<tbody>
{rankTable.map((row, i) => {
return (
<tr key={`rank-${i}`}>
<td className="border border-slate-300 text-center">
{row.startRank}
</td>
<td className="border border-slate-300 text-center">
{row.shareRatio}
</td>
</tr>
);
})}
</tbody>
</table>
) : null}
</div>
</div>
);
}
export function getScopeLabel(
scope: components['schemas']['vegaEntityScope'] | undefined,
teamScope: readonly string[] | undefined
): string {
if (scope === 'ENTITY_SCOPE_TEAMS') {
if (teamScope && teamScope.length !== 0) {
return ` ${teamScope.length} teams`;
} else {
return t('All teams');
}
} else if (scope === 'ENTITY_SCOPE_INDIVIDUALS') {
return t('Individuals');
} else {
return '';
}
}
export function getRewardTitle(
scope?: components['schemas']['vegaEntityScope']
) {
if (scope === 'ENTITY_SCOPE_TEAMS') {
return t('Game');
}
return t('Reward metrics');
interface TransferRecurringStrategyProps {
strategy: Strategy;
}
const individualScopeLabels: Record<
components['schemas']['vegaIndividualScope'],
string
> = {
// Unspecified and All are not rendered
INDIVIDUAL_SCOPE_UNSPECIFIED: '',
INDIVIDUAL_SCOPE_ALL: '',
INDIVIDUAL_SCOPE_IN_TEAM: '(in team)',
INDIVIDUAL_SCOPE_NOT_IN_TEAM: '(not in team)',
};
/**
* Simple renderer for a dispatch strategy in a recurring transfer
*
* @param strategy Dispatch strategy object
*/
export function TransferRecurringStrategy({
strategy,
}: TransferRecurringStrategyProps) {
if (!strategy) {
return null;
}
return (
<>
{strategy.assetForMetric ? (
<li>
<strong>{t('Asset for metric')}</strong>:{' '}
<AssetLink assetId={strategy.assetForMetric} />
</li>
) : null}
<li>
<strong>{t('Metric')}</strong>: {strategy.metric}
</li>
</>
);
}
@@ -1,84 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { headerClasses, wrapperClasses } from '../transfer-details';
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { IconName } from '@vegaprotocol/ui-toolkit';
import type { ApolloError } from '@apollo/client';
import { TransferStatus, TransferStatusMapping } from '@vegaprotocol/types';
import { IconNames } from '@blueprintjs/icons';
interface TransferStatusProps {
status: TransferStatus | undefined;
error: ApolloError | undefined;
loading: boolean;
}
/**
* Renderer for a transfer. These can vary quite
* widely, essentially every field can be null.
*
* @param transfer A recurring transfer object
*/
export function TransferStatusView({ status, loading }: TransferStatusProps) {
if (!status) {
status = TransferStatus.STATUS_PENDING;
}
return (
<div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Status')}</h2>
<div className="relative block rounded-lg py-6 text-center p-6">
{loading ? (
<div className="leading-10 mt-12">
<Loader size={'small'} />
</div>
) : (
<>
<p className="leading-10 my-2">
<Icon
name={getIconForStatus(status)}
className={getColourForStatus(status)}
/>
</p>
<p className="leading-10 my-2">{TransferStatusMapping[status]}</p>
</>
)}
</div>
</div>
);
}
/**
* Simple mapping from status to icon name
* @param status TransferStatus
* @returns IconName
*/
export function getIconForStatus(status: TransferStatus): IconName {
switch (status) {
case TransferStatus.STATUS_PENDING:
return IconNames.TIME;
case TransferStatus.STATUS_DONE:
return IconNames.TICK;
case TransferStatus.STATUS_REJECTED:
return IconNames.CROSS;
default:
return IconNames.TIME;
}
}
/**
* Simple mapping from status to colour
* @param status TransferStatus
* @returns string Tailwind classname
*/
export function getColourForStatus(status: TransferStatus): string {
switch (status) {
case TransferStatus.STATUS_PENDING:
return 'text-yellow-500';
case TransferStatus.STATUS_DONE:
return 'text-green-500';
case TransferStatus.STATUS_REJECTED:
return 'text-red-500';
default:
return 'text-yellow-500';
}
}
@@ -2,15 +2,12 @@ import type { components } from '../../../../../types/explorer';
import { TransferRepeat } from './blocks/transfer-repeat';
import { TransferRewards } from './blocks/transfer-rewards';
import { TransferParticipants } from './blocks/transfer-participants';
import { useExplorerTransferStatusQuery } from './__generated__/Transfer';
import { TransferStatusView } from './blocks/transfer-status';
import { TransferStatus } from '@vegaprotocol/types';
export type Recurring = components['schemas']['commandsv1RecurringTransfer'];
export type Metric = components['schemas']['vegaDispatchMetric'];
export const wrapperClasses =
'border border-vega-light-150 dark:border-vega-dark-200 pv-2 w-full sm:w-1/3 basis-1/3';
'border border-vega-light-150 dark:border-vega-dark-200 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] ';
export const headerClasses =
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt';
@@ -19,7 +16,6 @@ export type Transfer = components['schemas']['commandsv1Transfer'];
interface TransferDetailsProps {
transfer: Transfer;
from: string;
id: string;
}
/**
@@ -28,24 +24,13 @@ interface TransferDetailsProps {
*
* @param transfer A recurring transfer object
*/
export function TransferDetails({ transfer, from, id }: TransferDetailsProps) {
export function TransferDetails({ transfer, from }: TransferDetailsProps) {
const recurring = transfer.recurring;
// Currently all this is passed in to TransferStatus, but the extra details
// may be useful in the future.
const { data, error, loading } = useExplorerTransferStatusQuery({
variables: { id },
});
const status = error
? TransferStatus.STATUS_REJECTED
: data?.transfer?.transfer.status;
return (
<div className="flex flex-wrap">
<div className="flex gap-5 flex-wrap">
<TransferParticipants from={from} transfer={transfer} />
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
<TransferStatusView status={status} error={error} loading={loading} />
{recurring && recurring.dispatchStrategy ? (
<TransferRewards recurring={transfer.recurring} />
) : null}
@@ -1,172 +0,0 @@
import {
getScopeLabel,
getRewardTitle,
TransferRewards,
} from './blocks/transfer-rewards';
import { render } from '@testing-library/react';
import type { components } from '../../../../../types/explorer';
import type { Recurring } from './transfer-details';
import {
DispatchMetric,
DistributionStrategy,
EntityScope,
IndividualScope,
} from '@vegaprotocol/types';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getScopeLabel', () => {
it('should return the correct label for ENTITY_SCOPE_TEAMS with teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = ['team1', 'team2', 'team3'];
const expectedLabel = ' 3 teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_TEAMS without teamScope', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const teamScope = undefined;
const expectedLabel = 'All teams';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return the correct label for ENTITY_SCOPE_INDIVIDUALS', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const teamScope = undefined;
const expectedLabel = 'Individuals';
const result = getScopeLabel(scope, teamScope);
expect(result).toEqual(expectedLabel);
});
it('should return an empty string for unknown scope', () => {
const scope = 'UNKNOWN_SCOPE';
const teamScope = undefined;
const expectedLabel = '';
const result = getScopeLabel(
scope as unknown as components['schemas']['vegaEntityScope'],
teamScope
);
expect(result).toEqual(expectedLabel);
});
});
describe('getRewardTitle', () => {
it('should return the correct title for ENTITY_SCOPE_TEAMS', () => {
const scope = 'ENTITY_SCOPE_TEAMS';
const expectedTitle = 'Game';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
it('should return the correct title for other scopes', () => {
const scope = 'ENTITY_SCOPE_INDIVIDUALS';
const expectedTitle = 'Reward metrics';
const result = getRewardTitle(scope);
expect(result).toEqual(expectedTitle);
});
});
describe('TransferRewards', () => {
it('should render nothing if recurring dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={null as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render nothing if recurring.dispatchStrategy is not provided', () => {
const { container } = render(
<TransferRewards recurring={{} as unknown as Recurring} />
);
expect(container.firstChild).toBeNull();
});
it('should render the reward details correctly', () => {
const recurring = {
dispatchStrategy: {
metric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
assetForMetric: '123',
entityScope: EntityScope.ENTITY_SCOPE_TEAMS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_IN_TEAM,
teamScope: [],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: '1',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
rankTable: [
{ startRank: 1, shareRatio: 0.2 },
{ startRank: 2, shareRatio: 0.3 },
],
nTopPerformers: 'nTopPerformers',
},
};
const { getByText } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(getByText('Game')).toBeInTheDocument();
expect(getByText('Scope')).toBeInTheDocument();
expect(getByText('Asset for metric')).toBeInTheDocument();
expect(getByText('Metric')).toBeInTheDocument();
expect(getByText('Reward lock')).toBeInTheDocument();
expect(getByText('Markets in scope')).toBeInTheDocument();
expect(getByText('Staking requirement')).toBeInTheDocument();
expect(getByText('Window length')).toBeInTheDocument();
expect(getByText('Notional TWAP')).toBeInTheDocument();
expect(getByText('Elligible team members:')).toBeInTheDocument();
expect(getByText('Distribution strategy')).toBeInTheDocument();
expect(getByText('Start rank')).toBeInTheDocument();
expect(getByText('Share of reward pool')).toBeInTheDocument();
});
it('should not render a rank table if recurring.dispatchStrategy.rankTable is not provided', () => {
const recurring = {
dispatchStrategy: {
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
teamScope: ['team1', 'team2', 'team3'],
distributionStrategy:
DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
lockPeriod: 'lockPeriod',
markets: ['market1', 'market2'],
stakingRequirement: 'stakingRequirement',
windowLength: 'windowLength',
notionalTimeWeightedAveragePositionRequirement:
'notionalTimeWeightedAveragePositionRequirement',
nTopPerformers: 'nTopPerformers',
},
};
const { container } = render(
<MemoryRouter>
<MockedProvider>
<TransferRewards recurring={recurring} />
</MockedProvider>
</MemoryRouter>
);
expect(container.querySelector('table')).toBeNull();
});
});
@@ -12,8 +12,6 @@ import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
import { MarketLink } from '../../links';
import { formatNumber } from '@vegaprotocol/utils';
import { TransferDetails } from './transfer/transfer-details';
import { proposalToTransfer } from '../lib/proposal-to-transfer';
export type Proposal = components['schemas']['v1ProposalSubmission'];
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
@@ -106,12 +104,6 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
? ProposalSignatureBundleNewAsset
: ProposalSignatureBundleUpdateAsset;
let transfer, from;
if (proposal.terms?.newTransfer?.changes) {
transfer = proposalToTransfer(proposal.terms?.newTransfer.changes);
from = proposal.terms.newTransfer.changes.source;
}
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -157,26 +149,14 @@ export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
</>
) : null}
</TableWithTbody>
<ProposalSummary
id={deterministicId}
rationale={proposal.rationale}
terms={proposal?.terms}
/>
{proposalRequiresSignatureBundle(proposal) && (
<SignatureBundleComponent id={deterministicId} tx={tx} />
)}
{transfer && (
<div className="mt-8">
<TransferDetails
transfer={transfer}
from={from || ''}
id={deterministicId}
/>
</div>
)}
</>
);
};
@@ -13,7 +13,6 @@ import {
SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import Hash from '../../links/hash';
type Transfer = components['schemas']['commandsv1Transfer'];
@@ -61,7 +60,7 @@ export const TxDetailsTransfer = ({
}
const from = txData.submitter;
const id = txSignatureToDeterministicId(txData.signature.value);
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -72,7 +71,7 @@ export const TxDetailsTransfer = ({
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
<Hash text={id} />
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared
@@ -106,7 +105,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
</TableWithTbody>
<TransferDetails from={from} transfer={transfer} id={id} />
<TransferDetails from={from} transfer={transfer} />
</>
);
};
@@ -1,29 +0,0 @@
import type { components } from '../../../../types/explorer';
type TransferProposal = components['schemas']['vegaNewTransferConfiguration'];
type ActualTransfer = components['schemas']['commandsv1Transfer'];
/**
* Converts a governance proposal for a transfer in to a transfer command that the
* TransferDetails component can then render. The types are very similar, but do not
* map precisely to each other due to some missing fields and some different field
* names.
*
* @param proposal Governance proposal for a transfer
* @returns transfer a Transfer object as if it had been submitted
*/
export function proposalToTransfer(proposal: TransferProposal): ActualTransfer {
return {
amount: proposal.amount,
asset: proposal.asset,
// On a transfer, 'from' is determined by the submitter, so there is no 'from' field
// fromAccountType does exist and is just named differently on the proposal
fromAccountType: proposal.sourceType,
oneOff: proposal.oneOff,
recurring: proposal.recurring,
// There is no reference applied on governance initiated transfers
reference: '',
to: proposal.destination,
toAccountType: proposal.destinationType,
};
}
@@ -241,16 +241,6 @@ 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: {
@@ -83,12 +83,6 @@ 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,
@@ -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 { useGameCards } from '../../lib/hooks/use-game-cards';
import { useEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch';
import { useGames } from '../../lib/hooks/use-games';
import { useCurrentEpochInfoQuery } from '../referrals/hooks/__generated__/Epoch';
import { Link, useNavigate } from 'react-router-dom';
import { Links } from '../../lib/links';
import {
@@ -25,10 +25,10 @@ export const CompetitionsHome = () => {
usePageTitle(t('Competitions'));
const { data: epochData } = useEpochInfoQuery();
const { data: epochData } = useCurrentEpochInfoQuery();
const currentEpoch = Number(epochData?.epoch.id);
const { data: gamesData, loading: gamesLoading } = useGameCards({
const { data: gamesData, loading: gamesLoading } = useGames({
onlyActive: true,
currentEpoch,
});
@@ -1,33 +1,18 @@
import { useState, type ButtonHTMLAttributes, useRef } from 'react';
import { useState, type ButtonHTMLAttributes } from 'react';
import { Link, useParams } from 'react-router-dom';
import orderBy from 'lodash/orderBy';
import {
Splash,
truncateMiddle,
Loader,
Dialog,
Button,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import {
TransferStatus,
type Asset,
type RecurringTransfer,
} from '@vegaprotocol/types';
import { Splash, truncateMiddle, Loader } from '@vegaprotocol/ui-toolkit';
import { DispatchMetricLabels, type DispatchMetric } from '@vegaprotocol/types';
import classNames from 'classnames';
import { useT } from '../../lib/use-t';
import { Table } from '../../components/table';
import {
addDecimalsFormatNumberQuantum,
formatNumber,
getDateTimeFormat,
} from '@vegaprotocol/utils';
import { formatNumber, getDateTimeFormat } from '@vegaprotocol/utils';
import {
useTeam,
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';
@@ -38,24 +23,6 @@ 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';
import { useEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch';
import {
type EnrichedTransfer,
isScopedToTeams,
useGameCards,
} from '../../lib/hooks/use-game-cards';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import {
ActiveRewardCard,
DispatchMetricInfo,
} from '../../components/rewards-container/active-rewards';
import { type MarketMap, useMarketsMapProvider } from '@vegaprotocol/markets';
import format from 'date-fns/format';
export const CompetitionsTeam = () => {
const t = useT();
@@ -71,20 +38,8 @@ export const CompetitionsTeam = () => {
const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => {
const t = useT();
const { pubKey } = useVegaWallet();
const { data, team, partyTeam, stats, members, loading, refetch } = useTeam(
teamId,
pubKey || undefined
);
const { data: games, loading: gamesLoading } = useGames(teamId);
const { data: epochData, loading: epochLoading } = useEpochInfoQuery();
const { data: transfersData, loading: transfersLoading } = useGameCards({
currentEpoch: Number(epochData?.epoch.id),
onlyActive: false,
});
const { data: markets } = useMarketsMapProvider();
const { data, team, partyTeam, stats, members, games, loading, refetch } =
useTeam(teamId, pubKey || undefined);
// only show spinner on first load so when users join teams its smoother
if (!data && loading) {
@@ -109,11 +64,7 @@ const TeamPageContainer = ({ teamId }: { teamId: string | undefined }) => {
partyTeam={partyTeam}
stats={stats}
members={members}
games={areTeamGames(games) ? games : undefined}
gamesLoading={gamesLoading}
transfers={transfersData}
transfersLoading={epochLoading || transfersLoading}
allMarkets={markets || undefined}
games={games}
refetch={refetch}
/>
);
@@ -125,10 +76,6 @@ const TeamPage = ({
stats,
members,
games,
gamesLoading,
transfers,
transfersLoading,
allMarkets,
refetch,
}: {
team: TeamType;
@@ -136,10 +83,6 @@ const TeamPage = ({
stats?: ITeamStats;
members?: Member[];
games?: TeamGame[];
gamesLoading?: boolean;
transfers?: EnrichedTransfer[];
transfersLoading?: boolean;
allMarkets?: MarketMap;
refetch: () => void;
}) => {
const t = useT();
@@ -170,11 +113,7 @@ const TeamPage = ({
onClick={() => setShowGames(true)}
data-testid="games-toggle"
>
{t('Results {{games}}', {
replace: {
games: gamesLoading ? '' : games ? `(${games.length})` : '(0)',
},
})}
{t('Games ({{count}})', { count: games ? games.length : 0 })}
</ToggleButton>
<ToggleButton
active={!showGames}
@@ -186,149 +125,53 @@ const TeamPage = ({
})}
</ToggleButton>
</div>
{showGames ? (
<Games
games={games}
gamesLoading={gamesLoading}
transfers={transfers}
transfersLoading={transfersLoading}
allMarkets={allMarkets}
/>
) : (
<Members members={members} />
)}
{showGames ? <Games games={games} /> : <Members members={members} />}
</section>
</LayoutWithGradient>
);
};
const Games = ({
games,
gamesLoading,
transfers,
transfersLoading,
allMarkets,
}: {
games?: TeamGame[];
gamesLoading?: boolean;
transfers?: EnrichedTransfer[];
transfersLoading?: boolean;
allMarkets?: MarketMap;
}) => {
const Games = ({ games }: { games?: TeamGame[] }) => {
const t = useT();
if (gamesLoading) {
return (
<div className="w-[15px]">
<Loader size="small" />
</div>
);
}
if (!games?.length) {
return <p>{t('No game results available')}</p>;
return <p>{t('No games')}</p>;
}
const dependable = (value: string | JSX.Element) => {
if (transfersLoading) return <Loader size="small" />;
return value;
};
return (
<Table
columns={[
{ name: 'rank', displayName: t('Rank') },
{
name: 'epoch',
displayName: t('Epoch'),
},
{
name: 'endtime',
displayName: t('End time'),
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
{ name: 'type', displayName: t('Type') },
{
name: 'asset',
displayName: t('Reward asset'),
},
{ name: 'daily', displayName: t('Daily reward amount') },
{ name: 'rank', displayName: t('Rank') },
{ name: 'amount', displayName: t('Amount earned this epoch') },
{ name: 'total', displayName: t('Cumulative amount earned') },
{ name: 'amount', displayName: t('Amount earned') },
{
name: 'participatingTeams',
displayName: t('No. of participating teams'),
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
{
name: 'participatingMembers',
displayName: t('No. of participating members'),
headerClassName: 'hidden md:table-cell',
className: 'hidden md:table-cell',
},
].map((c) => ({ ...c, headerClassName: 'text-left' }))}
data={games.map((game) => {
let transfer = transfers?.find((t) => {
if (!isScopedToTeams(t)) return false;
const idMatch = t.transfer.gameId === game.id;
const metricMatch =
t.transfer.kind.dispatchStrategy?.dispatchMetric ===
game.team.rewardMetric;
const start = t.transfer.kind.startEpoch <= game.epoch;
const end = t.transfer.kind.endEpoch
? t.transfer.kind.endEpoch >= game.epoch
: true;
const rejected = t.transfer.status === TransferStatus.STATUS_REJECTED;
return idMatch && metricMatch && start && end && !rejected;
});
if (!transfer || !isScopedToTeams(transfer)) transfer = undefined;
const asset = transfer?.transfer.asset;
const dailyAmount =
asset && transfer
? addDecimalsFormatNumberQuantum(
transfer.transfer.amount,
asset.decimals,
asset.quantum
)
: '-';
const earnedAmount = asset
? addDecimalsFormatNumberQuantum(
game.team.rewardEarned,
asset.decimals,
asset.quantum
)
: '-';
const totalAmount = asset
? addDecimalsFormatNumberQuantum(
game.team.totalRewardsEarned,
asset.decimals,
asset.quantum
)
: '-';
const assetSymbol = asset ? <RewardAssetCell asset={asset} /> : '-';
return {
id: game.id,
amount: dependable(earnedAmount),
asset: dependable(assetSymbol),
daily: dependable(dailyAmount),
endtime: <EndTimeCell epoch={game.epoch} />,
epoch: game.epoch,
participatingMembers: game.numberOfParticipants,
participatingTeams: game.entities.length,
rank: game.team.rank,
total: totalAmount,
// type: DispatchMetricLabels[game.team.rewardMetric as DispatchMetric],
type: dependable(
<GameTypeCell transfer={transfer} allMarkets={allMarkets} />
),
};
})}
noCollapse={false}
]}
data={games.map((game) => ({
rank: game.team.rank,
epoch: game.epoch,
type: DispatchMetricLabels[game.team.rewardMetric as DispatchMetric],
amount: formatNumber(game.team.totalRewardsEarned),
participatingTeams: game.entities.length,
participatingMembers: game.numberOfParticipants,
}))}
noCollapse={true}
/>
);
};
@@ -410,126 +253,3 @@ const ToggleButton = ({
/>
);
};
const EndTimeCell = ({ epoch }: { epoch?: number }) => {
const { data, loading } = useEpochInfoQuery({
variables: {
epochId: epoch ? epoch.toString() : undefined,
},
fetchPolicy: 'cache-and-network',
});
if (loading) return <Loader size="small" />;
if (data) {
return format(
new Date(data.epoch.timestamps.expiry),
'yyyy/MM/dd hh:mm:ss'
);
}
return null;
};
const RewardAssetCell = ({ asset }: { asset: Asset }) => {
const open = useAssetDetailsDialogStore((state) => state.open);
const ref = useRef<HTMLButtonElement>(null);
return (
<button
ref={ref}
onClick={(e) => {
e.preventDefault();
open(asset.id, ref.current);
}}
className="border-b border-dashed border-vega-clight-200 dark:border-vega-cdark-200 text-left text-nowrap whitespace-nowrap"
>
{asset.symbol}
</button>
);
};
const GameTypeCell = ({
transfer,
allMarkets,
}: {
transfer?: EnrichedTransfer;
allMarkets?: MarketMap;
}) => {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLButtonElement>(null);
if (!transfer) return '-';
return (
<>
<ActiveRewardCardDialog
open={open}
onChange={(isOpen) => setOpen(isOpen)}
trigger={ref.current}
transfer={transfer}
allMarkets={allMarkets}
/>
<button
onClick={(e) => {
e.preventDefault();
setOpen(true);
}}
ref={ref}
className="border-b border-dashed border-vega-clight-200 dark:border-vega-cdark-200 text-left md:truncate md:max-w-[25vw]"
>
<DispatchMetricInfo transferNode={transfer} allMarkets={allMarkets} />
</button>
</>
);
};
const ActiveRewardCardDialog = ({
open,
onChange,
trigger,
transfer,
allMarkets,
}: {
open: boolean;
onChange: (isOpen: boolean) => void;
trigger?: HTMLElement | null;
transfer: EnrichedTransfer;
allMarkets?: MarketMap;
}) => {
const t = useT();
const { data } = useEpochInfoQuery();
return (
<Dialog
open={open}
title={t('Game details')}
onChange={(isOpen) => onChange(isOpen)}
icon={<VegaIcon name={VegaIconNames.INFO} />}
onCloseAutoFocus={(e) => {
/**
* This mimics radix's default behaviour that focuses the dialog's
* trigger after closing itself
*/
if (trigger) {
e.preventDefault();
trigger.focus();
}
}}
>
<div className="py-5 max-w-[454px]">
<ActiveRewardCard
transferNode={transfer}
currentEpoch={Number(data?.epoch.id)}
kind={transfer.transfer.kind as RecurringTransfer}
allMarkets={allMarkets}
/>
</div>
<div className="w-1/4">
<Button
data-testid="close-asset-details-dialog"
fill={true}
size="sm"
onClick={() => onChange(false)}
>
{t('Close')}
</Button>
</div>
</Dialog>
);
};
@@ -46,7 +46,7 @@ const prepareTransaction = (
createReferralSet: {
isTeam: true,
team: {
name: fields.name.trim(),
name: fields.name,
teamUrl: fields.url,
avatarUrl: fields.avatarUrl,
closed: fields.private,
@@ -62,7 +62,7 @@ const prepareTransaction = (
id: fields.id,
isTeam: true,
team: {
name: fields.name.trim(),
name: fields.name,
teamUrl: fields.url,
avatarUrl: fields.avatarUrl,
closed: fields.private,
@@ -116,17 +116,7 @@ export const TeamForm = ({
<input type="hidden" {...register('id')} />
<TradingFormGroup label={t('Team name')} labelFor="name">
<TradingInput
{...register('name', {
required: t('Required'),
validate: {
notEmpty: (value) => {
if (/^\s*$/.test(value)) {
return t('Team name cannot be empty');
}
return true;
},
},
})}
{...register('name', { required: t('Required') })}
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">
@@ -72,14 +72,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
<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',
'funding',
]
{['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) => {
@@ -170,12 +163,9 @@ const ViewButton = ({
onClick: () => void;
}) => {
const label = useViewLabel(view);
const className = classNames(
'py-2 px-4 capitalize text-sm whitespace-nowrap',
{
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
}
);
const className = classNames('py-2 px-4 min-w-[100px] capitalize text-sm', {
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
});
return (
<button data-testid={view} onClick={onClick} className={className}>
@@ -191,8 +181,8 @@ const useViewLabel = (view: TradingView) => {
chart: t('Chart'),
depth: t('Depth'),
liquidity: t('Liquidity'),
funding: t('Funding history'),
fundingPayments: t('Funding payments'),
funding: t('Funding'),
fundingPayments: t('Funding'),
orderbook: t('Orderbook'),
trades: t('Trades'),
positions: t('Positions'),
@@ -0,0 +1,9 @@
query CurrentEpochInfo {
epoch {
id
timestamps {
start
end
}
}
}
@@ -0,0 +1,49 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type CurrentEpochInfoQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type CurrentEpochInfoQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null } } };
export const CurrentEpochInfoDocument = gql`
query CurrentEpochInfo {
epoch {
id
timestamps {
start
end
}
}
}
`;
/**
* __useCurrentEpochInfoQuery__
*
* To run a query within a React component, call `useCurrentEpochInfoQuery` and pass it any options that fit your needs.
* When your component renders, `useCurrentEpochInfoQuery` 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 } = useCurrentEpochInfoQuery({
* variables: {
* },
* });
*/
export function useCurrentEpochInfoQuery(baseOptions?: Apollo.QueryHookOptions<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>(CurrentEpochInfoDocument, options);
}
export function useCurrentEpochInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>(CurrentEpochInfoDocument, options);
}
export type CurrentEpochInfoQueryHookResult = ReturnType<typeof useCurrentEpochInfoQuery>;
export type CurrentEpochInfoLazyQueryHookResult = ReturnType<typeof useCurrentEpochInfoLazyQuery>;
export type CurrentEpochInfoQueryResult = Apollo.QueryResult<CurrentEpochInfoQuery, CurrentEpochInfoQueryVariables>;
@@ -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 { useEpochInfoQuery } from '../../../lib/hooks/__generated__/Epoch';
import { useCurrentEpochInfoQuery } from './__generated__/Epoch';
const REFETCH_INTERVAL = 60 * 60 * 1000; // 1h
const NON_ELIGIBLE_REFERRAL_SET_TOAST_ID = 'non-eligible-referral-set';
@@ -23,7 +23,7 @@ const useNonEligibleReferralSet = () => {
data: epochData,
loading: epochLoading,
refetch: epochRefetch,
} = useEpochInfoQuery();
} = useCurrentEpochInfoQuery();
useEffect(() => {
const interval = setInterval(() => {
@@ -34,10 +34,9 @@ import {
} from './hooks/use-referral';
import { ApplyCodeForm, ApplyCodeFormContainer } from './apply-code-form';
import { useReferralProgram } from './hooks/use-referral-program';
import { useEpochInfoQuery } from '../../lib/hooks/__generated__/Epoch';
import { useCurrentEpochInfoQuery } from './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();
@@ -95,7 +94,7 @@ export const useStats = ({
program: ReturnType<typeof useReferralProgram>;
}) => {
const { benefitTiers } = program;
const { data: epochData } = useEpochInfoQuery({
const { data: epochData } = useCurrentEpochInfoQuery({
fetchPolicy: 'network-only',
});
const { data: statsData } = useReferralSetStatsQuery({
@@ -577,8 +576,7 @@ export const RefereesTable = ({
};
const Team = ({ teamId }: { teamId?: string }) => {
const { team, members } = useTeam(teamId);
const { data: games } = useGames(teamId);
const { team, games, members } = useTeam(teamId);
if (!team) return null;
@@ -587,10 +585,7 @@ 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={areTeamGames(games) ? games : undefined}
/>
<TeamStats members={members} games={games} />
</div>
</Tile>
);
@@ -153,8 +153,5 @@ const cacheConfig: InMemoryCacheConfig = {
OrderUpdate: {
keyFields: false,
},
Game: {
keyFields: false,
},
},
};
@@ -56,10 +56,7 @@ export const CompetitionsLeaderboard = ({
className="hover:underline"
to={Links.COMPETITIONS_TEAM(td.teamId)}
>
{
// Its possible for a tx to be submitted with an empty space as team name
td.name.trim() !== '' ? td.name : t('[empty]')
}
{td.name}
</Link>
),
earned: num(td.totalQuantumRewards),
@@ -1,19 +1,49 @@
import { ActiveRewardCard } from '../rewards-container/active-rewards';
import { type TransferNode } from '@vegaprotocol/types';
import {
ActiveRewardCard,
isActiveReward,
} from '../rewards-container/active-rewards';
import { useT } from '../../lib/use-t';
import { type EnrichedTransfer } from '../../lib/hooks/use-game-cards';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { useMarketsMapProvider } from '@vegaprotocol/markets';
export const GamesContainer = ({
data,
currentEpoch,
}: {
data: EnrichedTransfer[];
data: TransferNode[];
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.')}
@@ -23,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 (
@@ -38,7 +68,6 @@ export const GamesContainer = ({
transferNode={game}
currentEpoch={currentEpoch}
kind={transfer.kind}
allMarkets={markets || undefined}
/>
);
})}
@@ -1,4 +1,4 @@
import { type TeamStats } from '../../lib/hooks/use-team';
import { type TeamGame, 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,7 +13,6 @@ 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,
@@ -29,35 +29,28 @@ export const MobileMarketHeader = () => {
if (!marketId) return null;
return (
<div className="pl-3 pr-2 grid grid-cols-2 h-10 bg-vega-clight-700 dark:bg-vega-cdark-700">
<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={
<button
data-testid="popover-trigger"
className="min-w-0 flex gap-1 items-center"
>
<h1 className="whitespace-nowrap overflow-hidden text-ellipsis items-center">
<span className="">
{data
? data.tradableInstrument.instrument.code
: t('Select market')}
</span>
</h1>
<VegaIcon
name={VegaIconNames.CHEVRON_DOWN}
size={16}
<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(
'origin-center transition-transform ease-in-out duration-300 flex',
'transition-transform ease-in-out duration-300 flex',
{
'rotate-180': openMarket,
}
)}
/>
</button>
>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={16} />
</span>
</h1>
}
>
<MarketSelector
@@ -71,40 +64,34 @@ export const MobileMarketHeader = () => {
setOpenPrice(x);
}}
trigger={
<button
data-testid="popover-trigger"
className="min-w-0 flex gap-2 items-center justify-end"
>
<span className="flex gap-2 items-end md:text-md whitespace-nowrap leading-3">
{data && (
<>
<span className="min-w-0 flex flex-col items-end gap-0">
<span className="text-sm">
<MarketMarkPrice
marketId={data.id}
decimalPlaces={data.decimalPlaces}
/>
</span>
<span className="text-xs">
<Last24hPriceChange
marketId={data.id}
decimalPlaces={data.decimalPlaces}
fallback={<span />} // dont render anything so price is vertically centered
/>
</span>
<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>
<VegaIcon
name={VegaIconNames.CHEVRON_DOWN}
size={16}
className={classNames(
'min-w-0 transition-transform ease-in-out duration-300',
{
'rotate-180': openPrice,
}
)}
/>
</>
)}
</button>
</span>
}
>
{data && (
@@ -117,11 +104,11 @@ export const MobileMarketHeader = () => {
);
};
interface PopoverProps extends PopoverPrimitive.PopoverProps {
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
trigger: React.ReactNode | string;
}
const FullScreenPopover = ({
export const FullScreenPopover = ({
trigger,
children,
open,
@@ -129,7 +116,7 @@ const FullScreenPopover = ({
}: PopoverProps) => {
return (
<PopoverPrimitive.Root open={open} onOpenChange={onOpenChange}>
<PopoverPrimitive.Trigger asChild={true}>
<PopoverPrimitive.Trigger data-testid="popover-trigger">
{trigger}
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal>
@@ -73,7 +73,6 @@ query ActiveRewards(
reference
status
timestamp
gameId
kind {
... on RecurringTransfer {
startEpoch
@@ -18,7 +18,7 @@ export type ActiveRewardsQueryVariables = Types.Exact<{
}>;
export type ActiveRewardsQuery = { __typename?: 'Query', transfersConnection?: { __typename?: 'TransferConnection', edges?: Array<{ __typename?: 'TransferEdge', node: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', amount: string, id: string, from: string, fromAccountType: Types.AccountType, to: string, toAccountType: Types.AccountType, reference?: string | null, status: Types.TransferStatus, timestamp: any, gameId?: string | null, reason?: string | null, asset?: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string, quantum: string, status: Types.AssetStatus } | null, kind: { __typename?: 'OneOffGovernanceTransfer' } | { __typename?: 'OneOffTransfer' } | { __typename?: 'RecurringGovernanceTransfer' } | { __typename?: 'RecurringTransfer', startEpoch: number, endEpoch?: number | null, dispatchStrategy?: { __typename?: 'DispatchStrategy', dispatchMetric: Types.DispatchMetric, dispatchMetricAssetId: string, marketIdsInScope?: Array<string> | null, entityScope: Types.EntityScope, individualScope?: Types.IndividualScope | null, teamScope?: Array<string | null> | null, nTopPerformers?: string | null, stakingRequirement: string, notionalTimeWeightedAveragePositionRequirement: string, windowLength: number, lockPeriod: number, distributionStrategy: Types.DistributionStrategy, rankTable?: Array<{ __typename?: 'RankTable', startRank: number, shareRatio: number } | null> | null } | null } }, fees?: Array<{ __typename?: 'TransferFee', transferId: string, amount: string, epoch: number } | null> | null } } | null> | null } | null };
export type ActiveRewardsQuery = { __typename?: 'Query', transfersConnection?: { __typename?: 'TransferConnection', edges?: Array<{ __typename?: 'TransferEdge', node: { __typename?: 'TransferNode', transfer: { __typename?: 'Transfer', amount: string, id: string, from: string, fromAccountType: Types.AccountType, to: string, toAccountType: Types.AccountType, reference?: string | null, status: Types.TransferStatus, timestamp: any, reason?: string | null, asset?: { __typename?: 'Asset', id: string, symbol: string, decimals: number, name: string, quantum: string, status: Types.AssetStatus } | null, kind: { __typename?: 'OneOffGovernanceTransfer' } | { __typename?: 'OneOffTransfer' } | { __typename?: 'RecurringGovernanceTransfer' } | { __typename?: 'RecurringTransfer', startEpoch: number, endEpoch?: number | null, dispatchStrategy?: { __typename?: 'DispatchStrategy', dispatchMetric: Types.DispatchMetric, dispatchMetricAssetId: string, marketIdsInScope?: Array<string> | null, entityScope: Types.EntityScope, individualScope?: Types.IndividualScope | null, teamScope?: Array<string | null> | null, nTopPerformers?: string | null, stakingRequirement: string, notionalTimeWeightedAveragePositionRequirement: string, windowLength: number, lockPeriod: number, distributionStrategy: Types.DistributionStrategy, rankTable?: Array<{ __typename?: 'RankTable', startRank: number, shareRatio: number } | null> | null } | null } }, fees?: Array<{ __typename?: 'TransferFee', transferId: string, amount: string, epoch: number } | null> | null } } | null> | null } | null };
export type RewardsHistoryQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
@@ -144,7 +144,6 @@ export const ActiveRewardsDocument = gql`
reference
status
timestamp
gameId
kind {
... on RecurringTransfer {
startEpoch
@@ -251,7 +251,12 @@ const StatusIndicator = ({
);
};
type ActiveRewardCardProps = {
export const ActiveRewardCard = ({
transferNode,
currentEpoch,
kind,
allMarkets,
}: {
transferNode: TransferNode & {
asset?: AssetFieldsFragment | null;
markets?: (MarketFieldsFragment | null)[];
@@ -259,13 +264,7 @@ type ActiveRewardCardProps = {
currentEpoch: number;
kind: RecurringTransfer;
allMarkets?: Record<string, MarketFieldsFragment | null>;
};
export const ActiveRewardCard = ({
transferNode,
currentEpoch,
kind,
allMarkets,
}: ActiveRewardCardProps) => {
}) => {
const t = useT();
const { transfer } = transferNode;
@@ -486,62 +485,6 @@ export const ActiveRewardCard = ({
);
};
export const DispatchMetricInfo = ({
transferNode,
allMarkets,
}: {
transferNode: ActiveRewardCardProps['transferNode'];
allMarkets?: ActiveRewardCardProps['allMarkets'];
}) => {
const dispatchStrategy =
transferNode.transfer.kind.__typename === 'RecurringTransfer'
? transferNode.transfer.kind.dispatchStrategy
: null;
const dispatchAsset = transferNode.transfer.asset;
const marketIdsInScope = dispatchStrategy?.marketIdsInScope;
const firstMarketData = transferNode.markets?.[0];
const specificMarkets = useMemo(() => {
if (
!firstMarketData ||
!marketIdsInScope ||
marketIdsInScope.length === 0
) {
return null;
}
if (marketIdsInScope.length > 1) {
const marketNames =
allMarkets &&
marketIdsInScope
.map((id) => allMarkets[id]?.tradableInstrument?.instrument?.name)
.join(', ');
return (
<Tooltip description={marketNames}>
<span>Specific markets</span>
</Tooltip>
);
}
const name = firstMarketData?.tradableInstrument?.instrument?.name;
if (name) {
return <span>{name}</span>;
}
return null;
}, [firstMarketData, marketIdsInScope, allMarkets]);
if (!dispatchStrategy) return null;
return (
<span data-testid="dispatch-metric-info">
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} {' '}
<span>{specificMarkets || dispatchAsset?.name}</span>
</span>
);
};
const RewardRequirements = ({
dispatchStrategy,
assetDecimalPlaces = 0,
-3
View File
@@ -93,9 +93,6 @@ export const Table = forwardRef<
key={i}
className={classNames(dataEntry['className'] as string, {
'max-md:flex flex-col w-full': !noCollapse,
// collapsed (mobile) row divider
'first:border-t-0 max-md:border-t border-vega-clight-500 dark:border-vega-cdark-500 first:mt-0 mt-1':
!noCollapse,
})}
onClick={() => {
if (onRowClick) {
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.74.1
VEGA_VERSION=v0.74.0-preview.10
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.74.1
VEGA_VERSION=v0.74.0-preview.6
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
VEGA_VERSION=v0.73.13
VEGA_VERSION=v0.73.10
LOCAL_SERVER=false
+1 -1
View File
@@ -1161,7 +1161,7 @@ profile = ["pytest-profiling", "snakeviz"]
type = "git"
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
reference = "HEAD"
resolved_reference = "a8afded34874a01cfd1bb771052aa12a062960b9"
resolved_reference = "026976549c21e59f6f9c48f06ab15a210c5a5bf3"
[[package]]
name = "websocket-client"
@@ -9,16 +9,6 @@ leverage_input = "#leverage-input"
tab_positions = "tab-positions"
margin_row = '[col-id="margin"]'
current_margin = "deal-ticket-fee-current-margin"
available_collateral = "deal-ticket-fee-available-collateral"
additional_margin_required = "deal-ticket-fee-additional-margin-required"
liquidation_estimate = "deal-ticket-fee-liquidation-estimate"
dialog_content = "dialog-content"
cross_margin = "cross-margin"
isolated_margin = "isolated-margin"
confirm_cross_margin_mode = "confirm-cross-margin-mode"
confirm_isolated_margin_mode = "confirm-isolated-margin-mode"
def create_position(vega: VegaServiceNull, market_id):
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
@@ -39,9 +29,10 @@ def test_switch_cross_isolated_margin(
expect(page.get_by_test_id(tooltip_content).nth(0)).to_have_text(
"Liquidation: 582.81328Margin: 874.21992General account: 998,084.95183"
)
page.get_by_test_id(isolated_margin).click()
page.locator(leverage_input).fill("1")
page.get_by_test_id(confirm_isolated_margin_mode).click()
page.get_by_role("button", name="Isolated 10x").click()
page.locator(leverage_input).clear()
page.locator(leverage_input).type("1")
page.get_by_role("button", name="Confirm").click()
wait_for_toast_confirmation(page)
next_epoch(vega=vega)
expect(page.get_by_test_id("toast-content")).to_have_text(
@@ -54,52 +45,9 @@ def test_switch_cross_isolated_margin(
expect(page.get_by_test_id(tooltip_content).nth(0)).to_have_text(
"Liquidation: 583.62409Margin: 11,109.99996Order: 11,000.00"
)
page.get_by_test_id(cross_margin).click()
page.get_by_test_id(confirm_cross_margin_mode).click()
page.get_by_role("button", name="Cross").click()
page.get_by_role("button", name="Confirm").click()
wait_for_toast_confirmation(page)
next_epoch(vega=vega)
expect(page.locator(margin_row).nth(1)).to_have_text(
"22,109.99996Cross1.0x")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_check_cross_isolated_margin_info(
continuous_market, vega: VegaServiceNull, page: Page):
create_position(vega, continuous_market)
page.goto(f"/#/markets/{continuous_market}")
expect(page.get_by_test_id(current_margin)).to_have_text(
"Current margin874.21992 tDAI")
expect(page.get_by_test_id(available_collateral)
).to_have_text("Available collateral998,084.95183 tDAI")
expect(page.get_by_test_id(additional_margin_required)
).to_have_text("Additional margin required0.00 tDAI")
expect(page.get_by_test_id(liquidation_estimate)
).to_have_text("Liquidation estimate- BTC")
page.get_by_test_id(isolated_margin).click()
page.locator(leverage_input).fill("6")
expect(page.get_by_test_id(dialog_content).get_by_test_id("notification")).to_have_text(
"You have an existing position and and open order on this market.Changing the margin mode and leverage will move 2,703.11341 tDAI from your general account to fund the position.")
expect(page.get_by_test_id(dialog_content).get_by_test_id(current_margin)).to_have_text(
"Current margin874.21992 tDAI")
expect(page.get_by_test_id(dialog_content).get_by_test_id(available_collateral)
).to_have_text("Available collateral998,084.95183 tDAI")
expect(page.get_by_test_id(dialog_content).get_by_test_id(additional_margin_required)
).to_have_text("Additional margin required2,703.11341 tDAI")
expect(page.get_by_test_id(dialog_content).get_by_test_id(liquidation_estimate)
).to_have_text("Liquidation estimate95.23553 BTC")
page.get_by_test_id(confirm_isolated_margin_mode).click()
wait_for_toast_confirmation(page)
next_epoch(vega=vega)
expect(page.get_by_test_id("toast-content")).to_have_text(
"ConfirmedYour transaction has been confirmedView in block explorerUpdate margin modeBTC:DAI_2023Isolated margin mode, leverage: 6.0x")
page.get_by_test_id(cross_margin).click()
expect(page.get_by_test_id(dialog_content).get_by_test_id(
"notification")).not_to_be_visible()
expect(page.get_by_test_id(dialog_content).get_by_test_id(current_margin)).to_have_text(
"Current margin4,223.33332 tDAI")
expect(page.get_by_test_id(dialog_content).get_by_test_id(available_collateral)
).to_have_text("Available collateral995,381.83843 tDAI")
expect(page.get_by_test_id(dialog_content).get_by_test_id(additional_margin_required)
).to_have_text("Additional margin required0.00 tDAI")
expect(page.get_by_test_id(dialog_content).get_by_test_id(liquidation_estimate)
).to_have_text("Liquidation estimate0.00 BTC")
@@ -15,8 +15,7 @@ deal_ticket_deposit_dialog_button = "deal-ticket-deposit-dialog-button"
@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
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
yield vega_instance
@@ -25,7 +24,6 @@ def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.skip("tbd - issue only on the sim, should work in vega 0.74.0")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_display_info_and_button_for_deposit(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
@@ -35,9 +35,9 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
# 6002-MDET-003
expect(page.get_by_test_id("market-price")).to_have_text("Mark Price0.00")
# 6002-MDET-004
expect(page.get_by_test_id("market-change")).to_have_text("Change (24h)-")
expect(page.get_by_test_id("market-change")).to_have_text("Change (24h)0.00%0.00")
# 6002-MDET-005
expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)-")
expect(page.get_by_test_id("market-volume")).to_have_text("Volume (24h)- (- BTC)")
# 6002-MDET-008
expect(page.get_by_test_id("market-settlement-asset")).to_have_text(
"Settlement assettDAI"
@@ -4,7 +4,7 @@ from playwright.sync_api import Page, expect
from vega_sim.null_service import VegaServiceNull
from vega_sim.service import MarketStateUpdateType
from datetime import datetime, timedelta
from conftest import init_vega, cleanup_container
from conftest import init_vega
from actions.utils import change_keys
from actions.vega import submit_multiple_orders
from fixtures.market import setup_perps_market
@@ -17,9 +17,8 @@ col_amount = '[col-id="amount"]'
class TestPerpetuals:
@pytest.fixture(scope="class")
def vega(self, request):
with init_vega(request) as vega_instance:
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
yield vega_instance
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="class")
def perps_market(self, vega: VegaServiceNull):
+25 -29
View File
@@ -8,13 +8,10 @@ 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_instance:
request.addfinalizer(
lambda: cleanup_container(vega_instance)
) # Register the cleanup function
request.addfinalizer(lambda: cleanup_container(vega_instance)) # Register the cleanup function
yield vega_instance
@@ -128,12 +125,11 @@ def setup_teams_and_games(vega: VegaServiceNull):
n_top_performers=1,
amount=100,
factor=1.0,
window_length=15,
window_length=15
)
vega.wait_fn(1)
vega.wait_for_total_catchup()
# this recurring transfer has been commented out as there appears to be a bug where individual rewards earned are showing on the teams page
""" vega.recurring_transfer(
vega.recurring_transfer(
from_key_name=PARTY_C.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
@@ -147,7 +143,7 @@ def setup_teams_and_games(vega: VegaServiceNull):
amount=100,
factor=1.0,
window_length=15
) """
)
next_epoch(vega)
print(f"[EPOCH: {vega.statistics().epoch_seq}] starting order activity")
@@ -217,21 +213,20 @@ 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("Results (10)")
expect(team_page.get_by_test_id("rank-0")).to_have_text("1")
expect(team_page.get_by_test_id("games-toggle")).to_have_text("Games (1)")
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 • tDAI "
)
# TODO skipped as the amount is wrong
# expect(team_page.get_by_test_id("amount-0")).to_have_text("74") # 50,000,000 on 74.1
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("participatingTeams-0")).to_have_text("2")
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text("3")
expect(team_page.get_by_test_id("participatingMembers-0")).to_have_text("4")
def test_team_page_members_table(team_page: Page):
team_page.get_by_test_id("members-toggle").click()
expect(team_page.get_by_test_id("members-toggle")).to_have_text("Members (4)")
expect(team_page.get_by_test_id("members-toggle")
).to_have_text("Members (4)")
expect(team_page.get_by_test_id("referee-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAt-0")).to_be_visible()
expect(team_page.get_by_test_id("joinedAtEpoch-0")).to_have_text("9")
@@ -242,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("1.2k")
expect(team_page.get_by_test_id("rewards-paid-stat")).to_have_text("78")
def test_switch_teams(team_page: Page, vega: VegaServiceNull):
@@ -264,35 +259,36 @@ def test_switch_teams(team_page: Page, vega: VegaServiceNull):
def test_leaderboard(competitions_page: Page, setup_teams_and_games):
team_name = setup_teams_and_games["team_name"]
competitions_page.reload()
competitions_page.pause()
expect(
competitions_page.get_by_test_id("rank-0").locator(".text-yellow-300")
).to_have_count(1)
expect(
competitions_page.get_by_test_id("rank-1").locator(".text-vega-clight-500")
competitions_page.get_by_test_id(
"rank-1").locator(".text-vega-clight-500")
).to_have_count(1)
expect(competitions_page.get_by_test_id("team-0")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("team-1")).to_have_text(team_name)
expect(competitions_page.get_by_test_id("status-1")).to_have_text("Open")
# 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("0")
expect(competitions_page.get_by_test_id("volume-0")).to_have_text("-")
def test_game_card(competitions_page: Page):
expect(competitions_page.get_by_test_id("active-rewards-card")).to_have_count(2)
expect(competitions_page.get_by_test_id(
"active-rewards-card")).to_have_count(2)
game_1 = competitions_page.get_by_test_id("active-rewards-card").first
expect(game_1).to_be_visible()
expect(game_1.get_by_test_id("entity-scope")).to_have_text("Individual")
expect(game_1.get_by_test_id("locked-for")).to_have_text("1 epoch")
expect(game_1.get_by_test_id("reward-value")).to_have_text("100.00")
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 • tDAI"
)
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 • 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")
-10
View File
@@ -1,10 +0,0 @@
query EpochInfo($epochId: ID) {
epoch(id: $epochId) {
id
timestamps {
start
end
expiry
}
}
}
-36
View File
@@ -1,36 +0,0 @@
fragment TeamEntity on TeamGameEntity {
rank
volume
rewardMetric
rewardEarned
totalRewardsEarned
team {
teamId
membersParticipating {
individual
rank
}
}
}
fragment GameFields on Game {
id
epoch
numberOfParticipants
rewardAssetId
entities {
... on TeamGameEntity {
...TeamEntity
}
}
}
query Games($epochFrom: Int) {
games(epochFrom: $epochFrom, entityScope: ENTITY_SCOPE_TEAMS) {
edges {
node {
...GameFields
}
}
}
}
+29
View File
@@ -29,6 +29,28 @@ 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
@@ -65,6 +87,13 @@ query Team($teamId: ID!, $partyId: ID, $aggregationEpochs: Int) {
}
}
}
games(entityScope: ENTITY_SCOPE_TEAMS) {
edges {
node {
...TeamGameFields
}
}
}
teamMembersStatistics(
teamId: $teamId
aggregationEpochs: $aggregationEpochs
-53
View File
@@ -1,53 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type EpochInfoQueryVariables = Types.Exact<{
epochId?: Types.InputMaybe<Types.Scalars['ID']>;
}>;
export type EpochInfoQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, expiry?: any | null } } };
export const EpochInfoDocument = gql`
query EpochInfo($epochId: ID) {
epoch(id: $epochId) {
id
timestamps {
start
end
expiry
}
}
}
`;
/**
* __useEpochInfoQuery__
*
* To run a query within a React component, call `useEpochInfoQuery` and pass it any options that fit your needs.
* When your component renders, `useEpochInfoQuery` 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 } = useEpochInfoQuery({
* variables: {
* epochId: // value for 'epochId'
* },
* });
*/
export function useEpochInfoQuery(baseOptions?: Apollo.QueryHookOptions<EpochInfoQuery, EpochInfoQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<EpochInfoQuery, EpochInfoQueryVariables>(EpochInfoDocument, options);
}
export function useEpochInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EpochInfoQuery, EpochInfoQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<EpochInfoQuery, EpochInfoQueryVariables>(EpochInfoDocument, options);
}
export type EpochInfoQueryHookResult = ReturnType<typeof useEpochInfoQuery>;
export type EpochInfoLazyQueryHookResult = ReturnType<typeof useEpochInfoLazyQuery>;
export type EpochInfoQueryResult = Apollo.QueryResult<EpochInfoQuery, EpochInfoQueryVariables>;
-84
View File
@@ -1,84 +0,0 @@
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, rewardAssetId: string, 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, rewardAssetId: string, 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
rewardAssetId
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>;
+37 -1
View File
@@ -9,6 +9,10 @@ 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<{
@@ -18,7 +22,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, 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, 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 const TeamFieldsFragmentDoc = gql`
fragment TeamFields on Team {
@@ -54,6 +58,30 @@ 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
@@ -92,6 +120,13 @@ export const TeamDocument = gql`
}
}
}
games(entityScope: ENTITY_SCOPE_TEAMS) {
edges {
node {
...TeamGameFields
}
}
}
teamMembersStatistics(teamId: $teamId, aggregationEpochs: $aggregationEpochs) {
edges {
node {
@@ -103,6 +138,7 @@ export const TeamDocument = gql`
${TeamFieldsFragmentDoc}
${TeamStatsFieldsFragmentDoc}
${TeamRefereeFieldsFragmentDoc}
${TeamGameFieldsFragmentDoc}
${TeamMemberStatsFieldsFragmentDoc}`;
/**
-92
View File
@@ -1,92 +0,0 @@
import compact from 'lodash/compact';
import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards';
import { isActiveReward } from '../../components/rewards-container/active-rewards';
import {
type RecurringTransfer,
type TransferNode,
EntityScope,
IndividualScope,
} from '@vegaprotocol/types';
import {
type AssetFieldsFragment,
useAssetsMapProvider,
} from '@vegaprotocol/assets';
import {
type MarketFieldsFragment,
useMarketsMapProvider,
} from '@vegaprotocol/markets';
import { type ApolloError } from '@apollo/client';
export type EnrichedTransfer = TransferNode & {
asset?: AssetFieldsFragment | null;
markets?: (MarketFieldsFragment | null)[];
};
type RecurringTransferKind = EnrichedTransfer & {
transfer: {
kind: RecurringTransfer;
};
};
export const isScopedToTeams = (
node: TransferNode
): node is RecurringTransferKind =>
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;
}): { data: EnrichedTransfer[]; loading: boolean; error?: ApolloError } => {
const { data, loading, error } = useActiveRewardsQuery({
variables: {
isReward: true,
},
fetchPolicy: 'cache-and-network',
});
const { data: assets, loading: assetsLoading } = useAssetsMapProvider();
const { data: markets, loading: marketsLoading } = useMarketsMapProvider();
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);
})
.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 };
});
return {
data: games,
loading: loading || assetsLoading || marketsLoading,
error,
};
};
+34 -66
View File
@@ -1,80 +1,48 @@
import compact from 'lodash/compact';
import { useActiveRewardsQuery } from '../../components/rewards-container/__generated__/Rewards';
import { isActiveReward } from '../../components/rewards-container/active-rewards';
import {
useGamesQuery,
type GameFieldsFragment,
type TeamEntityFragment,
} from './__generated__/Games';
import orderBy from 'lodash/orderBy';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useEpochInfoQuery } from './__generated__/Epoch';
import { type ApolloError } from '@apollo/client';
EntityScope,
IndividualScope,
type TransferNode,
} from '@vegaprotocol/types';
const TAKE_EPOCHS = 30; // TODO: should this be DEFAULT_AGGREGATION_EPOCHS?
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 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,
} = useEpochInfoQuery({
skip: Boolean(epochFrom),
});
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({
export const useGames = ({
currentEpoch,
onlyActive,
}: {
currentEpoch: number;
onlyActive: boolean;
}) => {
const { data, loading, error } = useActiveRewardsQuery({
variables: {
epochFrom: from,
isReward: true,
},
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 = 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);
});
const games = orderBy(allOrScoped, 'epoch', 'desc');
return {
data: games,
loading: loading || epochLoading,
error: error || epochError,
loading,
error,
};
};
+2 -4
View File
@@ -4,7 +4,6 @@ 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();
@@ -20,8 +19,7 @@ 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 { stats } = useTeam(team?.teamId);
const { data: games } = useGames(team?.teamId);
const { games, stats } = useTeam(team?.teamId);
return { team, stats, games: areTeamGames(games) ? games : undefined, rank };
return { team, stats, games, rank };
};
+27
View File
@@ -1,8 +1,11 @@
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';
@@ -15,6 +18,8 @@ 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) => {
@@ -75,11 +80,33 @@ 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,
};
};
+1 -10
View File
@@ -3,19 +3,10 @@ import { useMemo } from 'react';
import { useTeamsQuery } from './__generated__/Teams';
import { useTeamsStatisticsQuery } from './__generated__/TeamsStatistics';
import compact from 'lodash/compact';
import { type TeamStatsFieldsFragment } from './__generated__/Team';
// 192
export const DEFAULT_AGGREGATION_EPOCHS = 192;
const EMPTY_STATS: Partial<TeamStatsFieldsFragment> = {
totalQuantumVolume: '0',
totalQuantumRewards: '0',
totalGamesPlayed: 0,
gamesPlayed: [],
quantumRewards: [],
};
export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => {
const {
data: teamsData,
@@ -42,7 +33,7 @@ export const useTeams = (aggregationEpochs = DEFAULT_AGGREGATION_EPOCHS) => {
const data = useMemo(() => {
const data = teams.map((t) => ({
...t,
...(stats.find((s) => s.teamId === t.teamId) || EMPTY_STATS),
...stats.find((s) => s.teamId === t.teamId),
}));
return orderBy(data, (d) => Number(d.totalQuantumRewards || 0), 'desc').map(
-3
View File
@@ -48,8 +48,5 @@ export const DEFAULT_CACHE_CONFIG: InMemoryCacheConfig = {
statistics: {
keyFields: false,
},
Game: {
keyFields: false,
},
},
};
+1 -1
View File
@@ -7,7 +7,7 @@ import {
type FirstDataRenderedEvent,
type SortChangedEvent,
type GridReadyEvent,
type GridApi,
GridApi,
} from 'ag-grid-community';
import { useCallback, useEffect, useRef } from 'react';
@@ -281,7 +281,7 @@ export const DealTicket = ({
marginFactor: margin?.marginFactor || '1',
marginMode:
margin?.marginMode || Schema.MarginMode.MARGIN_MODE_CROSS_MARGIN,
includeRequiredPositionMarginInAvailableCollateral: true,
includeCollateralIncreaseInAvailableCollateral: true,
},
!normalizedOrder ||
(normalizedOrder.type !== Schema.OrderType.TYPE_MARKET &&
@@ -89,7 +89,7 @@ export const MarginChange = ({
openVolume,
marketId,
orderMarginAccountBalance: orderMarginAccountBalance || '0',
includeRequiredPositionMarginInAvailableCollateral: true,
includeCollateralIncreaseInAvailableCollateral: true,
orders,
},
skip
@@ -222,7 +222,6 @@ const CrossMarginModeDialog = ({
<NoWalletWarning noWalletConnected={!partyId} isReadOnly={isReadOnly} />
<Button
className="w-full"
data-testid="confirm-cross-margin-mode"
onClick={() => {
partyId &&
!isReadOnly &&
@@ -330,11 +329,7 @@ const IsolatedMarginModeDialog = ({
marginFactor={`${1 / leverage}`}
/>
<NoWalletWarning noWalletConnected={!partyId} isReadOnly={isReadOnly} />
<Button
className="w-full"
type="submit"
data-testid="confirm-isolated-margin-mode"
>
<Button className="w-full" type="submit">
{t('Confirm')}
</Button>
</form>
@@ -368,7 +363,6 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
<div className="mb-4 grid h-8 leading-8 font-alpha text-xs grid-cols-2">
<button
type="button"
data-testid="cross-margin"
onClick={() => setDialog('cross')}
className={classnames('rounded', {
[enabledModeClassName]:
@@ -380,7 +374,6 @@ export const MarginModeSelector = ({ marketId }: { marketId: string }) => {
</button>
<button
type="button"
data-testid="isolated-margin"
onClick={() => setDialog('isolated')}
className={classnames('rounded', {
[enabledModeClassName]:
+1 -4
View File
@@ -2,7 +2,7 @@
"{{liquidityPriceRange}} of mid price": "{{liquidityPriceRange}} of mid price",
"{{probability}} probability price bounds": "{{probability}} probability price bounds",
"24 hour change is unavailable at this time. The price change in the last 120 hours is:": "24 hour change is unavailable at this time. The price change in the last 120 hours is:",
"24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}": "24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}",
"24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}": "24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}}",
"A concept derived from traditional markets. It is a calculated value for the current market price on a market.": "A concept derived from traditional markets. It is a calculated value for the current market price on a market.",
"A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.": "A number that will be calculated by an appropriate stochastic risk model, dependent on the type of risk model used and its parameters.",
"A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.": "A sliding penalty for how much an LP bond is slashed if an LP fails to reach the minimum SLA. This is a network parameter.",
@@ -49,9 +49,6 @@
"Market": "Market",
"Market data": "Market data",
"Market governance": "Market governance",
"Market has not been active for 24 hours. The price change between {{start}} and {{end}} is:": "Market has not been active for 24 hours. The price change between {{start}} and {{end}} is:",
"Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is:": "Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is:",
"Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}": "Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}",
"Market ID": "Market ID",
"Market price": "Market price",
"Market specification": "Market specification",
+1 -12
View File
@@ -90,7 +90,6 @@
"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",
@@ -123,7 +122,6 @@
"Funding rate": "Funding rate",
"Futures": "Futures",
"Games ({{count}})": "Games ({{count}})",
"Results ({{count}})": "Results ({{count}})",
"Generate a referral code to share with your friends and start earning commission.": "Generate a referral code to share with your friends and start earning commission.",
"Generate code": "Generate code",
"Get rewards for providing liquidity.": "Get rewards for providing liquidity.",
@@ -200,7 +198,6 @@
"No funding history data": "No funding history data",
"No future markets.": "No future markets.",
"No games": "No games",
"No game results available": "No game results available",
"No ledger entries to export": "No ledger entries to export",
"No market": "No market",
"No markets": "No markets",
@@ -320,7 +317,6 @@
"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.",
@@ -455,12 +451,5 @@
"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.",
"Results {{games}}": "Results {{games}}",
"End time": "End time",
"Reward asset": "Reward asset",
"Daily reward amount": "Daily reward amount",
"Amount earned this epoch": "Amount earned this epoch",
"Cumulative amount earned": "Cumulative amount earned",
"Game details": "Game details"
"Changes successfully saved to your team.": "Changes successfully saved to your team."
}
@@ -1,8 +1,7 @@
import { type ReactNode } from 'react';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
getDateTimeFormat,
isNumeric,
priceChange,
priceChangePercentage,
} from '@vegaprotocol/utils';
@@ -15,57 +14,29 @@ import { useT } from '../../use-t';
interface Props {
marketId?: string;
decimalPlaces: number;
fallback?: ReactNode;
decimalPlaces?: number;
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, fiveDaysCandles, error } = useCandles({
const { oneDayCandles, error, fiveDaysCandles } = useCandles({
marketId,
});
const nonIdeal = fallback || <span>{'-'}</span>;
if (error || !oneDayCandles || !fiveDaysCandles) {
return nonIdeal;
}
if (fiveDaysCandles.length < 24) {
return (
<Tooltip
description={
<span className="justify-start">
{t(
'Market has not been active for 24 hours. The price change between {{start}} and {{end}} is:',
{
start: getDateTimeFormat().format(
new Date(fiveDaysCandles[0].periodStart)
),
end: getDateTimeFormat().format(
new Date(
fiveDaysCandles[fiveDaysCandles.length - 1].periodStart
)
),
}
)}
<PriceChangeCell
candles={fiveDaysCandles.map((c) => c.close) || []}
decimalPlaces={decimalPlaces}
/>
</span>
}
>
<span>{nonIdeal}</span>
</Tooltip>
);
}
if (oneDayCandles.length < 24) {
if (
fiveDaysCandles &&
fiveDaysCandles.length > 0 &&
(!oneDayCandles || oneDayCandles?.length === 0)
) {
return (
<Tooltip
description={
@@ -80,12 +51,16 @@ export const Last24hPriceChange = ({
</span>
}
>
<span>{nonIdeal}</span>
<span>{fallback}</span>
</Tooltip>
);
}
const candles = oneDayCandles?.map((c) => c.close) || [];
if (error || !isNumeric(decimalPlaces)) {
return <span>{fallback}</span>;
}
const candles = oneDayCandles?.map((c) => c.close) || initialValue || [];
const change = priceChange(candles);
const changePercentage = priceChangePercentage(candles);
@@ -2,7 +2,6 @@ import { calcCandleVolume, calcCandleVolumePrice } from '../../market-utils';
import {
addDecimalsFormatNumber,
formatNumber,
getDateTimeFormat,
isNumeric,
} from '@vegaprotocol/utils';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
@@ -27,17 +26,15 @@ export const Last24hVolume = ({
quoteUnit,
}: Props) => {
const t = useT();
const { oneDayCandles, fiveDaysCandles, error } = useCandles({
const { oneDayCandles, fiveDaysCandles } = useCandles({
marketId,
});
const nonIdeal = <span>{'-'}</span>;
if (error || !oneDayCandles || !fiveDaysCandles) {
return nonIdeal;
}
if (fiveDaysCandles.length < 24) {
if (
fiveDaysCandles &&
fiveDaysCandles.length > 0 &&
(!oneDayCandles || oneDayCandles?.length === 0)
) {
const candleVolume = calcCandleVolume(fiveDaysCandles);
const candleVolumePrice = calcCandleVolumePrice(
fiveDaysCandles,
@@ -58,59 +55,14 @@ export const Last24hVolume = ({
<div>
<span className="flex flex-col">
{t(
'Market has not been active for 24 hours. The volume traded between {{start}} and {{end}} is {{candleVolumeValue}} for a total of {{candleVolumePrice}} {{quoteUnit}}',
{
start: getDateTimeFormat().format(
new Date(fiveDaysCandles[0].periodStart)
),
end: getDateTimeFormat().format(
new Date(
fiveDaysCandles[fiveDaysCandles.length - 1].periodStart
)
),
candleVolumeValue,
candleVolumePrice,
quoteUnit,
}
)}
</span>
</div>
}
>
<span>{nonIdeal}</span>
</Tooltip>
);
}
if (oneDayCandles.length < 24) {
const candleVolume = calcCandleVolume(fiveDaysCandles);
const candleVolumePrice = calcCandleVolumePrice(
fiveDaysCandles,
marketDecimals,
positionDecimalPlaces
);
const candleVolumeValue =
candleVolume && isNumeric(positionDecimalPlaces)
? addDecimalsFormatNumber(
candleVolume,
positionDecimalPlaces,
formatDecimals
)
: '-';
return (
<Tooltip
description={
<div>
<span className="flex flex-col">
{t(
'24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} for a total of ({{candleVolumePrice}} {{quoteUnit}})',
'24 hour change is unavailable at this time. The volume change in the last 120 hours is {{candleVolumeValue}} ({{candleVolumePrice}} {{quoteUnit}})',
{ candleVolumeValue, candleVolumePrice, quoteUnit }
)}
</span>
</div>
}
>
<span>{nonIdeal}</span>
<span>-</span>
</Tooltip>
);
}
@@ -15,6 +15,7 @@ const mockData = [
periodStart: today.toISOString(),
__typename: 'Candle',
},
null,
{
high: '6309988',
low: '6296335',
+1 -6
View File
@@ -18,12 +18,7 @@ export const useCandles = ({ marketId }: { marketId?: string }) => {
skip: !marketId,
});
const fiveDaysCandles = data?.filter((c) => {
if (c.open === '' || c.close === '' || c.high === '' || c.close === '') {
return false;
}
return true;
});
const fiveDaysCandles = data?.filter(Boolean);
const oneDayCandles = fiveDaysCandles?.filter((candle) =>
isCandleLessThan24hOld(candle, yesterday)
+2 -3
View File
@@ -47,9 +47,8 @@ export const marketsProvider = makeDataProvider<
errorPolicy: 'all',
});
export type MarketMap = Record<string, Market>;
export const marketsMapProvider = makeDerivedDataProvider<
MarketMap,
Record<string, Market>,
never,
undefined
>(
@@ -60,7 +59,7 @@ export const marketsMapProvider = makeDerivedDataProvider<
markets[market.id] = market;
return markets;
},
{} as MarketMap
{} as Record<string, Market>
);
}
);
+2 -2
View File
@@ -48,7 +48,7 @@ query EstimatePosition(
$orderMarginAccountBalance: String!
$marginMode: MarginMode!
$marginFactor: String
$includeRequiredPositionMarginInAvailableCollateral: Boolean
$includeCollateralIncreaseInAvailableCollateral: Boolean
) {
estimatePosition(
marketId: $marketId
@@ -60,7 +60,7 @@ query EstimatePosition(
orderMarginAccountBalance: $orderMarginAccountBalance
marginMode: $marginMode
marginFactor: $marginFactor
includeRequiredPositionMarginInAvailableCollateral: $includeRequiredPositionMarginInAvailableCollateral
includeCollateralIncreaseInAvailableCollateral: $includeCollateralIncreaseInAvailableCollateral
# Everywhere in the codebase we expect price values of the underlying to have the right
# number of digits for formatting with market.decimalPlaces. By default the estimatePosition
# query will return a full value requiring formatting using asset.decimals. For consistency
+4 -4
View File
@@ -29,7 +29,7 @@ export type EstimatePositionQueryVariables = Types.Exact<{
orderMarginAccountBalance: Types.Scalars['String'];
marginMode: Types.MarginMode;
marginFactor?: Types.InputMaybe<Types.Scalars['String']>;
includeRequiredPositionMarginInAvailableCollateral?: Types.InputMaybe<Types.Scalars['Boolean']>;
includeCollateralIncreaseInAvailableCollateral?: Types.InputMaybe<Types.Scalars['Boolean']>;
}>;
@@ -130,7 +130,7 @@ export function usePositionsSubscriptionSubscription(baseOptions: Apollo.Subscri
export type PositionsSubscriptionSubscriptionHookResult = ReturnType<typeof usePositionsSubscriptionSubscription>;
export type PositionsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<PositionsSubscriptionSubscription>;
export const EstimatePositionDocument = gql`
query EstimatePosition($marketId: ID!, $openVolume: String!, $averageEntryPrice: String!, $orders: [OrderInfo!], $marginAccountBalance: String!, $generalAccountBalance: String!, $orderMarginAccountBalance: String!, $marginMode: MarginMode!, $marginFactor: String, $includeRequiredPositionMarginInAvailableCollateral: Boolean) {
query EstimatePosition($marketId: ID!, $openVolume: String!, $averageEntryPrice: String!, $orders: [OrderInfo!], $marginAccountBalance: String!, $generalAccountBalance: String!, $orderMarginAccountBalance: String!, $marginMode: MarginMode!, $marginFactor: String, $includeCollateralIncreaseInAvailableCollateral: Boolean) {
estimatePosition(
marketId: $marketId
openVolume: $openVolume
@@ -141,7 +141,7 @@ export const EstimatePositionDocument = gql`
orderMarginAccountBalance: $orderMarginAccountBalance
marginMode: $marginMode
marginFactor: $marginFactor
includeRequiredPositionMarginInAvailableCollateral: $includeRequiredPositionMarginInAvailableCollateral
includeCollateralIncreaseInAvailableCollateral: $includeCollateralIncreaseInAvailableCollateral
scaleLiquidationPriceToMarketDecimals: true
) {
collateralIncreaseEstimate {
@@ -185,7 +185,7 @@ export const EstimatePositionDocument = gql`
* orderMarginAccountBalance: // value for 'orderMarginAccountBalance'
* marginMode: // value for 'marginMode'
* marginFactor: // value for 'marginFactor'
* includeRequiredPositionMarginInAvailableCollateral: // value for 'includeRequiredPositionMarginInAvailableCollateral'
* includeCollateralIncreaseInAvailableCollateral: // value for 'includeCollateralIncreaseInAvailableCollateral'
* },
* });
*/
+1 -1
View File
@@ -1,7 +1,7 @@
import { useState, useCallback } from 'react';
import {
OpenVolumeData,
openVolumeDataProvider,
type OpenVolumeData,
} from './positions-data-providers';
import { useDataProvider } from '@vegaprotocol/data-provider';
+2 -24
View File
@@ -1640,8 +1640,6 @@ export type Game = {
id: Scalars['ID'];
/** Number of participants that took part in the game during the epoch. */
numberOfParticipants: Scalars['Int'];
/** ID of asset in which the rewards were paid. */
rewardAssetId: Scalars['ID'];
};
/** Edge type containing the game metrics and cursor information returned by a GameConnection */
@@ -1706,14 +1704,10 @@ export type IndividualGameEntity = {
rank: Scalars['Int'];
/** The rewards earned by the individual during the epoch */
rewardEarned: Scalars['String'];
/** The rewards earned by the individual during the epoch in quantum value */
rewardEarnedQuantum: Scalars['String'];
/** The reward metric applied to the game */
rewardMetric: DispatchMetric;
/** Total rewards earned by the individual during the game */
totalRewardsEarned: Scalars['String'];
/** Total rewards earned by the individual during the game in quantum value */
totalRewardsEarnedQuantum: Scalars['String'];
/** The volume traded by the individual */
volume: Scalars['String'];
};
@@ -4739,18 +4733,10 @@ export type QuantumRewardsPerEpoch = {
__typename?: 'QuantumRewardsPerEpoch';
/** Epoch for which this information is valid. */
epoch: Scalars['Int'];
/** Total of rewards accumulated over the epoch period expressed in quantum value. */
/** Total of rewards accumulated over the epoch period expressed in quantum value. */
totalQuantumRewards: Scalars['String'];
};
export type QuantumVolumesPerEpoch = {
__typename?: 'QuantumVolumesPerEpoch';
/** Epoch for which this information is valid. */
epoch: Scalars['Int'];
/** Total volume across all markets, accumulated over the epoch period, expressed in quantum value. */
totalQuantumVolumes: Scalars['String'];
};
/** Queries allow a caller to read data and filter data via GraphQL. */
export type Query = {
__typename?: 'Query';
@@ -5081,7 +5067,7 @@ export type QueryestimateOrderArgs = {
export type QueryestimatePositionArgs = {
averageEntryPrice: Scalars['String'];
generalAccountBalance: Scalars['String'];
includeRequiredPositionMarginInAvailableCollateral?: InputMaybe<Scalars['Boolean']>;
includeCollateralIncreaseInAvailableCollateral?: InputMaybe<Scalars['Boolean']>;
marginAccountBalance: Scalars['String'];
marginFactor?: InputMaybe<Scalars['String']>;
marginMode: MarginMode;
@@ -6426,16 +6412,12 @@ export type TeamGameEntity = {
rank: Scalars['Int'];
/** Total rewards earned by the team during the epoch */
rewardEarned: Scalars['String'];
/** Total rewards earned by the team during the epoch in quantum value */
rewardEarnedQuantum: Scalars['String'];
/** Reward metric applied to the game. */
rewardMetric: DispatchMetric;
/** Breakdown of the team members and their contributions to the total team metrics. */
team: TeamParticipation;
/** Total rewards earned by the team for the game */
totalRewardsEarned: Scalars['String'];
/** Total rewards earned by the team for the game in quantum value */
totalRewardsEarnedQuantum: Scalars['String'];
/** Total volume traded by the team */
volume: Scalars['String'];
};
@@ -6449,8 +6431,6 @@ export type TeamMemberStatistics = {
partyId: Scalars['String'];
/** List of rewards over the requested epoch period, expressed in quantum value for each epoch */
quantumRewards: Array<QuantumRewardsPerEpoch>;
/** List of trading volume totals per epoch, for the requested epoch period, expressed in quantum value */
quantumVolumes: Array<QuantumVolumesPerEpoch>;
/** Total number of games played. */
totalGamesPlayed: Scalars['Int'];
/** Total of rewards accumulated over the requested epoch period, expressed in quantum value. */
@@ -6553,8 +6533,6 @@ export type TeamStatistics = {
gamesPlayed: Array<Scalars['String']>;
/** List of rewards over the requested epoch period, expressed in quantum value for each epoch */
quantumRewards: Array<QuantumRewardsPerEpoch>;
/** List of trading volume totals per epoch, over the requested epoch period, expressed in quantum value */
quantumVolumes: Array<QuantumVolumesPerEpoch>;
/** Team ID the statistics are related to. */
teamId: Scalars['String'];
/** Total of games played. */
+1 -1
View File
@@ -41,7 +41,7 @@ const ethereumRequest = <T>(args: RequestArguments): Promise<T> => {
export const LOCAL_SNAP_ID = 'local:http://localhost:8080';
export const DEFAULT_SNAP_ID = 'npm:@vegaprotocol/snap';
export const DEFAULT_SNAP_VERSION = '1.0.1';
export const DEFAULT_SNAP_VERSION = '0.3.1';
type GetSnapsResponse = Record<string, Snap>;