Compare commits

...
Author SHA1 Message Date
Dariusz Majcherczyk 7a8397302b fix(trading): update pubkey-view test - fix 2024-01-20 20:55:09 +01:00
Dariusz Majcherczyk 9a6f0cb021 fix(trading): update pubkey-view test 2024-01-20 20:20:37 +01:00
Dariusz Majcherczyk f29360de86 fix(trading): revert because of issue 10343 - propose market 2024-01-20 19:05:16 +01:00
Dariusz Majcherczyk aff46ef882 fix(trading): revert because of issue 10343 2024-01-20 18:49:19 +01:00
m.ray da5ac5714a Update apps/governance-e2e/src/fixtures/proposals/update-market.json 2024-01-19 14:18:09 +00:00
m.ray 6fd7659c7a Update apps/governance-e2e/src/fixtures/proposals/successor-market.json 2024-01-19 14:17:57 +00:00
m.ray b4a2cfe9bb Update apps/governance-e2e/src/fixtures/proposals/new-market.json 2024-01-19 14:17:44 +00:00
m.ray 45b625cd4d Update apps/governance-e2e/src/fixtures/proposals/new-market-raw.json 2024-01-19 14:17:32 +00:00
Madalina Raicu 4232878edf fix: revert gov and explorer tests 2024-01-19 14:16:50 +00:00
Madalina Raicu 1d9a83fb29 chore: update label for insurance pool balance 2024-01-18 17:20:20 +00:00
Madalina Raicu ba90f0f2b4 fix(trading): remove quadratic slippage factor 2024-01-18 17:15:08 +00:00
Ben 97f1f40f2c chore(trading): remove waits and tests (#5637) 2024-01-18 13:20:58 +00:00
Bartłomiej GłowniaandMatthew Russell a8e6963521 feat(deposits): if wrong network dont show the form and prompt switching to the correct network (#5571)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-01-18 10:41:19 +01:00
m.ray a2bffa1dfd fix(trading): alphabetically order translations in trading.json (#5635) 2024-01-17 21:28:20 +00:00
Ben 9253e8067a chore(trading): market-sim branch parralel fix (#5630) 2024-01-17 14:44:11 +00:00
Bartłomiej Głownia 58972f0a11 feat(ui-toolkit): add leverage slider component (#5594) 2024-01-17 13:46:11 +00:00
Ben a3c55fd7c3 chore(trading): update vega-market-sim to use to 0.74.0-preview.2 (#5603) 2024-01-17 12:19:52 +00:00
Matthew Russell c63cba1071 chore(trading): enable trading view on mainnet (#5628) 2024-01-16 18:16:58 +00:00
m.ray bc9d87fe30 chore(trading): clean up triggering ratio (#5604) 2024-01-16 18:10:32 +01:00
m.ray 6a21862378 fix(trading): use No trading instead of Trading terminated (#5624) 2024-01-16 18:30:42 +02:00
Ben 0fb4348e9c chore(trading): upload market-sim logs (#5622) 2024-01-15 20:56:52 +00:00
m.ray b751bcf17d feat(trading): mobile responsiveness tweaks (#5613) 2024-01-15 15:26:13 +00:00
m.ray d32f27fcb1 feat(trading): mobile responsiveness - market selector (#5582) 2024-01-11 11:23:48 +00:00
m.ray d05cd6a2ed fix(trading): tiny scroll for rewards (#5586) 2024-01-10 15:49:58 +00:00
m.ray c003e5fa30 fix(trading): liquidity table improve readability and remove grouping (#5598) 2024-01-10 15:49:35 +00:00
Ben f62d3289ab chore(trading): retry on http error (#5601) 2024-01-10 15:44:01 +00:00
Ben a3d3d18c5c chore(trading): fix price monitoring test (#5600) 2024-01-10 14:56:17 +00:00
77ca101781 chore(trading): merge main back in develop (#5597)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Edd <edd@vega.xyz>
2024-01-10 10:31:15 +00:00
Ben d238c37d0c chore(trading): vesting test (#5595) 2024-01-10 10:07:59 +00:00
Edd 79feb485f6 fix(governance): improve error message for configuration error (#5589) 2024-01-09 17:21:56 +00:00
Ben 6aa5c3b6e3 chore(trading): rewards page e2e (#5578) 2024-01-09 15:54:40 +00:00
Ben 82abc13fda chore(trading): skip test due to issue (#5583) 2024-01-08 16:16:03 +00:00
Edd 933f07cddf fix(explorer): minor tidyup of deterministic order view (#5573) 2024-01-08 13:25:03 +00:00
Matthew Russell c8067669c2 fix(trading): ensure fees queries always fetch on mount and poll (#5566) 2024-01-05 12:43:30 +00:00
78f5a9c520 feat(trading): activity streaks, reward hoarder bonus and active rewards (#5491)
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: bwallacee <ben@vega.xyz>
2024-01-05 11:16:59 +00:00
Ben 462066959d chore(trading): update vega (#5563) 2024-01-03 12:00:00 +00:00
daro-maj f12f5ab961 chore(trading): fix volume issue for market selector tests (#5564) 2024-01-03 12:04:49 +01:00
147 changed files with 3807 additions and 1564 deletions
+46 -3
View File
@@ -19,7 +19,7 @@ jobs:
create-docker-image:
name: Create docker image for console-test
runs-on: ubuntu-22.04
timeout-minutes: 20
timeout-minutes: 90
steps:
#----------------------------------------------
# check-out frontend-monorepo
@@ -138,7 +138,7 @@ jobs:
name: run-tests
runs-on: 8-cores
needs: [create-docker-image, console-test-branch]
timeout-minutes: 20
timeout-minutes: 90
steps:
#----------------------------------------------
# load docker image
@@ -205,7 +205,7 @@ jobs:
# run tests
#----------------------------------------------
- name: Run tests
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=90
working-directory: apps/trading/e2e
#----------------------------------------------
# upload traces
@@ -227,3 +227,46 @@ jobs:
name: worker-logs
path: ./logs/
retention-days: 15
#----------------------------------------------
# ----- upload market-sim logs -----
#----------------------------------------------
- name: Prepare and Zip market-sim-logs
if: always()
run: |
parent_dir="/tmp/market-sim-logs"
echo "Creating parent directory at $parent_dir"
mkdir -p "$parent_dir"
echo "Waiting for vega-sim-* folders to be created..."
sleep 10 # Waits 10 seconds to ensure all folders are created
echo "Before searching for vega-sim-* folders in /tmp..."
folders=$(find /tmp -mindepth 1 -type d -name 'vega-sim-*' -print) || echo "Find command failed with exit code $?"
echo "After searching for vega-sim-* folders in /tmp..."
if [ -z "$folders" ]; then
echo "No vega-sim-* folders found."
exit 0
fi
echo "Moving vega-sim-* folders to $parent_dir"
echo "$folders" | xargs -I {} mv {} "$parent_dir/"
echo "Checking if $parent_dir is not empty..."
if [ "$(ls -A $parent_dir)" ]; then
echo "Zipping the parent directory..."
zip -r market-sim-logs.zip "$parent_dir" && echo "Zip file created successfully."
else
echo "$parent_dir is empty. No zip file created."
exit 0
fi
shell: /usr/bin/bash -e {0}
- name: Upload market-sim-logs
uses: actions/upload-artifact@v3
if: always()
with:
name: market-sim-logs
path: market-sim-logs.zip
retention-days: 15
@@ -44,7 +44,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
cy.getByTestId('icon-cross').click();
});
it.skip('Proposal page displayed on mobile', function () {
it('Proposal page displayed on mobile', function () {
const proposalTitle = 'Add Lorem Ipsum market';
cy.common_switch_to_mobile_and_click_toggle();
@@ -55,7 +55,7 @@ context('Proposal page', { tags: '@smoke' }, function () {
});
});
it('Able to view new asset proposal', function () {
it.skip('Able to view new asset proposal', function () {
const proposalTitle = 'Test new asset proposal';
const newAssetProposalBody = getNewAssetTxBody();
cy.VegaWalletSubmitProposal(newAssetProposalBody);
@@ -60,7 +60,7 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
}
return (
<span className="whitespace-nowrap">
<span>
{useName && <Icon size={4} name="cube" className="mr-2" />}
<Link
className="underline font-mono"
@@ -60,7 +60,7 @@ const DeterministicOrderDetails = ({
const o = data.orderByID;
return (
<div className={wrapperClasses}>
<div className="mb-12 lg:mb-0">
<div className="mb-0">
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
<h2 className="text-3xl font-bold mb-4 display-5">
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
@@ -89,9 +89,9 @@ const DeterministicOrderDetails = ({
<span>{t('Reference')}</span>: {o.reference}
</p>
) : null}
<div className="grid md:grid-cols-5 gap-x-6 mt-4">
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">
<div className="grid grid-cols-2 md:grid-cols-5 gap-x-6 mt-4">
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Status')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
@@ -99,15 +99,17 @@ const DeterministicOrderDetails = ({
</h5>
</div>
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Size')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
<SizeInMarket size={o.size} marketId={o.market.id} />
</h5>
</div>
<div className="">
<h2 className="text-2xl font-bold text-dark mb-4">
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Version')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
@@ -115,8 +117,8 @@ const DeterministicOrderDetails = ({
</h5>
</div>
{o.type ? (
<div className="">
<h2 className="text-2xl font-bold text-dark mb-4">
<div className="mb-6 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-0 md:mb-4">
{t('Type')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
@@ -30,12 +30,12 @@ export const Signature = ({ signature }: SignatureProps) => {
return (
<div className="inline-flex border rounded signature-component relative pr-[20px]">
<span
<div
className="bg-gray-100 px-2.5 py-0.5 text-xs text-gray-500 select-none cursor-default"
title={`Version ${signature.version}`}
title={`${signature.algo}`}
>
{signature.algo}
</span>
<span>v{signature.version}</span>
</div>
<div
className={
isOpen
+1 -1
View File
@@ -73,7 +73,7 @@ export const Layout = () => {
<ProtocolUpgradeInProgressNotification />
</div>
<div className={fixedWidthClasses}>
<main className="p-4">
<main className="md:p-4">
{!isHome && <BreadcrumbsContainer className="mb-4" />}
<Outlet />
</main>
@@ -55,7 +55,10 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
cy.getByTestId('dialog-content')
.first()
.within(() => {
cy.get('h1').should('have.text', 'Transaction failed');
cy.getByTestId('dialog-title').should(
'have.text',
'Transaction failed'
);
cy.getByTestId('Error').should('have.text', expectedErrorTxt);
});
});
@@ -112,6 +112,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
performanceHysteresisEpochs: 2,
slaCompetitionFactor: '0.1',
},
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
quadraticSlippageFactor: '0',
instrument: {
name: 'Token test market',
@@ -196,6 +197,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
timeWindow: '3600',
scalingFactor: 10,
},
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
triggeringRatio: '0.7',
auctionExtension: '1',
},
@@ -240,6 +242,7 @@ export function createSuccessorMarketProposalTxBody(
decimalPlaces: '5',
positionDecimalPlaces: '5',
linearSlippageFactor: '0.001',
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
quadraticSlippageFactor: '0',
liquiditySlaParameters: {
priceRange: '0.5',
@@ -334,6 +337,7 @@ export function createSuccessorMarketProposalTxBody(
timeWindow: '3600',
scalingFactor: 10,
},
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
triggeringRatio: '0.7',
auctionExtension: '1',
},
@@ -205,7 +205,6 @@ query Proposal(
}
}
liquidityMonitoringParameters {
triggeringRatio
targetStakeParameters {
timeWindow
scalingFactor
@@ -213,7 +212,6 @@ query Proposal(
}
positionDecimalPlaces
linearSlippageFactor
quadraticSlippageFactor
}
... on UpdateMarket {
marketId
@@ -366,7 +364,6 @@ query Proposal(
}
}
liquidityMonitoringParameters {
triggeringRatio
targetStakeParameters {
timeWindow
scalingFactor
File diff suppressed because one or more lines are too long
+7 -2
View File
@@ -22,7 +22,12 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TEAM_COMPETITION=true
# NX_DISABLE_CLOSE_POSITION=false
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+3
View File
@@ -28,3 +28,6 @@ NX_REFERRALS=true
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
+2
View File
@@ -54,3 +54,5 @@ To run the UI automation tests with a mocked API, run:
```bash
yarn nx run trading-e2e:e2e
```
To run tests with market sim please read [the readme](e2e/README.md).
@@ -314,7 +314,7 @@ describe('Closed', () => {
});
it('display market actions', async () => {
// Use market with a succcessor Id as the actions dropdown will optionally
// Use market with a successor Id as the actions dropdown will optionally
// show a link to the successor market
const marketsWithSuccessorAndParent = [
{
@@ -137,6 +137,8 @@ const ClosedMarketsDataGrid = ({
headerName: t('Market'),
field: 'code',
cellRenderer: 'MarketCodeCell',
width: 150,
resizable: true,
},
{
headerName: t('Status'),
@@ -280,6 +282,7 @@ const ClosedMarketsDataGrid = ({
return (
<AgGrid
rowData={rowData}
defaultColDef={COL_DEFS.default}
columnDefs={colDefs}
getRowId={({ data }) => data.id}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
@@ -17,6 +17,7 @@ const defaultColDef = {
filter: true,
resizable: true,
filterParams: { buttons: ['reset'] },
minWidth: 120,
};
const components = {
@@ -53,6 +53,7 @@ export const MarketsPage = () => {
size="extra-small"
data-testid="propose-new-market"
href={externalLink}
target="_blank"
>
{t('Propose a new market')}
</TradingAnchorButton>
@@ -29,6 +29,7 @@ export const useColumnDefs = () => {
{
headerName: t('Market'),
field: 'tradableInstrument.instrument.code',
pinned: true,
cellRenderer: ({
value,
data,
@@ -4,6 +4,7 @@ import { useT } from '../../lib/use-t';
import { RewardsContainer } from '../../components/rewards-container';
import { usePageTitleStore } from '../../stores';
import { ErrorBoundary } from '../../components/error-boundary';
import { TinyScroll } from '@vegaprotocol/ui-toolkit';
export const Rewards = () => {
const t = useT();
@@ -16,10 +17,10 @@ export const Rewards = () => {
}, [updateTitle, title]);
return (
<ErrorBoundary feature="rewards">
<div className="container mx-auto p-4">
<TinyScroll className="p-4 max-h-full overflow-auto">
<h1 className="px-4 pb-4 text-2xl">{title}</h1>
<RewardsContainer />
</div>
</TinyScroll>
</ErrorBoundary>
);
};
@@ -12,6 +12,7 @@ import { VegaWalletProvider } from '@vegaprotocol/wallet';
import type { ReactNode } from 'react';
import { Web3Provider } from './web3-provider';
import { useT } from '../../lib/use-t';
import { DataLoader } from './data-loader';
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
const t = useT();
@@ -52,28 +53,38 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
/>
}
>
<Web3Provider
<DataLoader
skeleton={<AppLoader />}
failure={
<AppFailure title={t('Could not configure web3 provider')} />
<AppFailure
title={t('Could not load market data or asset data')}
error={error}
/>
}
>
<VegaWalletProvider
config={{
network: VEGA_ENV,
vegaUrl: VEGA_URL,
vegaWalletServiceUrl: VEGA_WALLET_URL,
links: {
explorer: VEGA_EXPLORER_URL,
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
chromeExtensionUrl: CHROME_EXTENSION_URL,
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
},
}}
<Web3Provider
skeleton={<AppLoader />}
failure={
<AppFailure title={t('Could not configure web3 provider')} />
}
>
{children}
</VegaWalletProvider>
</Web3Provider>
<VegaWalletProvider
config={{
network: VEGA_ENV,
vegaUrl: VEGA_URL,
vegaWalletServiceUrl: VEGA_WALLET_URL,
links: {
explorer: VEGA_EXPLORER_URL,
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
chromeExtensionUrl: CHROME_EXTENSION_URL,
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
},
}}
>
{children}
</VegaWalletProvider>
</Web3Provider>
</DataLoader>
</NodeGuard>
</NetworkLoader>
);
@@ -0,0 +1,34 @@
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { useMarketsMapProvider } from '@vegaprotocol/markets';
import type { ReactNode } from 'react';
export const DataLoader = ({
children,
failure,
skeleton,
}: {
children: ReactNode;
failure: ReactNode;
skeleton: ReactNode;
}) => {
// Query all markets and assets to ensure they are cached
const { data: markets, error, loading } = useMarketsMapProvider();
const {
data: assets,
error: errorAssets,
loading: loadingAssets,
} = useAssetsMapProvider();
if (loading || loadingAssets) {
// eslint-disable-next-line
return <>{skeleton}</>;
}
if (error || errorAssets || !markets || !assets) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{failure}</>;
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
};
@@ -38,7 +38,11 @@ export const FeesContainer = () => {
const { data: markets, loading: marketsLoading } = useMarketList();
const { data: programData, loading: programLoading } =
useDiscountProgramsQuery({ errorPolicy: 'ignore' });
useDiscountProgramsQuery({
errorPolicy: 'ignore',
fetchPolicy: 'cache-and-network',
pollInterval: 15000,
});
const volumeDiscountWindowLength =
programData?.currentVolumeDiscountProgram?.windowLength || 1;
@@ -49,6 +53,8 @@ export const FeesContainer = () => {
partyId: pubKey || '',
},
skip: !pubKey,
fetchPolicy: 'cache-and-network',
pollInterval: 15000,
});
const previousEpoch = (Number(feesData?.epoch.id) || 0) - 1;
@@ -1,4 +1,5 @@
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
export const useReferralStats = (
previousEpoch?: number,
referralStats?: NonNullable<
@@ -42,13 +42,9 @@ export const LiquidityHeader = () => {
const assetDecimalPlaces = asset?.decimals || 0;
const symbol = asset?.symbol;
const triggeringRatio =
market?.liquidityMonitoringParameters.triggeringRatio || '1';
const { percentage, status } = useCheckLiquidityStatus({
suppliedStake: suppliedStake || 0,
targetStake: targetStake || 0,
triggeringRatio,
});
const feesObject = feesPaidRes?.paidLiquidityFees?.edges?.find(
@@ -47,9 +47,6 @@ export const MarketLiquiditySupplied = ({
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
const triggeringRatio = Number(
params.market_liquidity_targetstake_triggering_ratio
);
const variables = useMemo(
() => ({
@@ -94,7 +91,6 @@ export const MarketLiquiditySupplied = ({
const { percentage, status } = useCheckLiquidityStatus({
suppliedStake: market?.suppliedStake || 0,
targetStake: market?.targetStake || 0,
triggeringRatio,
});
const showMessage =
@@ -97,9 +97,9 @@ const MarketData = ({
return (
<>
<div className="w-2/5" role="gridcell">
<div className="w-2/6" role="gridcell">
<h3 className="flex items-baseline">
<span className="overflow-hidden text-sm lg:text-base text-ellipsis whitespace-nowrap">
<span className="overflow-hidden text-xs md:text-sm lg:text-base text-ellipsis whitespace-nowrap">
{market.tradableInstrument.instrument.code}
</span>
{allProducts && productType && (
@@ -113,7 +113,7 @@ const MarketData = ({
)}
</div>
<div
className="w-1/5 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis"
className="w-2/6 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis text-right"
title={symbol}
data-testid="market-selector-price"
role="gridcell"
@@ -121,14 +121,14 @@ const MarketData = ({
{price} {symbol}
</div>
<div
className="w-1/5 overflow-hidden text-xs text-right lg:text-sm whitespace-nowrap text-ellipsis"
className="w-2/6 sm:w-1/6 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis text-right"
title={t('24h vol')}
data-testid="market-selector-volume"
role="gridcell"
>
{volume}
</div>
<div className="flex justify-end w-1/5" role="gridcell">
<div className="hidden sm:w-1/6 sm:flex justify-end" role="gridcell">
{oneDayCandles && (
<Sparkline
width={64}
@@ -64,7 +64,7 @@ export const MarketSelector = ({
setFilter((curr) => ({ ...curr, product }));
}}
/>
<div className="text-sm grid grid-cols-[2fr_1fr_1fr] gap-1 ">
<div className="text-sm flex sm:grid grid-cols-[2fr_1fr_1fr] gap-1 ">
<div className="flex-1">
<TradingInput
onChange={(e) =>
@@ -182,16 +182,16 @@ const MarketList = ({
'p-2 mx-2 border-b border-default text-xs text-secondary'
)}
>
<div className="w-2/5" role="columnheader">
<div className="w-2/6" role="columnheader">
{t('Name')}
</div>
<div className="w-1/5" role="columnheader">
<div className="w-2/6 text-right pr-4" role="columnheader">
{t('Price')}
</div>
<div className="w-1/5 text-right" role="columnheader">
<div className="w-2/6 sm:w-1/6 text-right" role="columnheader">
{t('24h volume')}
</div>
<div className="w-1/5" role="columnheader" />
<div className="hidden sm:w-1/6" role="columnheader" />
</div>
<div ref={listRef}>
<List
+11 -1
View File
@@ -5,6 +5,7 @@ import { useParams } from 'react-router-dom';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { useState } from 'react';
import { useT } from '../../lib/use-t';
import classNames from 'classnames';
/**
* This is only rendered for the mobile navigation
@@ -30,7 +31,16 @@ export const NavHeader = () => {
trigger={
<h1 className="flex gap-1 sm:gap-2 md:gap-4 items-center text-default text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default">
{data ? data.tradableInstrument.instrument.code : t('Select market')}
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
<span
className={classNames(
'transition-transform ease-in-out duration-300',
{
'rotate-180': open,
}
)}
>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={20} />
</span>
</h1>
}
>
@@ -2,14 +2,19 @@ query RewardsPage($partyId: ID!) {
party(id: $partyId) {
id
vestingStats {
# AKA hoarder reward multiplier
rewardBonusMultiplier
quantumBalance
epochSeq
}
activityStreak {
# vesting multiplier
rewardVestingMultiplier
# AKA streak multiplier
activeFor
isActive
inactiveFor
rewardDistributionMultiplier
rewardVestingMultiplier
epoch
tradedVolume
openVolume
}
vestingBalancesSummary {
epoch
@@ -36,6 +41,74 @@ query RewardsPage($partyId: ID!) {
}
}
query ActiveRewards(
$isReward: Boolean
$partyId: ID
$direction: TransferDirection
$pagination: Pagination
) {
transfersConnection(
partyId: $partyId
isReward: $isReward
direction: $direction
pagination: $pagination
) {
edges {
node {
transfer {
amount
id
from
fromAccountType
to
toAccountType
asset {
id
symbol
decimals
name
quantum
status
}
reference
status
timestamp
kind {
... on RecurringTransfer {
startEpoch
endEpoch
dispatchStrategy {
dispatchMetric
dispatchMetricAssetId
marketIdsInScope
entityScope
individualScope
teamScope
nTopPerformers
stakingRequirement
notionalTimeWeightedAveragePositionRequirement
windowLength
lockPeriod
distributionStrategy
rankTable {
startRank
shareRatio
}
}
}
}
reason
}
fees {
transferId
amount
epoch
}
}
}
}
}
query RewardsHistory(
$partyId: ID!
$epochRewardSummariesPagination: Pagination
@@ -92,3 +165,18 @@ query RewardsEpoch {
id
}
}
query MarketForRewards($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
}
}
}
}
@@ -8,7 +8,17 @@ export type RewardsPageQueryVariables = Types.Exact<{
}>;
export type RewardsPageQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string } | null, activityStreak?: { __typename?: 'PartyActivityStreak', rewardVestingMultiplier: string, rewardDistributionMultiplier: string } | null, vestingBalancesSummary: { __typename?: 'PartyVestingBalancesSummary', epoch?: number | null, vestingBalances?: Array<{ __typename?: 'PartyVestingBalance', balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null, lockedBalances?: Array<{ __typename?: 'PartyLockedBalance', balance: string, untilEpoch: number, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null } } | null };
export type RewardsPageQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, vestingStats?: { __typename?: 'PartyVestingStats', rewardBonusMultiplier: string, quantumBalance: string, epochSeq: number } | null, activityStreak?: { __typename?: 'PartyActivityStreak', activeFor: number, isActive: boolean, inactiveFor: number, rewardDistributionMultiplier: string, rewardVestingMultiplier: string, epoch: number, tradedVolume: string, openVolume: string } | null, vestingBalancesSummary: { __typename?: 'PartyVestingBalancesSummary', epoch?: number | null, vestingBalances?: Array<{ __typename?: 'PartyVestingBalance', balance: string, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null, lockedBalances?: Array<{ __typename?: 'PartyLockedBalance', balance: string, untilEpoch: number, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string } }> | null } } | null };
export type ActiveRewardsQueryVariables = Types.Exact<{
isReward?: Types.InputMaybe<Types.Scalars['Boolean']>;
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
direction?: Types.InputMaybe<Types.TransferDirection>;
pagination?: Types.InputMaybe<Types.Pagination>;
}>;
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'];
@@ -26,6 +36,13 @@ export type RewardsEpochQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type RewardsEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string } };
export type MarketForRewardsQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type MarketForRewardsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null } } } } | null };
export const RewardsPageDocument = gql`
query RewardsPage($partyId: ID!) {
@@ -33,10 +50,18 @@ export const RewardsPageDocument = gql`
id
vestingStats {
rewardBonusMultiplier
quantumBalance
epochSeq
}
activityStreak {
rewardVestingMultiplier
activeFor
isActive
inactiveFor
rewardDistributionMultiplier
rewardVestingMultiplier
epoch
tradedVolume
openVolume
}
vestingBalancesSummary {
epoch
@@ -91,6 +116,101 @@ export function useRewardsPageLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
export type RewardsPageQueryHookResult = ReturnType<typeof useRewardsPageQuery>;
export type RewardsPageLazyQueryHookResult = ReturnType<typeof useRewardsPageLazyQuery>;
export type RewardsPageQueryResult = Apollo.QueryResult<RewardsPageQuery, RewardsPageQueryVariables>;
export const ActiveRewardsDocument = gql`
query ActiveRewards($isReward: Boolean, $partyId: ID, $direction: TransferDirection, $pagination: Pagination) {
transfersConnection(
partyId: $partyId
isReward: $isReward
direction: $direction
pagination: $pagination
) {
edges {
node {
transfer {
amount
id
from
fromAccountType
to
toAccountType
asset {
id
symbol
decimals
name
quantum
status
}
reference
status
timestamp
kind {
... on RecurringTransfer {
startEpoch
endEpoch
dispatchStrategy {
dispatchMetric
dispatchMetricAssetId
marketIdsInScope
entityScope
individualScope
teamScope
nTopPerformers
stakingRequirement
notionalTimeWeightedAveragePositionRequirement
windowLength
lockPeriod
distributionStrategy
rankTable {
startRank
shareRatio
}
}
}
}
reason
}
fees {
transferId
amount
epoch
}
}
}
}
}
`;
/**
* __useActiveRewardsQuery__
*
* To run a query within a React component, call `useActiveRewardsQuery` and pass it any options that fit your needs.
* When your component renders, `useActiveRewardsQuery` 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 } = useActiveRewardsQuery({
* variables: {
* isReward: // value for 'isReward'
* partyId: // value for 'partyId'
* direction: // value for 'direction'
* pagination: // value for 'pagination'
* },
* });
*/
export function useActiveRewardsQuery(baseOptions?: Apollo.QueryHookOptions<ActiveRewardsQuery, ActiveRewardsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ActiveRewardsQuery, ActiveRewardsQueryVariables>(ActiveRewardsDocument, options);
}
export function useActiveRewardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ActiveRewardsQuery, ActiveRewardsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ActiveRewardsQuery, ActiveRewardsQueryVariables>(ActiveRewardsDocument, options);
}
export type ActiveRewardsQueryHookResult = ReturnType<typeof useActiveRewardsQuery>;
export type ActiveRewardsLazyQueryHookResult = ReturnType<typeof useActiveRewardsLazyQuery>;
export type ActiveRewardsQueryResult = Apollo.QueryResult<ActiveRewardsQuery, ActiveRewardsQueryVariables>;
export const RewardsHistoryDocument = gql`
query RewardsHistory($partyId: ID!, $epochRewardSummariesPagination: Pagination, $partyRewardsPagination: Pagination, $fromEpoch: Int, $toEpoch: Int) {
epochRewardSummaries(
@@ -202,4 +322,48 @@ export function useRewardsEpochLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio
}
export type RewardsEpochQueryHookResult = ReturnType<typeof useRewardsEpochQuery>;
export type RewardsEpochLazyQueryHookResult = ReturnType<typeof useRewardsEpochLazyQuery>;
export type RewardsEpochQueryResult = Apollo.QueryResult<RewardsEpochQuery, RewardsEpochQueryVariables>;
export type RewardsEpochQueryResult = Apollo.QueryResult<RewardsEpochQuery, RewardsEpochQueryVariables>;
export const MarketForRewardsDocument = gql`
query MarketForRewards($marketId: ID!) {
market(id: $marketId) {
tradableInstrument {
instrument {
id
name
code
metadata {
tags
}
}
}
}
}
`;
/**
* __useMarketForRewardsQuery__
*
* To run a query within a React component, call `useMarketForRewardsQuery` and pass it any options that fit your needs.
* When your component renders, `useMarketForRewardsQuery` 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 } = useMarketForRewardsQuery({
* variables: {
* marketId: // value for 'marketId'
* },
* });
*/
export function useMarketForRewardsQuery(baseOptions: Apollo.QueryHookOptions<MarketForRewardsQuery, MarketForRewardsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<MarketForRewardsQuery, MarketForRewardsQueryVariables>(MarketForRewardsDocument, options);
}
export function useMarketForRewardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MarketForRewardsQuery, MarketForRewardsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<MarketForRewardsQuery, MarketForRewardsQueryVariables>(MarketForRewardsDocument, options);
}
export type MarketForRewardsQueryHookResult = ReturnType<typeof useMarketForRewardsQuery>;
export type MarketForRewardsLazyQueryHookResult = ReturnType<typeof useMarketForRewardsLazyQuery>;
export type MarketForRewardsQueryResult = Apollo.QueryResult<MarketForRewardsQuery, MarketForRewardsQueryVariables>;
@@ -0,0 +1,174 @@
import { render, screen } from '@testing-library/react';
import {
ActiveRewardCard,
applyFilter,
isActiveReward,
} from './active-rewards';
import {
AccountType,
AssetStatus,
DispatchMetric,
DistributionStrategy,
EntityScope,
IndividualScope,
type RecurringTransfer,
type TransferNode,
TransferStatus,
type Transfer,
} from '@vegaprotocol/types';
jest.mock('./__generated__/Rewards', () => ({
useMarketForRewardsQuery: () => ({
data: undefined,
}),
}));
jest.mock('@vegaprotocol/assets', () => ({
useAssetDataProvider: () => {
return {
data: {
assetId: 'asset-1',
},
};
},
}));
describe('ActiveRewards', () => {
const mockRecurringTransfer: RecurringTransfer = {
__typename: 'RecurringTransfer',
startEpoch: 115332,
endEpoch: 115432,
factor: '1',
dispatchStrategy: {
__typename: 'DispatchStrategy',
dispatchMetric: DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED,
dispatchMetricAssetId:
'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
marketIdsInScope: null,
entityScope: EntityScope.ENTITY_SCOPE_INDIVIDUALS,
individualScope: IndividualScope.INDIVIDUAL_SCOPE_ALL,
teamScope: null,
nTopPerformers: '',
stakingRequirement: '',
notionalTimeWeightedAveragePositionRequirement: '',
windowLength: 1,
lockPeriod: 0,
distributionStrategy: DistributionStrategy.DISTRIBUTION_STRATEGY_PRO_RATA,
rankTable: null,
},
};
const mockTransferNode: TransferNode = {
__typename: 'TransferNode',
transfer: {
__typename: 'Transfer',
amount: '1613000000',
id: 'c4e59bd389c8098e6c7669f2d3e1613b9f42d30b1c4c3793ac44380f4c522835',
from: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: 'network',
toAccountType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
asset: {
__typename: 'Asset',
id: 'c9fe6fc24fce121b2cc72680543a886055abb560043fda394ba5376203b7527d',
symbol: 'tUSDC',
decimals: 5,
name: 'tUSDC TEST',
quantum: '1',
status: AssetStatus.STATUS_ENABLED,
source: {
__typename: 'ERC20' as const,
contractAddress: '0x123',
lifetimeLimit: '100',
withdrawThreshold: '100',
},
},
reference: 'reward',
status: TransferStatus.STATUS_PENDING,
timestamp: '2023-12-18T13:05:35.948706Z',
kind: mockRecurringTransfer,
reason: null,
},
fees: [],
};
it('renders with valid props', () => {
render(
<ActiveRewardCard
transferNode={mockTransferNode}
currentEpoch={1}
kind={mockRecurringTransfer}
/>
);
expect(
screen.getByText(/Liquidity provision fees received/i)
).toBeInTheDocument();
expect(screen.getByText('Entity scope')).toBeInTheDocument();
expect(screen.getByText('Average position')).toBeInTheDocument();
expect(screen.getByText('Ends in')).toBeInTheDocument();
expect(screen.getByText('115431 epochs')).toBeInTheDocument();
expect(screen.getByText('Assessed over')).toBeInTheDocument();
expect(screen.getByText('1 epoch')).toBeInTheDocument();
});
describe('isActiveReward', () => {
it('returns true for valid active reward', () => {
const node = {
transfer: {
kind: {
__typename: 'RecurringTransfer',
dispatchStrategy: {},
endEpoch: 10,
},
status: TransferStatus.STATUS_PENDING,
},
} as TransferNode;
expect(isActiveReward(node, 5)).toBeTruthy();
});
it('returns false for invalid active reward', () => {
const node = {
transfer: {
kind: {
__typename: 'RecurringTransfer',
dispatchStrategy: {},
endEpoch: 10,
},
status: TransferStatus.STATUS_PENDING,
},
} as TransferNode;
expect(isActiveReward(node, 15)).toBeFalsy();
});
});
describe('applyFilter', () => {
it('returns true when filter matches dispatch metric label', () => {
const transfer = {
kind: {
__typename: 'RecurringTransfer',
dispatchStrategy: {
dispatchMetric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
},
},
asset: { symbol: 'XYZ' },
} as Transfer;
const filter = { searchTerm: 'average position' };
expect(applyFilter({ transfer }, filter)).toBeTruthy();
});
it('returns true when filter matches asset symbol', () => {
const transfer = {
kind: {
__typename: 'RecurringTransfer',
dispatchStrategy: {
dispatchMetric: DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION,
},
},
asset: { symbol: 'XYZ' },
} as Transfer;
const filter = { searchTerm: 'average position' };
expect(applyFilter({ transfer }, filter)).toBeTruthy();
});
});
});
@@ -0,0 +1,568 @@
import {
useActiveRewardsQuery,
useMarketForRewardsQuery,
} from './__generated__/Rewards';
import { useT } from '../../lib/use-t';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import classNames from 'classnames';
import {
Icon,
type IconName,
Intent,
Tooltip,
VegaIcon,
VegaIconNames,
type VegaIconSize,
TradingInput,
TinyScroll,
} from '@vegaprotocol/ui-toolkit';
import { IconNames } from '@blueprintjs/icons';
import {
DistributionStrategyDescriptionMapping,
DistributionStrategyMapping,
EntityScope,
EntityScopeMapping,
type Maybe,
type Transfer,
type TransferNode,
TransferStatus,
TransferStatusMapping,
DispatchMetric,
DispatchMetricDescription,
DispatchMetricLabels,
type RecurringTransfer,
EntityScopeLabelMapping,
} from '@vegaprotocol/types';
import { Card } from '../card/card';
import { useMemo, useState } from 'react';
import {
type AssetFieldsFragment,
useAssetDataProvider,
useAssetsMapProvider,
} from '@vegaprotocol/assets';
import {
type MarketFieldsFragment,
useMarketsMapProvider,
} from '@vegaprotocol/markets';
export type Filter = {
searchTerm: string;
};
export const isActiveReward = (node: TransferNode, currentEpoch: number) => {
const { transfer } = node;
if (transfer.kind.__typename !== 'RecurringTransfer') {
return false;
}
const { dispatchStrategy } = transfer.kind;
if (!dispatchStrategy) {
return false;
}
if (transfer.kind.endEpoch && transfer.kind.endEpoch < currentEpoch) {
return false;
}
if (transfer.status !== TransferStatus.STATUS_PENDING) {
return false;
}
return true;
};
export const applyFilter = (
node: TransferNode & {
asset?: AssetFieldsFragment | null;
marketIds?: (MarketFieldsFragment | null)[];
},
filter: Filter
) => {
const { transfer } = node;
if (
transfer.kind.__typename !== 'RecurringTransfer' ||
!transfer.kind.dispatchStrategy?.dispatchMetric
) {
return false;
}
if (
DispatchMetricLabels[transfer.kind.dispatchStrategy.dispatchMetric]
.toLowerCase()
.includes(filter.searchTerm.toLowerCase()) ||
transfer.asset?.symbol
.toLowerCase()
.includes(filter.searchTerm.toLowerCase()) ||
EntityScopeLabelMapping[transfer.kind.dispatchStrategy.entityScope]
.toLowerCase()
.includes(filter.searchTerm.toLowerCase()) ||
node.asset?.name
.toLocaleLowerCase()
.includes(filter.searchTerm.toLowerCase()) ||
node.marketIds?.some((m) =>
m?.tradableInstrument?.instrument?.name
.toLocaleLowerCase()
.includes(filter.searchTerm.toLowerCase())
)
) {
return true;
}
return false;
};
export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
const t = useT();
const { data: activeRewardsData } = useActiveRewardsQuery({
variables: {
isReward: true,
},
});
const [filter, setFilter] = useState<Filter>({
searchTerm: '',
});
const { data: assets } = useAssetsMapProvider();
const { data: markets } = useMarketsMapProvider();
const transfers = activeRewardsData?.transfersConnection?.edges
?.map((e) => e?.node as TransferNode)
.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 marketIds =
node.transfer.kind.dispatchStrategy?.marketIdsInScope?.map(
(id) => markets && markets[id]
);
return { ...node, asset, marketIds };
});
if (!transfers || !transfers.length) return null;
return (
<Card title={t('Active rewards')} className="lg:col-span-full">
{transfers.length > 1 && (
<TradingInput
onChange={(e) =>
setFilter((curr) => ({ ...curr, searchTerm: e.target.value }))
}
value={filter.searchTerm}
type="text"
placeholder={t(
'Search by reward dispatch metric, entity scope or asset name'
)}
data-testid="search-term"
className="mb-4 w-20 mr-2"
prependElement={<VegaIcon name={VegaIconNames.SEARCH} />}
/>
)}
<TinyScroll className="grid gap-x-8 gap-y-10 h-fit grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] md:grid-cols-[repeat(auto-fill,_minmax(230px,_1fr))] lg:grid-cols-[repeat(auto-fill,_minmax(320px,_1fr))] xl:grid-cols-[repeat(auto-fill,_minmax(335px,_1fr))] max-h-[40rem] overflow-auto pr-2">
{transfers
.filter((n) => applyFilter(n, filter))
.map((node, i) => {
const { transfer } = node;
if (
transfer.kind.__typename !== 'RecurringTransfer' ||
!transfer.kind.dispatchStrategy?.dispatchMetric
) {
return null;
}
return (
node && (
<ActiveRewardCard
key={i}
transferNode={node}
kind={transfer.kind}
currentEpoch={currentEpoch}
/>
)
);
})}
</TinyScroll>
</Card>
);
};
// This was built to be a status indicator for the rewards based on the transfer status
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StatusIndicator = ({
status,
reason,
}: {
status: TransferStatus;
reason?: Maybe<string> | undefined;
}) => {
const t = useT();
const getIconIntent = (status: string) => {
switch (status) {
case TransferStatus.STATUS_DONE:
return { icon: IconNames.TICK_CIRCLE, intent: Intent.Success };
case TransferStatus.STATUS_CANCELLED:
return { icon: IconNames.MOON, intent: Intent.None };
case TransferStatus.STATUS_PENDING:
return { icon: IconNames.HELP, intent: Intent.Primary };
case TransferStatus.STATUS_REJECTED:
return { icon: IconNames.ERROR, intent: Intent.Danger };
case TransferStatus.STATUS_STOPPED:
return { icon: IconNames.ERROR, intent: Intent.Danger };
default:
return { icon: IconNames.HELP, intent: Intent.Primary };
}
};
const { icon, intent } = getIconIntent(status);
return (
<Tooltip
description={
<span>
{t('Transfer status: {{status}} {{reason}}', {
status: TransferStatusMapping[status],
reason: reason ? `(${reason})` : '',
})}
</span>
}
>
<span
className={classNames(
{
'text-gray-700 dark:text-gray-300': intent === Intent.None,
'text-vega-blue': intent === Intent.Primary,
'text-vega-green dark:text-vega-green': intent === Intent.Success,
'dark:text-yellow text-yellow-600': intent === Intent.Warning,
'text-vega-red': intent === Intent.Danger,
},
'flex items-start p-1 align-text-bottom'
)}
>
<Icon size={3} name={icon as IconName} />
</span>
</Tooltip>
);
};
export const ActiveRewardCard = ({
transferNode,
currentEpoch,
kind,
}: {
transferNode: TransferNode;
currentEpoch: number;
kind: RecurringTransfer;
}) => {
const t = useT();
const { transfer } = transferNode;
const { dispatchStrategy } = kind;
const marketIds = dispatchStrategy?.marketIdsInScope;
const { data: marketNameData } = useMarketForRewardsQuery({
variables: {
marketId: marketIds ? marketIds[0] : '',
},
});
const marketName = useMemo(() => {
if (marketNameData && marketIds && marketIds.length > 1) {
return 'Specific markets';
} else if (
marketNameData &&
marketIds &&
marketNameData &&
marketIds.length === 1
) {
return marketNameData?.market?.tradableInstrument?.instrument?.name || '';
}
return '';
}, [marketIds, marketNameData]);
const { data: dispatchAsset } = useAssetDataProvider(
dispatchStrategy?.dispatchMetricAssetId || ''
);
if (!dispatchStrategy) {
return null;
}
const { gradientClassName, mainClassName } = getGradientClasses(
dispatchStrategy.dispatchMetric
);
const entityScope = dispatchStrategy.entityScope;
return (
<div>
<div
className={classNames(
'bg-gradient-to-r col-span-full p-0.5 lg:col-auto h-full',
'rounded-lg',
gradientClassName
)}
>
<div
className={classNames(
mainClassName,
'bg-gradient-to-b bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded p-4 flex flex-col gap-4'
)}
>
<div className="flex justify-between gap-4">
<div className="flex flex-col gap-2 items-center text-center">
<EntityIcon transfer={transfer} />
{entityScope && (
<span className="text-muted text-xs">
{EntityScopeLabelMapping[entityScope] || t('Unspecified')}
</span>
)}
</div>
<div className="flex flex-col gap-2 items-center text-center">
<h3 className="flex flex-col gap-1 text-2xl shrink-1 text-center">
<span className="font-glitch">
{addDecimalsFormatNumber(
transferNode.transfer.amount,
transferNode.transfer.asset?.decimals || 0,
6
)}
</span>
<span className="font-alpha">
{transferNode.transfer.asset?.symbol}
</span>
</h3>
{
<Tooltip
description={t(
DistributionStrategyDescriptionMapping[
dispatchStrategy.distributionStrategy
]
)}
underline={true}
>
<span className="text-xs">
{
DistributionStrategyMapping[
dispatchStrategy.distributionStrategy
]
}
</span>
</Tooltip>
}
</div>
<div className="flex flex-col gap-2 items-center text-center">
<CardIcon
iconName={VegaIconNames.LOCK}
tooltip={t(
'Number of epochs after distribution to delay vesting of rewards by'
)}
/>
<span className="text-muted text-xs whitespace-nowrap">
{t('numberEpochs', '{{count}} epochs', {
count: kind.dispatchStrategy?.lockPeriod,
})}
</span>
</div>
</div>
<span className="border-[0.5px] border-gray-700" />
<span>
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]}
{marketName ? `${marketName}` : `${dispatchAsset?.name}`}
</span>
<div className="flex items-center gap-8 flex-wrap">
{kind.endEpoch && (
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Ends in')}</span>
<span>
{t('numberEpochs', '{{count}} epochs', {
count: kind.endEpoch - currentEpoch,
})}
</span>
</span>
)}
{
<span className="flex flex-col">
<span className="text-muted text-xs">{t('Assessed over')}</span>
<span>
{t('numberEpochs', '{{count}} epochs', {
count: dispatchStrategy.windowLength,
})}
</span>
</span>
}
</div>
{dispatchStrategy?.dispatchMetric && (
<span className="text-muted text-sm h-[2rem]">
{t(DispatchMetricDescription[dispatchStrategy?.dispatchMetric])}
</span>
)}
<span className="border-[0.5px] border-gray-700" />
<div className="flex justify-between flex-wrap items-center gap-3 text-xs">
<span className="flex flex-col gap-1">
<span className="flex items-center gap-1 text-muted">
{t('Entity scope')}{' '}
</span>
<span className="flex items-center gap-1">
{kind.dispatchStrategy?.teamScope && (
<Tooltip
description={
<span>{kind.dispatchStrategy?.teamScope}</span>
}
>
<span className="flex items-center p-1 rounded-full border border-gray-600">
{<VegaIcon name={VegaIconNames.TEAM} size={16} />}
</span>
</Tooltip>
)}
{kind.dispatchStrategy?.individualScope && (
<Tooltip
description={
<span>{kind.dispatchStrategy?.individualScope}</span>
}
>
<span className="flex items-center p-1 rounded-full border border-gray-600">
{<VegaIcon name={VegaIconNames.MAN} size={16} />}
</span>
</Tooltip>
)}
{/* Shows transfer status */}
{/* <StatusIndicator
status={transfer.status}
reason={transfer.reason}
/> */}
</span>
</span>
<span className="flex flex-col gap-1">
<span className="flex items-center gap-1 text-muted">
{t('Staked VEGA')}{' '}
</span>
<span className="flex items-center gap-1">
{addDecimalsFormatNumber(
kind.dispatchStrategy?.stakingRequirement || 0,
transfer.asset?.decimals || 0
)}
</span>
</span>
<span className="flex flex-col gap-1">
<span className="flex items-center gap-1 text-muted">
{t('Average position')}{' '}
</span>
<span className="flex items-center gap-1">
{addDecimalsFormatNumber(
kind.dispatchStrategy
?.notionalTimeWeightedAveragePositionRequirement || 0,
transfer.asset?.decimals || 0
)}
</span>
</span>
</div>
</div>
</div>
</div>
);
};
const getGradientClasses = (d: DispatchMetric | undefined) => {
switch (d) {
case DispatchMetric.DISPATCH_METRIC_AVERAGE_POSITION:
return {
gradientClassName: 'from-vega-pink-500 to-vega-purple-400',
mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_LP_FEES_RECEIVED:
return {
gradientClassName: 'from-vega-green-500 to-vega-yellow-500',
mainClassName: 'from-vega-green-400 dark:from-vega-green-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_MAKER_FEES_PAID:
return {
gradientClassName: 'from-vega-orange-500 to-vega-pink-400',
mainClassName: 'from-vega-orange-400 dark:from-vega-orange-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_MARKET_VALUE:
case DispatchMetric.DISPATCH_METRIC_RELATIVE_RETURN:
return {
gradientClassName: 'from-vega-purple-500 to-vega-blue-400',
mainClassName: 'from-vega-purple-400 dark:from-vega-purple-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_RETURN_VOLATILITY:
return {
gradientClassName: 'from-vega-blue-500 to-vega-green-400',
mainClassName: 'from-vega-blue-400 dark:from-vega-blue-600 to-20%',
};
case DispatchMetric.DISPATCH_METRIC_VALIDATOR_RANKING:
default:
return {
gradientClassName: 'from-vega-pink-500 to-vega-purple-400',
mainClassName: 'from-vega-pink-400 dark:from-vega-pink-600 to-20%',
};
}
};
const CardIcon = ({
size = 18,
iconName,
tooltip,
}: {
size?: VegaIconSize;
iconName: VegaIconNames;
tooltip: string;
}) => {
return (
<Tooltip description={<span>{tooltip}</span>}>
<span className="flex items-center p-2 rounded-full border border-gray-600">
<VegaIcon name={iconName} size={size} />
</span>
</Tooltip>
);
};
const EntityIcon = ({
transfer,
size = 18,
}: {
transfer: Transfer;
size?: VegaIconSize;
}) => {
if (transfer.kind.__typename !== 'RecurringTransfer') {
return null;
}
const entityScope = transfer.kind.dispatchStrategy?.entityScope;
const getIconName = () => {
switch (entityScope) {
case EntityScope.ENTITY_SCOPE_TEAMS:
return VegaIconNames.TEAM;
case EntityScope.ENTITY_SCOPE_INDIVIDUALS:
return VegaIconNames.MAN;
default:
return VegaIconNames.QUESTION_MARK;
}
};
const iconName = getIconName();
return (
<Tooltip
description={
<span>{entityScope ? EntityScopeMapping[entityScope] : ''}</span>
}
>
<span className="flex items-center p-2 rounded-full border border-gray-600">
{iconName && <VegaIcon name={iconName} size={size} />}
</span>
</Tooltip>
);
};
@@ -33,6 +33,10 @@ import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { RewardsHistoryContainer } from './rewards-history';
import { useT } from '../../lib/use-t';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { ActiveRewards } from './active-rewards';
import { ActivityStreak } from './streaks/activity-streaks';
import { RewardHoarderBonus } from './streaks/reward-hoarder-bonus';
import classNames from 'classnames';
const ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA = [
'bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba', // USDT mainnet
@@ -45,6 +49,7 @@ export const RewardsContainer = () => {
const { params, loading: paramsLoading } = useNetworkParams([
NetworkParams.reward_asset,
NetworkParams.rewards_activityStreak_benefitTiers,
NetworkParams.rewards_vesting_benefitTiers,
NetworkParams.rewards_vesting_baseRate,
]);
@@ -54,6 +59,14 @@ export const RewardsContainer = () => {
const { data: epochData } = useRewardsEpochQuery();
const { rewards_activityStreak_benefitTiers, rewards_vesting_benefitTiers } =
params || {};
const activityStreakBenefitTiers = JSON.parse(
rewards_activityStreak_benefitTiers
);
const vestingBenefitTiers = JSON.parse(rewards_vesting_benefitTiers);
// No need to specify the fromEpoch as it will by default give you the last
// Note activityStreak in query will fail
const { data: rewardsData, loading: rewardsLoading } = useRewardsPageQuery({
@@ -68,6 +81,9 @@ export const RewardsContainer = () => {
pollInterval: 10000,
});
const partyActivityStreak = rewardsData?.party?.activityStreak;
const vestingDetails = rewardsData?.party?.vestingStats;
if (!epochData?.epoch || !assetMap) return null;
const loading = paramsLoading || accountsLoading || rewardsLoading;
@@ -114,74 +130,98 @@ export const RewardsContainer = () => {
]);
return (
<div className="grid auto-rows-min grid-cols-6 gap-3">
{/* Always show reward information for vega */}
<Card
key={params.reward_asset}
title={t('Vega Reward pot')}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
highlight={true}
>
<RewardPot
pubKey={pubKey}
accounts={accounts}
assetId={params.reward_asset}
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
/>
</Card>
<Card
title={t('Vesting')}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
>
<Vesting
pubKey={pubKey}
baseRate={params.rewards_vesting_baseRate}
multiplier={
rewardsData?.party?.activityStreak?.rewardVestingMultiplier
}
/>
</Card>
<Card
title={t('Rewards multipliers')}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
highlight={true}
>
<Multipliers
pubKey={pubKey}
hoarderMultiplier={
rewardsData?.party?.vestingStats?.rewardBonusMultiplier
}
streakMultiplier={
rewardsData?.party?.activityStreak?.rewardDistributionMultiplier
}
/>
</Card>
<div className="flex flex-col w-full gap-3">
<div className="grid auto-rows-min grid-cols-6 gap-3">
{/* Always show reward information for vega */}
<Card
key={params.reward_asset}
title={t('Vega Reward pot')}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
highlight={true}
>
<RewardPot
pubKey={pubKey}
accounts={accounts}
assetId={params.reward_asset}
vestingBalancesSummary={rewardsData?.party?.vestingBalancesSummary}
/>
</Card>
<Card
title={t('Vesting')}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
>
<Vesting
pubKey={pubKey}
baseRate={params.rewards_vesting_baseRate}
multiplier={
rewardsData?.party?.activityStreak?.rewardVestingMultiplier
}
/>
</Card>
<Card
title={t('Rewards multipliers')}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
highlight={true}
>
<Multipliers
pubKey={pubKey}
hoarderMultiplier={
rewardsData?.party?.vestingStats?.rewardBonusMultiplier
}
streakMultiplier={
rewardsData?.party?.activityStreak?.rewardDistributionMultiplier
}
/>
</Card>
{/* Show all other reward pots, most of the time users will not have other rewards */}
{assets
.filter((assetId) => assetId !== params.reward_asset)
.map((assetId) => {
const asset = assetMap ? assetMap[assetId] : null;
{/* Show all other reward pots, most of the time users will not have other rewards */}
{assets
.filter((assetId) => assetId !== params.reward_asset)
.map((assetId) => {
const asset = assetMap ? assetMap[assetId] : null;
if (!asset) return null;
if (!asset) return null;
// Following code is for mitigating an issue due to a core bug where locked and vesting
// balances were incorrectly increased for infrastructure rewards for USDT on mainnet
//
// We don't want to incorrectly show the wring locked/vesting values, but we DO want to
// show the user that they have rewards available to withdraw
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
const accountsForAsset = rewardAccountsAssetMap[asset.id];
const vestedAccount = accountsForAsset?.find(
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
);
// Following code is for mitigating an issue due to a core bug where locked and vesting
// balances were incorrectly increased for infrastructure rewards for USDT on mainnet
//
// We don't want to incorrectly show the wring locked/vesting values, but we DO want to
// show the user that they have rewards available to withdraw
if (ASSETS_WITH_INCORRECT_VESTING_REWARD_DATA.includes(asset.id)) {
const accountsForAsset = rewardAccountsAssetMap[asset.id];
const vestedAccount = accountsForAsset?.find(
(a) => a.type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS
);
// No vested rewards available to withdraw, so skip over USDT
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
return null;
// No vested rewards available to withdraw, so skip over USDT
if (!vestedAccount || Number(vestedAccount.balance) <= 0) {
return null;
}
return (
<Card
key={assetId}
title={t('{{assetSymbol}} Reward pot', {
assetSymbol: asset.symbol,
})}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
>
<RewardPot
pubKey={pubKey}
accounts={accounts}
assetId={assetId}
// Ensure that these values are shown as 0
vestingBalancesSummary={{
lockedBalances: [],
vestingBalances: [],
}}
/>
</Card>
);
}
return (
@@ -197,48 +237,69 @@ export const RewardsContainer = () => {
pubKey={pubKey}
accounts={accounts}
assetId={assetId}
// Ensure that these values are shown as 0
vestingBalancesSummary={{
lockedBalances: [],
vestingBalances: [],
}}
vestingBalancesSummary={
rewardsData?.party?.vestingBalancesSummary
}
/>
</Card>
);
}
return (
<Card
key={assetId}
title={t('{{assetSymbol}} Reward pot', {
assetSymbol: asset.symbol,
})}
className="lg:col-span-3 xl:col-span-2"
loading={loading}
>
<RewardPot
pubKey={pubKey}
accounts={accounts}
assetId={assetId}
vestingBalancesSummary={
rewardsData?.party?.vestingBalancesSummary
}
})}
</div>
<div className="grid auto-rows-min grid-cols-6 gap-3">
{pubKey && activityStreakBenefitTiers.tiers?.length > 0 && (
<Card
title={t('Activity Streak')}
className={classNames(
{
'lg:col-span-6 xl:col-span-3':
activityStreakBenefitTiers.tiers.length <= 4,
'xl:col-span-6': activityStreakBenefitTiers.tiers.length > 4,
},
'hidden md:block'
)}
>
<span className="flex flex-col mr-8 pr-4">
<ActivityStreak
tiers={activityStreakBenefitTiers.tiers}
streak={partyActivityStreak}
/>
</Card>
);
})}
<Card
title={t('Rewards history')}
className="lg:col-span-full"
loading={rewardsLoading}
noBackgroundOnMobile={true}
>
<RewardsHistoryContainer
epoch={Number(epochData?.epoch.id)}
pubKey={pubKey}
assets={assetMap}
/>
</Card>
</span>
</Card>
)}
{pubKey && vestingBenefitTiers.tiers?.length > 0 && (
<Card
title={t('Reward Hoarder Bonus')}
className={classNames(
{
'lg:col-span-6 xl:col-span-3':
vestingBenefitTiers.tiers.length <= 4,
'xl:col-span-6': vestingBenefitTiers.tiers.length > 4,
},
'hidden md:block'
)}
>
<span className="flex flex-col mr-8 pr-4">
<RewardHoarderBonus
tiers={vestingBenefitTiers.tiers}
vestingDetails={vestingDetails}
/>
</span>
</Card>
)}
<ActiveRewards currentEpoch={Number(epochData?.epoch.id)} />
<Card
title={t('Rewards history')}
className="lg:col-span-full hidden md:block"
loading={rewardsLoading}
noBackgroundOnMobile={true}
>
<RewardsHistoryContainer
epoch={Number(epochData?.epoch.id)}
pubKey={pubKey}
assets={assetMap}
/>
</Card>
</div>
</div>
);
};
@@ -343,7 +404,7 @@ export const RewardPot = ({
})}
<VegaIcon name={VegaIconNames.LOCK} size={12} />
</CardTableTH>
<CardTableTD>
<CardTableTD data-testid="locked-value">
{addDecimalsFormatNumberQuantum(
totalLocked.toString(),
rewardAsset.decimals,
@@ -357,7 +418,7 @@ export const RewardPot = ({
assetSymbol: rewardAsset.symbol,
})}
</CardTableTH>
<CardTableTD>
<CardTableTD data-testid="vesting-value">
{addDecimalsFormatNumberQuantum(
totalVesting.toString(),
rewardAsset.decimals,
@@ -369,7 +430,7 @@ export const RewardPot = ({
<CardTableTH>
{t('Available to withdraw this epoch')}
</CardTableTH>
<CardTableTD>
<CardTableTD data-testid="available-to-withdraw-value">
{addDecimalsFormatNumberQuantum(
totalVestedRewardsByRewardAsset.toString(),
rewardAsset.decimals,
@@ -388,6 +449,7 @@ export const RewardPot = ({
)
}
size="small"
data-testid="redeem-rewards-button"
>
{t('Redeem rewards')}
</TradingButton>
@@ -422,12 +484,16 @@ export const Vesting = ({
<CardTable>
<tr>
<CardTableTH>{t('Base rate')}</CardTableTH>
<CardTableTD>{baseRateFormatted}%</CardTableTD>
<CardTableTD data-testid="base-rate-value">
{baseRateFormatted}%
</CardTableTD>
</tr>
{pubKey && (
<tr>
<CardTableTH>{t('Vesting multiplier')}</CardTableTH>
<CardTableTD>{multiplier ? `${multiplier}x` : '-'}</CardTableTD>
<CardTableTD data-testid="vesting multiplier-value">
{multiplier ? `${multiplier}x` : '-'}
</CardTableTD>
</tr>
)}
</CardTable>
@@ -467,13 +533,13 @@ export const Multipliers = ({
<CardTable>
<tr>
<CardTableTH>{t('Streak reward multiplier')}</CardTableTH>
<CardTableTD>
<CardTableTD data-testid="streak-reward-multiplier-value">
{streakMultiplier ? `${streakMultiplier}x` : '-'}
</CardTableTD>
</tr>
<tr>
<CardTableTH>{t('Hoarder reward multiplier')}</CardTableTH>
<CardTableTD>
<CardTableTD data-testid="hoarder-reward-multiplier-value">
{hoarderMultiplier ? `${hoarderMultiplier}x` : '-'}
</CardTableTD>
</tr>
@@ -319,6 +319,7 @@ export const RewardHistoryTable = ({
onClick={() => setIsParty(false)}
size="extra-small"
minimal={isParty}
data-testid="total-distributed-button"
>
{t('Total distributed')}
</TradingButton>
@@ -327,6 +328,7 @@ export const RewardHistoryTable = ({
size="extra-small"
disabled={!pubKey}
minimal={!isParty}
data-testid="earned-by-me-button"
>
{t('Earned by me')}
</TradingButton>
@@ -0,0 +1,66 @@
import { render, screen } from '@testing-library/react';
import { ActivityStreak } from './activity-streaks';
describe('ActivityStreak', () => {
it('renders null when streak is not active', () => {
const tiers: {
minimum_activity_streak: number;
reward_multiplier: string;
vesting_multiplier: string;
}[] = [];
const streak = null;
render(<ActivityStreak tiers={tiers} streak={streak} />);
const component = screen.queryByText(/epochs streak/i);
expect(component).toBeNull();
});
it('renders null when tiers are empty', () => {
const tiers: {
minimum_activity_streak: number;
reward_multiplier: string;
vesting_multiplier: string;
}[] = [];
const streak = {
activeFor: 10,
isActive: true,
inactiveFor: 10,
rewardDistributionMultiplier: '45678',
rewardVestingMultiplier: '45678',
epoch: 10,
tradedVolume: '45678',
openVolume: '45678',
};
render(<ActivityStreak tiers={tiers} streak={streak} />);
const component = screen.queryByText(/epochs streak/i);
expect(component).toBeNull();
});
it('renders the component with tiers and active streak', () => {
const tiers = [
{
minimum_activity_streak: 5,
reward_multiplier: '1.5x',
vesting_multiplier: '2x',
},
{
minimum_activity_streak: 10,
reward_multiplier: '2x',
vesting_multiplier: '3x',
},
];
const streak = {
activeFor: 7,
isActive: true,
inactiveFor: 10,
rewardDistributionMultiplier: '45678',
rewardVestingMultiplier: '45678',
epoch: 10,
tradedVolume: '45678',
openVolume: '45678',
};
render(<ActivityStreak tiers={tiers} streak={streak} />);
const tierLabels = screen.getAllByText(/Tier/i);
expect(tierLabels.length).toBe(3); // 2 tiers + 1 label
});
});
@@ -0,0 +1,226 @@
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { useT } from '../../../lib/use-t';
import classNames from 'classnames';
import BigNumber from 'bignumber.js';
import type { PartyActivityStreak } from '@vegaprotocol/types';
export const safeProgress = (
i: number,
userTierIndex: number,
total: number | string,
progress?: number | string
) => {
if (i < userTierIndex) return 100;
if (i > userTierIndex) return 0;
if (!progress || !total) return 0;
if (new BigNumber(progress).isGreaterThan(total)) return 100;
return new BigNumber(progress)
.multipliedBy(100)
.dividedBy(total || 1)
.toNumber();
};
export const useGetUserTier = (
tiers: {
minimum_activity_streak: number;
reward_multiplier: string;
vesting_multiplier: string;
}[],
progress?: number
) => {
if (!progress) return 0;
if (!tiers || tiers.length === 0) return 0;
let userTier = 0;
let i = 0;
while (
i < tiers.length &&
tiers[userTier].minimum_activity_streak < progress
) {
userTier = i;
i++;
}
if (
i === tiers.length &&
tiers[userTier].minimum_activity_streak <= progress
) {
userTier = i;
}
if (userTier > tiers.length) {
userTier--;
}
return userTier;
};
export const ActivityStreak = ({
tiers,
streak,
}: {
tiers: {
minimum_activity_streak: number;
reward_multiplier: string;
vesting_multiplier: string;
}[];
streak?: PartyActivityStreak | null;
}) => {
const t = useT();
const userTierIndex = useGetUserTier(tiers, streak?.activeFor);
if (!tiers || tiers.length === 0) return null;
const progressBarHeight = 'h-10';
return (
<>
<div className="flex flex-col gap-1 w-full">
<div className="flex flex-col gap-1">
<div
className="grid"
style={{
gridTemplateColumns:
'repeat(' + tiers.length + ', minmax(0, 1fr))',
}}
>
{tiers.map((tier, index) => {
return (
<div key={index} className="flex justify-end -mr-[2.85rem]">
<span className="flex flex-col items-center gap-4 justify-between">
<span className="flex flex-col items-center gap-1">
<span className="flex flex-col items-center font-medium">
<span className="text-sm">
{t('Tier {{tier}}', {
tier: index + 1,
})}
</span>
<span className="text-muted text-xs">
{t('numberEpochs', '{{count}} epochs', {
count: tier.minimum_activity_streak,
})}
</span>
</span>
<span
className={classNames(
'text-xs flex flex-col items-center justify-center px-2 py-1 rounded-lg text-white border',
{
'border-pink-600 bg-pink-900': index % 6 === 0,
'border-purple-600 bg-purple-900': index % 6 === 1,
'border-blue-600 bg-blue-900': index % 6 === 2,
'border-orange-600 bg-orange-900': index % 6 === 3,
'border-green-600 bg-green-900': index % 6 === 4,
'border-yellow-600 bg-yellow-900': index % 6 === 5,
}
)}
>
<span>
{t('Reward {{reward}}x', {
reward: tier.reward_multiplier,
})}
</span>
<span>
{t('Vesting {{vesting}}x', {
vesting: tier.vesting_multiplier,
})}
</span>
</span>
</span>
<span
className={classNames(
{
'text-pink-500': index % 6 === 0,
'text-purple-500': index % 6 === 1,
'text-blue-500': index % 6 === 2,
'text-orange-500': index % 6 === 3,
'text-green-500': index % 6 === 4,
'text-yellow-500': index % 6 === 5,
},
'leading-[0] font-sans text-[48px]'
)}
>
</span>
</span>
</div>
);
})}
</div>
</div>
<div className="flex items-center gap-1">
{tiers.map((_tier, index) => {
return (
<div
key={index}
className="bg-white dark:bg-gray-800 shadow-card rounded-[100px] grow"
>
<div
className={classNames(
'relative w-full rounded-[100px] bg-gray-200 dark:bg-gray-800',
progressBarHeight
)}
>
<div
className={classNames(
'absolute left-0 top-0 h-full rounded-[100px] bg-gradient-to-r',
{
'from-vega-dark-400 to-vega-dark-200':
userTierIndex === 0 || streak?.isActive === false,
'from-vega-pink-600 to-vega-pink-500':
userTierIndex % 6 === 1,
'from-vega-purple-600 to-vega-purple-500':
userTierIndex % 6 === 2,
'from-vega-blue-600 to-vega-blue-500':
userTierIndex % 6 === 3,
'from-vega-orange-600 to-vega-orange-500':
userTierIndex % 6 === 4,
'from-vega-green-600 to-vega-green-500':
userTierIndex % 6 === 5,
'from-vega-yellow-600 to-vega-yellow-500':
userTierIndex % 6 === 0,
}
)}
style={{
width:
safeProgress(
index,
userTierIndex,
tiers[index].minimum_activity_streak,
streak?.activeFor
) + '%',
}}
></div>
</div>
</div>
);
})}
</div>
<div className="flex items-center gap-1">
<VegaIcon name={VegaIconNames.STREAK} />
<span className="flex flex-col">
{streak?.isActive && (
<span data-testid="epoch-streak">
{t('userActive', '{{active}} trader: {{count}} epochs so far', {
active: streak?.isActive ? 'Active' : 'Inactive',
count: streak?.activeFor || 0,
})}{' '}
{userTierIndex > 0 &&
new BigNumber(
tiers[0].minimum_activity_streak
).isLessThanOrEqualTo(streak?.activeFor || 0) &&
t('(Tier {{tier}} as of last epoch)', {
tier: userTierIndex,
})}
</span>
)}
</span>
</div>
</div>
</>
);
};
@@ -0,0 +1,58 @@
import { render, screen } from '@testing-library/react';
import { RewardHoarderBonus } from './reward-hoarder-bonus';
import type { PartyVestingStats } from '@vegaprotocol/types';
describe('RewardHoarderBonus', () => {
it('renders null when vestingDetails is not provided', () => {
const tiers: {
minimum_quantum_balance: string;
reward_multiplier: string;
}[] = [];
const vestingDetails = null;
render(
<RewardHoarderBonus tiers={tiers} vestingDetails={vestingDetails} />
);
const component = screen.queryByText(/Reward bonus/i);
expect(component).toBeNull();
});
it('renders null when tiers are empty', () => {
const tiers: {
minimum_quantum_balance: string;
reward_multiplier: string;
}[] = [];
const vestingDetails: PartyVestingStats = {
epochSeq: 0,
rewardBonusMultiplier: '1.5',
quantumBalance: '100',
};
render(
<RewardHoarderBonus tiers={tiers} vestingDetails={vestingDetails} />
);
const component = screen.queryByText(/Reward bonus/i);
expect(component).toBeNull();
});
it('renders the component with tiers and vestingDetails', () => {
const tiers = [
{
minimum_quantum_balance: '50',
reward_multiplier: '1.5x',
},
{
minimum_quantum_balance: '100',
reward_multiplier: '2x',
},
];
const vestingDetails = {
epochSeq: 0,
rewardBonusMultiplier: '1.5',
quantumBalance: '75',
};
render(
<RewardHoarderBonus tiers={tiers} vestingDetails={vestingDetails} />
);
const tierLabels = screen.getAllByText(/Tier/i);
expect(tierLabels.length).toBe(3); // 2 tiers + 1 label
});
});
@@ -0,0 +1,197 @@
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { useT } from '../../../lib/use-t';
import classNames from 'classnames';
import type { PartyVestingStats } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js';
import { formatNumber } from '@vegaprotocol/utils';
import { safeProgress } from './activity-streaks';
export const useGetUserTier = (
tiers: {
minimum_quantum_balance: string;
reward_multiplier: string;
}[],
progress?: number | string
) => {
if (!progress) return 0;
if (!tiers || tiers.length === 0) return 0;
let userTier = 0;
let i = 0;
let minProgress = '0';
while (i < tiers.length && new BigNumber(minProgress).isLessThan(progress)) {
userTier = i;
i++;
minProgress = tiers[userTier].minimum_quantum_balance;
}
if (
i === tiers.length &&
new BigNumber(minProgress).isLessThanOrEqualTo(progress)
) {
userTier = i;
}
if (userTier > tiers.length) {
userTier--;
}
return userTier;
};
export const RewardHoarderBonus = ({
tiers,
vestingDetails,
}: {
tiers: {
minimum_quantum_balance: string;
reward_multiplier: string;
}[];
vestingDetails?: PartyVestingStats | null;
}) => {
const t = useT();
const userTierIndex = useGetUserTier(tiers, vestingDetails?.quantumBalance);
if (!tiers || tiers.length === 0) return null;
// There is only value to compare to the tiers that covers all the user' rewards across all assets
const qUSD = 'qUSD';
const progressBarHeight = 'h-10';
return (
<>
<div className="flex flex-col gap-1 w-full">
<div className="flex flex-col gap-1">
<div
className="grid"
style={{
gridTemplateColumns:
'repeat(' + tiers.length + ', minmax(0, 1fr))',
}}
>
{tiers.map((tier, index) => {
return (
<div key={index} className="flex justify-end -mr-[2.95rem]">
<span className="flex flex-col items-center gap-4 justify-between">
<span className="flex flex-col items-center gap-1">
<span className="flex flex-col items-center font-medium">
<span className="text-sm">
{t('Tier {{tier}}', {
tier: index + 1,
})}
</span>
<span className="text-muted text-xs">
{formatNumber(tier.minimum_quantum_balance)} {qUSD}
</span>
</span>
<span
className={classNames(
'text-xs flex flex-col items-center justify-center px-2 py-1 rounded-lg text-white border',
{
'border-pink-600 bg-pink-900': index % 6 === 0,
'border-purple-600 bg-purple-900': index % 6 === 1,
'border-blue-600 bg-blue-900': index % 6 === 2,
'border-orange-600 bg-orange-900': index % 6 === 3,
'border-green-600 bg-green-900': index % 6 === 4,
'border-yellow-600 bg-yellow-900': index % 6 === 5,
}
)}
>
<span>{t('Reward bonus')}</span>
<span>
{t('{{reward}}x', {
reward: tier.reward_multiplier,
})}
</span>
</span>
</span>
<span
className={classNames(
{
'text-pink-500': index % 6 === 0,
'text-purple-500': index % 6 === 1,
'text-blue-500': index % 6 === 2,
'text-orange-500': index % 6 === 3,
'text-green-500': index % 6 === 4,
'text-yellow-500': index % 6 === 5,
},
'leading-[0] font-sans text-[48px]'
)}
>
</span>
</span>
</div>
);
})}
</div>
</div>
<div className="flex items-center gap-1">
{tiers.map((_tier, index) => {
return (
<div
key={index}
className="bg-white dark:bg-gray-800 shadow-card rounded-[100px] grow"
>
<div
className={classNames(
'relative w-full rounded-[100px] bg-gray-200 dark:bg-gray-800',
progressBarHeight
)}
>
<div
className={classNames(
'absolute left-0 top-0 h-full rounded-[100px] bg-gradient-to-r',
{
'from-vega-dark-400 to-vega-dark-200':
userTierIndex === 0,
'from-vega-pink-600 to-vega-pink-500':
userTierIndex % 6 === 1,
'from-vega-purple-600 to-vega-purple-500':
userTierIndex % 6 === 2,
'from-vega-blue-600 to-vega-blue-500':
userTierIndex % 6 === 3,
'from-vega-orange-600 to-vega-orange-500':
userTierIndex % 6 === 4,
'from-vega-green-600 to-vega-green-500':
userTierIndex % 6 === 5,
'from-vega-yellow-600 to-vega-yellow-500':
userTierIndex % 6 === 0,
}
)}
style={{
width:
safeProgress(
index,
userTierIndex,
tiers[index].minimum_quantum_balance,
vestingDetails?.quantumBalance
) + '%',
}}
></div>
</div>
</div>
);
})}
</div>
<div className="flex items-center gap-1">
<VegaIcon name={VegaIconNames.STREAK} />
<span data-testid="hoarder-bonus-total-hoarded">
{formatNumber(vestingDetails?.quantumBalance || 0)} {qUSD}{' '}
{userTierIndex > 0 &&
new BigNumber(
tiers[0].minimum_quantum_balance
).isLessThanOrEqualTo(vestingDetails?.quantumBalance || 0) &&
t('(Tier {{tier}} as of last epoch)', { tier: userTierIndex })}
</span>
</div>
</div>
</>
);
};
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.73.9
VEGA_VERSION=v0.74.0-preview.2
LOCAL_SERVER=false
+2 -1
View File
@@ -1,2 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.73.9
VEGA_VERSION=v0.74.0-preview.2
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
VEGA_VERSION=v0.73.8
VEGA_VERSION=v0.73.10
LOCAL_SERVER=false
+16 -1
View File
@@ -8,7 +8,7 @@ import docker
import http.server
import sys
from dotenv import load_dotenv
from playwright.sync_api import Error as PlaywrightError
from docker.models.containers import Container
from docker.errors import APIError
from contextlib import contextmanager
@@ -274,3 +274,18 @@ def perps_market(vega, request):
if hasattr(request, "param"):
kwargs.update(request.param)
return setup_perps_market(vega, **kwargs)
@pytest.fixture(autouse=True)
def retry_on_http_error(request):
retry_count = 3
for i in range(retry_count):
try:
yield
return
except requests.exceptions.HTTPError:
if i < retry_count - 1:
print(f"Retrying due to HTTPError (attempt {i+1}/{retry_count})")
else:
raise
+56 -18
View File
@@ -18,14 +18,14 @@ def setup_simple_market(
custom_market_name=market_name,
custom_asset_name="tDAI",
custom_asset_symbol="tDAI",
custom_quantum=1
custom_quantum=1,
):
for wallet in wallets:
vega.create_key(wallet.name)
vega.mint(
MM_WALLET.name,
asset="VOTE",
asset=vega.find_asset_id(symbol="VOTE", enabled=True),
amount=mint_amount,
)
@@ -117,18 +117,30 @@ def setup_simple_successor_market(
return market_id
def setup_opening_auction_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
def setup_opening_auction_market(
vega: VegaService,
market_id: str = None,
buy_orders=default_buy_orders,
sell_orders=default_sell_orders,
add_liquidity=True,
custom_market_name="BTC:DAI_2023",
custom_asset_name="tDAI",
custom_asset_symbol="tDAI",
**kwargs,
):
if not market_exists(vega, market_id):
market_id = setup_simple_market(vega, **kwargs)
market_id = setup_simple_market(
vega,
custom_market_name=custom_market_name,
custom_asset_name=custom_asset_name,
custom_asset_symbol=custom_asset_symbol,
**kwargs,
)
if add_liquidity:
submit_liquidity(vega, MM_WALLET.name, market_id)
submit_multiple_orders(
vega, MM_WALLET.name, market_id, "SIDE_SELL", sell_orders
)
submit_multiple_orders(
vega, MM_WALLET2.name, market_id, "SIDE_BUY", buy_orders
)
submit_multiple_orders(vega, MM_WALLET.name, market_id, "SIDE_SELL", sell_orders)
submit_multiple_orders(vega, MM_WALLET2.name, market_id, "SIDE_BUY", buy_orders)
vega.forward("10s")
vega.wait_fn(1)
@@ -146,13 +158,37 @@ def market_exists(vega: VegaService, market_id: str):
# Add sell orders and buy orders to put on the book
def setup_continuous_market(vega: VegaService, market_id: str = None, buy_orders=default_buy_orders, sell_orders=default_sell_orders, add_liquidity=True, **kwargs):
if not market_exists(vega, market_id) or buy_orders != default_buy_orders or sell_orders != default_sell_orders:
def setup_continuous_market(
vega: VegaService,
market_id: str = None,
buy_orders=default_buy_orders,
sell_orders=default_sell_orders,
add_liquidity=True,
custom_market_name="BTC:DAI_2023",
custom_asset_name="tDAI",
custom_asset_symbol="tDAI",
**kwargs,
):
if (
not market_exists(vega, market_id)
or buy_orders != default_buy_orders
or sell_orders != default_sell_orders
):
market_id = setup_opening_auction_market(
vega, market_id, buy_orders, sell_orders, add_liquidity, **kwargs)
vega,
market_id,
buy_orders,
sell_orders,
add_liquidity,
custom_market_name=custom_market_name,
custom_asset_name=custom_asset_name,
custom_asset_symbol=custom_asset_symbol,
**kwargs,
)
submit_order(vega, "Key 1", market_id, "SIDE_BUY",
sell_orders[0][0], sell_orders[0][1])
submit_order(
vega, "Key 1", market_id, "SIDE_BUY", sell_orders[0][0], sell_orders[0][1]
)
vega.forward("10s")
vega.wait_fn(1)
@@ -171,7 +207,7 @@ def setup_perps_market(
vega.mint(
MM_WALLET.name,
asset="VOTE",
asset=vega.find_asset_id(symbol="VOTE", enabled=True),
amount=mint_amount,
)
@@ -250,6 +286,8 @@ def setup_perps_market(
def market_exists(vega: VegaService, market_id: str):
if market_id is None:
return False
all_markets = vega.all_markets()
all_markets = vega.all_markets()
market_ids = [market.id for market in all_markets]
return market_id in market_ids
print("Checking for market ID:", market_id)
print("Available market IDs:", market_ids)
return market_id in market_ids
+4 -4
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "certifi"
@@ -1160,8 +1160,8 @@ profile = ["pytest-profiling", "snakeviz"]
[package.source]
type = "git"
url = "https://github.com/vegaprotocol/vega-market-sim.git/"
reference = "fix/genesis_panic"
resolved_reference = "de30d2d4c7a1b81a830527ca76473e23ef59de12"
reference = "HEAD"
resolved_reference = "2aed8c94b25d8fa2e376d3b63ca1f9193d28cdfd"
[[package]]
name = "websocket-client"
@@ -1342,4 +1342,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.11"
content-hash = "68ed0de55290a3b929d47eb7f7b031fb7e172261c7bbeb4f554b7c27a4462754"
content-hash = "39ce8400de7bf060857447281ef27bd78c9b1d9639da063b051e3ae6e7887a67"
+1 -1
View File
@@ -9,7 +9,7 @@ packages = [{include = "trading market-sim e2e"}]
[tool.poetry.dependencies]
python = ">=3.9,<3.11"
psutil = "^5.9.5"
vega-sim = {git = "https://github.com/vegaprotocol/vega-market-sim.git/", branch = "fix/genesis_panic"}
vega-sim = {git = "https://github.com/vegaprotocol/vega-market-sim.git/"}
pytest-playwright = "^0.4.2"
docker = "^6.1.3"
pytest-xdist = "^3.3.1"
@@ -14,15 +14,18 @@ market_order = "order-type-Market"
tif = "order-tif"
expire = "expire"
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_buy_order_GTT(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}")
@@ -42,31 +45,26 @@ def test_limit_buy_order_GTT(continuous_market, vega: VegaServiceNull, page: Pag
)
page.get_by_test_id(place_order).click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.get_by_test_id("All").click()
# 7002-SORD-017
expect(page.get_by_role("row").nth(2)).to_contain_text(
"BTC:DAI_2023Futr10+10LimitFilled120.00GTT:"
)
expect(page.get_by_role("row").nth(5)).to_contain_text("10+10LimitFilled120.00GTT:")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("10")
page.get_by_test_id(order_price).fill("120")
page.get_by_test_id(place_order).click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_fn(2)
vega.wait_for_total_catchup()
page.get_by_test_id("All").click()
# 7002-SORD-017
expect(page.get_by_role("row").nth(2)).to_contain_text(
"BTC:DAI_2023Futr10+10LimitFilled120.00GTC"
)
expect(page.get_by_role("row").nth(6)).to_contain_text("10+10LimitFilled120.00GTC")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
@@ -84,13 +82,11 @@ def test_limit_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
)
page.get_by_test_id(place_order).click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.get_by_test_id("All").click()
expect(page.get_by_role("row").nth(2)).to_contain_text(
"BTC:DAI_2023Futr10-10LimitFilled100.00GFN"
)
expect(page.get_by_role("row").nth(7)).to_contain_text("10-10LimitFilled100.00GFN")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_market_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
@@ -107,14 +103,12 @@ def test_market_sell_order(continuous_market, vega: VegaServiceNull, page: Page)
)
page.get_by_test_id(place_order).click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.get_by_test_id("All").click()
expect(page.get_by_role("row").nth(2)).to_contain_text(
"BTC:DAI_2023Futr10-10MarketFilled-IOC"
)
expect(page.get_by_role("row").nth(8)).to_contain_text("10-10MarketFilled-IOC")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_market_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
@@ -124,13 +118,32 @@ def test_market_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
page.get_by_test_id(tif).select_option("Fill or Kill (FOK)")
page.get_by_test_id(place_order).click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.get_by_test_id("All").click()
# 7002-SORD-010
# 0003-WTXN-012
# 0003-WTXN-003
expect(page.get_by_role("row").nth(2)).to_contain_text(
"BTC:DAI_2023Futr10+10MarketFilled-FOK"
)
expect(page.get_by_role("row").nth(9)).to_contain_text("10+10MarketFilled-FOK")
@pytest.mark.usefixtures("risk_accepted")
def test_sidebar_should_be_open_after_reload(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
page.get_by_test_id("Order").click()
expect(page.get_by_test_id("deal-ticket-form")).not_to_be_visible()
page.reload()
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
@pytest.mark.skip("We currently can't approve wallet connection through Sim")
@pytest.mark.usefixtures("risk_accepted")
def test_connect_vega_wallet(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("order-price").fill("101")
page.get_by_test_id("order-connect-wallet").click()
expect(page.locator('[role="dialog"]')).to_be_visible()
page.get_by_test_id("connector-jsonRpc").click()
expect(page.get_by_test_id("wallet-dialog-title")).to_be_visible()
# TODO: accept wallet connection and assert wallet is connected.
expect(page.get_by_test_id("order-type-Limit")).to_be_checked()
expect(page.get_by_test_id("order-price")).to_have_value("101")
@@ -1,35 +0,0 @@
import pytest
from playwright.sync_api import Page, expect
from conftest import init_vega
from fixtures.market import setup_continuous_market
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.skip("We currently can't approve wallet connection through Sim")
@pytest.mark.usefixtures("risk_accepted")
def test_connect_vega_wallet(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("order-price").fill("101")
page.get_by_test_id("order-connect-wallet").click()
expect(page.locator('[role="dialog"]')).to_be_visible()
page.get_by_test_id("connector-jsonRpc").click()
expect(page.get_by_test_id("wallet-dialog-title")).to_be_visible()
# TODO: accept wallet connection and assert wallet is connected.
expect(page.get_by_test_id("order-type-Limit")).to_be_checked()
expect(page.get_by_test_id("order-price")).to_have_value("101")
@pytest.mark.usefixtures("risk_accepted")
def test_sidebar_should_be_open_after_reload(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
page.get_by_test_id("Order").click()
expect(page.get_by_test_id("deal-ticket-form")).not_to_be_visible()
page.reload()
expect(page.get_by_test_id("deal-ticket-form")).to_be_visible()
@@ -27,7 +27,7 @@ submit_stop_order = "place-order"
stop_orders_tab = "Stop orders"
row_table = "row"
cancel = "cancel"
market_name_col = '[col-id="market.tradableInstrument.instrument.code"]'
market_name_col = '[data-testid="market-code"]'
trigger_col = '[col-id="trigger"]'
expiresAt_col = '[col-id="expiresAt"]'
size_col = '[col-id="submission.size"]'
@@ -41,7 +41,6 @@ close_toast = "toast-close"
def create_position(vega: VegaServiceNull, market_id):
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup
@@ -78,7 +77,6 @@ def test_submit_stop_order_rejected(continuous_market, vega: VegaServiceNull, pa
page.get_by_test_id(trigger_price).fill("103")
page.get_by_test_id(order_size).fill("3")
page.get_by_test_id(submit_stop_order).click()
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.get_by_test_id(close_toast).click()
@@ -269,82 +267,6 @@ class TestStopOcoValidation:
def continuous_market(self, vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_stop_market_order_form_validation(self, continuous_market, page: Page):
# 7002-SORD-052
# 7002-SORD-055
# 7002-SORD-056
# 7002-SORD-057
# 7002-SORD-058
# 7002-SORD-064
# 7002-SORD-065
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(stop_order_btn).click()
page.get_by_test_id(stop_market_order_btn).is_visible()
page.get_by_test_id(stop_market_order_btn).click()
expect(
page.get_by_test_id("sidebar-content").get_by_text("Trigger").first
).to_be_visible()
expect(page.locator('[for="triggerDirection-risesAbove"]')).to_have_text(
"Rises above"
)
expect(page.locator('[for="triggerDirection-fallsBelow"]')).to_have_text(
"Falls below"
)
page.get_by_test_id(trigger_price).click()
expect(page.get_by_test_id(trigger_price)).to_be_empty
expect(page.locator('[for="triggerType-price"]')).to_have_text("Price")
expect(page.locator('[for="triggerType-trailingPercentOffset"]')).to_have_text(
"Trailing Percent Offset"
)
expect(page.locator('[for="order-size"]')).to_have_text("Size")
page.get_by_test_id(order_size).click()
expect(page.get_by_test_id(order_size)).to_be_empty
expect(page.get_by_test_id(order_price)).not_to_be_visible()
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_stop_limit_order_form_validation(self, continuous_market, page: Page):
# 7002-SORD-020
# 7002-SORD-021
# 7002-SORD-022
# 7002-SORD-033
# 7002-SORD-034
# 7002-SORD-035
# 7002-SORD-036
# 7002-SORD-037
# 7002-SORD-038
# 7002-SORD-049
# 7002-SORD-050
# 7002-SORD-051
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(stop_order_btn).click()
page.get_by_test_id(stop_limit_order_btn).is_visible()
page.get_by_test_id(stop_limit_order_btn).click()
expect(
page.get_by_test_id("sidebar-content").get_by_text("Trigger").first
).to_be_visible()
expect(page.locator('[for="triggerDirection-risesAbove"]')).to_have_text(
"Rises above"
)
expect(page.locator('[for="triggerDirection-risesAbove"]')).to_be_checked
expect(page.locator('[for="triggerDirection-fallsBelow"]')).to_have_text(
"Falls below"
)
page.get_by_test_id(trigger_price).click()
expect(page.get_by_test_id(trigger_price)).to_be_empty
expect(page.locator('[for="triggerType-price"]')).to_have_text("Price")
expect(page.locator('[for="triggerType-price"]')).to_be_checked
expect(page.locator('[for="triggerType-trailingPercentOffset"]')).to_have_text(
"Trailing Percent Offset"
)
expect(page.locator('[for="order-size"]').first).to_have_text("Size")
expect(page.locator('[for="order-price"]').last).to_have_text("Price")
page.get_by_test_id(order_size).click()
expect(page.get_by_test_id(order_size)).to_be_empty
page.get_by_test_id(order_price).click()
expect(page.get_by_test_id(order_price)).to_be_empty()
@pytest.mark.skip("core issue")
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_maximum_number_of_active_stop_orders(
+1 -3
View File
@@ -53,7 +53,7 @@ FEE_BREAKDOWN_TOOLTIP = "fee-breakdown-tooltip"
PINNED_ROW_LOCATOR = ".ag-pinned-left-cols-container .ag-row"
ROW_LOCATOR = ".ag-center-cols-container .ag-row"
# Col-Ids:
COL_INSTRUMENT_CODE = '[col-id="market.tradableInstrument.instrument.code"]'
COL_INSTRUMENT_CODE = '[data-testid="market-code"]'
COL_CODE = '[col-id="code"]'
COL_SIZE = '[col-id="size"]'
COL_PRICE = '[col-id="price"]'
@@ -563,7 +563,6 @@ def test_fills_taker_discount_program(
page.goto(f"/#/markets/{market_id}")
page.get_by_test_id(FILLS).click()
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
expect(row.locator(COL_INSTRUMENT_CODE)).to_have_text("BTC:DAI_2023Futr")
expect(row.locator(COL_SIZE)).to_have_text(size)
expect(row.locator(COL_PRICE)).to_have_text("103.50 tDAI")
expect(row.locator(COL_PRICE_1)).to_have_text(price_1)
@@ -605,7 +604,6 @@ def test_fills_maker_discount_program(
change_keys(page, vega_instance, MM_WALLET.name)
page.get_by_test_id(FILLS).click()
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
expect(row.locator(COL_INSTRUMENT_CODE)).to_have_text("BTC:DAI_2023Futr")
expect(row.locator(COL_SIZE)).to_have_text(size)
expect(row.locator(COL_PRICE)).to_have_text("103.50 tDAI")
expect(row.locator(COL_PRICE_1)).to_have_text(price_1)
@@ -77,7 +77,7 @@ class TestGetStarted:
vega.mint(
MM_WALLET.name,
asset="VOTE",
asset=vega.find_asset_id(symbol="VOTE", enabled=True),
amount=mint_amount,
)
@@ -105,6 +105,8 @@ class TestGetStarted:
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.reload()
# Assert step 2 complete
expect(page.get_by_test_id("icon-tick")).to_have_count(2)
@@ -35,7 +35,6 @@ class TestIcebergOrdersValidations:
"Awaiting confirmationPlease wait for your transaction to be confirmedView in block explorer"
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expect(page.get_by_test_id("toast-content")).to_have_text(
@@ -51,7 +50,6 @@ def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page
page.goto(f"/#/markets/{continuous_market}")
submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 102, 101, 2, 1)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -70,7 +68,6 @@ def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page
expect(
page.locator(".ag-center-cols-container .ag-row [col-id='size']").first
).to_have_text("-102")
page.pause()
expect(
page.locator(".ag-center-cols-container .ag-row [col-id='type'] ").first
).to_have_text("Limit (Iceberg)")
@@ -85,7 +82,6 @@ def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page
submit_order(vega, MM_WALLET2.name, continuous_market, "SIDE_BUY", 103, 101)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expect(
@@ -16,7 +16,6 @@ def vega(request):
def continuous_market(vega):
return setup_continuous_market(vega)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_liquidity_provision_amendment(continuous_market, vega: VegaServiceNull, page: Page):
# TODO Refactor asserting the grid
@@ -5,6 +5,7 @@ from vega_sim.null_service import VegaServiceNull
from playwright.sync_api import Page, expect
from fixtures.market import setup_continuous_market
from conftest import init_vega
from actions.utils import next_epoch
@pytest.fixture(scope="class")
@@ -21,9 +22,7 @@ def create_settled_market(vega: VegaServiceNull):
settlement_price=110,
market_id=market_id,
)
vega.forward("10s")
vega.wait_fn(10)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
class TestSettledMarket:
@@ -123,9 +122,7 @@ def test_terminated_market_no_settlement_date(page: Page, vega: VegaServiceNull)
payload={"trading.terminated": "true"},
key_name="FJMKnwfZdd48C8NqvYrG",
)
vega.forward("60s")
vega.wait_fn(10)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
page.goto(f"/#/markets/all")
page.get_by_test_id("Closed markets").click()
row_selector = page.locator(
@@ -15,72 +15,13 @@ def test_market_selector(continuous_market, page: Page):
# 6001-MARK-025
btc_market = page.locator('[data-testid="market-selector-list"] a')
expect(btc_market.locator("h3")).to_have_text("BTC:DAI_2023Futr")
expect(btc_market.locator('[data-testid="market-selector-volume"]')).to_have_text(
"0.00"
)
# tbd - 5465
# expect(btc_market.locator('[data-testid="market-selector-volume"]')).to_have_text(
# "1"
# )
expect(btc_market.locator('[data-testid="market-selector-price"]')).to_have_text(
"107.50 tDAI"
)
expect(btc_market.locator("span.rounded-md.leading-none")).to_be_visible()
expect(btc_market.locator("span.rounded-md.leading-none")).to_have_text("Futr")
expect(btc_market.locator('[data-testid="sparkline-svg"]')).not_to_be_visible
@pytest.mark.usefixtures("simple_market", "auth", "risk_accepted")
@pytest.mark.parametrize(
"simple_market",
[
{
"custom_market_name": "APPL.MF21",
"custom_asset_name": "tUSDC",
"custom_asset_symbol": "tUSDC",
}
],
indirect=True,
)
def test_market_selector_filter(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("header-title").click()
# 6001-MARK-027
page.get_by_test_id("product-Spot").click()
expect(page.get_by_test_id("market-selector-list")).to_contain_text(
"Spot markets coming soon."
)
page.get_by_test_id("product-Perpetual").click()
expect(page.get_by_test_id("market-selector-list")).to_contain_text(
"No perpetual markets."
)
page.get_by_test_id("product-Future").click()
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(2)
# 6001-MARK-029
page.get_by_test_id("search-term").fill("btc")
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text(
"BTC:DAI_2023107.50 tDAI0.00"
)
page.get_by_test_id("search-term").clear()
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(2)
# 6001-MARK-030
# 6001-MARK-031
# 6001-MARK-032
# 6001-MARK-033
page.get_by_test_id("sort-trigger").click()
expect(page.get_by_test_id("sort-item-Gained")).to_have_text("Top gaining")
expect(page.get_by_test_id("sort-item-Gained")).to_be_visible()
expect(page.get_by_test_id("sort-item-Lost")).to_have_text("Top losing")
expect(page.get_by_test_id("sort-item-Lost")).to_be_visible()
expect(page.get_by_test_id("sort-item-New")).to_have_text("New markets")
expect(page.get_by_test_id("sort-item-New")).to_be_visible()
# 6001-MARK-028
page.get_by_test_id("sort-trigger").click(force=True)
page.get_by_test_id("asset-trigger").click()
page.get_by_role("menuitemcheckbox").nth(0).get_by_text("tDAI").click()
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text(
"BTC:DAI_2023107.50 tDAI0.00"
)
@@ -1,35 +0,0 @@
import pytest
from playwright.sync_api import Page, expect
from conftest import init_page, init_vega, risk_accepted_setup
@pytest.fixture(scope="module")
def vega(request):
with init_vega(request) as vega:
yield vega
@pytest.fixture(scope="module")
def page(vega, browser, request):
with init_page(vega, browser, request) as page:
risk_accepted_setup(page)
page.goto("/#/markets/all")
yield page
def test_no_open_markets(page: Page):
# 6001-MARK-034
page.get_by_test_id("Open markets").click()
expect(page.locator(".ag-overlay-wrapper")).to_have_text("No markets")
def test_no_closed_markets(page: Page):
page.get_by_test_id("Closed markets").click()
expect(page.locator(".ag-overlay-wrapper")).to_have_text("No markets")
def test_no_proposed_markets(page: Page):
# 6001-MARK-061
page.get_by_test_id("Proposed markets").click()
expect(page.locator(".ag-overlay-wrapper")).to_have_text("No proposed markets")
@@ -56,9 +56,6 @@ def test_renders_markets_correctly(proposed_market, page: Page):
page.goto(f"/#/markets/all")
page.click('[data-testid="Proposed markets"]')
row = page.locator(row_selector)
# 6001-MARK-049
expect(row.locator(col_market_id)).to_have_text("BTC:DAI_2023")
# 6001-MARK-051
expect(row.locator('[col-id="asset"]')).to_have_text("tDAI")
@@ -64,18 +64,16 @@ def setup_market_monitoring_auction(vega: VegaServiceNull, simple_market):
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 1, 1 + 0.1 / 2)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_SELL", 1, 1)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
# add orders that change the price so that it goes beyond the limits of price monitoring
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 110)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 90)
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 105)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 95)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 1, 105)
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 300)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 290)
submit_order(vega, MM_WALLET.name, simple_market, "SIDE_SELL", 100, 305)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 295)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 1, 305)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -91,7 +89,6 @@ def test_market_monitoring_auction_price_volatility_limit_order(
page.get_by_test_id("order-price").type("110")
page.get_by_test_id("order-tif").select_option("Fill or Kill (FOK)")
page.get_by_test_id("place-order").click()
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_have_text(
"This market is in auction due to high price volatility. Until the auction ends, you can only place GFA, GTT, or GTC limit orders."
)
@@ -109,12 +106,11 @@ def test_market_monitoring_auction_price_volatility_limit_order(
page.get_by_test_id("place-order").click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.get_by_test_id("All").click()
expect(page.get_by_role("row").nth(2)).to_contain_text(
"BTC:DAI_2023Futr0+1LimitActive110.00GTC"
expect(page.get_by_role("row").nth(4)).to_contain_text(
"0+1LimitActive110.00GTC"
)
@@ -128,7 +124,6 @@ def test_market_monitoring_auction_price_volatility_market_order(
page.get_by_test_id("order-size").type("1")
# 7002-SORD-060
page.get_by_test_id("place-order").click()
expect(page.get_by_test_id("deal-ticket-error-message-tif")).to_have_text(
"This market is in auction due to high price volatility. Until the auction ends, you can only place GFA, GTT, or GTC limit orders."
)
@@ -73,9 +73,6 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
)
# "wait" for market to be approved and enacted
vega.forward("60s")
vega.wait_fn(10)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
# check that market is in pending state
expect(trading_mode).to_have_text("Opening auction")
@@ -118,8 +115,7 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
submit_order(vega, MM_WALLET.name, market_id, "SIDE_SELL", 1, 100)
submit_order(vega, MM_WALLET2.name, market_id, "SIDE_BUY", 1, 100)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_fn(2)
vega.wait_for_total_catchup()
# check market state is now active and trading mode is continuous
@@ -139,9 +135,7 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
.get_by_test_id(f"update-state-banner-{market_id}")
).to_be_visible()
vega.forward("60s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
expect(
page.get_by_test_id("market-banner")
@@ -155,9 +149,7 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
forward_time_to_enactment = False
)
vega.forward("60s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
expect(page.get_by_test_id("market-banner")).not_to_be_visible()
@@ -170,11 +162,9 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
payload={"trading.terminated": "true"},
key_name=GOVERNANCE_WALLET.name,
)
vega.forward("60s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
# market state should be changed to "Trading Terminated" because of the invalid oracle
# market state should be changed to "No trading" because of the invalid oracle
expect(trading_mode).to_have_text("No trading")
expect(market_state).to_have_text("Trading Terminated")
@@ -184,9 +174,7 @@ def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
settlement_price=100,
market_id=market_id,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
# check market state is now settled
expect(trading_mode).to_have_text("No trading")
@@ -44,7 +44,7 @@ def verify_order_value(
actual_text = element.text_content()
if actual_text is None:
raise Exception(f"no text found for test_id {test_id}")
raise Exception(f"no text found for test_id {test_id}")
assert re.match(
expected_text, actual_text
@@ -65,7 +65,6 @@ def test_limit_order_trade_open_order(
expect(orderbook_trade).to_be_visible()
expected_open_order = [
"BTC:DAI_2023",
"+1",
"Limit",
"Active",
@@ -87,9 +86,6 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
secondary_id = "stack-cell-secondary"
position = {
"market_code": "BTC:DAI_2023",
"settlement_asset": "tDAI",
"product_type": "Futr",
"size": "+1",
"notional": "107.50",
"average_entry_price": "107.50",
@@ -107,12 +103,6 @@ def test_limit_order_trade_open_position(continuous_market, page: Page):
# 7004-POSI-001
# 7004-POSI-002
market = table.locator("[col-id='marketCode']")
expect(market.get_by_test_id(primary_id)).to_have_text(position["market_code"])
expect(market.get_by_test_id(secondary_id)).to_have_text(
position["settlement_asset"] + position["product_type"]
)
size_and_notional = table.locator("[col-id='openVolume']")
expect(size_and_notional.get_by_test_id(primary_id)).to_have_text(position["size"])
expect(size_and_notional.get_by_test_id(secondary_id)).to_have_text(
@@ -154,5 +144,4 @@ def test_limit_order_trade_order_trade_away(continuous_market, page: Page):
page.get_by_test_id("Orderbook").click()
price_element = page.get_by_test_id("price-11000000").nth(1)
# 6003-ORDB-010
print(price_element)
expect(price_element).to_be_hidden()
@@ -257,46 +257,63 @@ def test_order_sorted(page: Page):
def test_order_status_active(page: Page):
# 7002-SORD-041
expect(page.get_by_test_id(order_tab)).to_contain_text(
"market-2Futr" + "0" + "-10" + "Limit" + "Active" + "150.00" + "GTC"
expect(page.locator('[row-index="2"]').first).to_contain_text(
"market-2Futr"
)
expect(page.locator('[row-index="2"]').nth(1)).to_contain_text(
"0" + "-10" + "Limit" + "Active" + "150.00" + "GTC"
)
def test_status_expired(page: Page):
# 7002-SORD-042
expect(page.get_by_test_id(order_tab)).to_contain_text(
"market-3Futr" + "0" + "-10" + "Limit" + "Expired" + "120.00" + "GTT:"
expect(page.locator('[row-index="7"]').first).to_contain_text(
"market-3Futr"
)
expect(page.locator('[row-index="7"]').nth(1)).to_contain_text(
"0" + "-10" + "Limit" + "Expired" + "120.00" + "GTT:"
)
def test_order_status_Stopped(page: Page):
# 7002-SORD-044
expect(page.get_by_test_id(order_tab)).to_contain_text(
"market-1Futr" + "0" + "-100" + "Limit" + "Stopped" + "130.00" + "IOC"
expect(page.locator('[row-index="12"]').first).to_contain_text(
"market-1Futr"
)
expect(page.locator('[row-index="12"]').nth(1)).to_contain_text(
"0" + "-100" + "Limit" + "Stopped" + "130.00" + "IOC"
)
def test_order_status_partially_filled(page: Page):
# 7002-SORD-045
expect(page.get_by_test_id(order_tab)).to_contain_text(
"market-2Futr" + "99" + "+100" + "Limit" + "Partially Filled" + "104.00" + "IOC"
expect(page.locator('[row-index="8"]').first).to_contain_text(
"market-2Futr"
)
expect(page.locator('[row-index="8"]').nth(1)).to_contain_text(
"99" + "+100" + "Limit" + "Partially Filled" + "104.00" + "IOC"
)
def test_order_status_filled(page: Page):
# 7002-SORD-046
# 7003-MORD-020
expect(page.get_by_test_id(order_tab)).to_contain_text(
"market-1Futr" + "100" + "-100" + "Limit" + "Filled" + "88.00" + "GTC"
expect(page.locator('[row-index="11"]').first).to_contain_text(
"market-1Futr"
)
expect(page.locator('[row-index="11"]').nth(1)).to_contain_text(
"100" + "-100" + "Limit" + "Filled" + "88.00" + "GTC"
)
def test_order_status_rejected(page: Page):
# 7002-SORD-047
# 7003-MORD-018
expect(page.get_by_test_id(order_tab)).to_contain_text(
expect(page.locator('[row-index="9"]').first).to_contain_text(
"market-1Futr"
+ "0"
)
expect(page.locator('[row-index="9"]').nth(1)).to_contain_text(
"0"
+ "-10,000,000,000"
+ "Limit"
+ "Rejected: Margin check failed"
@@ -308,9 +325,11 @@ def test_order_status_rejected(page: Page):
def test_order_status_parked(page: Page):
# 7002-SORD-048
# 7003-MORD-016
expect(page.get_by_test_id(order_tab)).to_contain_text(
expect(page.locator('[row-index="3"]').first).to_contain_text(
"market-5Futr"
+ "0"
)
expect(page.locator('[row-index="3"]').nth(1)).to_contain_text(
"0"
+ "-60"
+ "Ask + 15.00 Peg limit"
+ "Parked"
@@ -321,9 +340,11 @@ def test_order_status_parked(page: Page):
def test_order_status_pegged_ask(page: Page):
# 7003-MORD-016
expect(page.get_by_test_id(order_tab)).to_contain_text(
expect(page.locator('[row-index="4"]').first).to_contain_text(
"market-4Futr"
+ "0"
)
expect(page.locator('[row-index="4"]').nth(1)).to_contain_text(
"0"
+ "-60"
+ "Ask + 15.00 Peg limit"
+ "Active"
@@ -334,9 +355,11 @@ def test_order_status_pegged_ask(page: Page):
def test_order_status_pegged_bid(page: Page):
# 7003-MORD-016
expect(page.get_by_test_id(order_tab)).to_contain_text(
expect(page.locator('[row-index="5"]').first).to_contain_text(
"market-4Futr"
+ "0"
)
expect(page.locator('[row-index="5"]').nth(1)).to_contain_text(
"0"
+ "+40"
+ "Bid - 10.00 Peg limit"
+ "Active"
@@ -347,9 +370,11 @@ def test_order_status_pegged_bid(page: Page):
def test_order_status_pegged_mid(page: Page):
# 7003-MORD-016
expect(page.get_by_test_id(order_tab)).to_contain_text(
expect(page.locator('[row-index="6"]').first).to_contain_text(
"market-4Futr"
+ "0"
)
expect(page.locator('[row-index="6"]').nth(1)).to_contain_text(
"0"
+ "+20"
+ "Mid - 5.00 Peg limit"
+ "Active"
@@ -372,9 +397,11 @@ def test_order_amend_order(vega: VegaServiceNull, page: Page):
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expect(page.get_by_test_id(order_tab)).to_contain_text(
"market-2Futr" + "0" + "-15" + "Limit" + "Active" + "170.00" + "GTC"
expect(page.locator('[row-index="1"]').first).to_contain_text(
"market-2Futr"
)
expect(page.locator('[row-index="1"]').nth(1)).to_contain_text(
"0" + "-15" + "Limit" + "Active" + "170.00" + "GTC"
)
@@ -389,9 +416,11 @@ def test_order_cancel_single_order(vega: VegaServiceNull, page: Page):
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expect(page.get_by_test_id(order_tab)).to_contain_text(
"market-3Futr" + "0" + "+10" + "Limit" + "Cancelled" + "60.00" + "GTC"
expect(page.locator('[row-index="0"]').first).to_contain_text(
"market-3Futr"
)
expect(page.locator('[row-index="0"]').nth(1)).to_contain_text(
"0" + "+10" + "Limit" + "Cancelled" + "60.00" + "GTC"
)
@@ -106,7 +106,6 @@ def test_orderbook_grid_content(setup_market, page: Page):
matching_order[1],
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -233,7 +232,6 @@ def test_orderbook_price_movement(setup_market, page: Page):
matching_order_1[1],
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -254,7 +252,6 @@ def test_orderbook_price_movement(setup_market, page: Page):
matching_order_2[1],
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -34,7 +34,6 @@ class TestPerpetuals:
settlement_price=110,
market_id=perps_market,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
submit_multiple_orders(
@@ -48,8 +47,7 @@ class TestPerpetuals:
settlement_price=110,
market_id=perps_market,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_fn(10)
vega.wait_for_total_catchup()
return perps_market
@@ -110,7 +108,6 @@ def test_perps_market_termination_proposed(page: Page, vega: VegaServiceNull):
forward_time_to_enactment=False,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
banner_text = page.get_by_test_id(
@@ -135,7 +132,6 @@ def test_perps_market_terminated(page: Page, vega: VegaServiceNull):
approve_proposal=True,
forward_time_to_enactment=True,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
+1 -1
View File
@@ -11,7 +11,7 @@ def check_pnl_color_value(element, expected_color, expected_value):
assert color == expected_color, f"Unexpected color: {color}"
assert value == expected_value, f"Unexpected value: {value}"
#TODO move this test to jest
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_pnl(continuous_market, vega: VegaServiceNull, page: Page):
page.set_viewport_size({"width": 1748, "height": 977})
@@ -1,32 +0,0 @@
import os
import pytest
from playwright.sync_api import Page, expect
#TODO migrate to jest
@pytest.mark.usefixtures("auth", "risk_accepted", "continuous_market")
def test_ledger_entries_downloads(page: Page):
page.goto("/#/portfolio")
page.get_by_test_id("Ledger entries").click()
expect(page.get_by_test_id("ledger-download-button")).to_be_enabled()
# 7007-LEEN-001
page.get_by_test_id("ledger-download-button").click()
# 7007-LEEN-009
expect(page.get_by_test_id("toast-content")).to_contain_text(("Your file is ready"))
# Get the user's Downloads directory
downloads_directory = os.path.expanduser("~") + "/Downloads/"
# Start waiting for the download
with page.expect_download() as download_info:
# Perform the action that initiates download
page.get_by_role("link", name="Get file here").click()
download = download_info.value
# Wait for the download process to complete and save the downloaded file in the Downloads directory
download.save_as(os.path.join(downloads_directory, download.suggested_filename))
# Verify the download by asserting that the file exists
downloaded_file_path = os.path.join(
downloads_directory, download.suggested_filename
)
assert os.path.exists(
downloaded_file_path
), f"Download failed! File not found at: {downloaded_file_path}"
@@ -15,14 +15,13 @@ def test_closed_market_position(vega: VegaServiceNull, page: Page):
settlement_price=110,
market_id=market_id,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
page.goto(f"/#/markets/{market_id}")
expect(page.locator(".ag-overlay-panel")).to_have_text("No positions")
page.get_by_test_id("open-transfer").click()
tab = page.get_by_test_id("tab-positions")
table = tab.locator(".ag-center-cols-container")
table = tab.locator('[class="ag-body ag-layout-normal"]')
market = table.locator("[col-id='marketCode']")
expect(market.get_by_test_id("stack-cell-primary")).to_have_text("BTC:DAI_2023")
page.get_by_test_id("open-transfer").click()
@@ -0,0 +1,469 @@
import pytest
import vega_sim.proto.vega as vega_protos
from playwright.sync_api import Page, expect
from conftest import init_vega, init_page, auth_setup
from fixtures.market import setup_continuous_market, market_exists
from actions.utils import next_epoch, change_keys
from wallet_config import MM_WALLET, PARTY_A, PARTY_B, PARTY_C, PARTY_D
from vega_sim.null_service import VegaServiceNull
# region Constants
ACTIVITY = "activity"
HOARDER = "hoarder"
COMBO = "combo"
REWARDS_URL = "/#/rewards"
# test IDs
COMBINED_MULTIPLIERS = "combined-multipliers"
TOTAL_REWARDS = "total-rewards"
PRICE_TAKING_COL_ID = '[col-id="priceTaking"]'
TOTAL_COL_ID = '[col-id="total"]'
ROW = "row"
STREAK_REWARD_MULTIPLIER_VALUE = "streak-reward-multiplier-value"
HOARDER_REWARD_MULTIPLIER_VALUE = "hoarder-reward-multiplier-value"
HOARDER_BONUS_TOTAL_HOARDED = "hoarder-bonus-total-hoarded"
EARNED_BY_ME_BUTTON = "earned-by-me-button"
TRANSFER_AMOUNT = "transfer-amount"
EPOCH_STREAK = "epoch-streak"
# endregion
@pytest.fixture(scope="module")
def market_ids():
return {
"vega_activity_tier_0": "default_id",
"vega_hoarder_tier_0": "default_id",
"vega_combo_tier_0": "default_id",
"vega_activity_tier_1": "default_id",
"vega_hoarder_tier_1": "default_id",
"vega_combo_tier_1": "default_id",
}
@pytest.fixture(scope="module")
def vega_activity_tier_0(request):
with init_vega(request) as vega_activity_tier_0:
yield vega_activity_tier_0
@pytest.fixture(scope="module")
def vega_hoarder_tier_0(request):
with init_vega(request) as vega_hoarder_tier_0:
yield vega_hoarder_tier_0
@pytest.fixture(scope="module")
def vega_combo_tier_0(request):
with init_vega(request) as vega_combo_tier_0:
yield vega_combo_tier_0
@pytest.fixture(scope="module")
def vega_activity_tier_1(request):
with init_vega(request) as vega_activity_tier_1:
yield vega_activity_tier_1
@pytest.fixture(scope="module")
def vega_hoarder_tier_1(request):
with init_vega(request) as vega_hoarder_tier_1:
yield vega_hoarder_tier_1
@pytest.fixture(scope="module")
def vega_combo_tier_1(request):
with init_vega(request) as vega_combo_tier_1:
yield vega_combo_tier_1
@pytest.fixture
def auth(vega_instance, page):
return auth_setup(vega_instance, page)
@pytest.fixture
def page(vega_instance, browser, request):
with init_page(vega_instance, browser, request) as page_instance:
yield page_instance
@pytest.fixture
def vega_instance(
reward_program,
vega_activity_tier_0,
vega_hoarder_tier_0,
vega_combo_tier_0,
vega_activity_tier_1,
vega_hoarder_tier_1,
vega_combo_tier_1,
tier,
):
if reward_program == "activity":
return vega_activity_tier_0 if tier == 1 else vega_activity_tier_1
elif reward_program == "hoarder":
return vega_hoarder_tier_0 if tier == 1 else vega_hoarder_tier_1
elif reward_program == "combo":
return vega_combo_tier_0 if tier == 1 else vega_combo_tier_1
def setup_market_with_reward_program(vega: VegaServiceNull, reward_programs, tier):
print(f"Started setup_market_with_{reward_programs}_{tier}")
tDAI_market = setup_continuous_market(vega)
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000)
vega.mint(key_name=PARTY_C.name, asset=tDAI_asset_id, amount=100000)
vega.mint(key_name=PARTY_A.name, asset=tDAI_asset_id, amount=100000)
vega.mint(key_name=PARTY_D.name, asset=tDAI_asset_id, amount=100000)
next_epoch(vega=vega)
if ACTIVITY in reward_programs:
vega.update_network_parameter(
proposal_key=MM_WALLET.name,
parameter="rewards.activityStreak.benefitTiers",
new_value=ACTIVITY_STREAKS,
)
print("update_network_parameter activity done")
next_epoch(vega=vega)
if HOARDER in reward_programs:
vega.update_network_parameter(
proposal_key=MM_WALLET.name,
parameter="rewards.vesting.benefitTiers",
new_value=VESTING,
)
next_epoch(vega=vega)
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
vega.update_network_parameter(
MM_WALLET.name, parameter="reward.asset", new_value=tDAI_asset_id
)
next_epoch(vega=vega)
vega.recurring_transfer(
from_key_name=PARTY_A.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
asset=tDAI_asset_id,
reference="reward",
asset_for_metric=tDAI_asset_id,
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
# lock_period= 5,
# TODO test lock period
amount=100,
factor=1.0,
)
vega.submit_order(
trading_key=PARTY_B.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.submit_order(
trading_key=PARTY_A.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.wait_for_total_catchup()
if tier == 1:
next_epoch(vega=vega)
vega.submit_order(
trading_key=PARTY_B.name,
market_id=tDAI_market,
order_type="TYPE_LIMIT",
time_in_force="TIME_IN_FORCE_GTC",
side="SIDE_BUY",
price=1,
volume=1,
)
next_epoch(vega=vega)
vega.submit_order(
trading_key=PARTY_B.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.submit_order(
trading_key=PARTY_D.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
next_epoch(vega=vega)
if HOARDER in reward_programs:
vega.submit_order(
trading_key=PARTY_B.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.submit_order(
trading_key=PARTY_D.name,
market_id=tDAI_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.wait_for_total_catchup()
next_epoch(vega=vega)
next_epoch(vega=vega)
next_epoch(vega=vega)
return tDAI_market, tDAI_asset_id
def set_market_reward_program(vega, reward_program, market_ids, tier):
market_id_key = f"vega_{reward_program}_tier_{tier}"
if reward_program == COMBO:
market_id_key = COMBO
market_id = market_ids.get(market_id_key, "default_id")
print(f"Checking if market exists: {market_id}")
if not market_exists(vega, market_id):
print(
f"Market doesn't exist for {reward_program} {tier}. Setting up new market."
)
reward_programs = [reward_program]
if reward_program == COMBO:
reward_programs = [ACTIVITY, HOARDER]
market_id, _ = setup_market_with_reward_program(vega, reward_programs, tier)
market_ids[market_id_key] = market_id
return market_id, market_ids
ACTIVITY_STREAKS = """
{
"tiers": [
{
"minimum_activity_streak": 2,
"reward_multiplier": "2.0",
"vesting_multiplier": "1.1"
}
]
}
"""
VESTING = """
{
"tiers": [
{
"minimum_quantum_balance": "10000000",
"reward_multiplier": "2"
}
]
}
"""
@pytest.mark.parametrize(
"reward_program, tier, total_rewards",
[
(ACTIVITY, 0, "50.00 tDAI"),
(HOARDER, 0, "50.00 tDAI"),
(COMBO, 0, "50.00 tDAI"),
(ACTIVITY, 1, "116.66666 tDAI"),
(HOARDER, 1, "166.66666 tDAI "),
(COMBO, 1, "183.33333 tDAI"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted", "market_ids")
def test_network_reward_pot(
reward_program,
vega_instance: VegaServiceNull,
page: Page,
total_rewards,
tier,
market_ids,
):
print("reward program: " + reward_program, " tier:", tier)
market_id, market_ids = set_market_reward_program(
vega_instance, reward_program, market_ids, tier
)
page.goto(REWARDS_URL)
change_keys(page, vega_instance, PARTY_B.name)
expect(page.get_by_test_id(TOTAL_REWARDS)).to_have_text(total_rewards)
@pytest.mark.parametrize(
"reward_program, tier, reward_multiplier, streak_multiplier, hoarder_multiplier",
[
(ACTIVITY, 0, "1x", "1x", "1x"),
(HOARDER, 0, "1x", "1x", "1x"),
(COMBO, 0, "1x", "1x", "1x"),
(ACTIVITY, 1, "2x", "2x", "1x"),
(HOARDER, 1, "2x", "1x", "2x"),
(COMBO, 1, "4x", "2x", "2x"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted", "market_ids")
def test_reward_multiplier(
reward_program,
vega_instance: VegaServiceNull,
page: Page,
reward_multiplier,
streak_multiplier,
hoarder_multiplier,
tier,
market_ids,
):
print("reward program: " + reward_program, " tier:", tier)
market_id, market_ids = set_market_reward_program(
vega_instance, reward_program, market_ids, tier
)
page.goto(REWARDS_URL)
change_keys(page, vega_instance, PARTY_B.name)
expect(page.get_by_test_id(COMBINED_MULTIPLIERS)).to_have_text(reward_multiplier)
expect(page.get_by_test_id(STREAK_REWARD_MULTIPLIER_VALUE)).to_have_text(
streak_multiplier
)
expect(page.get_by_test_id(HOARDER_REWARD_MULTIPLIER_VALUE)).to_have_text(
hoarder_multiplier
)
@pytest.mark.parametrize(
"reward_program, tier, epoch_streak",
[
(ACTIVITY, 0, "1"),
(ACTIVITY, 1, "7"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted", "market_ids")
def test_activity_streak(
reward_program,
vega_instance: VegaServiceNull,
page: Page,
epoch_streak,
tier,
market_ids,
):
print("reward program: " + reward_program, " tier:", tier)
market_id, market_ids = set_market_reward_program(
vega_instance, reward_program, market_ids, tier
)
page.goto(REWARDS_URL)
change_keys(page, vega_instance, PARTY_B.name)
if tier == 1:
expect(page.get_by_test_id(EPOCH_STREAK)).to_have_text(
"Active trader: " + epoch_streak + " epochs so far (Tier 1 as of last epoch)"
)
else:
expect(page.get_by_test_id(EPOCH_STREAK)).to_have_text(
"Active trader: " + epoch_streak + " epochs so far "
)
@pytest.mark.parametrize(
"reward_program, tier, rewards_hoarded",
[
(HOARDER, 0, "5,000,000"),
(HOARDER, 1, "16,666,666"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted", "market_ids")
def test_hoarder_bonus(
reward_program,
vega_instance: VegaServiceNull,
page: Page,
rewards_hoarded,
tier,
market_ids,
):
print("reward program: " + reward_program, " tier:", tier)
market_id, market_ids = set_market_reward_program(
vega_instance, reward_program, market_ids, tier
)
page.goto(REWARDS_URL)
change_keys(page, vega_instance, PARTY_B.name)
expect(page.get_by_test_id(HOARDER_BONUS_TOTAL_HOARDED)).to_contain_text(
rewards_hoarded
)
@pytest.mark.parametrize(
"reward_program, tier, price_taking, total, earned_by_me",
[
(ACTIVITY, 0, "100.00100.00%", "100.00", "50.00"),
(HOARDER, 0, "100.00100.00%", "100.00", "50.00"),
(COMBO, 0, "100.00100.00%", "100.00", "50.00"),
(ACTIVITY, 1, "300.00100.00%", "300.00", "116.66666"),
(HOARDER, 1, "299.99999100.00%", "299.99999", "166.66666"),
(COMBO, 1, "299.99999100.00%", "299.99999", "183.33333"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted", "market_ids")
def test_reward_history(
reward_program,
vega_instance: VegaServiceNull,
page: Page,
price_taking,
total,
earned_by_me,
tier,
market_ids,
):
print("reward program: " + reward_program, " tier:", tier)
market_id, market_ids = set_market_reward_program(
vega_instance, reward_program, market_ids, tier
)
page.goto(REWARDS_URL)
change_keys(page, vega_instance, PARTY_B.name)
page.locator('[name="fromEpoch"]').fill("1")
expect((page.get_by_role(ROW).locator(PRICE_TAKING_COL_ID)).nth(1)).to_have_text(
price_taking
)
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text(total)
page.get_by_test_id(EARNED_BY_ME_BUTTON).click()
expect((page.get_by_role(ROW).locator(TOTAL_COL_ID)).nth(1)).to_have_text(
earned_by_me
)
@pytest.mark.parametrize(
"reward_program, tier",
[
(ACTIVITY, 1),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted", "market_ids")
def test_redeem(
reward_program, vega_instance: VegaServiceNull, page: Page, tier, market_ids
):
print("reward program: " + reward_program, " tier:", tier)
market_id, market_ids = set_market_reward_program(
vega_instance, reward_program, market_ids, tier
)
page.goto(REWARDS_URL)
change_keys(page, vega_instance, PARTY_B.name)
page.get_by_test_id("redeem-rewards-button").click()
available_to_withdraw = page.get_by_test_id(
"available-to-withdraw-value"
).text_content()
option_value = page.locator(
'[data-testid="transfer-form"] [name="fromAccount"] option[value^="ACCOUNT_TYPE_VESTED_REWARDS"]'
).first.get_attribute("value")
page.select_option(
'[data-testid="transfer-form"] [name="fromAccount"]', option_value
)
page.get_by_test_id("use-max-button").first.click()
expect(page.get_by_test_id(TRANSFER_AMOUNT)).to_have_text(available_to_withdraw)
@@ -0,0 +1,65 @@
import pytest
import vega_sim.proto.vega as vega_protos
from playwright.sync_api import Page, expect
from vega_sim.null_service import VegaServiceNull
from actions.utils import next_epoch, change_keys
from wallet_config import MM_WALLET, PARTY_A, PARTY_B
@pytest.mark.usefixtures("risk_accepted", "auth")
def test_vesting(continuous_market, vega: VegaServiceNull, page: Page):
tDAI_asset_id = vega.find_asset_id(symbol="tDAI")
vega.update_network_parameter(
MM_WALLET.name, parameter="reward.asset", new_value=tDAI_asset_id
)
vega.mint(key_name=PARTY_B.name, asset=tDAI_asset_id, amount=100000)
vega.mint(key_name=PARTY_A.name, asset=tDAI_asset_id, amount=100000)
next_epoch(vega=vega)
vega.recurring_transfer(
from_key_name=PARTY_A.name,
from_account_type=vega_protos.vega.ACCOUNT_TYPE_GENERAL,
to_account_type=vega_protos.vega.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES,
asset=tDAI_asset_id,
reference="reward",
asset_for_metric=tDAI_asset_id,
metric=vega_protos.vega.DISPATCH_METRIC_MAKER_FEES_PAID,
lock_period=5,
amount=100,
factor=1.0,
)
vega.submit_order(
trading_key=PARTY_B.name,
market_id=continuous_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.submit_order(
trading_key=PARTY_A.name,
market_id=continuous_market,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.wait_for_total_catchup()
page.goto("/#/rewards")
change_keys(page, vega, PARTY_B.name)
next_epoch(vega=vega)
page.reload()
expect(page.get_by_test_id("locked-value")).to_have_text("50.00")
# Proceed through the 5 epoch lock period
next_epoch(vega=vega)
next_epoch(vega=vega)
next_epoch(vega=vega)
next_epoch(vega=vega)
next_epoch(vega=vega)
page.reload()
expect(page.get_by_test_id("locked-value")).to_have_text("0.00")
expect(page.get_by_test_id("vesting-value")).to_have_text("37.50")
expect(page.get_by_test_id("available-to-withdraw-value")).to_have_text("12.50")
@@ -149,7 +149,5 @@ def provide_successor_liquidity(
)
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -73,6 +73,6 @@ def test_limit_order_new_trade_top_of_list(
def test_price_copied_to_deal_ticket(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("Trades").click()
page.locator("[col-id=price]").last.click()
page.locator("[col-id=price]").nth(1).click()
# 6005-THIS-007
expect(page.get_by_test_id("order-price")).to_have_value("107.50000")
@@ -22,8 +22,6 @@ def test_trade_match_table(opening_auction_market: str, vega: VegaServiceNull, p
price=10e15,
wait=False,
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -34,7 +32,6 @@ def test_trade_match_table(opening_auction_market: str, vega: VegaServiceNull, p
"SIDE_BUY",
[[5, 110], [5, 105], [1, 50]],
)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -45,7 +42,6 @@ def test_trade_match_table(opening_auction_market: str, vega: VegaServiceNull, p
"SIDE_SELL",
[[5, 90], [5, 95], [1, 150]],
)
vega.forward("60s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -50,7 +50,6 @@ def test_transfer_submit(continuous_market, vega: VegaServiceNull, page: Page):
page.locator('[data-testid=transfer-form] [type="submit"]').click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expected_confirmation_text = re.compile(
@@ -142,14 +141,12 @@ def test_transfer_vesting_below_minimum(
asset=asset_id,
amount=24.999999,
)
vega.forward("10s")
vega.wait_fn(10)
vega.wait_for_total_catchup()
page.get_by_text("Use max").first.click()
page.get_by_test_id("use-max-button").first.click()
page.locator('[data-testid=transfer-form] [type="submit"]').click()
wait_for_toast_confirmation(page)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expected_confirmation_text = re.compile(
+1 -1
View File
@@ -116,7 +116,7 @@ def test_wallet_transaction_rejected(continuous_market, page: Page):
page.get_by_test_id(order_price).fill("120")
page.route("**/*", handle_route_connection_rejected)
page.get_by_test_id(place_order).click()
expect(page.get_by_test_id("toast-content")).to_have_text(
expect(page.get_by_test_id("toast-content").nth(0)).to_have_text(
"Error occurredthe user rejected the wallet connection"
)
+3 -1
View File
@@ -11,5 +11,7 @@ GOVERNANCE_WALLET = WalletConfig(
"FJMKnwfZdd48C8NqvYrG", "bY3DxwtsCstMIIZdNpKs")
PARTY_A = WalletConfig("party_a", "party_a")
PARTY_B = WalletConfig("party_b", "party_b")
PARTY_C = WalletConfig("party_c", "party_c")
PARTY_D = WalletConfig("party_d", "party_d")
wallets = [MM_WALLET, MM_WALLET2, TERMINATE_WALLET, GOVERNANCE_WALLET]
wallets = [MM_WALLET, MM_WALLET2, TERMINATE_WALLET, GOVERNANCE_WALLET, PARTY_A, PARTY_B, PARTY_C, PARTY_D]
+2 -2
View File
@@ -13,9 +13,9 @@ export default function Document() {
{/* preload fonts */}
<link
rel="preload"
href="/AlphaLyrae.woff2"
href="/AlphaLyrae.woff"
as="font"
type="font/woff2"
type="font/woff"
/>
{/* icons */}
Binary file not shown.
+5 -2
View File
@@ -67,6 +67,7 @@ const defaultColDef = {
sortable: true,
tooltipComponent: TooltipCellComponent,
comparator: accountValuesComparator,
minWidth: 150,
};
export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> {
successCallback(rowsThisBlock: AccountFields[], lastRow?: number): void;
@@ -139,6 +140,8 @@ export const AccountTable = ({
{
headerName: t('Asset'),
field: 'asset.symbol',
pinned: true,
minWidth: 75,
headerTooltip: t(
'Asset is the collateral that is deposited into the Vega protocol.'
),
@@ -253,8 +256,8 @@ export const AccountTable = ({
colId: 'accounts-actions',
field: 'asset.id',
...COL_DEFS.actions,
minWidth: showDepositButton ? 105 : COL_DEFS.actions.minWidth,
maxWidth: showDepositButton ? 105 : COL_DEFS.actions.maxWidth,
minWidth: showDepositButton ? 110 : COL_DEFS.actions.minWidth,
maxWidth: showDepositButton ? 110 : COL_DEFS.actions.maxWidth,
cellRenderer: ({
value: assetId,
node,
+6 -3
View File
@@ -23,7 +23,9 @@ import { AccountType } from '@vegaprotocol/types';
const defaultColDef = {
resizable: true,
sortable: true,
minWidth: 100,
};
interface BreakdownTableProps extends AgGridReactProps {
data: AccountFields[] | null;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
@@ -32,12 +34,13 @@ interface BreakdownTableProps extends AgGridReactProps {
const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
({ data }, ref) => {
const t = useT();
const coldefs = useMemo(() => {
const colDefs = useMemo(() => {
const defs: ColDef[] = [
{
headerName: t('Market'),
field: 'market.tradableInstrument.instrument.code',
minWidth: 200,
width: 90,
pinned: true,
sort: 'desc',
cellRenderer: ({
value,
@@ -141,7 +144,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
components={{ PriceCell, ProgressBarCell }}
tooltipShowDelay={500}
defaultColDef={defaultColDef}
columnDefs={coldefs}
columnDefs={colDefs}
domLayout="autoHeight"
/>
);
+1
View File
@@ -438,6 +438,7 @@ export const TransferForm = ({
shouldValidate: true,
})
}
data-testid="use-max-button"
>
{t('Use max')}
</button>
@@ -15,7 +15,6 @@ export async function proposeMarket(publicKey: string) {
log('sending proposal tx');
const proposalTx = createNewMarketProposal();
const result = await sendVegaTx(publicKey, proposalTx);
return result.result;
}
@@ -119,6 +118,7 @@ function createNewMarketProposal(): ProposalSubmissionBody {
timeWindow: '3600',
scalingFactor: 10,
},
// FIXME: workaround because of https://github.com/vegaprotocol/vega/issues/10343
triggeringRatio: '0.7',
auctionExtension: '1',
},
@@ -9,4 +9,7 @@ export const COL_DEFS = {
type: 'rightAligned',
pinned: 'right' as const,
},
default: {
minWidth: 100,
},
};
@@ -329,8 +329,9 @@ export const DealTicket = ({
const marketTradingModeError = validateMarketTradingMode(
marketTradingMode,
t('Trading terminated')
t('No trading')
);
if (marketTradingModeError !== true) {
return {
message: marketTradingModeError,
-4
View File
@@ -11,9 +11,6 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
positionDecimalPlaces: 1,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
state: Schema.MarketState.STATE_ACTIVE,
liquidityMonitoringParameters: {
triggeringRatio: '1',
},
marketTimestamps: {
__typename: 'MarketTimestamps',
close: '',
@@ -75,7 +72,6 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
__typename: 'Instrument',
},
},
fees: {
factors: {
makerFee: '0.001',
+43 -37
View File
@@ -1,10 +1,4 @@
import {
waitFor,
fireEvent,
render,
screen,
act,
} from '@testing-library/react';
import { waitFor, fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import BigNumber from 'bignumber.js';
import type { DepositFormProps } from './deposit-form';
@@ -89,7 +83,10 @@ describe('Deposit form', () => {
render(<DepositForm {...props} />);
// Assert default values (including) from/to provided by useVegaWallet and useWeb3React
expect(screen.getByText('From (Ethereum address)')).toBeInTheDocument();
// Wait for first value to show as form is rendered conditionally based on chainId
expect(
await screen.findByText('From (Ethereum address)')
).toBeInTheDocument();
expect(screen.getByTestId('ethereum-address')).toHaveTextContent(
truncateMiddle(MOCK_ETH_ADDRESS)
);
@@ -319,34 +316,40 @@ describe('Deposit form', () => {
it('shows "View asset details" button when an asset is selected', async () => {
render(<DepositForm {...props} selectedAsset={asset} />);
expect(await screen.getByTestId('view-asset-details')).toBeInTheDocument();
expect(await screen.findByTestId('view-asset-details')).toBeInTheDocument();
});
it('does not shows "View asset details" button when no asset is selected', async () => {
render(<DepositForm {...props} />);
expect(await screen.queryAllByTestId('view-asset-details')).toHaveLength(0);
await waitFor(() => {
expect(screen.queryAllByTestId('view-asset-details')).toHaveLength(0);
});
});
it('renders a connect button if Ethereum wallet is not connected', () => {
it('renders a connect button if Ethereum wallet is not connected', async () => {
(useWeb3React as jest.Mock).mockReturnValue({
isActive: false,
account: '',
});
render(<DepositForm {...props} />);
expect(screen.getByRole('button', { name: 'Connect' })).toBeInTheDocument();
expect(
await screen.findByRole('button', { name: 'Connect' })
).toBeInTheDocument();
expect(
screen.queryByLabelText('From (Ethereum address)')
).not.toBeInTheDocument();
});
it('renders a disabled input if Ethereum wallet is connected', () => {
it('renders a disabled input if Ethereum wallet is connected', async () => {
(useWeb3React as jest.Mock).mockReturnValue({
isActive: true,
account: MOCK_ETH_ADDRESS,
});
render(<DepositForm {...props} />);
expect(await screen.findByTestId('deposit-form')).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: 'Connect' })
).not.toBeInTheDocument();
@@ -356,53 +359,56 @@ describe('Deposit form', () => {
);
});
it('prevents submission if you are on the wrong chain', () => {
it('prevents submission if you are on the wrong chain', async () => {
// Make mocks return a chain id mismatch
(useWeb3React as jest.Mock).mockReturnValue({
isActive: true,
account: MOCK_ETH_ADDRESS,
chainId: 1,
});
(useWeb3ConnectStore as unknown as jest.Mock).mockImplementation(
(useWeb3ConnectStore as unknown as jest.Mock).mockImplementationOnce(
// eslint-disable-next-line
(selector: (result: ReturnType<typeof useWeb3ConnectStore>) => any) => {
return selector({
desiredChainId: 11155111,
open: jest.fn(),
foo: 'asdf',
});
}
);
render(<DepositForm {...props} />);
expect(screen.getByTestId('chain-error')).toHaveTextContent(
expect(await screen.findByTestId('chain-error')).toHaveTextContent(
/this app only works on/i
);
expect(screen.queryByTestId('deposit-form')).not.toBeInTheDocument();
});
it('Remaining deposit allowance tooltip should be rendered', async () => {
render(<DepositForm {...props} selectedAsset={asset} />);
await act(async () => {
await userEvent.hover(screen.getByText('Remaining deposit allowance'));
});
await waitFor(async () => {
await expect(
screen.getByRole('tooltip', {
name: /VEGA has a lifetime deposit limit of 20 asset-symbol per address/,
})
).toBeInTheDocument();
});
expect(await screen.findByTestId('deposit-form')).toBeInTheDocument();
await userEvent.hover(screen.getByText('Remaining deposit allowance'));
expect(
await screen.findByRole('tooltip', {
name: /VEGA has a lifetime deposit limit of 20 asset-symbol per address/,
})
).toBeInTheDocument();
});
it('Ethereum deposit cap tooltip should be rendered', async () => {
render(<DepositForm {...props} selectedAsset={asset} />);
await act(async () => {
await userEvent.hover(screen.getByText('Ethereum deposit cap'));
});
await waitFor(async () => {
await expect(
screen.getByRole('tooltip', {
name: /The deposit cap is set when you approve an asset for use with this app/,
})
).toBeInTheDocument();
});
expect(await screen.findByTestId('deposit-form')).toBeInTheDocument();
await userEvent.hover(screen.getByText('Ethereum deposit cap'));
expect(
await screen.findByRole('tooltip', {
name: /The deposit cap is set when you approve an asset for use with this app/,
})
).toBeInTheDocument();
});
});
+33 -28
View File
@@ -92,7 +92,9 @@ export const DepositForm = ({
const maxSafe = useMaxSafe();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openDialog = useWeb3ConnectStore((store) => store.open);
const { isActive, account } = useWeb3React();
const { isActive, account, chainId } = useWeb3React();
const desiredChainId = useWeb3ConnectStore((store) => store.desiredChainId);
const invalidChain = isActive && chainId !== desiredChainId;
const { pubKey, pubKeys: _pubKeys } = useVegaWallet();
const [approveNotificationIntent, setApproveNotificationIntent] =
useState<Intent>(Intent.Warning);
@@ -152,7 +154,20 @@ export const DepositForm = ({
const approved =
balances && balances.allowance.isGreaterThan(0) ? true : false;
return (
return invalidChain ? (
<div className="mb-2">
<Notification
intent={Intent.Danger}
testId="chain-error"
message={t(
'This app only works on {{chainId}}. Switch your Ethereum wallet to the correct network.',
{
chainId: getChainName(desiredChainId),
}
)}
/>
</div>
) : (
<form
onSubmit={handleSubmit(onSubmit)}
noValidate={true}
@@ -417,7 +432,11 @@ export const DepositForm = ({
intent={approveNotificationIntent}
amount={amount}
/>
<FormButton approved={approved} selectedAsset={selectedAsset} />
<FormButton
approved={approved}
isActive={isActive}
selectedAsset={selectedAsset}
/>
</form>
);
};
@@ -425,35 +444,21 @@ export const DepositForm = ({
interface FormButtonProps {
approved: boolean;
selectedAsset: AssetFieldsFragment | undefined;
isActive: boolean;
}
const FormButton = ({ approved, selectedAsset }: FormButtonProps) => {
const FormButton = ({ approved, selectedAsset, isActive }: FormButtonProps) => {
const t = useT();
const { isActive, chainId } = useWeb3React();
const desiredChainId = useWeb3ConnectStore((store) => store.desiredChainId);
const invalidChain = isActive && chainId !== desiredChainId;
return (
<>
{invalidChain && (
<div className="mb-2">
<Notification
intent={Intent.Danger}
testId="chain-error"
message={t('This app only works on {{chainId}}.', {
chainId: getChainName(desiredChainId),
})}
/>
</div>
)}
<TradingButton
type="submit"
data-testid="deposit-submit"
fill
disabled={!isActive || invalidChain}
>
{t('Deposit')}
</TradingButton>
</>
<TradingButton
type="submit"
data-testid="deposit-submit"
fill
disabled={!isActive}
>
{t('Deposit')}
</TradingButton>
);
};
+9 -3
View File
@@ -6,7 +6,7 @@ import {
isNumeric,
} from '@vegaprotocol/utils';
import { type ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import {
type VegaICellRendererParams,
type VegaValueFormatterParams,
@@ -21,7 +21,7 @@ export const DepositsTable = (
) => {
const columnDefs = useMemo<ColDef[]>(
() => [
{ headerName: 'Asset', field: 'asset.symbol' },
{ headerName: 'Asset', field: 'asset.symbol', pinned: true },
{
headerName: 'Amount',
field: 'amount',
@@ -74,5 +74,11 @@ export const DepositsTable = (
],
[]
);
return <AgGrid columnDefs={columnDefs} {...props} />;
return (
<AgGrid
columnDefs={columnDefs}
defaultColDef={COL_DEFS.default}
{...props}
/>
);
};
+2
View File
@@ -48,6 +48,7 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
field: 'market.tradableInstrument.instrument.code',
cellRenderer: 'MarketNameCell',
cellRendererParams: { idPath: 'market.id', onMarketClick },
pinned: true,
},
{
headerName: t('Size'),
@@ -143,6 +144,7 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
<AgGrid
ref={ref}
columnDefs={columnDefs}
defaultColDef={COL_DEFS.default}
overlayNoRowsTemplate={t('No fills')}
getRowId={({ data }) => data?.id}
tooltipShowDelay={0}
-3
View File
@@ -54,9 +54,6 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
decimalPlaces: 5,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
liquidityMonitoringParameters: {
triggeringRatio: '1',
},
fees: {
__typename: 'Fees',
factors: {
@@ -22,9 +22,7 @@ export const generateFundingPayment = (
decimalPlaces: 5,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
liquidityMonitoringParameters: {
triggeringRatio: '1',
},
fees: {
__typename: 'Fees',
factors: {
+1 -1
View File
@@ -116,7 +116,7 @@
"Total fees": "Total fees",
"Total margin available": "Total margin available",
"TOTAL_MARGIN_AVAILABLE": "Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).",
"Trading terminated": "Trading terminated",
"No trading": "No trading",
"Trailing percent offset cannot be higher than 99.9": "Trailing percent offset cannot be higher than 99.9",
"Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}": "Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}",
"Trailing percentage offset": "Trailing percentage offset",
+1 -1
View File
@@ -27,7 +27,7 @@
"The {{symbol}} faucet is not available at this time": "The {{symbol}} faucet is not available at this time",
"The deposit cap is set when you approve an asset for use with this app. To increase this cap, approve {{assetSymbol}} again and choose a higher cap. Check the documentation for your Ethereum wallet app for details.": "The deposit cap is set when you approve an asset for use with this app. To increase this cap, approve {{assetSymbol}} again and choose a higher cap. Check the documentation for your Ethereum wallet app for details.",
"The faucet transaction was rejected by the connected Ethereum wallet": "The faucet transaction was rejected by the connected Ethereum wallet",
"This app only works on {{chainId}}.": "This app only works on {{chainId}}.",
"This app only works on {{chainId}}. Switch your Ethereum wallet to the correct network.": "This app only works on {{chainId}}. Switch your Ethereum wallet to the correct network.",
"To (Vega key)": "To (Vega key)",
"To date, {{currentDeposit}} {{assetSymbol}} has been deposited from this Ethereum address, so you can deposit up to {{remainingDeposit}} {{assetSymbol}} more.": "To date, {{currentDeposit}} {{assetSymbol}} has been deposited from this Ethereum address, so you can deposit up to {{remainingDeposit}} {{assetSymbol}} more.",
"Use maximum": "Use maximum",
+1 -1
View File
@@ -383,7 +383,7 @@
"MoreMarketsInfo": "To see Explorer data on existing markets visit",
"MoreNetParamsInfo": "To see Explorer data on network params visit",
"MoreProposalsInfo": "To see Explorer data on proposals visit",
"multisigContractIncorrect": "is incorrectly configured. Validator and delegator rewards will be penalised until this is resolved.",
"multisigContractIncorrect": "was incorrectly configured as at the end of the last epoch so rewards were penalised. Validator and delegator rewards will continue to be penalised until this is resolved.",
"multisigContractLink": "Ethereum Multisig Contract",
"multisigPenalty": "Multisig penalty",
"myPendingStake": "My pending stake",
+1
View File
@@ -30,6 +30,7 @@
"How often the quality of liquidity supplied by each liquidity provider is evaluated and the fees arising from that period are earmarked for specific providers. This is a market parameter. ": "How often the quality of liquidity supplied by each liquidity provider is evaluated and the fees arising from that period are earmarked for specific providers. This is a market parameter. ",
"Instrument": "Instrument",
"Insurance pool": "Insurance pool",
"Insurance Pool Balance": "Insurance Pool Balance",
"Internal conditions": "Internal conditions",
"Invalid data source": "Invalid data source",
"involvedInMarkets_one": "Involved in {{count}} market",
+85 -61
View File
@@ -1,39 +1,34 @@
{
"(Combined set volume {{runningVolume}} over last {{epochs}} epochs)": "(Combined set volume {{runningVolume}} over last {{epochs}} epochs)",
"(Created at: {{createdAt}})": "(Created at: {{createdAt}})",
"{{amount}} $VEGA staked": "{{amount}} $VEGA staked",
"{{assetSymbol}} Reward pot": "{{assetSymbol}} Reward pot",
"{{checkedAssets}} Assets": "{{checkedAssets}} Assets",
"{{distance}} ago": "{{distance}} ago",
"{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision",
"<0>No running Desktop App/CLI detected. Open your app now to connect or enter a</0> <1>custom wallet location</1>": "<0>No running Desktop App/CLI detected. Open your app now to connect or enter a</0> <1>custom wallet location</1>",
"(Tier {{tier}} as of last epoch)": "(Tier {{tier}} as of last epoch)",
"24h vol": "24h vol",
"24h volume": "24h volume",
"<0>No running Desktop App/CLI detected. Open your app now to connect or enter a</0> <1>custom wallet location</1>": "<0>No running Desktop App/CLI detected. Open your app now to connect or enter a</0> <1>custom wallet location</1>",
"A percentage of commission earned by the referrer": "A percentage of commission earned by the referrer",
"A successor to this market has been proposed": "A successor to this market has been proposed",
"About the referral program": "About the referral program",
"Active": "Active",
"Activity Streak": "Activity Streak",
"All": "All",
"An unknown error occurred.": "An unknown error occurred.",
"Anonymous": "Anonymous",
"Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction": "Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction",
"Assessed over": "Assessed over",
"Asset (1)": "Asset (1)",
"Assets": "Assets",
"Available to withdraw this epoch": "Available to withdraw this epoch",
"Average position": "Average position",
"Base commission rate": "Base commission rate",
"Base rate": "Base rate",
"Best bid": "Best bid",
"Best offer": "Best offer",
"Browse": "Browse",
"By using the Vega Console, you acknowledge that you have read and understood the <0>Vega Console Disclaimer</0>": "By using the Vega Console, you acknowledge that you have read and understood the <0>Vega Console Disclaimer</0>",
"Chart": "Chart",
"Change (24h)": "Change (24h)",
"Changes have been proposed for this market. <0>View proposals</0>": "Changes have been proposed for this market. <0>View proposals</0>",
"Chart": "Chart",
"Chart by <0>TradingView</0>": "Chart by <0>TradingView</0>",
"checkOutProposalsAndVote": "Check out the terms of the proposals and vote:",
"checkOutProposalsAndVote_one": "Check out the terms of the proposal and vote:",
"checkOutProposalsAndVote_other": "Check out the terms of the proposals and vote:",
"Close": "Close",
"Close menu": "Close menu",
"Closed": "Closed",
@@ -56,6 +51,13 @@
"Countdown": "Countdown",
"Create a referral code": "Create a referral code",
"Current tier": "Current tier",
"DISCLAIMER_P1": "Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets. The Vega Protocol is an implementation layer (layer one) protocol made of free, public, open-source or source-available software. Use of the Vega Protocol involves various risks, including but not limited to, losses while digital assets are supplied to the Vega Protocol and losses due to the fluctuation of prices of assets.",
"DISCLAIMER_P2": "Before using the Vega Protocol, review the relevant documentation at docs.vega.xyz to make sure that you understand how it works. Conduct your own due diligence and consult your financial advisor before making any investment decisions.",
"DISCLAIMER_P3": "As described in the Vega Protocol core license, the Vega Protocol is provided “as is”, at your own risk, and without warranties of any kind. Although Gobalsky Labs Limited developed much of the initial code for the Vega Protocol, it does not provide or control the Vega Protocol, which is run by third parties deploying it on a bespoke blockchain. Upgrades and modifications to the Vega Protocol are managed in a community-driven way by holders of the VEGA governance token.",
"DISCLAIMER_P4": "No developer or entity involved in creating the Vega Protocol will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Vega Protocol, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or legal costs, or loss of profits, cryptoassets, tokens or anything else of value.",
"DISCLAIMER_P5": "This website is hosted on a decentralised network, the Interplanetary File System (“IPFS”). The IPFS decentralised web is made up of all the computers (nodes) connected to it. Data is therefore stored on many different computers.",
"DISCLAIMER_P6": "The information provided on this website does not constitute investment advice, financial advice, trading advice, or any other sort of advice and you should not treat any of the website's content as such. No party recommends that any cryptoasset should be bought, sold, or held by you via this website. No party ensures the accuracy of information listed on this website or holds any responsibility for any missing or wrong information. You understand that you are using any and all information available here at your own risk.",
"DISCLAIMER_P7": "Additionally, just as you can access email protocols such as SMTP through multiple email clients, you can potentially access the Vega Protocol through many web or mobile interfaces. You are responsible for doing your own diligence on those interfaces to understand the associated risks and any fees.",
"Dark mode": "Dark mode",
"Date Joined": "Date Joined",
"Depending on data node retention you may not be able see the full 30 days": "Depending on data node retention you may not be able see the full 30 days",
@@ -65,13 +67,6 @@
"Depth": "Depth",
"Description": "Description",
"Disclaimer": "Disclaimer",
"DISCLAIMER_P1": "Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets. The Vega Protocol is an implementation layer (layer one) protocol made of free, public, open-source or source-available software. Use of the Vega Protocol involves various risks, including but not limited to, losses while digital assets are supplied to the Vega Protocol and losses due to the fluctuation of prices of assets.",
"DISCLAIMER_P2": "Before using the Vega Protocol, review the relevant documentation at docs.vega.xyz to make sure that you understand how it works. Conduct your own due diligence and consult your financial advisor before making any investment decisions.",
"DISCLAIMER_P3": "As described in the Vega Protocol core license, the Vega Protocol is provided “as is”, at your own risk, and without warranties of any kind. Although Gobalsky Labs Limited developed much of the initial code for the Vega Protocol, it does not provide or control the Vega Protocol, which is run by third parties deploying it on a bespoke blockchain. Upgrades and modifications to the Vega Protocol are managed in a community-driven way by holders of the VEGA governance token.",
"DISCLAIMER_P4": "No developer or entity involved in creating the Vega Protocol will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Vega Protocol, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or legal costs, or loss of profits, cryptoassets, tokens or anything else of value.",
"DISCLAIMER_P5": "This website is hosted on a decentralised network, the Interplanetary File System (“IPFS”). The IPFS decentralised web is made up of all the computers (nodes) connected to it. Data is therefore stored on many different computers.",
"DISCLAIMER_P6": "The information provided on this website does not constitute investment advice, financial advice, trading advice, or any other sort of advice and you should not treat any of the website's content as such. No party recommends that any cryptoasset should be bought, sold, or held by you via this website. No party ensures the accuracy of information listed on this website or holds any responsibility for any missing or wrong information. You understand that you are using any and all information available here at your own risk.",
"DISCLAIMER_P7": "Additionally, just as you can access email protocols such as SMTP through multiple email clients, you can potentially access the Vega Protocol through many web or mobile interfaces. You are responsible for doing your own diligence on those interfaces to understand the associated risks and any fees.",
"Disconnect": "Disconnect",
"Discount": "Discount",
"Discounts are applied automatically during trading based on the key(s) used": "Discounts are applied automatically during trading based on the key(s) used",
@@ -79,12 +74,13 @@
"Earn commission & stake rewards": "Earn commission & stake rewards",
"Earned by me": "Earned by me",
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
"Ends in": "Ends in",
"Entity scope": "Entity scope",
"Environment not configured": "Environment not configured",
"epochs in referral set": "epochs in referral set",
"Epochs in set": "Epochs in set",
"Epochs to next tier": "Epochs to next tier",
"Expected {{distance}} ago": "Expected {{distance}} ago",
"Expected in {{distance}}": "Expected in {{distance}}",
"Expected {{distance}} ago": "Expected {{distance}} ago",
"Experiment for free with virtual assets on <0>Fairground Testnet</0>": "Experiment for free with virtual assets on <0>Fairground Testnet</0>",
"Expiry": "Expiry",
"Explore": "Explore",
@@ -98,14 +94,15 @@
"From epoch": "From epoch",
"Fully decentralised high performance peer-to-network trading.": "Fully decentralised high performance peer-to-network trading.",
"Funding": "Funding",
"Funding history": "Funding history",
"Funding Payments": "Funding Payments",
"Funding payments": "Funding payments",
"Funding Rate": "Funding Rate",
"Funding history": "Funding history",
"Funding payments": "Funding payments",
"Funding rate": "Funding rate",
"Futures": "Futures",
"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.": "Get rewards for providing liquidity. Get rewards for providing liquidity.",
"Get started": "Get started",
"Give Feedback": "Give Feedback",
"Go back and try again": "Go back and try again",
@@ -120,18 +117,19 @@
"Hoarder reward multiplier": "Hoarder reward multiplier",
"How it works": "How it works",
"I want a code": "I want a code",
"INTERVAL_I15M": "15m",
"INTERVAL_I1D": "1D",
"INTERVAL_I1H": "1H",
"INTERVAL_I1M": "1m",
"INTERVAL_I5M": "5m",
"INTERVAL_I6H": "6H",
"Improve vega console": "Improve vega console",
"Inactive": "Inactive",
"Index Price": "Index Price",
"Indicators": "Indicators",
"Individual": "Individual",
"Infrastructure": "Infrastructure",
"Interval: {{interval}}": "Interval: {{interval}}",
"INTERVAL_I1M": "1m",
"INTERVAL_I5M": "5m",
"INTERVAL_I15M": "15m",
"INTERVAL_I1H": "1H",
"INTERVAL_I6H": "6H",
"INTERVAL_I1D": "1D",
"Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.": "Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.",
"Learn about providing liquidity": "Learn about providing liquidity",
"Learn more": "Learn more",
@@ -155,16 +153,11 @@
"Metamask Snap <0>quick start</0>": "Metamask Snap <0>quick start</0>",
"Min. epochs": "Min. epochs",
"Min. trading volume": "Min. trading volume",
"minTradingVolume": "Min. trading volume (last {{count}} epochs)",
"minTradingVolume_one": "Min. trading volume (last {{count}} epoch)",
"minTradingVolume_other": "Min. trading volume (last {{count}} epochs)",
"My current volume": "My current volume",
"My liquidity provision": "My liquidity provision",
"My trading fees": "My trading fees",
"myVolume": "My volume (last {{count}} epochs)",
"myVolume_one": "My volume (last {{count}} epoch)",
"myVolume_other": "My volume (last {{count}} epochs)",
"Name": "Name",
"No MetaMask version that supports snaps detected. Learn more about <0>MetaMask Snaps</0>": "No MetaMask version that supports snaps detected. Learn more about <0>MetaMask Snaps</0>",
"No closed orders": "No closed orders",
"No data": "No data",
"No deposits": "No deposits",
@@ -174,7 +167,6 @@
"No market": "No market",
"No markets": "No markets",
"No markets.": "No markets.",
"No MetaMask version that supports snaps detected. Learn more about <0>MetaMask Snaps</0>": "No MetaMask version that supports snaps detected. Learn more about <0>MetaMask Snaps</0>",
"No open orders": "No open orders",
"No orders": "No orders",
"No party accepts any liability for any losses whatsoever.": "No party accepts any liability for any losses whatsoever.",
@@ -182,6 +174,7 @@
"No referral program active": "No referral program active",
"No rejected orders": "No rejected orders",
"No rewards": "No rewards",
"No rows": "No rows",
"No thanks": "No thanks",
"No third party has access to your funds.": "No third party has access to your funds.",
"No volume discount program active": "No volume discount program active",
@@ -190,6 +183,7 @@
"Non-custodial and pseudonymous": "Non-custodial and pseudonymous",
"None": "None",
"Not connected": "Not connected",
"Number of epochs after distribution to delay vesting of rewards by": "Number of epochs after distribution to delay vesting of rewards by",
"Number of traders": "Number of traders",
"Open": "Open",
"Open a position": "Open a position",
@@ -198,11 +192,9 @@
"Order": "Order",
"Orderbook": "Orderbook",
"Orders": "Orders",
"PRNT": "PRNT",
"Page not found": "Page not found",
"Parent of a market": "Parent of a market",
"pastEpochs": "Past {{count}} epochs",
"pastEpochs_one": "Past {{count}} epoch",
"pastEpochs_other": "Past {{count}} epochs",
"Pennant": "Pennant",
"Perpetuals": "Perpetuals",
"Please choose another market from the <0>market list</0>": "Please choose another market from the <0>market list</0>",
@@ -210,15 +202,12 @@
"Portfolio": "Portfolio",
"Positions": "Positions",
"Price": "Price",
"PRNT": "PRNT",
"Program ends:": "Program ends:",
"Propose a new market": "Propose a new market",
"Proposed final price is {{price}} {{assetSymbol}}.": "Proposed final price is {{price}} {{assetSymbol}}.",
"Proposed markets": "Proposed markets",
"Providing liquidity": "Providing liquidity",
"Purpose built proof of stake blockchain": "Purpose built proof of stake blockchain",
"qUSD": "qUSD",
"qUSD provides a rough USD equivalent of balances across all assets using the value of \"Quantum\" for that asset": "qUSD provides a rough USD equivalent of balances across all assets using the value of \"Quantum\" for that asset",
"Read the terms": "Read the terms",
"Ready to trade": "Ready to trade",
"Ready to trade with real funds? <0>Switch to Mainnet</0>": "Ready to trade with real funds? <0>Switch to Mainnet</0>",
@@ -226,9 +215,6 @@
"Referral benefits": "Referral benefits",
"Referral discount": "Referral discount",
"Referrals": "Referrals",
"referralStatisticsCommission": "Commission earned in <0>qUSD</0> (<1>last {{count}} epochs</1>)",
"referralStatisticsCommission_one": "Commission earned in <0>qUSD</0> (<1>last {{count}} epoch</1>)",
"referralStatisticsCommission_other": "Commission earned in <0>qUSD</0> (<1>last {{count}} epochs</1>)",
"Referrer commission": "Referrer commission",
"Referrer trading discount": "Referrer trading discount",
"Referrers earn commission based on a percentage of the taker fees their referees pay": "Referrers earn commission based on a percentage of the taker fees their referees pay",
@@ -238,12 +224,12 @@
"Required for next tier": "Required for next tier",
"Reset Columns": "Reset Columns",
"Resources": "Resources",
"Reward bonus": "Reward bonus",
"Reward {{reward}}x": "Reward {{reward}}x",
"Rewards": "Rewards",
"Rewards funded using the pro-rata strategy should be distributed pro-rata by each entity's reward metric scaled by any active multipliers that party has": " Rewards funded using the pro-rata strategy should be distributed pro-rata by each entity's reward metric scaled by any active multipliers that party has",
"Rewards history": "Rewards history",
"Rewards multipliers": "Rewards multipliers",
"runningNotionalOverEpochs": "Combined running notional over the {{count}} epochs",
"runningNotionalOverEpochs_one": "Combined running notional over the {{count}} epoch",
"runningNotionalOverEpochs_other": "Combined running notional over the {{count}} epochs",
"SCCR": "SCCR",
"Search": "Search",
"See all markets": "See all markets",
@@ -262,6 +248,7 @@
"Spread": "Spread",
"Stake a minimum of {{minimumStakedTokens}} $VEGA tokens": "Stake a minimum of {{minimumStakedTokens}} $VEGA tokens",
"Stake some $VEGA now": "Stake some $VEGA now",
"Staked VEGA": "Staked VEGA",
"Staking multiplier": "Staking multiplier",
"Start trading": "Start trading",
"Start trading on the worlds most advanced decentralised exchange.": "Start trading on the worlds most advanced decentralised exchange.",
@@ -274,6 +261,7 @@
"Supplied stake": "Supplied stake",
"Suspended due to price or liquidity monitoring trigger": "Suspended due to price or liquidity monitoring trigger",
"Target stake": "Target stake",
"Team": "Team",
"The amount of fees paid to liquidity providers across the whole market during the last epoch {{epoch}}.": "The amount of fees paid to liquidity providers across the whole market during the last epoch {{epoch}}.",
"The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee": "The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee",
"The external time weighted average price (TWAP) received from the data source defined in the data sourcing specification.": "The external time weighted average price (TWAP) received from the data source defined in the data sourcing specification.",
@@ -283,42 +271,41 @@
"The successor market <0>{{instrumentName}}</0> has a 24h trading volume of {{successorVolume}}": "The successor market <0>{{instrumentName}}</0> has a 24h trading volume of {{successorVolume}}",
"The successor market is <0>{{instrumentName}}</0>": "The successor market is <0>{{instrumentName}}</0>",
"The transaction could not be sent": "The transaction could not be sent",
"This market URL is not available any more.": "This market URL is not available any more.",
"This market expires in {{duration}}.": "This market expires in {{duration}}.",
"This market expires when triggered by its oracle, not on a set date.": "This market expires when triggered by its oracle, not on a set date.",
"This market has been settled": "This market has been settled",
"This market has been succeeded": "This market has been succeeded",
"This market has been suspended via a governance vote and can be resumed or terminated by further votes.": "This market has been suspended via a governance vote and can be resumed or terminated by further votes.",
"This market URL is not available any more.": "This market URL is not available any more.",
"This timestamp is user curated metadata and does not drive any on-chain functionality.": "This timestamp is user curated metadata and does not drive any on-chain functionality.",
"Tier": "Tier",
"to": "to",
"Tier {{tier}}": "Tier {{tier}}",
"Tier {{userTier}}": "Tier {{userTier}}",
"To protect the network from spam, you must have at least {{requiredFunds}} qUSD of any asset on the network to proceed.": "To protect the network from spam, you must have at least {{requiredFunds}} qUSD of any asset on the network to proceed.",
"Toast location": "Toast location",
"Total discount": "Total discount",
"Total distributed": "Total distributed",
"Total fee after discount": "Total fee after discount",
"Total fee before discount": "Total fee before discount",
"totalCommission": "Total commission (<0>last {{count}} epochs</0>)",
"totalCommission_one": "Total commission (<0>last {{count}} epoch</0>)",
"totalCommission_other": "Total commission (<0>last {{count}} epochs</0>)",
"Trader": "Trader",
"Trades": "Trades",
"Trading": "Trading",
"TradingView": "TradingView",
"Trading has been terminated as a result of the product definition": "Trading has been terminated as a result of the product definition",
"Trading mode": "Trading mode",
"Trading on market {{name}} may stop on {{date}}. There is an open proposal to close this market.": "Trading on market {{name}} may stop on {{date}}. There is an open proposal to close this market.",
"Trading on market {{name}} may stop. There are open proposals to close this market": "Trading on market {{name}} may stop. There are open proposals to close this market",
"Trading on market {{name}} will stop on {{date}}": "Trading on market {{name}} will stop on {{date}}",
"TradingView": "TradingView",
"Transfer": "Transfer",
"Unknown": "Unknown",
"Unknown settlement date": "Unknown settlement date",
"Vega chart": "Vega chart",
"Vega Reward pot": "Vega Reward pot",
"Vega Wallet <0>full featured<0>": "Vega Wallet <0>full featured<0>",
"Vega chart": "Vega chart",
"Vesting": "Vesting",
"Vesting {{assetSymbol}}": "Vesting {{assetSymbol}}",
"Vesting multiplier": "Vesting multiplier",
"Vesting {{assetSymbol}}": "Vesting {{assetSymbol}}",
"Vesting {{vesting}}x": "Vesting {{vesting}}x",
"View as party": "View as party",
"View liquidity provision table": "View liquidity provision table",
"View on Explorer": "View on Explorer",
@@ -331,9 +318,6 @@
"Volume (24h)": "Volume (24h)",
"Volume discount": "Volume discount",
"Volume to next tier": "Volume to next tier",
"volumeLastEpochs": "Volume (last {{count}} epochs)",
"volumeLastEpochs_one": "Volume (last {{count}} epoch)",
"volumeLastEpochs_other": "Volume (last {{count}} epochs)",
"Wallet": "Wallet",
"We're sorry but we don't have an active referral programme currently running. You can propose a new programme <0>here</0>.": "We're sorry but we don't have an active referral programme currently running. You can propose a new programme <0>here</0>.",
"Welcome to Vega trading!": "Welcome to Vega trading!",
@@ -345,11 +329,51 @@
"You need a <0>Vega wallet</0> to start trading in this market.": "You need a <0>Vega wallet</0> to start trading in this market.",
"You need at least {{requiredStake}} VEGA staked to generate a referral code and participate in the referral program.": "You need at least {{requiredStake}} VEGA staked to generate a referral code and participate in the referral program.",
"You will no longer be able to hold a position on this market when it closes in {{duration}}.": "You will no longer be able to hold a position on this market when it closes in {{duration}}.",
"youAreJoiningTheGroup": "You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epochs.",
"youAreJoiningTheGroup_one": "You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epoch.",
"youAreJoiningTheGroup_other": "You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epochs.",
"Your code has been rejected": "Your code has been rejected",
"Your identity is always anonymous on Vega": "Your identity is always anonymous on Vega",
"Your referral code": "Your referral code",
"Your tier": "Your tier"
"Your tier": "Your tier",
"checkOutProposalsAndVote": "Check out the terms of the proposals and vote:",
"checkOutProposalsAndVote_one": "Check out the terms of the proposal and vote:",
"checkOutProposalsAndVote_other": "Check out the terms of the proposals and vote:",
"epochStreak_one": "{{count}} epoch streak",
"epochs in referral set": "epochs in referral set",
"epochsStreak": "{{count}} epochs streak",
"minTradingVolume": "Min. trading volume (last {{count}} epochs)",
"minTradingVolume_one": "Min. trading volume (last {{count}} epoch)",
"minTradingVolume_other": "Min. trading volume (last {{count}} epochs)",
"myVolume": "My volume (last {{count}} epochs)",
"myVolume_one": "My volume (last {{count}} epoch)",
"myVolume_other": "My volume (last {{count}} epochs)",
"numberEpochs": "{{count}} epochs",
"numberEpochs_one": "{{count}} epoch",
"numberEpochs_other": "{{count}} epochs",
"pastEpochs": "Past {{count}} epochs",
"pastEpochs_one": "Past {{count}} epoch",
"pastEpochs_other": "Past {{count}} epochs",
"qUSD": "qUSD",
"qUSD provides a rough USD equivalent of balances across all assets using the value of \"Quantum\" for that asset": "qUSD provides a rough USD equivalent of balances across all assets using the value of \"Quantum\" for that asset",
"referralStatisticsCommission": "Commission earned in <0>qUSD</0> (<1>last {{count}} epochs</1>)",
"referralStatisticsCommission_one": "Commission earned in <0>qUSD</0> (<1>last {{count}} epoch</1>)",
"referralStatisticsCommission_other": "Commission earned in <0>qUSD</0> (<1>last {{count}} epochs</1>)",
"runningNotionalOverEpochs": "Combined running notional over the {{count}} epochs",
"runningNotionalOverEpochs_one": "Combined running notional over the {{count}} epoch",
"runningNotionalOverEpochs_other": "Combined running notional over the {{count}} epochs",
"to": "to",
"totalCommission": "Total commission (<0>last {{count}} epochs</0>)",
"totalCommission_one": "Total commission (<0>last {{count}} epoch</0>)",
"totalCommission_other": "Total commission (<0>last {{count}} epochs</0>)",
"userActive": "{{active}} trader: {{count}} epochs so far",
"volumeLastEpochs": "Volume (last {{count}} epochs)",
"volumeLastEpochs_one": "Volume (last {{count}} epoch)",
"volumeLastEpochs_other": "Volume (last {{count}} epochs)",
"youAreJoiningTheGroup": "You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epochs.",
"youAreJoiningTheGroup_one": "You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epoch.",
"youAreJoiningTheGroup_other": "You are joining the group shown, but will not have access to benefits until you have completed at least {{count}} epochs.",
"{{amount}} $VEGA staked": "{{amount}} $VEGA staked",
"{{assetSymbol}} Reward pot": "{{assetSymbol}} Reward pot",
"{{checkedAssets}} Assets": "{{checkedAssets}} Assets",
"{{distance}} ago": "{{distance}} ago",
"{{instrumentCode}} liquidity provision": "{{instrumentCode}} liquidity provision",
"{{reward}}x": "{{reward}}x"
}
@@ -83,11 +83,6 @@ describe('LiquidityTable', () => {
h.querySelector('[ref="eText"]')?.textContent?.trim()
);
const expectedHeaders = [
undefined,
undefined,
undefined,
undefined,
undefined,
'Party',
'Status',
'Commitment ()',
+277 -309
View File
@@ -18,7 +18,7 @@ import {
truncateMiddle,
} from '@vegaprotocol/ui-toolkit';
import type {
ColGroupDef,
ColDef,
ITooltipParams,
ValueFormatterParams,
} from 'ag-grid-community';
@@ -60,10 +60,11 @@ const dateValueFormatter = ({ value }: { value?: string | null }) => {
return getDateTimeFormat().format(new Date(value));
};
const defaultColDef = {
const defaultColDef: ColDef = {
resizable: true,
sortable: true,
tooltipComponent: TooltipCellComponent,
minWidth: 120,
};
export interface LiquidityTableProps
@@ -168,333 +169,300 @@ export const LiquidityTable = ({
)}`;
};
const defs: ColGroupDef[] = [
const defs: ColDef[] = [
{
headerName: '',
children: [
{
headerName: t('Party'),
field: 'partyId',
headerTooltip: t(
'The public key of the party making this commitment.'
),
cellRenderer: copyCellRenderer,
},
],
headerName: t('Party'),
field: 'partyId',
headerTooltip: t('The public key of the party making this commitment.'),
cellRenderer: copyCellRenderer,
pinned: 'left',
},
{
headerName: t('Commitment details'),
marryChildren: true,
children: [
{
headerName: t('Status'),
headerTooltip: t('The current status of this liquidity provision.'),
field: 'status',
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<LiquidityProvisionData, 'status'>) => {
if (!value) return value;
if (
data?.status === LiquidityProvisionStatus.STATUS_PENDING &&
(data?.currentCommitmentAmount || data?.currentFee)
) {
return (
<span className="text-warning">
{t('Updating next epoch')}
</span>
);
headerName: t('Status'),
headerTooltip: t('The current status of this liquidity provision.'),
field: 'status',
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<LiquidityProvisionData, 'status'>) => {
if (!value) return value;
if (
data?.status === LiquidityProvisionStatus.STATUS_PENDING &&
(data?.currentCommitmentAmount || data?.currentFee)
) {
return (
<span className="text-warning">{t('Updating next epoch')}</span>
);
}
return (
<span>
{
LiquidityProvisionStatusMapping[
value as LiquidityProvisionStatus
]
}
return (
<span>
{
LiquidityProvisionStatusMapping[
value as LiquidityProvisionStatus
]
}
</span>
</span>
);
},
},
{
headerName: t(`Commitment ({{symbol}})`, { symbol }),
field: 'commitmentAmount',
type: 'rightAligned',
headerTooltip: t(
'The amount committed to the market by this liquidity provider.'
),
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<
LiquidityProvisionData,
'commitmentAmount'
>) => {
if (!value) return '-';
const currentCommitmentAmount = data?.currentCommitmentAmount;
const pendingCommitmentAmount = value;
const formattedPendingCommitmentAmount =
addDecimalsFormatNumberQuantum(
pendingCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
if (
currentCommitmentAmount &&
currentCommitmentAmount !== pendingCommitmentAmount
) {
const formattedCurrentCommitmentAmount =
addDecimalsFormatNumberQuantum(
currentCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
},
},
{
headerName: t(`Commitment ({{symbol}})`, { symbol }),
field: 'commitmentAmount',
type: 'rightAligned',
headerTooltip: t(
'The amount committed to the market by this liquidity provider.'
),
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<
LiquidityProvisionData,
'commitmentAmount'
>) => {
if (!value) return '-';
const currentCommitmentAmount = data?.currentCommitmentAmount;
const pendingCommitmentAmount = value;
const formattedPendingCommitmentAmount =
addDecimalsFormatNumberQuantum(
pendingCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
return (
<>
<span>{formattedCurrentCommitmentAmount}</span> (
<span className="text-warning">
{formattedPendingCommitmentAmount}
</span>
)
</>
);
} else {
return formattedPendingCommitmentAmount;
}
},
tooltipValueGetter: assetDecimalsFormatter,
},
{
headerName: t('Obligation'),
field: 'commitmentAmount',
type: 'rightAligned',
headerTooltip: t(
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume.`
),
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<
LiquidityProvisionData,
'commitmentAmount'
>) => {
if (!value) return '-';
if (
currentCommitmentAmount &&
currentCommitmentAmount !== pendingCommitmentAmount
) {
const formattedCurrentCommitmentAmount =
addDecimalsFormatNumberQuantum(
currentCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
return (
<>
<span>{formattedCurrentCommitmentAmount}</span> (
<span className="text-warning">
{formattedPendingCommitmentAmount}
</span>
)
</>
);
} else {
return formattedPendingCommitmentAmount;
}
},
tooltipValueGetter: assetDecimalsFormatter,
},
{
headerName: t('Obligation'),
field: 'commitmentAmount',
type: 'rightAligned',
headerTooltip: t(
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume.`
),
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<
LiquidityProvisionData,
'commitmentAmount'
>) => {
if (!value) return '-';
const currentCommitmentAmount = data?.currentCommitmentAmount
? new BigNumber(data?.currentCommitmentAmount)
.times(Number(stakeToCcyVolume) || 1)
.toString()
: undefined;
const pendingCommitmentAmount = new BigNumber(value)
const currentCommitmentAmount = data?.currentCommitmentAmount
? new BigNumber(data?.currentCommitmentAmount)
.times(Number(stakeToCcyVolume) || 1)
.toString();
.toString()
: undefined;
const formattedPendingCommitmentAmount =
addDecimalsFormatNumberQuantum(
pendingCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
const pendingCommitmentAmount = new BigNumber(value)
.times(Number(stakeToCcyVolume) || 1)
.toString();
if (
currentCommitmentAmount &&
currentCommitmentAmount !== pendingCommitmentAmount
) {
const formattedCurrentCommitmentAmount =
addDecimalsFormatNumberQuantum(
currentCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
const formattedPendingCommitmentAmount =
addDecimalsFormatNumberQuantum(
pendingCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
return (
<>
<span>{formattedCurrentCommitmentAmount}</span> (
<span className="text-warning">
{formattedPendingCommitmentAmount}
</span>
)
</>
);
} else {
return formattedPendingCommitmentAmount;
}
},
tooltipValueGetter: stakeToCcyVolumeFormatter,
},
{
headerName: t('Fee'),
headerTooltip: t(
'The fee percentage (per trade) proposed by each liquidity provider.'
),
field: 'fee',
type: 'rightAligned',
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<LiquidityProvisionData, 'fee'>) => {
if (!value) return '-';
const formattedPendingFee =
formatNumberPercentage(new BigNumber(value).times(100), 2) ||
'-';
if (data?.currentFee && data?.currentFee !== value) {
const formattedCurrentFee = formatNumberPercentage(
new BigNumber(data.currentFee).times(100),
2
);
return (
<>
<span>{formattedCurrentFee}</span> (
<span className="text-warning">{formattedPendingFee}</span>)
</>
);
}
return formattedPendingFee;
},
},
{
headerName: t('Adjusted stake'),
field: 'feeShare.virtualStake',
type: 'rightAligned',
headerTooltip: t(
'The effective stake of the liquidity provider, adjusted for length of commitment and impact on equity like share.'
),
if (
currentCommitmentAmount &&
currentCommitmentAmount !== pendingCommitmentAmount
) {
const formattedCurrentCommitmentAmount =
addDecimalsFormatNumberQuantum(
currentCommitmentAmount,
assetDecimalPlaces ?? 0,
quantum ?? 0
);
valueFormatter: assetDecimalsQuantumFormatter,
tooltipValueGetter: assetDecimalsFormatter,
},
{
headerName: t(`Share`),
field: 'feeShare.equityLikeShare',
type: 'rightAligned',
headerTooltip: t(
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
),
valueFormatter: percentageFormatter,
},
],
return (
<>
<span>{formattedCurrentCommitmentAmount}</span> (
<span className="text-warning">
{formattedPendingCommitmentAmount}
</span>
)
</>
);
} else {
return formattedPendingCommitmentAmount;
}
},
tooltipValueGetter: stakeToCcyVolumeFormatter,
},
{
headerName: t('Live liquidity data'),
marryChildren: true,
children: [
{
headerName: t('Live supplied liquidity'),
field: 'balance',
type: 'rightAligned',
headerTooltip: t(
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
),
valueFormatter: stakeToCcyVolumeQuantumFormatter,
tooltipValueGetter: stakeToCcyVolumeFormatter,
},
{
headerName: t('Fees accrued this epoch'),
field: 'earmarkedFees',
type: 'rightAligned',
headerTooltip: t(
`The liquidity fees accrued by each provider, which will be distributed at the end of the epoch after applying any penalties.`
),
valueFormatter: assetDecimalsQuantumFormatter,
tooltipValueGetter: feesAccruedTooltip,
cellClassRules: {
'text-warning': ({ data }: { data: LiquidityProvisionData }) => {
if (!data.sla) return false;
return (
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isLessThan(1) &&
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isGreaterThan(data.commitmentMinTimeFraction)
);
},
'text-red-500': ({ data }: { data: LiquidityProvisionData }) => {
if (!data.sla) return false;
return new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isLessThan(data.commitmentMinTimeFraction);
},
},
},
{
headerName: t(`Live time on book`),
field: 'sla.currentEpochFractionOfTimeOnBook',
type: 'rightAligned',
headerTooltip: t('Current epoch fraction of time on the book.'),
valueFormatter: percentageFormatter,
},
{
headerName: t('Live liquidity score (%)'),
field: 'feeShare.averageScore',
type: 'rightAligned',
headerTooltip: t(
'The liquidity score of the provider, used to determine allocation of fees to the best performing LPs. Posting volume closer to the mid on both sides of the book will improve this score.'
),
valueFormatter: percentageFormatter,
},
],
headerName: t('Fee'),
headerTooltip: t(
'The fee percentage (per trade) proposed by each liquidity provider.'
),
field: 'fee',
type: 'rightAligned',
cellRenderer: ({
data,
value,
}: VegaICellRendererParams<LiquidityProvisionData, 'fee'>) => {
if (!value) return '-';
const formattedPendingFee =
formatNumberPercentage(new BigNumber(value).times(100), 2) || '-';
if (data?.currentFee && data?.currentFee !== value) {
const formattedCurrentFee = formatNumberPercentage(
new BigNumber(data.currentFee).times(100),
2
);
return (
<>
<span>{formattedCurrentFee}</span> (
<span className="text-warning">{formattedPendingFee}</span>)
</>
);
}
return formattedPendingFee;
},
},
{
headerName: t('Last epoch SLA details'),
marryChildren: true,
children: [
{
headerName: t(`Last time on book`),
field: 'sla.lastEpochFractionOfTimeOnBook',
type: 'rightAligned',
headerTooltip: t(
'Fraction of time on the book at the end of the last epoch.'
),
valueFormatter: percentageFormatter,
},
{
headerName: t(`Last fee penalty`),
field: 'sla.lastEpochFeePenalty',
type: 'rightAligned',
headerTooltip: t(
'Penalty applied on the fees a liquidity provider collected in the last epoch. This percentage increased if an LP did not meet the SLA, or if they met it but other LPs outscored them in the previous epoch.'
),
valueFormatter: percentageFormatter,
},
{
headerName: t(`Last bond penalty`),
field: 'sla.lastEpochBondPenalty',
type: 'rightAligned',
headerTooltip: t(
`Penalty applied on a provider's bond penalty at the end of the last epoch. This percentage increased if an LP: had a shortfall and their bond needed to be used to cover it, did not meet the SLA, and/or reduced their commitment to the point that the market was below its target stake.`
),
valueFormatter: percentageFormatter,
},
],
headerName: t('Adjusted stake'),
field: 'feeShare.virtualStake',
type: 'rightAligned',
headerTooltip: t(
'The effective stake of the liquidity provider, adjusted for length of commitment and impact on equity like share.'
),
valueFormatter: assetDecimalsQuantumFormatter,
tooltipValueGetter: assetDecimalsFormatter,
},
{
headerName: '',
marryChildren: true,
children: [
{
headerName: t('Created'),
headerTooltip: t(
'The date and time this liquidity provision was created.'
),
field: 'createdAt',
type: 'rightAligned',
valueFormatter: dateValueFormatter,
headerName: t(`Share`),
field: 'feeShare.equityLikeShare',
type: 'rightAligned',
headerTooltip: t(
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
),
valueFormatter: percentageFormatter,
},
{
headerName: t('Live supplied liquidity'),
field: 'balance',
type: 'rightAligned',
headerTooltip: t(
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
),
valueFormatter: stakeToCcyVolumeQuantumFormatter,
tooltipValueGetter: stakeToCcyVolumeFormatter,
},
{
headerName: t('Fees accrued this epoch'),
field: 'earmarkedFees',
type: 'rightAligned',
headerTooltip: t(
`The liquidity fees accrued by each provider, which will be distributed at the end of the epoch after applying any penalties.`
),
valueFormatter: assetDecimalsQuantumFormatter,
tooltipValueGetter: feesAccruedTooltip,
cellClassRules: {
'text-warning': ({ data }: { data: LiquidityProvisionData }) => {
if (!data.sla) return false;
return (
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isLessThan(1) &&
new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isGreaterThan(data.commitmentMinTimeFraction)
);
},
{
headerName: t('Updated'),
headerTooltip: t(
'The date and time this liquidity provision was last updated.'
),
field: 'updatedAt',
type: 'rightAligned',
valueFormatter: dateValueFormatter,
'text-red-500': ({ data }: { data: LiquidityProvisionData }) => {
if (!data.sla) return false;
return new BigNumber(
data.sla.currentEpochFractionOfTimeOnBook
).isLessThan(data.commitmentMinTimeFraction);
},
],
},
},
{
headerName: t(`Live time on book`),
field: 'sla.currentEpochFractionOfTimeOnBook',
type: 'rightAligned',
headerTooltip: t('Current epoch fraction of time on the book.'),
valueFormatter: percentageFormatter,
},
{
headerName: t('Live liquidity score (%)'),
field: 'feeShare.averageScore',
type: 'rightAligned',
headerTooltip: t(
'The liquidity score of the provider, used to determine allocation of fees to the best performing LPs. Posting volume closer to the mid on both sides of the book will improve this score.'
),
valueFormatter: percentageFormatter,
},
{
headerName: t(`Last time on book`),
field: 'sla.lastEpochFractionOfTimeOnBook',
type: 'rightAligned',
headerTooltip: t(
'Fraction of time on the book at the end of the last epoch.'
),
valueFormatter: percentageFormatter,
},
{
headerName: t(`Last fee penalty`),
field: 'sla.lastEpochFeePenalty',
type: 'rightAligned',
headerTooltip: t(
'Penalty applied on the fees a liquidity provider collected in the last epoch. This percentage increased if an LP did not meet the SLA, or if they met it but other LPs outscored them in the previous epoch.'
),
valueFormatter: percentageFormatter,
},
{
headerName: t(`Last bond penalty`),
field: 'sla.lastEpochBondPenalty',
type: 'rightAligned',
headerTooltip: t(
`Penalty applied on a provider's bond penalty at the end of the last epoch. This percentage increased if an LP: had a shortfall and their bond needed to be used to cover it, did not meet the SLA, and/or reduced their commitment to the point that the market was below its target stake.`
),
valueFormatter: percentageFormatter,
},
{
headerName: t('Created'),
headerTooltip: t(
'The date and time this liquidity provision was created.'
),
field: 'createdAt',
type: 'rightAligned',
valueFormatter: dateValueFormatter,
},
{
headerName: t('Updated'),
headerTooltip: t(
'The date and time this liquidity provision was last updated.'
),
field: 'updatedAt',
type: 'rightAligned',
valueFormatter: dateValueFormatter,
},
];
return defs;
@@ -124,27 +124,11 @@ describe('getChange', () => {
});
describe('useCheckLiquidityStatus', () => {
it('should return amber if liquidity is enough', () => {
const { result } = renderHook(() =>
useCheckLiquidityStatus({
suppliedStake: '60',
targetStake: '100',
triggeringRatio: '0.5',
})
);
expect(result.current).toEqual({
status: Intent.Warning,
percentage: new BigNumber('60'),
});
});
it('should return red if liquidity is not enough', () => {
const { result } = renderHook(() =>
useCheckLiquidityStatus({
suppliedStake: '60',
targetStake: '100',
triggeringRatio: '1',
})
);
@@ -159,7 +143,6 @@ describe('useCheckLiquidityStatus', () => {
useCheckLiquidityStatus({
suppliedStake: '101',
targetStake: '100',
triggeringRatio: '1',
})
);
@@ -121,11 +121,9 @@ export const getTargetStake = (
export const useCheckLiquidityStatus = ({
suppliedStake,
targetStake,
triggeringRatio,
}: {
suppliedStake: string | number;
targetStake: string | number;
triggeringRatio: string | number;
}): {
status: Intent;
percentage: BigNumber;
@@ -142,23 +140,12 @@ export const useCheckLiquidityStatus = ({
percentage,
};
}
if (new BigNumber(suppliedStake).gte(new BigNumber(targetStake))) {
if (new BigNumber(suppliedStake).gte(targetStake)) {
// show a green status, e.g. "🟢 $13,666,999 liquidity supplied"
return {
status: Intent.Success,
percentage,
};
// ELSE IF supplied_stake > NETPARAM[market.liquidity.targetstake.triggering.ratio] * target_stake THEN
} else if (
new BigNumber(suppliedStake).gte(
new BigNumber(targetStake).multipliedBy(triggeringRatio)
)
) {
// show an amber status, e.g. "🟠 $3,456,123 liquidity supplied"
return {
status: Intent.Warning,
percentage,
};
// ELSE show a red status, e.g. "🔴 $600,002 liquidity supplied"
} else {
return {
File diff suppressed because one or more lines are too long
@@ -139,7 +139,6 @@ query MarketInfo($marketId: ID!) {
state
tradingMode
linearSlippageFactor
quadraticSlippageFactor
proposal {
id
rationale {
@@ -188,7 +187,6 @@ query MarketInfo($marketId: ID!) {
long
}
liquidityMonitoringParameters {
triggeringRatio
targetStakeParameters {
timeWindow
scalingFactor

Some files were not shown because too many files have changed in this diff Show More