Compare commits

..
Author SHA1 Message Date
asiaznik b48306c366 chore: disabled timeouts as it's broken with browser wallet 2024-01-18 18:07:20 +01:00
asiaznik 3cc4ec9ef5 chore: make connect button red 2024-01-18 16:03:20 +01:00
asiaznik 8ba323072b chore: increase timeout to 1s 2024-01-18 14:53:10 +01:00
asiaznik fd22c00269 chore: extract disconnect toast hook, enable in governance 2024-01-18 14:52:26 +01:00
asiaznik 85b8380827 chore: backward compatibility 2024-01-16 13:36:23 +01:00
asiaznik 28ac02ce1e fix: incomplete objects 2024-01-15 15:46:04 +01:00
asiaznik 756781097c chore: vega chain id refactor 2024-01-15 15:35:47 +01:00
asiaznik 808232f1c5 chore: handle disconnection per extension crash 2024-01-15 15:34:18 +01:00
asiaznik f743cffcd4 fix: diconnect event 2024-01-15 15:34:18 +01:00
ArtandDexter Edwards ec1032cbd2 Update libs/wallet/src/connectors/injected-connector.ts
Co-authored-by: Dexter Edwards <dexter.edwards93@gmail.com>
2024-01-15 15:34:18 +01:00
asiaznik 0bd40744c6 chore: new browser wallet connection model 2024-01-15 15:34:18 +01: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
52 changed files with 1162 additions and 874 deletions
@@ -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>
+9 -2
View File
@@ -26,7 +26,7 @@ import {
} from '@vegaprotocol/web3';
import { Web3Provider } from '@vegaprotocol/web3';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
import {
useVegaTransactionManager,
useVegaTransactionUpdater,
@@ -96,7 +96,9 @@ const cache: InMemoryCacheConfig = {
const Web3Container = ({
chainId,
}: {
/** Ethereum chain id */
chainId: number;
/** Ethereum provider url */
providerUrl: string;
}) => {
const InitializeHandlers = () => {
@@ -123,6 +125,9 @@ const Web3Container = ({
MOZILLA_EXTENSION_URL,
VEGA_WALLET_URL,
} = useEnvironment();
const vegaChainId = useChainId(VEGA_URL);
useEffect(() => {
if (chainId) {
return initializeConnectors(
@@ -157,7 +162,8 @@ const Web3Container = ({
!VEGA_EXPLORER_URL ||
!DocsLinks ||
!CHROME_EXTENSION_URL ||
!MOZILLA_EXTENSION_URL
!MOZILLA_EXTENSION_URL ||
!vegaChainId
) {
return null;
}
@@ -169,6 +175,7 @@ const Web3Container = ({
config={{
network: VEGA_ENV,
vegaUrl: VEGA_URL,
chainId: vegaChainId,
vegaWalletServiceUrl: VEGA_WALLET_URL,
links: {
explorer: VEGA_EXPLORER_URL,
@@ -48,6 +48,7 @@ const vegaWalletConfig: VegaWalletConfig = {
chromeExtensionUrl: 'chrome',
mozillaExtensionUrl: 'mozilla',
},
chainId: 'VEGA_CHAIN_ID',
};
const renderComponent = (proposal: ProposalQuery['proposal']) => {
+37 -1
View File
@@ -1,14 +1,50 @@
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
import {
Intent,
ToastsContainer,
TradingButton,
VegaIcon,
VegaIconNames,
useToasts,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import {
useEthereumTransactionToasts,
useEthereumWithdrawApprovalsToasts,
useVegaTransactionToasts,
useWalletDisconnectToastActions,
useWalletDisconnectedToasts,
} from '@vegaprotocol/web3';
import { useTranslation } from 'react-i18next';
const WalletDisconnectAdditionalContent = () => {
const { t } = useTranslation();
const { hideToast } = useWalletDisconnectToastActions();
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
return (
<p className="mt-2">
<TradingButton
data-testid="connect-vega-wallet"
onClick={() => {
hideToast();
openVegaWalletDialog();
}}
size="small"
intent={Intent.Danger}
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
>
<span className="whitespace-nowrap uppercase">{t('Connect')}</span>
</TradingButton>
</p>
);
};
export const ToastsManager = () => {
useVegaTransactionToasts();
useEthereumTransactionToasts();
useEthereumWithdrawApprovalsToasts();
useWalletDisconnectedToasts(<WalletDisconnectAdditionalContent />);
const toasts = useToasts((store) => store.toasts);
return <ToastsContainer order="desc" toasts={toasts} />;
+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).
@@ -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>
);
};
@@ -13,6 +13,7 @@ import type { ReactNode } from 'react';
import { Web3Provider } from './web3-provider';
import { useT } from '../../lib/use-t';
import { DataLoader } from './data-loader';
import { useChainId } from '@vegaprotocol/wallet';
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
const t = useT();
@@ -26,13 +27,16 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
CHROME_EXTENSION_URL,
} = useEnvironment();
const chainId = useChainId(VEGA_URL);
if (
!VEGA_URL ||
!VEGA_WALLET_URL ||
!VEGA_EXPLORER_URL ||
!CHROME_EXTENSION_URL ||
!MOZILLA_EXTENSION_URL ||
!DocsLinks
!DocsLinks ||
!chainId
) {
return <AppLoader />;
}
@@ -72,6 +76,7 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
config={{
network: VEGA_ENV,
vegaUrl: VEGA_URL,
chainId,
vegaWalletServiceUrl: VEGA_WALLET_URL,
links: {
explorer: VEGA_EXPLORER_URL,
@@ -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<
@@ -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>
}
>
@@ -14,6 +14,7 @@ import {
VegaIconNames,
type VegaIconSize,
TradingInput,
TinyScroll,
} from '@vegaprotocol/ui-toolkit';
import { IconNames } from '@blueprintjs/icons';
import {
@@ -149,47 +150,45 @@ export const ActiveRewards = ({ currentEpoch }: { currentEpoch: number }) => {
return (
<Card title={t('Active rewards')} className="lg:col-span-full">
<div className="">
{transfers.length > 1 && (
<TradingInput
onChange={(e) =>
setFilter((curr) => ({ ...curr, searchTerm: e.target.value }))
{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;
}
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"
prependElement={<VegaIcon name={VegaIconNames.SEARCH} />}
/>
)}
<div 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(343px,_1fr))] max-h-[40rem] overflow-auto">
{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}
/>
)
);
})}
</div>
</div>
return (
node && (
<ActiveRewardCard
key={i}
transferNode={node}
kind={transfer.kind}
currentEpoch={currentEpoch}
/>
)
);
})}
</TinyScroll>
</Card>
);
};
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.73.9
VEGA_VERSION=v0.73.10
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.73.10
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
+54 -16
View File
@@ -18,7 +18,7 @@ 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)
@@ -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)
@@ -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
@@ -70,7 +70,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)")
@@ -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
@@ -15,9 +15,10 @@ 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"
)
@@ -56,8 +57,9 @@ def test_market_selector_filter(continuous_market, page: Page):
# 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"
# tbd - 5465
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
"BTC:DAI_2023107.50 tDAI"
)
page.get_by_test_id("search-term").clear()
@@ -81,6 +83,7 @@ def test_market_selector_filter(continuous_market, page: Page):
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"
# tbd - 5465
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
"BTC:DAI_2023107.50 tDAI"
)
@@ -64,7 +64,6 @@ 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()
@@ -75,7 +74,6 @@ def setup_market_monitoring_auction(vega: VegaServiceNull, simple_market):
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 100, 95)
submit_order(vega, MM_WALLET2.name, simple_market, "SIDE_BUY", 1, 105)
vega.forward("10s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
@@ -109,7 +107,6 @@ 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()
+269 -195
View File
@@ -1,13 +1,34 @@
import pytest
import logging
import vega_sim.proto.vega as vega_protos
from typing import Tuple, Any
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.service import VegaService
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")
@@ -24,19 +45,19 @@ def market_ids():
@pytest.fixture(scope="module")
def vega_activity_tier_0(request):
# with init_vega(request) as vega_activity_tier_0:
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:
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:
with init_vega(request) as vega_combo_tier_0:
yield vega_combo_tier_0
@@ -48,79 +69,48 @@ def vega_activity_tier_1(request):
@pytest.fixture(scope="module")
def vega_hoarder_tier_1(request):
#with init_vega(request) as vega_hoarder_tier_1:
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:
with init_vega(request) as vega_combo_tier_1:
yield vega_combo_tier_1
@pytest.fixture
def auth(vega_instance, page):
vega, _, _ = vega_instance
return auth_setup(vega, page)
return auth_setup(vega_instance, page)
@pytest.fixture
def page(vega_instance, browser, request):
vega, _, _ = vega_instance
with init_page(vega, browser, request) as page_instance:
with init_page(vega_instance, browser, request) as page_instance:
yield page_instance
@pytest.fixture
def vega_instance(
reward_program: str,
vega_activity_tier_0: Any,
vega_hoarder_tier_0: Any,
vega_combo_tier_0: Any,
vega_activity_tier_1: Any,
vega_hoarder_tier_1: Any,
vega_combo_tier_1: Any,
market_ids: list,
tier: int,
) -> Tuple[Any, Any, Any]:
"""
Create a Vega instance based on the reward program and tier.
:param reward_program: The reward program type.
:param vega_activity_tier_0: The Vega instance for activity tier 0.
:param vega_hoarder_tier_0: The Vega instance for hoarder tier 0.
:param vega_combo_tier_0: The Vega instance for combo tier 0.
:param vega_activity_tier_1: The Vega instance for activity tier 1.
:param vega_hoarder_tier_1: The Vega instance for hoarder tier 1.
:param vega_combo_tier_1: The Vega instance for combo tier 1.
:param market_ids: List of market IDs.
:param tier: The tier level.
:return: Tuple containing the Vega instance, market ID, and tDAI asset ID.
"""
vega_tiers = {
"activity": (vega_activity_tier_0, vega_activity_tier_1),
"hoarder": (vega_hoarder_tier_0, vega_hoarder_tier_1),
"combo": (vega_combo_tier_0, vega_combo_tier_1),
}
if reward_program not in vega_tiers or tier not in (0, 1):
logging.error(f"Invalid reward_program '{reward_program}' or tier '{tier}'")
raise ValueError(f"Invalid reward_program '{reward_program}' or tier '{tier}'")
vega = vega_tiers[reward_program][tier]
# Set up market with the reward program
logging.info("Setting up Vega Instance")
market_id, tDAI_asset_id = set_market_reward_program(
vega, reward_program, market_ids, tier
)
return vega, market_id, tDAI_asset_id
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: VegaService, reward_programs, tier):
print("Started setup_market_with_reward_program")
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)
@@ -128,7 +118,7 @@ def setup_market_with_reward_program(vega: VegaService, reward_programs, tier):
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:
if ACTIVITY in reward_programs:
vega.update_network_parameter(
proposal_key=MM_WALLET.name,
parameter="rewards.activityStreak.benefitTiers",
@@ -137,7 +127,7 @@ def setup_market_with_reward_program(vega: VegaService, reward_programs, tier):
print("update_network_parameter activity done")
next_epoch(vega=vega)
if "hoarder" in reward_programs:
if HOARDER in reward_programs:
vega.update_network_parameter(
proposal_key=MM_WALLET.name,
parameter="rewards.vesting.benefitTiers",
@@ -160,6 +150,7 @@ def setup_market_with_reward_program(vega: VegaService, reward_programs, tier):
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,
)
@@ -181,7 +172,17 @@ def setup_market_with_reward_program(vega: VegaService, reward_programs, tier):
)
vega.wait_for_total_catchup()
""" if tier == 1:
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,
@@ -199,46 +200,62 @@ def setup_market_with_reward_program(vega: VegaService, reward_programs, tier):
side="SIDE_BUY",
volume=1,
)
vega.wait_for_total_catchup() """
#next_epoch(vega=vega)
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}"
if reward_program == "combo":
market_id_key = "combo"
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}. Setting up new market.")
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"]
if reward_program == COMBO:
reward_programs = [ACTIVITY, HOARDER]
market_id = setup_market_with_reward_program(vega, reward_programs, tier)
market_id, _ = setup_market_with_reward_program(vega, reward_programs, tier)
market_ids[market_id_key] = market_id
print(f"Using market ID: {market_id}")
return market_id
return market_id, market_ids
ACTIVITY_STREAKS = """
{
"tiers": [
{
"minimum_activity_streak": 1,
"minimum_activity_streak": 2,
"reward_multiplier": "2.0",
"vesting_multiplier": "1.1"
},
{
"minimum_activity_streak": 5,
"reward_multiplier": "3.0",
"vesting_multiplier": "1.2"
}
]
}
@@ -247,12 +264,8 @@ VESTING = """
{
"tiers": [
{
"minimum_quantum_balance": "5000000",
"minimum_quantum_balance": "10000000",
"reward_multiplier": "2"
},
{
"minimum_quantum_balance": "11666668",
"reward_multiplier": "3"
}
]
}
@@ -262,134 +275,195 @@ VESTING = """
@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, "110.00 tDAI"),
#("hoarder", 1, "116.66666 tDAI"),
#("combo", 1, "125.00 tDAI"),
(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.skip("tbd")
@pytest.mark.usefixtures("auth", "risk_accepted")
@pytest.mark.usefixtures("auth", "risk_accepted", "market_ids")
def test_network_reward_pot(
reward_program, vega_instance: VegaService, page: Page, total_rewards, tier
reward_program,
vega_instance: VegaServiceNull,
page: Page,
total_rewards,
tier,
market_ids,
):
vega, market_id, tDAI_asset_id = vega_instance
next_epoch(vega=vega)
page.goto(f"/#/rewards")
if tier == 1:
page.pause()
next_epoch(vega=vega)
next_epoch(vega=vega)
vega.submit_order(
trading_key=PARTY_B.name,
market_id=market_id,
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=market_id,
order_type="TYPE_MARKET",
time_in_force="TIME_IN_FORCE_IOC",
side="SIDE_BUY",
volume=1,
)
vega.wait_for_total_catchup()
page.pause()
next_epoch(vega=vega)
page.pause()
next_epoch(vega=vega)
change_keys(page, vega, PARTY_B.name)
page.pause()
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)
expect(page.get_by_test_id("total-rewards")).to_have_text(total_rewards)
# TODO Add test ID and Assert for locked,
""" @pytest.mark.parametrize(
"reward_program",
[
("activity"),
# ("hoarder"),
# ("combo"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_vesting(vega_setup, vega: VegaService, page: Page):
expect() """
@pytest.mark.skip("tbd")
@pytest.mark.parametrize(
"reward_program, tier, reward_multiplier",
[
("activity", 0, "1x"),
("hoarder", 0, "1x"),
("combo", 0, "1x"),
("activity", 1, "2x"),
("hoarder", 1, "2x"),
("combo", 1, "4x"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_reward_multiplier(reward_program, vega_instance: VegaService, page: Page, reward_multiplier, tier):
vega, market_id, tDAI_asset_id = vega_instance
page.goto(f"/#/rewards")
change_keys(page, vega, PARTY_B.name)
expect(page.get_by_test_id("combined-multipliers")).to_have_text(reward_multiplier)
#TODO add test ids and assert for individual multipliers
"""
@pytest.mark.parametrize(
"reward_program",
[
("activity"),
# ("hoarder"),
# ("combo"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_activity_streak(vega_setup, vega: VegaService, page: Page):
expect()
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",
"reward_program, tier, reward_multiplier, streak_multiplier, hoarder_multiplier",
[
("activity"),
# ("hoarder"),
# ("combo"),
(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")
def test_hoarder_Bonus(vega_setup, vega: VegaService, page: Page):
expect()
@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",
"reward_program, tier, epoch_streak",
[
("activity"),
# ("hoarder"),
# ("combo"),
(ACTIVITY, 0, "1"),
(ACTIVITY, 1, "7"),
],
)
@pytest.mark.usefixtures("auth", "risk_accepted")
def test_Rewards_history(vega_setup, vega: VegaService, page: Page):
expect()
@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.usefixtures("auth", "risk_accepted")
def test_redeem(vega_setup, vega: VegaService, page: Page):
expect()
@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.usefixtures("auth", "risk_accepted")
def test_redeem(vega_setup, vega: VegaService, page: Page):
expect()
"""
@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")
@@ -146,7 +146,7 @@ def test_transfer_vesting_below_minimum(
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")
@@ -1,62 +0,0 @@
import {
Intent,
useToasts,
ToastHeading,
CLOSE_AFTER,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEffect, useMemo } from 'react';
import { useT } from '../use-t';
import { VegaWalletConnectButton } from '../../components/vega-wallet-connect-button';
const WALLET_DISCONNECTED_TOAST_ID = 'WALLET_DISCONNECTED_TOAST_ID';
export const useWalletDisconnectedToasts = () => {
const t = useT();
const [hasToast, setToast, updateToast] = useToasts((state) => [
state.hasToast,
state.setToast,
state.update,
]);
const { isAlive } = useVegaWallet();
const toast = useMemo(
() => ({
id: WALLET_DISCONNECTED_TOAST_ID,
intent: Intent.Danger,
content: (
<>
<ToastHeading>{t('Wallet connection lost')}</ToastHeading>
<p>{t('The connection to the Vega wallet has been lost.')}</p>
<p className="mt-2">
<VegaWalletConnectButton
intent={Intent.Danger}
onClick={() => {
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
hidden: true,
});
}}
/>
</p>
</>
),
onClose: () => {
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
hidden: true,
});
},
closeAfter: CLOSE_AFTER,
}),
[t, updateToast]
);
useEffect(() => {
if (isAlive === false) {
if (hasToast(WALLET_DISCONNECTED_TOAST_ID)) {
updateToast(WALLET_DISCONNECTED_TOAST_ID, { hidden: false });
} else {
setToast(toast);
}
}
}, [hasToast, isAlive, setToast, t, toast, updateToast]);
};
+22 -3
View File
@@ -1,4 +1,4 @@
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
import { Intent, ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
import { useProposalToasts } from '@vegaprotocol/proposals';
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
@@ -6,7 +6,26 @@ import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
import { Links } from '../lib/links';
import { useReferralToasts } from '../client-pages/referrals/hooks/use-referral-toasts';
import { useWalletDisconnectedToasts } from '../lib/hooks/use-wallet-disconnected-toasts';
import {
useWalletDisconnectToastActions,
useWalletDisconnectedToasts,
} from '@vegaprotocol/web3';
import { VegaWalletConnectButton } from '../components/vega-wallet-connect-button';
const WalletDisconnectAdditionalContent = () => {
const { hideToast } = useWalletDisconnectToastActions();
return (
<p className="mt-2">
<VegaWalletConnectButton
intent={Intent.Danger}
onClick={() => {
// hide toast when clicked on `Connect`
hideToast();
}}
/>
</p>
);
};
export const ToastsManager = () => {
useProposalToasts();
@@ -17,7 +36,7 @@ export const ToastsManager = () => {
withdrawalsLink: Links.PORTFOLIO(),
});
useReferralToasts();
useWalletDisconnectedToasts();
useWalletDisconnectedToasts(<WalletDisconnectAdditionalContent />);
const toasts = useToasts((store) => store.toasts);
return <ToastsContainer order="desc" toasts={toasts} />;
+1
View File
@@ -438,6 +438,7 @@ export const TransferForm = ({
shouldValidate: true,
})
}
data-testid="use-max-button"
>
{t('Use max')}
</button>
+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",
@@ -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;
@@ -262,6 +262,7 @@ export const MarketInfoAccordion = ({
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={triggerIndex}
key={id}
/>
}
/>
@@ -698,17 +698,13 @@ export const PriceMonitoringBoundsInfoPanel = ({
const quoteUnit = getQuoteName(market);
const trigger =
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
const bounds = data?.priceMonitoringBounds?.[triggerIndex];
const trigger = bounds?.trigger;
if (!trigger) {
console.error(
`Could not find data for trigger ${triggerIndex} (market id: ${market.id})`
);
return null;
}
return (
<>
<div className="mb-2 grid grid-cols-2 text-sm">
@@ -22,7 +22,7 @@ const mockUpdateDialogOpen = jest.fn();
const mockCloseVegaDialog = jest.fn();
let mockIsDesktopRunning = true;
const mockChainId = 'chain-id';
const mockChainId = 'VEGA_CHAIN_ID';
jest.mock('../use-is-wallet-service-running', () => ({
useIsWalletServiceRunning: jest
@@ -30,10 +30,6 @@ jest.mock('../use-is-wallet-service-running', () => ({
.mockImplementation(() => mockIsDesktopRunning),
}));
jest.mock('./use-chain-id', () => ({
useChainId: jest.fn().mockImplementation(() => mockChainId),
}));
let defaultProps: VegaConnectDialogProps;
const INITIAL_KEY = 'some-key';
@@ -71,6 +67,7 @@ const defaultConfig: VegaWalletConfig = {
chromeExtensionUrl: 'chrome-link',
mozillaExtensionUrl: 'mozilla-link',
},
chainId: 'VEGA_CHAIN_ID',
};
function generateJSX(
@@ -209,7 +206,12 @@ describe('VegaConnectDialog', () => {
.mockClear()
.mockImplementation(() =>
delayedReject(
new WalletError('User error', 3001, 'The user rejected the request')
new WalletError(
'User error',
3001,
'The user rejected the request'
),
delay
)
);
@@ -314,13 +316,6 @@ describe('VegaConnectDialog', () => {
render(generateJSX());
await selectInjected();
// Chain check
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
expect(vegaWindow.getChainId).toHaveBeenCalled();
await act(async () => {
jest.advanceTimersByTime(delay);
});
// Await user connect
expect(screen.getByText('Connecting...')).toBeInTheDocument();
expect(vegaWindow.connectWallet).toHaveBeenCalled();
@@ -341,43 +336,6 @@ describe('VegaConnectDialog', () => {
expect(mockCloseVegaDialog).toHaveBeenCalledWith();
});
it('handles invalid chain', async () => {
const delay = 100;
const invalidChain = 'invalid chain';
const vegaWindow = {
getChainId: jest.fn(() =>
delayedResolve({ chainID: invalidChain }, delay)
),
connectWallet: jest.fn(() => delayedResolve(null, delay)),
disconnectWallet: jest.fn(() => delayedResolve(undefined, delay)),
listKeys: jest.fn(() =>
delayedResolve(
{
keys: [{ name: 'test key', publicKey: '0x123' }],
},
100
)
),
};
mockBrowserWallet(vegaWindow);
render(generateJSX());
await selectInjected();
// Chain check
expect(screen.getByText('Verifying chain')).toBeInTheDocument();
expect(vegaWindow.getChainId).toHaveBeenCalled();
await act(async () => {
jest.advanceTimersByTime(delay);
});
expect(screen.getByText('Wrong network')).toBeInTheDocument();
expect(
screen.getByText(
new RegExp(`set your wallet network in your app to "${mockChainId}"`)
)
).toBeInTheDocument();
});
async function selectInjected() {
expect(await screen.findByRole('dialog')).toBeInTheDocument();
fireEvent.click(await screen.findByTestId('connector-injected'));
@@ -44,7 +44,6 @@ import { isBrowserWalletInstalled } from '../utils';
import { useIsWalletServiceRunning } from '../use-is-wallet-service-running';
import { SnapStatus, useSnapStatus } from '../use-snap-status';
import { useVegaWalletDialogStore } from './vega-wallet-dialog-store';
import { useChainId } from './use-chain-id';
import { useT } from '../use-t';
import { Trans } from 'react-i18next';
@@ -65,7 +64,7 @@ export const VegaConnectDialog = ({
contentOnly,
onClose,
}: VegaConnectDialogProps) => {
const { disconnect, acknowledgeNeeded } = useVegaWallet();
const { chainId, disconnect, acknowledgeNeeded } = useVegaWallet();
const vegaWalletDialogOpen = useVegaWalletDialogStore(
(store) => store.vegaWalletDialogOpen
);
@@ -85,10 +84,6 @@ export const VegaConnectDialog = ({
[updateVegaWalletDialog, acknowledgeNeeded, disconnect]
);
// Ensure we have a chain Id so we can compare with wallet chain id.
// This value will already be in the cache, if it failed the app wont render
const chainId = useChainId();
const content = chainId && (
<ConnectDialogContainer
connectors={connectors}
@@ -1,72 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useVegaWallet } from '../use-vega-wallet';
import { useChainId } from './use-chain-id';
global.fetch = jest.fn();
const mockFetch = global.fetch as jest.Mock;
mockFetch.mockImplementation((url: string) => {
return Promise.resolve({ ok: true });
});
jest.mock('../use-vega-wallet', () => {
const original = jest.requireActual('../use-vega-wallet');
return {
...original,
useVegaWallet: jest.fn(),
};
});
describe('useChainId', () => {
it('does not call fetch when statistics url could not be determined', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: '',
});
renderHook(() => useChainId());
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledTimes(0);
});
});
it('calls fetch with correct statistics url', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
renderHook(() => useChainId());
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:1234/statistics'
);
});
});
it('does not return chain id when chain id is not present in response', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
const { result } = renderHook(() => useChainId());
await waitFor(() => {
expect(result.current).toBeUndefined();
});
});
it('returns chain id when chain id is present in response', async () => {
(useVegaWallet as jest.Mock).mockReturnValue({
vegaUrl: 'http://localhost:1234/graphql',
});
mockFetch.mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => ({
statistics: {
chainId: '1234',
},
}),
});
});
const { result } = renderHook(() => useChainId());
await waitFor(() => {
expect(result.current).not.toBeUndefined();
expect(result.current).toEqual('1234');
});
});
});
@@ -1,10 +1,11 @@
import { clearConfig, setConfig } from '../storage';
import type { Transaction, VegaConnector } from './vega-connector';
type VegaWalletEvent = 'client.disconnected';
declare global {
interface Vega {
getChainId: () => Promise<{ chainID: string }>;
connectWallet: () => Promise<null>;
connectWallet: (args: { chainId: string }) => Promise<null>;
disconnectWallet: () => Promise<void>;
listKeys: () => Promise<{
keys: Array<{ name: string; publicKey: string }>;
@@ -34,6 +35,9 @@ declare global {
};
transactionHash: string;
}>;
on: (event: VegaWalletEvent, callback: () => void) => void;
isConnected?: () => Promise<boolean>;
}
interface Window {
@@ -47,15 +51,60 @@ export const InjectedConnectorErrors = {
INVALID_CHAIN: new Error('Invalid chain'),
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const wait = (ms: number) =>
new Promise<boolean>((_, reject) => {
setTimeout(() => {
reject(false);
}, ms);
});
const INJECTED_CONNECTOR_TIMEOUT = 1000;
export class InjectedConnector implements VegaConnector {
isConnected = false;
chainId: string | null = null;
description = 'Connects using the Vega wallet browser extension';
alive: ReturnType<typeof setInterval> | undefined = undefined;
async getChainId() {
return window.vega.getChainId();
}
async connectWallet(chainId: string) {
this.chainId = chainId;
try {
await window.vega.connectWallet({ chainId });
this.isConnected = true;
window.vega.on('client.disconnected', () => {
this.isConnected = false;
});
connectWallet() {
return window.vega.connectWallet();
this.alive = setInterval(async () => {
try {
const connected = await Promise.race([
// FIXME: All of the `window.vega` initiated promises are `pending`
// while waiting for the user action when transaction is sent.
// (Probably due to the FIFO queue of the `PortServer`?)
// Because of that we cannot `wait` here as while waiting for the
// user action in wallet this will `reject`. It'd be cool if the
// `window.vega` was not blocking the api calls.
// wait(INJECTED_CONNECTOR_TIMEOUT),
// `isConnected` is only available in the newer versions
// of the browser wallet
'isConnected' in window.vega &&
typeof window.vega.isConnected === 'function'
? window.vega.isConnected()
: window.vega.listKeys(),
]);
this.isConnected = Boolean(connected);
} catch {
this.isConnected = false;
}
}, INJECTED_CONNECTOR_TIMEOUT * 2);
} catch {
throw new Error(
`could not connect to the vega wallet on chain: ${chainId}`
);
}
}
async connect() {
@@ -69,19 +118,11 @@ export class InjectedConnector implements VegaConnector {
}
async isAlive() {
try {
const keys = await window.vega.listKeys();
if (keys.keys.length > 0) {
return true;
}
} catch (err) {
return false;
}
return false;
return this.isConnected;
}
disconnect() {
clearInterval(this.alive);
clearConfig();
return window.vega.disconnectWallet();
}
+3
View File
@@ -13,6 +13,9 @@ export interface VegaWalletContextShape {
/** Url of current connected node */
vegaUrl: string;
/** Vega chain id */
chainId: string;
/** Url of running wallet service */
vegaWalletServiceUrl: string;
+1
View File
@@ -7,3 +7,4 @@ export * from './provider';
export * from './connect-dialog';
export * from './utils';
export * from './storage';
export * from './use-chain-id';
+2
View File
@@ -22,6 +22,7 @@ const defaultConfig: VegaWalletConfig = {
chromeExtensionUrl: 'chrome-link',
mozillaExtensionUrl: 'mozilla-link',
},
chainId: 'VEGA_CHAIN_ID',
};
const setup = (config?: Partial<VegaWalletConfig>) => {
@@ -68,6 +69,7 @@ describe('VegaWalletProvider', () => {
expect(result.current).toEqual({
network: defaultConfig.network,
vegaUrl: defaultConfig.vegaUrl,
chainId: defaultConfig.chainId,
vegaWalletServiceUrl: defaultConfig.vegaWalletServiceUrl,
acknowledgeNeeded: false,
pubKey: null,
+2
View File
@@ -39,6 +39,7 @@ interface VegaWalletLinks {
export interface VegaWalletConfig {
network: Networks;
vegaUrl: string;
chainId: string;
vegaWalletServiceUrl: string;
links: VegaWalletLinks;
keepAlive?: number;
@@ -168,6 +169,7 @@ export const VegaWalletProvider = ({
const contextValue = useMemo<VegaWalletContextShape>(() => {
return {
vegaUrl: config.vegaUrl,
chainId: config.chainId,
vegaWalletServiceUrl: config.vegaWalletServiceUrl,
network: config.network,
links: {
+2 -1
View File
@@ -1,6 +1,5 @@
export function mockBrowserWallet(overrides?: Partial<Vega>) {
const vega: Vega = {
getChainId: jest.fn().mockReturnValue(Promise.resolve({ chainID: '1' })),
connectWallet: jest.fn().mockReturnValue(Promise.resolve(null)),
disconnectWallet: jest.fn().mockReturnValue(Promise.resolve()),
listKeys: jest
@@ -14,6 +13,8 @@ export function mockBrowserWallet(overrides?: Partial<Vega>) {
success: true,
txHash: '0x123',
}),
on: jest.fn(),
isConnected: jest.fn().mockRejectedValue(Promise.resolve(true)),
...overrides,
};
// @ts-ignore globalThis has no index signature
+100
View File
@@ -0,0 +1,100 @@
import { renderHook, waitFor } from '@testing-library/react';
import { MAX_FETCH_ATTEMPTS, useChainId } from './use-chain-id';
global.fetch = jest.fn();
const mockFetch = global.fetch as jest.Mock;
mockFetch.mockImplementation((url: string) => {
return Promise.resolve({ ok: true });
});
describe('useChainId', () => {
it('does not call fetch when statistics url could not be determined', async () => {
renderHook(() => useChainId(''));
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledTimes(0);
});
});
it('calls fetch with correct statistics url', async () => {
renderHook(() => useChainId('http://localhost:1234/graphql'));
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:1234/statistics'
);
});
});
it('does not return chain id when chain id is not present in response', async () => {
const { result } = renderHook(() =>
useChainId('http://localhost:1234/graphql')
);
await waitFor(() => {
expect(result.current).toBeUndefined();
});
});
it('returns chain id when chain id is present in response', async () => {
mockFetch.mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => ({
statistics: {
chainId: '1234',
},
}),
});
});
const { result } = renderHook(() =>
useChainId('http://localhost:1234/graphql')
);
await waitFor(() => {
expect(result.current).not.toBeUndefined();
expect(result.current).toEqual('1234');
});
});
it('returns chain id when within max number of attempts', async () => {
mockFetch.mockImplementation(() => {
if (mockFetch.mock.calls.length < MAX_FETCH_ATTEMPTS) {
return Promise.reject();
}
return Promise.resolve({
ok: true,
json: () => ({
statistics: {
chainId: '1234',
},
}),
});
});
const { result } = renderHook(() =>
useChainId('http://localhost:1234/graphql')
);
await waitFor(() => {
expect(result.current).not.toBeUndefined();
expect(result.current).toEqual('1234');
});
});
it('does not return chain id when max number of attempts exceeded', async () => {
mockFetch.mockImplementation(() => {
if (mockFetch.mock.calls.length < MAX_FETCH_ATTEMPTS + 10) {
return Promise.reject();
}
return Promise.resolve({
ok: true,
json: () => ({
statistics: {
chainId: '1234',
},
}),
});
});
const { result } = renderHook(() =>
useChainId('http://localhost:5678/graphql')
);
await waitFor(() => {
expect(result.current).toBeUndefined();
});
});
});
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { useVegaWallet } from '../use-vega-wallet';
export const MAX_FETCH_ATTEMPTS = 3;
const cache: Record<string, string> = {};
@@ -18,16 +19,17 @@ const getNodeStatisticsUrl = (vegaUrl: string) => {
}
};
export const useChainId = () => {
const { vegaUrl } = useVegaWallet();
const [chainId, setChainId] = useState<undefined | string>(cache[vegaUrl]);
const [fetchAttempt, setFetchAttempt] = useState(1);
export const useChainId = (vegaUrl: string | undefined) => {
const [chainId, setChainId] = useState<undefined | string>(
vegaUrl ? cache[vegaUrl] : undefined
);
const [fetchAttempts, setFetchAttempts] = useState(1);
const statisticsUrl = getNodeStatisticsUrl(vegaUrl);
const statisticsUrl = vegaUrl ? getNodeStatisticsUrl(vegaUrl) : undefined;
useEffect(() => {
// abort when `/statistics` URL could not be determined
if (!statisticsUrl) return;
if (!statisticsUrl || !vegaUrl) return;
let isCancelled = false;
if (cache[vegaUrl]) {
setChainId(cache[vegaUrl]);
@@ -42,16 +44,19 @@ export const useChainId = () => {
if (!response?.statistics?.chainId) {
throw new Error('statistics.chainId not present in fetched response');
}
setChainId(response?.statistics?.chainId);
const chainId = response.statistics.chainId;
cache[vegaUrl] = chainId;
setChainId(chainId);
})
.catch(() => {
if (fetchAttempt < 3) {
setFetchAttempt((value) => (value += 1));
if (fetchAttempts < MAX_FETCH_ATTEMPTS) {
setFetchAttempts((value) => (value += 1));
}
});
return () => {
isCancelled = true;
};
}, [fetchAttempt, statisticsUrl, vegaUrl]);
}, [fetchAttempts, statisticsUrl, vegaUrl]);
return chainId;
};
+3 -3
View File
@@ -7,7 +7,7 @@ import { useVegaWallet } from './use-vega-wallet';
export function useEagerConnect(connectors: Connectors) {
const [connecting, setConnecting] = useState(true);
const { vegaUrl, connect, acknowledgeNeeded } = useVegaWallet();
const { vegaUrl, chainId, connect, acknowledgeNeeded } = useVegaWallet();
useEffect(() => {
const attemptConnect = async () => {
@@ -33,7 +33,7 @@ export function useEagerConnect(connectors: Connectors) {
try {
if (connector instanceof InjectedConnector) {
await connector.connectWallet();
await connector.connectWallet(chainId);
await connect(connector);
} else if (connector instanceof SnapConnector) {
connector.nodeAddress = new URL(vegaUrl).origin;
@@ -51,7 +51,7 @@ export function useEagerConnect(connectors: Connectors) {
if (typeof window !== 'undefined') {
attemptConnect();
}
}, [connect, connectors, acknowledgeNeeded, vegaUrl]);
}, [connect, connectors, acknowledgeNeeded, vegaUrl, chainId]);
return connecting;
}
@@ -16,6 +16,7 @@ const defaultConfig: VegaWalletConfig = {
chromeExtensionUrl: 'chrome-link',
mozillaExtensionUrl: 'mozilla-link',
},
chainId: 'VEGA_CHAIN_ID',
};
const setup = (callback = jest.fn(), config?: Partial<VegaWalletConfig>) => {
@@ -46,20 +47,10 @@ describe('useInjectedConnector', () => {
expect(result.current.status).toBe(Status.Error);
});
it('errors if chain ids dont match', async () => {
mockBrowserWallet();
const { result } = setup();
await act(async () => {
result.current.connect(injected, '2'); // default mock chainId is '1'
});
expect(result.current.error?.message).toBe('Invalid chain');
expect(result.current.status).toBe(Status.Error);
});
it('errors if connection throws', async () => {
const callback = jest.fn();
mockBrowserWallet({
getChainId: () => Promise.reject('failed'),
connectWallet: jest.fn().mockReturnValue(Promise.reject()),
});
const { result } = setup(callback);
@@ -67,7 +58,9 @@ describe('useInjectedConnector', () => {
result.current.connect(injected, '1'); // default mock chainId is '1'
});
expect(result.current.status).toBe(Status.Error);
expect(result.current.error?.message).toBe('injected connection failed');
expect(result.current.error?.message).toBe(
'could not connect to the vega wallet on chain: 1'
);
});
it('connects', async () => {
@@ -78,7 +71,6 @@ describe('useInjectedConnector', () => {
act(() => {
result.current.connect(injected, '1'); // default mock chainId is '1'
});
expect(result.current.status).toBe(Status.GettingChainId);
await waitFor(() => {
expect(vega.connectWallet).toHaveBeenCalled();
+8 -6
View File
@@ -40,17 +40,19 @@ export const useInjectedConnector = (onConnect: () => void) => {
connector.nodeAddress = new URL(vegaUrl).origin;
}
setStatus(Status.GettingChainId);
const { chainID } = await connector.getChainId();
if (chainID !== appChainId) {
throw InjectedConnectorErrors.INVALID_CHAIN;
// check the chain id for snap connector
if (connector instanceof SnapConnector) {
setStatus(Status.GettingChainId);
const { chainID } = await connector.getChainId();
if (chainID !== appChainId) {
throw InjectedConnectorErrors.INVALID_CHAIN;
}
}
setStatus(Status.Connecting);
if (connector instanceof InjectedConnector) {
// extra step for injected connector - authorize wallet
await connector.connectWallet();
await connector.connectWallet(appChainId);
}
await connect(connector); // connect with keys
+9 -8
View File
@@ -1,7 +1,11 @@
export * from './lib/__generated__/TransactionResult';
export * from './lib/__generated__/WithdrawalApproval';
export * from './lib/constants';
export * from './lib/default-web3-provider';
export * from './lib/eip-1193-custom-bridge';
export * from './lib/ethereum-error';
export * from './lib/ethereum-transaction-dialog';
export * from './lib/types';
export * from './lib/url-connector';
export * from './lib/use-bridge-contract';
export * from './lib/use-eager-connect';
@@ -19,7 +23,12 @@ export * from './lib/use-get-withdraw-delay';
export * from './lib/use-get-withdraw-threshold';
export * from './lib/use-token-contract';
export * from './lib/use-token-decimals';
export * from './lib/use-transaction-result';
export * from './lib/use-vega-transaction-manager';
export * from './lib/use-vega-transaction-store';
export * from './lib/use-vega-transaction-toasts';
export * from './lib/use-vega-transaction-updater';
export * from './lib/use-wallet-disconnected-toasts';
export * from './lib/use-web3-disconnect';
export * from './lib/web3-connect-dialog';
export * from './lib/web3-connect-store';
@@ -27,11 +36,3 @@ export * from './lib/web3-connectors';
export * from './lib/web3-provider';
export * from './lib/withdrawal-approval-dialog';
export * from './lib/withdrawal-approval-status';
export * from './lib/default-web3-provider';
export * from './lib/use-vega-transaction-manager';
export * from './lib/use-vega-transaction-store';
export * from './lib/use-vega-transaction-updater';
export * from './lib/use-transaction-result';
export * from './lib/types';
export * from './lib/__generated__/TransactionResult';
export * from './lib/__generated__/WithdrawalApproval';
@@ -0,0 +1,78 @@
import {
Intent,
useToasts,
ToastHeading,
CLOSE_AFTER,
type Toast,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEffect, useMemo } from 'react';
import { useT } from './use-t';
import { usePrevious } from '@vegaprotocol/react-helpers';
export const WALLET_DISCONNECTED_TOAST_ID = 'WALLET_DISCONNECTED_TOAST_ID';
export const useWalletDisconnectToastActions = () => {
const [hasToast, updateToast] = useToasts((state) => [
state.hasToast,
state.update,
]);
const hideToast = () => {
if (!hasToast(WALLET_DISCONNECTED_TOAST_ID)) return;
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
hidden: true,
});
};
const showToast = () => {
if (!hasToast(WALLET_DISCONNECTED_TOAST_ID)) return;
updateToast(WALLET_DISCONNECTED_TOAST_ID, {
hidden: false,
});
};
return { showToast, hideToast };
};
export const useWalletDisconnectedToasts = (
additionalContent?: JSX.Element
) => {
const t = useT();
const [hasToast, setToast] = useToasts((state) => [
state.hasToast,
state.setToast,
state.update,
]);
const { showToast, hideToast } = useWalletDisconnectToastActions();
const { isAlive } = useVegaWallet();
const wasAlive = usePrevious(isAlive);
const disconnected = wasAlive && !isAlive;
const toast: Toast = useMemo(
() => ({
id: WALLET_DISCONNECTED_TOAST_ID,
intent: Intent.Danger,
content: (
<>
<ToastHeading>{t('Wallet connection lost')}</ToastHeading>
<p>{t('The connection to the Vega wallet has been lost.')}</p>
{additionalContent}
</>
),
onClose: () => {
hideToast();
},
closeAfter: CLOSE_AFTER,
}),
[additionalContent, hideToast, t]
);
useEffect(() => {
if (disconnected) {
if (hasToast(WALLET_DISCONNECTED_TOAST_ID)) {
showToast();
} else {
setToast(toast);
}
}
}, [disconnected, hasToast, isAlive, setToast, showToast, t, toast]);
};