Compare commits

..
136 changed files with 139862 additions and 140444 deletions
-6
View File
@@ -18,12 +18,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- name: Run Cypress tests
uses: cypress-io/github-action@v4
with:
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
node-version: 16.14.0
- name: Install root dependencies
run: yarn install
- name: Generate queries
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
node-version: 16.14.0
- name: Install root dependencies
run: yarn install
- name: Check PR title
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
node-version: 16.14.0
- name: Install root dependencies
run: yarn install
- name: Generate queries
-47
View File
@@ -1,47 +0,0 @@
name: Publish libs to npm
on:
workflow_dispatch:
inputs:
project:
description: 'Monorepo project to publish'
required: true
type: choice
options:
- ui-toolkit
- react-helpers
- tailwindcss-config
- types
jobs:
publish:
name: Build & Publish - Tag
runs-on: ubuntu-latest
permissions:
contents: 'read'
actions: 'read'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: User Node.js 16
id: Node
uses: actions/setup-node@v3
with:
node-version: 16.15.1
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: node_modules-${{ hashFiles('**/yarn.lock') }}
- name: Install root dependencies
run: yarn install --frozen-lockfile
- name: Build project
run: yarn nx build ${{inputs.project}}
- name: Publish project to @vegaprotocol
uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
package: dist/libs/${{inputs.project}}/package.json
access: public
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
id: Node
uses: actions/setup-node@v3
with:
node-version: 16.15.1
node-version: 16.14.0
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
+1 -1
View File
@@ -1 +1 @@
16.15.1
16.14.0
@@ -48,7 +48,7 @@ const MarketLink = ({
<Link
className="underline"
{...props}
to={`/${Routes.MARKETS}/${id}`}
to={`/${Routes.MARKETS}#${id}`}
title={id}
>
{label}
@@ -56,7 +56,7 @@ const MarketLink = ({
);
} else {
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}/${id}`}>
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
<Hash text={id} />
</Link>
);
@@ -22,7 +22,8 @@ export const MarketDetails = ({
}: {
market: MarketInfoNoCandlesQuery['market'];
}) => {
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol;
const assetId = useMemo(
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
[market]
@@ -38,10 +39,6 @@ export const MarketDetails = ({
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const liquidityPriceRange = formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
);
const panels = [
{
title: t('Key details'),
@@ -182,41 +179,32 @@ export const MarketDetails = ({
{
title: t('Liquidity price range'),
content: (
<>
<p className="text-xs mb-4">
{`For liquidity orders count towards a commitment they have to be
within either the liquidity or price monitoring bounds (whichever is
tighter).`}
</p>
<p className="text-xs mb-4">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
<MarketInfoTable
noBorder={false}
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
}}
></MarketInfoTable>
</>
<MarketInfoTable
noBorder={false}
data={{
liquidityPriceRange: formatNumberPercentage(
new BigNumber(market.lpPriceRange).times(100)
),
LPVolumeMin:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
LPVolumeMax:
market.data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(market.data.midPrice)
.toString(),
market.decimalPlaces
)} ${assetSymbol}`,
}}
></MarketInfoTable>
),
},
{
@@ -0,0 +1 @@
[]
+15 -1
View File
@@ -1,3 +1,17 @@
{
"hosts": ["https://vega.xprv.io/datanode/query"]
"hosts": [
"https://vega-data-graphql.chorus.one/query",
"https://vega.xprv.io/datanode/query",
"http://nala.mainnet.vega.community:3008/query",
"http://commodum.mainnet.vega.community:3008/query",
"http://lovali.mainnet.vega.community:3008/query",
"http://b-harvest.mainnet.vega.community:3008/query",
"http://staking-facilities.mainnet.vega.community:3008/query",
"http://figment.mainnet.vega.community:3008/query",
"http://nodes-guru.mainnet.vega.community:3008/query",
"http://p2p.mainnet.vega.community:3008/query",
"http://rockaway.mainnet.vega.community:3008/query",
"http://greenfield-one.mainnet.vega.community:3008/query",
"http://ryabina.mainnet.vega.community:3008/query"
]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
[]
@@ -0,0 +1 @@
[]
@@ -0,0 +1 @@
[]
@@ -0,0 +1 @@
[]
@@ -0,0 +1 @@
[]
-1
View File
@@ -12,7 +12,6 @@ NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit suppl
NX_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
#Test configuration variables
CYPRESS_FAIRGROUND=false
@@ -201,21 +201,12 @@ context('Staking Page - verify elements on page', function () {
cy.get(stakeShare)
.invoke('text')
.then(($stakePercentage) => {
// The pattern must start at a word boundary (\b).
// The pattern cannot be immediately preceded by a dot ((?<!\.)).
// The pattern can be one of the following:
// A percentage value of zero (0%), or
// A non-zero percentage value that can be:
// A single digit (\d) between 0 and 9, or
// A two-digit number between 0 and 99 (\d{1,2}), or
// The number 100.
// The pattern can optionally include a decimal point and one or more digits after the decimal point ((?:(?<!100)\.\d+)?). However, if the number is 100, it cannot have a decimal point.
// The pattern must end with a percentage sign (%).
cy.wrap($stakePercentage).should(
'match',
/\b(?<!\.)(?:0+(?:\.0+)?%|(?:\d|\d{1,2}|100)(?:(?<!100)\.\d+)?)%/
);
if ($stakePercentage != '-') {
cy.wrap($stakePercentage).should(
'match',
/\b(?<!\.)(?!0+(?:\.0+)?%)(?:\d|[1-9]\d|100)(?:(?<!100)\.\d+)?%/
);
}
});
});
@@ -1,3 +1,4 @@
const connectPrompt = '[data-testid="eth-connect-prompt"]';
const connectButton = '[data-testid="connect-to-eth-btn"]';
context(
@@ -17,7 +18,7 @@ context(
});
it('should have connect Eth wallet info', function () {
cy.get(connectButton).should('be.visible');
cy.get(connectPrompt).should('be.visible');
});
it('should have connect Eth wallet button', function () {
+1 -1
View File
@@ -13,4 +13,4 @@ last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.
not IE 9-11 # For IE 9-11 support, remove 'not'.
-1
View File
@@ -12,7 +12,6 @@ NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
#Test configuration variables
CYPRESS_FAIRGROUND=false
-1
View File
@@ -15,7 +15,6 @@ NX_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
#Test configuration variables
CYPRESS_FAIRGROUND=false
-1
View File
@@ -9,4 +9,3 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
-1
View File
@@ -10,4 +10,3 @@ NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
-1
View File
@@ -6,4 +6,3 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
-1
View File
@@ -7,4 +7,3 @@ NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
-1
View File
@@ -10,4 +10,3 @@ NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
@@ -1,17 +1,17 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import { useEffect } from 'react';
import { useAppState } from '../../contexts/app-state/app-state-context';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { useGetAssociationBreakdown } from '../../hooks/use-get-association-breakdown';
import { useGetUserBalances } from '../../hooks/use-get-user-balances';
import { useGetUserTrancheBalances } from '../../hooks/use-get-user-tranche-balances';
import { useBalances } from '../../lib/balances/balances-store';
import type { ReactElement } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
import { useTranches } from '../../lib/tranches/tranches-store';
import { useUserTrancheBalances } from '../../routes/redemption/hooks';
import { useEthereumConfig } from '@vegaprotocol/web3';
interface BalanceManagerProps {
children: ReactElement;
@@ -24,13 +24,7 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
const {
appState: { decimals },
} = useAppState();
const updateStoreBalances = useBalances((state) => state.updateBalances);
const setTranchesBalances = useBalances((state) => state.setTranchesBalances);
const getUserBalances = useGetUserBalances(account);
const userTrancheBalances = useUserTrancheBalances(account);
useEffect(() => {
setTranchesBalances(userTrancheBalances);
}, [setTranchesBalances, userTrancheBalances]);
const { updateBalances: updateStoreBalances } = useBalances();
const { config } = useEthereumConfig();
const numberOfConfirmations = config?.confirmations || 0;
@@ -47,10 +41,10 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
numberOfConfirmations
);
const getTranches = useTranches((state) => state.getTranches);
useEffect(() => {
getTranches(decimals);
}, [decimals, getTranches]);
const getUserTrancheBalances = useGetUserTrancheBalances(
account || '',
contracts?.vesting
);
const getAssociationBreakdown = useGetAssociationBreakdown(
account || '',
contracts?.staking,
@@ -60,14 +54,50 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
// update balances on connect to Ethereum
useEffect(() => {
const updateBalances = async () => {
const balances = await getUserBalances();
if (balances) {
updateStoreBalances(balances);
if (!account || !config) return;
try {
const [b, w, stats, a] = await Promise.all([
contracts.vesting.user_total_all_tranches(account),
contracts.token.balanceOf(account),
contracts.vesting.user_stats(account),
contracts.token.allowance(
account,
config.staking_bridge_contract.address
),
]);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
updateStoreBalances({
balanceFormatted: balance,
walletBalance,
lien,
allowance,
});
} catch (err) {
Sentry.captureException(err);
}
};
updateBalances();
}, [getUserBalances, updateStoreBalances]);
}, [
decimals,
contracts.token,
contracts.vesting,
account,
config,
updateStoreBalances,
]);
// This use effect hook is very expensive and is kept separate to prevent expensive reloading of data.
useEffect(() => {
if (account) {
getUserTrancheBalances();
}
}, [account, getUserTrancheBalances]);
useEffect(() => {
if (account) {
@@ -6,22 +6,27 @@ import {
useAppState,
} from '../../contexts/app-state/app-state-context';
export const EthConnectPrompt = () => {
interface EthConnectPrompProps {
children?: React.ReactNode;
}
export const EthConnectPrompt = ({ children }: EthConnectPrompProps) => {
const { t } = useTranslation();
const { appDispatch } = useAppState();
return (
<Button
variant="primary"
onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
fill={true}
data-testid="connect-to-eth-btn"
>
{t('connectEthWallet')}
</Button>
<>
{children}
<Button
onClick={() =>
appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true,
})
}
data-testid="connect-to-eth-btn"
>
{t('connectEthWallet')}
</Button>
</>
);
};
-1
View File
@@ -58,7 +58,6 @@ const envName = windowOrDefault('NX_VEGA_ENV') ?? 'local';
export const ENV = {
// Environment
tranchesServiceUrl: windowOrDefault('NX_TRANCHES_SERVICE_URL'),
dsn: windowOrDefault('NX_SENTRY_DSN'),
urlConnect: TRUTHY.includes(windowOrDefault('NX_ETH_URL_CONNECT')),
explorerUrl: windowOrDefault('NX_VEGA_EXPLORER'),
@@ -1,3 +1,4 @@
import type { Tranche } from '@vegaprotocol/smart-contracts';
import React from 'react';
import type { BigNumber } from '../../lib/bignumber';
@@ -18,7 +19,21 @@ export interface VegaKey {
meta: Array<{ key: string; value: string }> | null;
}
export interface UserTrancheBalance {
/** ID of tranche */
id: number;
/** Users vesting tokens on tranche */
locked: BigNumber;
/** Users vested tokens on tranche */
vested: BigNumber;
}
export interface AppState {
/** Array of tranche objects */
tranches: Tranche[] | null;
/** Number of decimal places of the VEGA token (18 on Mainnet, 5 on Testnet) */
decimals: number;
@@ -37,6 +52,9 @@ export interface AppState {
/** Whether or not the connect to Ethereum wallet overlay is open */
ethConnectOverlay: boolean;
/** The error if one was thrown during retrieval of tranche data */
trancheError: Error | null;
/** Whether or not the mobile drawer is open. Only relevant on screens smaller than 960 */
drawerOpen: boolean;
@@ -53,10 +71,12 @@ export enum AppStateActionType {
SET_TOKEN,
SET_ALLOWANCE,
REFRESH_BALANCES,
SET_TRANCHE_DATA,
SET_VEGA_WALLET_OVERLAY,
SET_VEGA_WALLET_MANAGE_OVERLAY,
SET_ETH_WALLET_OVERLAY,
SET_DRAWER,
SET_TRANCHE_ERROR,
REFRESH_ASSOCIATED_BALANCES,
SET_ASSOCIATION_BREAKDOWN,
SET_TRANSACTION_OVERLAY,
@@ -70,6 +90,14 @@ export type AppStateAction =
totalSupply: BigNumber;
totalAssociated: BigNumber;
}
| {
type: AppStateActionType.SET_TRANCHE_DATA;
tranches: Tranche[];
}
| {
type: AppStateActionType.SET_TRANCHE_ERROR;
error: Error | null;
}
| {
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY;
isOpen: boolean;
@@ -14,9 +14,11 @@ const initialAppState: AppState = {
totalAssociated: new BigNumber(0),
decimals: 0,
totalSupply: new BigNumber(0),
tranches: null,
vegaWalletOverlay: false,
vegaWalletManageOverlay: false,
ethConnectOverlay: false,
trancheError: null,
drawerOpen: false,
transactionOverlay: false,
bannerMessage: '',
@@ -32,6 +34,17 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
totalAssociated: action.totalAssociated,
};
}
case AppStateActionType.SET_TRANCHE_DATA:
return {
...state,
tranches: action.tranches,
};
case AppStateActionType.SET_TRANCHE_ERROR: {
return {
...state,
trancheError: action.error,
};
}
case AppStateActionType.SET_VEGA_WALLET_OVERLAY: {
return {
...state,
@@ -0,0 +1,666 @@
import parseJSON from 'date-fns/parseJSON';
import { BigNumber } from '../../lib/bignumber';
import type { Tranche } from '@vegaprotocol/smart-contracts';
const json: Tranche[] = [
{
tranche_id: 1,
tranche_start: parseJSON('2022-03-05T00:00:00.000Z'),
tranche_end: parseJSON('2023-06-05T00:00:00.000Z'),
total_added: new BigNumber('3505372.53445'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('3505372.53445'),
deposits: [
{
amount: new BigNumber('187637.95'),
user: '0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0',
tx: '0xc4491908e3347b05f2394ac7e1006f573fe8cbc490a57cd1dadad70785e95024',
},
{
amount: new BigNumber('200000'),
user: '0x93b478148FF792B00076B7EdC89Db1FdE7772079',
tx: '0xcc8fba855a0d965044d4cc587701fe9ee9f5863852cede017382ffe02963d9b8',
},
{
amount: new BigNumber('21666.5743'),
user: '0xB523235B6c7C74DDB26b10E78bFb2d0Cb63Ae289',
tx: '0x8cc5159f3d665dd33a2bdac6e990cf1b65dc18b6b5e37a07b37dd54495a8d33c',
},
{
amount: new BigNumber('200000'),
user: '0x7227e17101E6C70F4dAfC7DDB77BB7D83DdfC1C8',
tx: '0x0cdef6868bd6b977362396fd06525e1e757e827659c5d75cd512db121947e6f7',
},
{
amount: new BigNumber('137998.56'),
user: '0x69eFc5642CfcCB1777bc663433640531F044D1F5',
tx: '0xaa9b3b39836a69f760f5d1d2fdba44ad348b27c8f31eb094ad6f779b39d7949c',
},
{
amount: new BigNumber('16249.93'),
user: '0x006B59DD3bC838A74476c0F4a33C1565831dA0DD',
tx: '0xda9bdc846300600c0d360edcaf4a5ed40fa2586ba7872f2bf68251a669adff0e',
},
],
withdrawals: [],
users: [
{
address: '0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0',
deposits: [
{
amount: new BigNumber('187637.95'),
user: '0xE6CacAE56Cca8dFdB7910b5A13578719D4E57DA0',
tranche_id: 1,
tx: '0xc4491908e3347b05f2394ac7e1006f573fe8cbc490a57cd1dadad70785e95024',
},
],
withdrawals: [],
total_tokens: new BigNumber('187637.95'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('187637.95'),
},
{
address: '0x1A71e3ED1996CAbB91bB043f880CE963D601707e',
deposits: [
{
amount: new BigNumber('112323.67'),
user: '0x1A71e3ED1996CAbB91bB043f880CE963D601707e',
tranche_id: 1,
tx: '0xaa378d2d0d4d7b964a675bb19f19c4f7401deec6ce66b1bd98ad5b812026e53e',
},
],
withdrawals: [],
total_tokens: new BigNumber('112323.67'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('112323.67'),
},
],
},
{
tranche_id: 2,
tranche_start: parseJSON('2022-06-05T00:00:00.000Z'),
tranche_end: parseJSON('2023-12-05T00:00:00.000Z'),
total_added: new BigNumber('15464357.550320999700000001'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('15464357.550320999700000001'),
deposits: [
{
amount: new BigNumber('1'),
user: '0x7fff551249D223f723557a96a0e1a469C79cC934',
tx: '0x55fa59d71aac37428a5804442c48da677a4426a4e92447919a8199520ce20f53',
},
{
amount: new BigNumber('100000'),
user: '0xe20D4d4fFb165e4b9926467d82d03c0e9ab66D89',
tx: '0xabf2cc96c41c8b4f0cff8d8ff7448e241ff83e296a7b7dea79d9d9868f33b7ad',
},
{
amount: new BigNumber('500000'),
user: '0x5f01A497e4033E4812ba5D494bCBc2220cd510Ed',
tx: '0xe3a1a74378a42eace12c21c24889d8b0da6a3736ca7987720e70f59886caa5a3',
},
{
amount: new BigNumber('59228.95'),
user: '0x1d20f66eF3889aa48Bb4Badbbf993dE965BDb029',
tx: '0xf31eab593b86aac54f5fa9fe24bb26105ff52386be60bc24617dd362df7c6f15',
},
{
amount: new BigNumber('206374.1211'),
user: '0x1b979e8AE3BbbaF96Dd1bbbC0060b360A23f2EBE',
tx: '0x2983daa9acf6da5fba0debc2aaaaf47389b92aea0de0c27b20809fb69a63ce69',
},
{
amount: new BigNumber('200000'),
user: '0xe45993F39183E148bC35BA02ba8C289111181c0f',
tx: '0xbda7b70ab8ac629617e74daaaa5451b04d7830bc0da516e10d533efc2eef1530',
},
],
withdrawals: [
{
amount: new BigNumber('0'),
user: '0x4527F5A12bbbbb7c88c5863F8AB9a708928Fe702',
tx: '0xbaa8632cd162265ca46baaaad746ec3d283a474e344727854d962e057380a51',
},
],
users: [
{
address: '0x7fff551249D223f723557a96a0e1aCCCC79cC934',
deposits: [
{
amount: new BigNumber('1'),
user: '0x7fff551249D223f723557a96a0e1aCCCC79cC934',
tranche_id: 2,
tx: '0x55fa59d71aac37428a0000042c48da677a4426a4e92447919a8199520ce20f53',
},
],
withdrawals: [],
total_tokens: new BigNumber('1'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('1'),
},
{
address: '0xCc5CAFD3daA3bb2c1168521F35d1eBEB6cf7c051',
deposits: [
{
amount: new BigNumber('200000'),
user: '0xCc5CAFD3daA3bb2c1168521F35d1eBEB6cf7c051',
tranche_id: 2,
tx: '0x8168230eb08320a7a874ebeeea20f0def842b8059a2b05a036870e00ca624c88',
},
],
withdrawals: [],
total_tokens: new BigNumber('200000'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('200000'),
},
{
address: '0x9cd59376F896a5F2084232E386A65c17EEA4Fe9f',
deposits: [
{
amount: new BigNumber('200000'),
user: '0x9cd59376F896a5F2084232E386A65c17EEA4Fe9f',
tranche_id: 2,
tx: '0x2eb27449e08a245314faaaaf0d93fafc70733586495f18cf2fb69ef979459efd',
},
],
withdrawals: [],
total_tokens: new BigNumber('200000'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('200000'),
},
{
address: '0xe45993F39183E148bC35BA02ba8aaaa807981caa',
deposits: [
{
amount: new BigNumber('200000'),
user: '0xe45993F39183E148bC35BA02ba8aaaa07981caa',
tranche_id: 2,
tx: '0xbda7b70ab8ac629617e74d33575451b04d7830bc0da516e10d533efc2eef1530',
},
],
withdrawals: [],
total_tokens: new BigNumber('200000'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('200000'),
},
],
},
{
tranche_id: 3,
tranche_start: parseJSON('2021-11-05T00:00:00.000Z'),
tranche_end: parseJSON('2023-05-05T00:00:00.000Z'),
total_added: new BigNumber('14597706.0446472999'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('14445316.74229298796336861823365303'),
deposits: [
{
amount: new BigNumber('129284.449'),
user: '0x26100B2C8168Cb0A6c869a5698265086A3Dfeaaa',
tx: '0xcc67a776a2e3b48864470aaa9e0940c1814663b4fde6df60c1a099a636dcae79',
},
{
amount: new BigNumber('1151405.093'),
user: '0x777Ec2e2beaB6a63c1E763D0dc4120AF60BEe39F',
tx: '0x1237d45a40aacc5dff7f8f69e8a4c0536fd0460b915de576bd3f0fdf5892c0a4',
},
{
amount: new BigNumber('1151073.595'),
user: '0x5CD0Ec63687588817044794bF15d4e37991efAB3',
tx: '0xb4eaca7d8abeaf7e1d9d28b1f1fa09fc1933af0e11bff4b0120dcef7d2dfc64d',
},
{
amount: new BigNumber('54034.5'),
user: '0xc01F2E57554Bb392384feCA6a54c8E3A3Ca94E42',
tx: '0xf33289a2a73a65b132accdddd600a8d2c7556c2d80784d5f8d4eea1ecacf93cc',
},
{
amount: new BigNumber('115049.22'),
user: '0x01a8055A97b461b58ba8e37cd349721FeAe77A8D',
tx: '0xf650c3599b24d01dfa1f3e19b22f870ff8ac8dfac529893f0b22d548c3535eda',
},
],
withdrawals: [
{
amount: new BigNumber('0'),
user: '0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b',
tx: '0x1af894d1f9ce5ea79aa52d180fbff5f30b8b456e43b76ca5d7d73366e422ea37',
},
],
users: [
{
address: '0x26100B2C8168Cb0A6c869a5698265086A3DfeF98',
deposits: [
{
amount: new BigNumber('129284.449'),
user: '0x26100B2C8168Cb0A6c869a5698265086A3DfeF98',
tranche_id: 3,
tx: '0xcc67a776a2e3b48864470eff9e0940c1814663b4fde6df60c1a099a636dcae79',
},
],
withdrawals: [],
total_tokens: new BigNumber('129284.449'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('129284.449'),
},
{
address: '0xd4632B682228Db5f38E2283869AEe8c29ee6Eec8',
deposits: [
{
amount: new BigNumber('44499.2'),
user: '0xd4632B682228Db5f38E2283869AEe8c29ee6Eec8',
tranche_id: 3,
tx: '0x57019840d1ce05e0ef65c45801d6699e5eb1032bd8ad56493594d4866996ea82',
},
],
withdrawals: [],
total_tokens: new BigNumber('44499.2'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('44499.2'),
},
{
address: '0xe2E6F37cb1f1980418012BF69f43910d6Bc73e73',
deposits: [
{
amount: new BigNumber('66748.8'),
user: '0xe2E6F37cb1f1980418012BF69f43910d6Bc73e73',
tranche_id: 3,
tx: '0xd257a3aacd5bdf86cbea0ed3db3697f55dff9092129db6baedacaf00b50af936',
},
],
withdrawals: [],
total_tokens: new BigNumber('66748.8'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('66748.8'),
},
{
address: '0xc01F2E57554Bb392384feCA6a54c8E3A3Ca94E42',
deposits: [
{
amount: new BigNumber('54034.5'),
user: '0xc01F2E57554Bb392384feCA6a54c8E3A3Ca94E42',
tranche_id: 3,
tx: '0xf33289a2a73a65b132accdddd600a8d2c7556c2d80784d5f8d4eea1ecacf93cc',
},
],
withdrawals: [],
total_tokens: new BigNumber('54034.5'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('54034.5'),
},
],
},
{
tranche_id: 4,
tranche_start: parseJSON('2021-10-05T00:00:00.000Z'),
tranche_end: parseJSON('2023-04-05T00:00:00.000Z'),
total_added: new BigNumber('5198082.8647159303'),
total_removed: new BigNumber('12706.1452878164044708'),
locked_amount: new BigNumber('4849328.20502099651595959714823613'),
deposits: [
{
amount: new BigNumber('110499.5291'),
user: '0xa8679b60612Fb2e19d68964326CA02dCe6a08D08',
tx: '0x9d3432b818054796489848c415af5c523acb16c1540e5865010baf71964c03a7',
},
{
amount: new BigNumber('331498.5873'),
user: '0x39fEc2e2beaB6a63c1E763D0dc4120AF60BEe39F',
tx: '0xdf5c6e44ef0763e785721802704e5fd186fa3964302a566899027447d0dda57b',
},
{
amount: new BigNumber('27624.032275'),
user: '0xF5Fb27b912D987B5b6e02A1B1BE0C1F0740E2c6f',
tx: '0x1860d39ae3e9ad710da9e2a9bf9606eedc1b176fca673473d6854ea91ed8beb5',
},
{
amount: new BigNumber('73666.3527333333'),
user: '0x2895059cB5a492BEd58D1fB22713006EfaD465eA',
tx: '0x260619e2129cc58ae03f6b4ecfc5a6eeab29b5998de69875accb3cb945beed04',
},
],
withdrawals: [
{
amount: new BigNumber('1290.014571009862016'),
user: '0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75',
tx: '0x637c3648ce941a77e08e741f981806a5d56275db6d280f041902386ae8567d06',
},
{
amount: new BigNumber('5197.621879605058623'),
user: '0xafa64cCa337eFEE0AD827F6C2684e69275226e90',
tx: '0xe8b8c1a38d3ae809dcaae54a11adeeba0f46be03924e91d8f251f3f2d48c3553',
},
{
amount: new BigNumber('376.2625308599198808'),
user: '0x1dD2718fd01d05C9F50Fce8Bb723A4C7483A1E15',
tx: '0x78af737235f1123bb1c6a607a0b4b7a3c5ed6859a4e8912f45bb7c812724b1de',
},
{
amount: new BigNumber('4162.301372742020875'),
user: '0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75',
tx: '0x85c7d9979f0e3a3660dc5952732eaf9414f7c28a1c00a9a10f449843f91d85e0',
},
{
amount: new BigNumber('1679.944933599543076'),
user: '0x9058e12e2F32cB1cD4D3123359963D77786477FC',
tx: '0xd92083f2e90e84cb70ef27ac410ed1144200c41b1714fb54d29f5f23ca086ecb',
},
],
users: [
{
address: '0xa8679b60612Fb2e19d68964326CA02dCe6a08D08',
deposits: [
{
amount: new BigNumber('110499.5291'),
user: '0xa8679b60612Fb2e19d68964326CA02dCe6a08D08',
tranche_id: 4,
tx: '0x9d3432b818054796489848c415af5c523acb16c1540e5865010baf71964c03a7',
},
],
withdrawals: [],
total_tokens: new BigNumber('110499.5291'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('110499.5291'),
},
{
address: '0x8767d65677Cabaa2050b764AEf40610f2f9796F5',
deposits: [
{
amount: new BigNumber('1104995.291'),
user: '0x8767d65677Cabaa2050b764AEf40610f2f9796F5',
tranche_id: 4,
tx: '0xc6298f52c173a837abd051ed810b01eb5731be307376b73df6e31b4de39d0122',
},
],
withdrawals: [],
total_tokens: new BigNumber('1104995.291'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('1104995.291'),
},
{
address: '0x91715128a71c9C734CDC20E5EdaaeA02E72e422E',
deposits: [
{
amount: new BigNumber('165749.29365'),
user: '0x91715128a71c9C734CDC20E5EdEEeA02E72e422E',
tranche_id: 4,
tx: '0xf828cea685a0689f27446f02d3376c3afa4398182d3b5bf1c81e949f5965d1c1',
},
],
withdrawals: [],
total_tokens: new BigNumber('165749.29365'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('165749.29365'),
},
{
address: '0x2895059cB5a492BEd58D1fB22713006EfaD465eA',
deposits: [
{
amount: new BigNumber('73666.3527333333'),
user: '0x2895059cB5a492BEd58D1fB22713006EfaD465eA',
tranche_id: 4,
tx: '0x260619e2129cc58ae03f6b4ecfc5a6eeab29b5998de69875accb3cb945beed04',
},
],
withdrawals: [],
total_tokens: new BigNumber('73666.3527333333'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('73666.3527333333'),
},
],
},
{
tranche_id: 23,
tranche_start: parseJSON('2022-04-30T00:00:00.000Z'),
tranche_end: parseJSON('2022-04-30T00:00:00.000Z'),
total_added: new BigNumber('10833.29'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('10833.29'),
deposits: [
{
amount: new BigNumber('10833.29'),
user: '0xF3359E5B89f7804c8c9283781Aaaa33BBd979c9D',
tx: '0x65f904ef34d6992b52f449a709d7a24411a501fd07f90aaa9255eacc994bc229',
},
],
withdrawals: [],
users: [
{
address: '0xF3359E5B89f7804c8c9283781Aaaa33BBd979c9D',
deposits: [
{
amount: new BigNumber('10833.29'),
user: '0xF3359E5B89f7804c8c9283781Aaaa33BBd979c9D',
tranche_id: 23,
tx: '0x65f904ef34d6992b52f449a709d7a24411a501fd07f90aaa9255eacc994bc229',
},
],
withdrawals: [],
total_tokens: new BigNumber('10833.29'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('10833.29'),
},
],
},
{
tranche_id: 24,
tranche_start: parseJSON('2022-09-26T00:00:00.000Z'),
tranche_end: parseJSON('2022-09-26T00:00:00.000Z'),
total_added: new BigNumber('16249.93'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('16249.93'),
deposits: [
{
amount: new BigNumber('16249.93'),
user: '0xcE96670971ec2E1E79D0d96688adbA2FfD6F6C7f',
tx: '0x3dbd991b7914986505d89a7c1562278dffffa2f5f444bdbf5d6bbd838e3d8d5d',
},
],
withdrawals: [],
users: [
{
address: '0xcE96670971ec2E1E79D0d96688adbA2FfD6F6C7f',
deposits: [
{
amount: new BigNumber('16249.93'),
user: '0xcE96670971ec2E1E79D0d96688adbA2FfD6F6C7f',
tranche_id: 24,
tx: '0x3dbd991b7914986505d89a7c1562278dffffa2f5f444bdbf5d6bbd838e3d8d5d',
},
],
withdrawals: [],
total_tokens: new BigNumber('16249.93'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('16249.93'),
},
],
},
{
tranche_id: 25,
tranche_start: parseJSON('2022-02-10T00:00:00.000Z'),
tranche_end: parseJSON('2023-04-05T00:00:00.000Z'),
total_added: new BigNumber('0'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('0'),
deposits: [],
withdrawals: [],
users: [],
},
{
tranche_id: 26,
tranche_start: parseJSON('2022-02-04T00:00:00.000Z'),
tranche_end: parseJSON('2023-04-05T00:00:00.000Z'),
total_added: new BigNumber('135173.4239508'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('135173.4239508'),
deposits: [
{
amount: new BigNumber('135173.4239508'),
user: '0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2',
tx: '0x123fb1e293a8246b92d85a47c8d33def9fb6468c7cbb70f1754d78914b12dbf8',
},
],
withdrawals: [],
users: [
{
address: '0x222eA4d8D214D548221EE3622a8BE1D61f7077A2',
deposits: [
{
amount: new BigNumber('135173.4239508'),
user: '0x222eA4d8D214D548221EE3622a8BE1D61f7077A2',
tranche_id: 26,
tx: '0x9fafb1e293a8246b92d85a47c8d33def9fb6468c7cbb70f1754d78914b12dbf8',
},
],
withdrawals: [],
total_tokens: new BigNumber('135173.4239508'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('135173.4239508'),
},
],
},
{
tranche_id: 27,
tranche_start: parseJSON('2022-05-09T00:00:00.000Z'),
tranche_end: parseJSON('2023-04-05T00:00:00.000Z'),
total_added: new BigNumber('32499.86'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('32499.86'),
deposits: [
{
amount: new BigNumber('32499.86'),
user: '0x1E7c4E57A1dc4dD4bBE81b833e3E437f69619DaB',
tx: '0x25a3dd4852ce8ac1c15fe42123cfab14e4bf8a5c1cb97167cb4d6fe20bd319ae',
},
],
withdrawals: [],
users: [
{
address: '0x3E7c4E57A1dc4dD4bBE81bbEFBe3Eaaaf69619Da9',
deposits: [
{
amount: new BigNumber('32499.86'),
user: '0x3E7c4E57A1dc4dD4bBE81bbEFBe3Eaaaf69619Da9',
tranche_id: 27,
tx: '0x75a3dd4852ce8ac1c15fe42ff6cfab1455bf8a5c1cb97167cb4d6fe20bd319ae',
},
],
withdrawals: [],
total_tokens: new BigNumber('32499.86'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('32499.86'),
},
],
},
{
tranche_id: 28,
tranche_start: parseJSON('2022-04-30T00:00:00.000Z'),
tranche_end: parseJSON('2023-04-05T00:00:00.000Z'),
total_added: new BigNumber('10833.29'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('10833.29'),
deposits: [
{
amount: new BigNumber('10833.29'),
user: '0xF3359E5B89f7804c8c9283781Aaa133BBd979c9D',
tx: '0x166d235eff44e7bcc63597c0d6e698552e4440fe90ec7cf07a1d510c275cf3e0',
},
],
withdrawals: [],
users: [
{
address: '0x12349E5B89f7804c8c9283781A23133BBd979c22',
deposits: [
{
amount: new BigNumber('10833.29'),
user: '0x12349E5B89f7804c8c9283781A23133BBd979c22',
tranche_id: 28,
tx: '0x166d235eff44e7baa6359712345698552e6150fe90ec7cf07a1d510c275cf3e0',
},
],
withdrawals: [],
total_tokens: new BigNumber('10833.29'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('10833.29'),
},
],
},
{
tranche_id: 29,
tranche_start: parseJSON('2022-09-26T00:00:00.000Z'),
tranche_end: parseJSON('2023-04-05T00:00:00.000Z'),
total_added: new BigNumber('16249.93'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('16249.93'),
deposits: [
{
amount: new BigNumber('16249.93'),
user: '0xcE96670971ec2E1E79D0d12388adbA2FfD6F6C7f',
tx: '0xa770ab2d05e6c81be46b73ac2326e81a111da4ce55a2d8544bd95b48ad530674',
},
],
withdrawals: [],
users: [
{
address: '0xcE96670971ec2E1E79D0d96688adbA2FfD6F6C7f',
deposits: [
{
amount: new BigNumber('16249.93'),
user: '0xcE96670971ec212379D0d12388adbA2Ffc6F6C7f',
tranche_id: 29,
tx: '0xa220ab2d05e6c81be12373ac2326e81a912da4ce55a2d8544bd95b48ad530674',
},
],
withdrawals: [],
total_tokens: new BigNumber('16249.93'),
withdrawn_tokens: new BigNumber('0'),
remaining_tokens: new BigNumber('16249.93'),
},
],
},
{
tranche_id: 30,
tranche_start: parseJSON('2021-11-01T00:00:00.000Z'),
tranche_end: parseJSON('2022-05-01T00:00:00.000Z'),
total_added: new BigNumber('0'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('0'),
deposits: [],
withdrawals: [],
users: [],
},
{
tranche_id: 31,
tranche_start: parseJSON('2022-02-01T00:00:00.000Z'),
tranche_end: parseJSON('2022-08-01T00:00:00.000Z'),
total_added: new BigNumber('0'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('0'),
deposits: [],
withdrawals: [],
users: [],
},
{
tranche_id: 32,
tranche_start: parseJSON('2022-05-01T00:00:00.000Z'),
tranche_end: parseJSON('2022-11-01T00:00:00.000Z'),
total_added: new BigNumber('0'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('0'),
deposits: [],
withdrawals: [],
users: [],
},
{
tranche_id: 33,
tranche_start: parseJSON('2022-11-01T00:00:00.000Z'),
tranche_end: parseJSON('2023-05-01T00:00:00.000Z'),
total_added: new BigNumber('0'),
total_removed: new BigNumber('0'),
locked_amount: new BigNumber('0'),
deposits: [],
withdrawals: [],
users: [],
},
];
export default json;
@@ -0,0 +1,21 @@
import React from 'react';
import mock from './tranches-mock';
import type { Tranche } from '@vegaprotocol/smart-contracts';
export function useTranches() {
const [tranches, setTranches] = React.useState<Tranche[] | null>(null);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
const run = async () => {
try {
setTranches(mock);
} catch (err) {
setError((err as Error).message);
}
};
run();
}, []);
return { tranches, error };
}
@@ -1,41 +0,0 @@
import { useCallback } from 'react';
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { useContracts } from '../contexts/contracts/contracts-context';
import { useAppState } from '../contexts/app-state/app-state-context';
import { useEthereumConfig } from '@vegaprotocol/web3';
export const useGetUserBalances = (account: string | undefined) => {
const { token, vesting } = useContracts();
const { config } = useEthereumConfig();
const {
appState: { decimals },
} = useAppState();
return useCallback(async () => {
if (!account || !config) return;
try {
const [b, w, stats, a] = await Promise.all([
vesting.user_total_all_tranches(account),
token.balanceOf(account),
vesting.user_stats(account),
token.allowance(account, config.staking_bridge_contract.address),
]);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
return {
balanceFormatted: balance,
walletBalance,
lien,
allowance,
balance,
};
} catch (err) {
Sentry.captureException(err);
return null;
}
}, [account, config, decimals, token, vesting]);
};
@@ -0,0 +1,70 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import type { TokenVesting } from '@vegaprotocol/smart-contracts';
import {
AppStateActionType,
useAppState,
} from '../contexts/app-state/app-state-context';
import { BigNumber } from '../lib/bignumber';
import { useTranches } from './use-tranches';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { useBalances } from '../lib/balances/balances-store';
export const useGetUserTrancheBalances = (
address: string,
vesting: TokenVesting
) => {
const {
appState: { decimals },
appDispatch,
} = useAppState();
const { setTranchesBalances } = useBalances();
const { tranches } = useTranches();
return React.useCallback(async () => {
appDispatch({
type: AppStateActionType.SET_TRANCHE_ERROR,
error: null,
});
try {
if (!tranches) {
return;
}
const userTranches = tranches?.filter((t) =>
t.users.some(
({ address: a }) =>
a && address && a.toLowerCase() === address.toLowerCase()
)
);
const trancheIds = [0, ...userTranches.map((t) => t.tranche_id)];
const promises = trancheIds.map(async (tId) => {
const [t, v] = await Promise.all([
vesting.get_tranche_balance(address, tId),
vesting.get_vested_for_tranche(address, tId),
]);
const total = toBigNum(t, decimals);
const vested = toBigNum(v, decimals);
return {
id: tId,
locked: tId === 0 ? total : total.minus(vested),
vested: tId === 0 ? new BigNumber(0) : vested,
};
});
const trancheBalances = await Promise.all(promises);
setTranchesBalances(trancheBalances);
appDispatch({
type: AppStateActionType.SET_TRANCHE_DATA,
tranches,
});
} catch (e) {
Sentry.captureException(e);
appDispatch({
type: AppStateActionType.SET_TRANCHE_ERROR,
error: e as Error,
});
}
}, [appDispatch, tranches, setTranchesBalances, address, vesting, decimals]);
};
+70
View File
@@ -0,0 +1,70 @@
import { useFetch } from '@vegaprotocol/react-helpers';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import React, { useEffect } from 'react';
import type { Networks } from '@vegaprotocol/environment';
import { useEnvironment } from '@vegaprotocol/environment';
import { BigNumber } from '../lib/bignumber';
const TRANCHES_URLS: { [N in Networks]: string } = {
MAINNET: 'https://static.vega.xyz/assets/mainnet-tranches.json',
MIRROR: 'https://static.vega.xyz/assets/mirror-tranches.json',
TESTNET: 'https://static.vega.xyz/assets/testnet-tranches.json',
SANDBOX: 'https://static.vega.xyz/assets/sandbox-tranches.json',
STAGNET1: 'https://static.vega.xyz/assets/stagnet1-tranches.json',
STAGNET3: 'https://static.vega.xyz/assets/stagnet3-tranches.json',
DEVNET: 'https://static.vega.xyz/assets/devnet-tranches.json',
CUSTOM: 'https://static.vega.xyz/assets/testnet-tranches.json',
};
export function useTranches() {
const { VEGA_ENV } = useEnvironment();
const [tranches, setTranches] = React.useState<Tranche[] | null>(null);
const url = React.useMemo(() => TRANCHES_URLS[VEGA_ENV], [VEGA_ENV]);
const {
state: { data, loading, error },
} = useFetch<Tranche[] | null>(url);
useEffect(() => {
const processedTrances = data
?.map((t) => ({
...t,
tranche_start: new Date(t.tranche_start),
tranche_end: new Date(t.tranche_end),
total_added: new BigNumber(t.total_added),
total_removed: new BigNumber(t.total_removed),
locked_amount: new BigNumber(t.locked_amount),
deposits: t.deposits.map((d) => ({
...d,
amount: new BigNumber(d.amount),
})),
withdrawals: t.withdrawals.map((w) => ({
...w,
amount: new BigNumber(w.amount),
})),
users: t.users.map((u) => ({
...u,
// @ts-ignore - types are incorrect in the SDK lib
deposits: u.deposits.map((d) => ({
...d,
amount: new BigNumber(d.amount),
})),
// @ts-ignore - types are incorrect in the SDK lib
withdrawals: u.withdrawals.map((w) => ({
...w,
amount: new BigNumber(w.amount),
})),
total_tokens: new BigNumber(u.total_tokens),
withdrawn_tokens: new BigNumber(u.withdrawn_tokens),
remaining_tokens: new BigNumber(u.remaining_tokens),
})),
}))
.sort((a: Tranche, b: Tranche) => a.tranche_id - b.tranche_id);
setTranches(processedTrances ? processedTrances : null);
}, [data]);
return {
tranches,
loading,
error,
};
}
+1 -10
View File
@@ -744,22 +744,13 @@
"Proposals": "Proposals",
"Validators": "Validators",
"Redeem": "Redeem",
"validatorFormIntro": "To learn more about validators and how scores are calculated,",
"readMoreValidatorForm": "read about staking on Vega",
"StakeDescription": "The total amount $VEGA staked to this validator including self-stake and all delegation.",
"NormalisedVotingPowerDescription": "Voting power is the relative weighting given to the validator in tendermint consensus. It is calculated based on the stake share of the validator minus any penalties due to overstaking or performance.",
"StakeShareDescription": "The stake a validator represents as a share of total stake across the network.",
"TotalPenaltiesDescription": "Total of penalties taking into account performance (considering proportion of blocks proposed against the number of blocks the validator was expected to propose) and any overstaking.",
"PendingStakeDescription": "The amount of stake that will be added or removed from the validator from the next epoch.",
"StakeNeededForPromotionStandbyDescription": "{{prefix}} additional stake needed for promotion to consensus, assuming constant performance in line with previous epoch.",
"StakeNeededForPromotionCandidateDescription": "{{prefix}} additional stake needed for promotion to standby, assuming constant performance in line with previous epoch.",
"StakedByOperatorDescription": "The stake provided as self-stake by the node operator, must be at least the minimum stake as defined by network parameter",
"AboutThisValidatorDescription": "External URL provided by the validator linking to information about themselves",
"ValidatorStatusDescription": "Consensus, Standby or Pending (Candidate), depending on how much stake the validator has attracted",
"StakedByDelegatesDescription": "The stake delegated to the node by other users",
"OverstakedPenaltyDescription": "A penalty applied for having more stake than the optimal stake for the network. Designed to avoid concentration of voting power with a small number of validators",
"PerformancePenaltyDescription": "Performance score is a measure of how often a validator proposed blocks in the last epoch relative to how many they should be expected to propose based on their voting power. Performance penalty is applied for having a performance score of less than 1",
"UnnormalisedVotingPowerDescription": "The voting power of the validator based on their final validator score after all penalties have been applied",
"NormalisedVotingPowerDescription": "The voting power of the validator, adjusted to ensure all validator scores sum to 1, used for distribution of rewards",
"Score": "Score",
"performancePenalty": "Performance penalty",
"overstaked": "Overstaked",
@@ -0,0 +1,21 @@
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { BigNumber } from '../bignumber';
export function generateTranche(id: number): Tranche {
return {
tranche_id: id,
tranche_start: new Date(),
tranche_end: new Date(),
total_added: new BigNumber(0),
total_removed: new BigNumber(0),
locked_amount: new BigNumber(0),
deposits: [],
withdrawals: [],
users: [],
};
}
export function generateTranches(count = 1) {
return new Array(count).fill(null).map((_, i) => generateTranche(i));
}
@@ -1,11 +1,6 @@
import BigNumber from 'bignumber.js';
import { create } from 'zustand';
interface UserTrancheBalance {
id: number;
locked: BigNumber;
vested: BigNumber;
}
import type { UserTrancheBalance } from '../../contexts/app-state/app-state-context';
export interface AssociationBreakdown {
stakingAssociations: { [vegaKey: string]: BigNumber };
@@ -18,8 +13,8 @@ export type BalancesStore = {
balanceFormatted: BigNumber;
totalVestedBalance: BigNumber;
totalLockedBalance: BigNumber;
trancheBalances: UserTrancheBalance[];
walletBalance: BigNumber;
trancheBalances: UserTrancheBalance[];
lien: BigNumber;
walletAssociatedBalance: BigNumber;
vestingAssociatedBalance: BigNumber;
@@ -43,8 +38,8 @@ export const useBalances = create<BalancesStore>((set) => ({
stakingAssociations: {},
vestingAssociations: {},
},
allowance: new BigNumber(0),
trancheBalances: [],
allowance: new BigNumber(0),
totalVestedBalance: new BigNumber(0),
totalLockedBalance: new BigNumber(0),
balanceFormatted: new BigNumber(0),
@@ -1,80 +0,0 @@
import { toBigNum } from '@vegaprotocol/react-helpers';
import type { TrancheServiceResponse } from '@vegaprotocol/smart-contracts';
import type BigNumber from 'bignumber.js';
import create from 'zustand';
import { ENV } from '../../config';
export interface Tranche {
tranche_id: number;
tranche_start: Date;
tranche_end: Date;
total_added: BigNumber;
total_removed: BigNumber;
locked_amount: BigNumber;
users: string[];
}
const URL = `${ENV.tranchesServiceUrl}/tranches/stats`;
export interface UserTrancheBalance {
/** ID of tranche */
id: number;
/** Users vesting tokens on tranche */
locked: BigNumber;
/** Users vested tokens on tranche */
vested: BigNumber;
}
export type TranchesStore = {
tranches: Tranche[] | null;
loading: boolean;
error: Error | null;
getTranches: (decimals: number) => void;
};
const secondsToDate = (seconds: number) => new Date(seconds * 1000);
export const useTranches = create<TranchesStore>((set) => ({
tranches: null,
loading: false,
error: null,
getTranches: async (decimals: number) => {
set({ loading: true, error: null });
try {
const res = await fetch(URL);
const data = (await res.json()) as TrancheServiceResponse;
const now = Math.round(Date.now() / 1000);
const tranches = Object.values(data.tranches)
?.map((t) => {
const tranche_progress =
t.duration !== 0 ? (now - t.cliff_start) / t.duration : 0;
const lockedDecimal = tranche_progress < 0 ? 1 : 1 - tranche_progress;
return {
tranche_id: t.tranche_id,
tranche_start: secondsToDate(t.cliff_start),
tranche_end: secondsToDate(t.cliff_start + t.duration),
total_added: toBigNum(t.initial_balance, decimals),
total_removed: toBigNum(t.initial_balance, decimals).minus(
toBigNum(t.current_balance, decimals)
),
locked_amount: toBigNum(t.initial_balance, decimals).times(
lockedDecimal
),
users: t.users,
};
})
.sort((a, b) => a.tranche_id - b.tranche_id);
set({
tranches,
});
} catch (e) {
set({ error: e as unknown as Error });
} finally {
set({
loading: false,
});
}
},
}));
+1 -1
View File
@@ -4,6 +4,7 @@ import { format } from 'date-fns';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { useContracts } from '../../contexts/contracts/contracts-context';
@@ -21,7 +22,6 @@ import { TrancheNotFound } from './tranche-not-found';
import { UntargetedClaim } from './untargeted-claim';
import { Verifying } from './verifying';
import type { ClaimAction, ClaimState } from './claim-reducer';
import type { Tranche } from '../../lib/tranches/tranches-store';
interface ClaimFlowProps {
state: ClaimState;
+1 -1
View File
@@ -1,8 +1,8 @@
import { useTranslation } from 'react-i18next';
import { format } from 'date-fns';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { DATE_FORMAT_LONG } from '../../lib/date-formats';
import type { Tranche } from '../../lib/tranches/tranches-store';
interface ClaimInfoProps {
tranche: Tranche;
+7 -2
View File
@@ -1,5 +1,7 @@
import React from 'react';
import { useAppState } from '../../contexts/app-state/app-state-context';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { useGetUserTrancheBalances } from '../../hooks/use-get-user-tranche-balances';
import { useRefreshBalances } from '../../hooks/use-refresh-balances';
import { useSearchParams } from '../../hooks/use-search-params';
import { ClaimError } from './claim-error';
@@ -11,7 +13,7 @@ import {
initialClaimState,
} from './claim-reducer';
import type { Tranche } from '../../lib/tranches/tranches-store';
import type { Tranche } from '@vegaprotocol/smart-contracts';
const Claim = ({
address,
@@ -21,8 +23,10 @@ const Claim = ({
tranches: Tranche[];
}) => {
const params = useSearchParams();
const { vesting } = useContracts();
const { appState } = useAppState();
const [state, dispatch] = React.useReducer(claimReducer, initialClaimState);
const getUserTrancheBalances = useGetUserTrancheBalances(address, vesting);
const refreshBalances = useRefreshBalances(address);
React.useEffect(() => {
@@ -44,9 +48,10 @@ const Claim = ({
// If the claim has been committed refetch the new VEGA balance
React.useEffect(() => {
if (state.claimStatus === ClaimStatus.Finished && address) {
getUserTrancheBalances();
refreshBalances();
}
}, [address, refreshBalances, state.claimStatus]);
}, [address, getUserTrancheBalances, refreshBalances, state.claimStatus]);
if (state.error) {
return <ClaimError />;
+4 -9
View File
@@ -5,7 +5,7 @@ import { EthConnectPrompt } from '../../components/eth-connect-prompt';
import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { useTranches } from '../../lib/tranches/tranches-store';
import { useTranches } from '../../hooks/use-tranches';
import type { RouteChildProps } from '..';
import Claim from './claim';
import { ClaimRestricted } from './claim-restricted';
@@ -16,11 +16,7 @@ const ClaimIndex = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const { account } = useWeb3React();
const { tranches, loading, error } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
const { tranches, loading, error } = useTranches();
if (loading || !tranches) {
return (
@@ -42,14 +38,13 @@ const ClaimIndex = ({ name }: RouteChildProps) => {
if (!account) {
content = (
<>
<EthConnectPrompt>
<p data-testid="eth-connect-prompt">
{t(
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas."
)}
</p>
<EthConnectPrompt />
</>
</EthConnectPrompt>
);
} else {
content = isRestricted() ? (
@@ -51,9 +51,11 @@ const mockAppState: AppState = {
totalAssociated: new BigNumber('50063005'),
decimals: 18,
totalSupply: new BigNumber(65000000),
tranches: null,
vegaWalletOverlay: false,
vegaWalletManageOverlay: false,
ethConnectOverlay: false,
trancheError: null,
drawerOpen: false,
transactionOverlay: false,
bannerMessage: '',
@@ -14,9 +14,11 @@ const mockAppState: AppState = {
totalAssociated: new BigNumber('50063005'),
decimals: 18,
totalSupply: mockTotalSupply,
tranches: null,
vegaWalletOverlay: false,
vegaWalletManageOverlay: false,
ethConnectOverlay: false,
trancheError: null,
drawerOpen: false,
transactionOverlay: false,
bannerMessage: '',
@@ -1,81 +1,56 @@
import { Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { useEffect, useMemo, useState } from 'react';
import { useBalances } from '../../../lib/balances/balances-store';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Link, useNavigate, useOutletContext } from 'react-router-dom';
import { AddLockedTokenAddress } from '../../../components/add-locked-token';
import { formatNumber } from '../../../lib/format-number';
import { truncateMiddle } from '../../../lib/truncate-middle';
import Routes from '../../routes';
import type { RedemptionState } from '../redemption-reducer';
import { Tranche0Table, TrancheTable } from '../tranche-table';
import { VestingTable } from './vesting-table';
import { useTranches } from '../../../lib/tranches/tranches-store';
import { useGetUserBalances } from '../../../hooks/use-get-user-balances';
import BigNumber from 'bignumber.js';
import { useUserTrancheBalances } from '../hooks';
interface UserBalances {
balanceFormatted: BigNumber;
walletBalance: BigNumber;
lien: BigNumber;
allowance: BigNumber;
balance: BigNumber;
}
export const RedemptionInformation = () => {
const { t } = useTranslation();
const { state, account } = useOutletContext<{
state: RedemptionState;
account: string;
}>();
const navigate = useNavigate();
const tranches = useTranches((state) => state.tranches);
const { address } = useParams<{ address: string }>();
const [userBalances, setUserBalances] = useState<null | UserBalances>();
const getUsersBalances = useGetUserBalances(address);
useEffect(() => {
getUsersBalances().then(setUserBalances);
}, [getUsersBalances]);
const userTrancheBalances = useUserTrancheBalances(address);
const filteredTranches = useMemo(
const { t } = useTranslation();
const {
balanceFormatted,
lien,
totalVestedBalance,
totalLockedBalance,
trancheBalances,
} = useBalances();
const { userTranches } = state;
const filteredTranches = React.useMemo(
() =>
tranches?.filter((tr) => {
const balance = userTrancheBalances.find(
userTranches.filter((tr) => {
const balance = trancheBalances.find(
({ id }) => id.toString() === tr.tranche_id.toString()
);
return (
balance?.locked.isGreaterThan(0) || balance?.vested.isGreaterThan(0)
);
}) || [],
[userTrancheBalances, tranches]
}),
[trancheBalances, userTranches]
);
const { totalLocked, totalVested } = useMemo(() => {
return {
totalLocked: BigNumber.sum.apply(null, [
new BigNumber(0),
...userTrancheBalances.map(({ locked }) => locked),
]),
totalVested: BigNumber.sum.apply(null, [
new BigNumber(0),
...userTrancheBalances.map(({ vested }) => vested),
]),
};
}, [userTrancheBalances]);
const zeroTranche = useMemo(() => {
const zeroTranche = userTrancheBalances.find((t) => t.id === 0);
const zeroTranche = React.useMemo(() => {
const zeroTranche = trancheBalances.find((t) => t.id === 0);
if (zeroTranche && zeroTranche.locked.isGreaterThan(0)) {
return zeroTranche;
}
return null;
}, [userTrancheBalances]);
}, [trancheBalances]);
const isAccountValid = useMemo(
() => address && address.length === 42 && address.startsWith('0x'),
[address]
);
if (!isAccountValid || !address) {
return <div>The address {address} is not a valid Ethereum address</div>;
}
if (!filteredTranches.length || !userBalances) {
if (!filteredTranches.length) {
return (
<section data-testid="redemption-page">
<div className="mb-8">
@@ -104,17 +79,17 @@ export const RedemptionInformation = () => {
{t(
'{{address}} has {{balance}} VEGA tokens in {{tranches}} tranches of the vesting contract.',
{
address: truncateMiddle(address),
balance: formatNumber(userBalances.balanceFormatted),
address: truncateMiddle(account),
balance: formatNumber(balanceFormatted),
tranches: filteredTranches.length,
}
)}
</p>
<div className="mb-24">
<VestingTable
associated={userBalances.lien}
locked={totalLocked}
vested={totalVested}
associated={lien}
locked={totalLockedBalance}
vested={totalVestedBalance}
/>
</div>
{filteredTranches.length ? <h2>{t('Tranche breakdown')}</h2> : null}
@@ -123,7 +98,7 @@ export const RedemptionInformation = () => {
trancheId={0}
total={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
userTrancheBalances.find(
trancheBalances.find(
({ id }) => id.toString() === zeroTranche.id.toString()
)!.locked
}
@@ -133,25 +108,22 @@ export const RedemptionInformation = () => {
<TrancheTable
key={tr.tranche_id}
tranche={tr}
lien={userBalances.lien}
lien={lien}
locked={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
userTrancheBalances.find(
trancheBalances.find(
({ id }) => id.toString() === tr.tranche_id.toString()
)!.locked
}
vested={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
userTrancheBalances.find(
trancheBalances.find(
({ id }) => id.toString() === tr.tranche_id.toString()
)!.vested
}
totalVested={totalVested}
totalLocked={totalLocked}
onClick={() =>
navigate(`${Routes.REDEEM}/${address}/${tr.tranche_id}`)
}
address={address}
totalVested={totalVestedBalance}
totalLocked={totalLockedBalance}
onClick={() => navigate(`/vesting/${tr.tranche_id}`)}
/>
))}
<Callout
@@ -160,7 +132,7 @@ export const RedemptionInformation = () => {
intent={Intent.Warning}
>
<p>{t('Find out more about Staking.')}</p>
<Link to={Routes.VALIDATORS} className="underline text-white">
<Link to="/staking" className="underline text-white">
{t('Stake VEGA tokens')}
</Link>
</Callout>
@@ -12,7 +12,7 @@ export interface VestingTableProps {
}
const VestingTableIndicatorSquare = ({ colour }: { colour: string }) => (
<span className={`bg-${colour} inline-block h-4 w-4 mr-1`} />
<span className={`bg-${colour} inline-block h-12 w-12 mr-4`} />
);
export const VestingTable = ({
@@ -65,17 +65,17 @@ export const VestingTable = ({
</KeyValueTable>
<div className="flex border-white border">
<div
className="bg-vega-pink h-4"
className="bg-vega-pink h-16"
style={{ flex: lockedPercentage.toNumber() }}
/>
<div
className="bg-vega-green h-4"
className="bg-vega-green h-16"
style={{ flex: vestedPercentage.toNumber() }}
/>
</div>
<div className="flex h-1 mt-1">
<div className="flex h-4 mt-4">
<div
className="bg-vega-yellow h-1"
className="bg-vega-yellow h-4"
style={{ flex: stakedPercentage.toNumber() }}
/>
<div
-54
View File
@@ -1,54 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import { useTranches } from '../../lib/tranches/tranches-store';
import { useContracts } from '../../contexts/contracts/contracts-context';
import BigNumber from 'bignumber.js';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { useAppState } from '../../contexts/app-state/app-state-context';
export const useUserTrancheBalances = (address: string | undefined) => {
const [userTrancheBalances, setUserTrancheBalances] = useState<
{
id: number;
locked: BigNumber;
vested: BigNumber;
}[]
>([]);
const {
appState: { decimals },
} = useAppState();
const { vesting } = useContracts();
const tranches = useTranches((state) => state.tranches);
const loadUserTrancheBalances = useCallback(async () => {
if (!address) return;
const userTranches =
tranches?.filter((t) =>
t.users.some(
(a) => a && address && a.toLowerCase() === address.toLowerCase()
)
) || [];
const trancheIds = [0, ...userTranches.map((t) => t.tranche_id)];
const promises = trancheIds.map(async (tId) => {
const [t, v] = await Promise.all([
vesting.get_tranche_balance(address, tId),
vesting.get_vested_for_tranche(address, tId),
]);
const total = toBigNum(t, decimals);
const vested = toBigNum(v, decimals);
return {
id: tId,
locked: tId === 0 ? total : total.minus(vested),
vested: tId === 0 ? new BigNumber(0) : vested,
};
});
const trancheBalances = await Promise.all(promises);
setUserTrancheBalances(trancheBalances);
}, [address, decimals, tranches, vesting]);
useEffect(() => {
loadUserTrancheBalances();
}, [loadUserTrancheBalances]);
return userTrancheBalances;
};
@@ -0,0 +1,38 @@
import type { Tranche } from '@vegaprotocol/smart-contracts';
import type { BigNumber } from '../../lib/bignumber';
export interface TrancheBalance {
id: number;
locked: BigNumber;
vested: BigNumber;
}
export interface RedemptionState {
userTranches: Tranche[];
}
export const initialRedemptionState: RedemptionState = {
userTranches: [],
};
export enum RedemptionActionType {
SET_USER_TRANCHES,
}
export type RedemptionAction = {
type: RedemptionActionType.SET_USER_TRANCHES;
userTranches: Tranche[];
};
export function redemptionReducer(
state: RedemptionState,
action: RedemptionAction
): RedemptionState {
switch (action.type) {
case RedemptionActionType.SET_USER_TRANCHES:
return {
...state,
userTranches: action.userTranches,
};
}
}
+56 -85
View File
@@ -1,62 +1,51 @@
import {
Button,
Callout,
FormGroup,
Input,
InputError,
Intent,
Splash,
} from '@vegaprotocol/ui-toolkit';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Outlet, useNavigate, useParams } from 'react-router-dom';
import { Outlet } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { EthConnectPrompt } from '../../components/eth-connect-prompt';
import { SplashLoader } from '../../components/splash-loader';
import { useTranches } from '../../lib/tranches/tranches-store';
import { useTranches } from '../../hooks/use-tranches';
import { useBalances } from '../../lib/balances/balances-store';
import RoutesConfig from '../routes';
interface FormFields {
address: string;
}
import {
initialRedemptionState,
RedemptionActionType,
redemptionReducer,
} from './redemption-reducer';
const RedemptionRouter = () => {
const { address } = useParams<{ address: string }>();
const navigate = useNavigate();
const { t } = useTranslation();
const validatePubkey = useCallback(
(value: string) => {
if (!value.startsWith('0x')) {
return t('Address must begin with 0x');
} else if (value.length !== 42) {
return t('Pubkey must be 42 characters in length');
} else if (Number.isNaN(+value)) {
return t('Pubkey must be be valid hex');
}
return true;
},
[t]
const [state, dispatch] = React.useReducer(
redemptionReducer,
initialRedemptionState
);
const { trancheBalances } = useBalances();
const { account } = useWeb3React();
const { tranches, error, loading } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormFields>();
const { tranches, error, loading } = useTranches();
const onSubmit = useCallback(
(fields: FormFields) => {
navigate(`${RoutesConfig.REDEEM}/${fields.address}`);
},
[navigate]
);
React.useEffect(() => {
const run = (address: string) => {
const userTranches = tranches?.filter((t) =>
t.users.some(
({ address: a }) => a.toLowerCase() === address.toLowerCase()
)
);
if (userTranches) {
dispatch({
type: RedemptionActionType.SET_USER_TRANCHES,
userTranches,
});
}
};
if (account) {
run(account);
}
}, [account, tranches]);
if (error) {
return (
@@ -74,48 +63,30 @@ const RedemptionRouter = () => {
);
}
if (!address) {
if (!account) {
return (
<div className="max-w-md">
{!account ? (
<EthConnectPrompt />
) : (
<Button
fill={true}
variant="primary"
onClick={() => navigate(`${RoutesConfig.REDEEM}/${account}`)}
>
{t('View connected Eth Wallet')}
</Button>
)}
<p className="py-4 flex justify-center">{t('OR')}</p>
<form
onSubmit={handleSubmit(onSubmit)}
data-testid="view-connector-form"
>
<FormGroup label={'View Ethereum as user:'} labelFor="address">
<Input
{...register('address', {
required: t('Required'),
validate: validatePubkey,
})}
id="address"
data-testid="address"
type="text"
/>
{errors.address?.message && (
<InputError intent="danger">{errors.address.message}</InputError>
)}
</FormGroup>
<Button data-testid="connect" type="submit" fill={true}>
{t('View Ethereum user')}
</Button>
</form>
</div>
<EthConnectPrompt>
<p data-testid="eth-connect-prompt">
{t(
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas."
)}
</p>
</EthConnectPrompt>
);
}
return <Outlet />;
if (!trancheBalances.length) {
return (
<>
<Callout>
<p>{t('You have no VEGA tokens currently vesting.')}</p>
</Callout>
<Link to={RoutesConfig.SUPPLY}>{t('viewAllTranches')}</Link>
</>
);
}
return <Outlet context={{ state, account }} />;
};
export default RedemptionRouter;
@@ -33,10 +33,10 @@ export const TrancheItem = ({
}: TrancheItemProps) => {
const { t } = useTranslation();
const labelClasses =
'inline-block uppercase bg-white text-black py-1 px-2 font-mono';
'inline-block uppercase bg-white text-black py-4 px-8 font-mono';
return (
<section data-testid="tranche-item" className="mb-8">
<section data-testid="tranche-item" className="mb-40">
<div className="flex border-b">
{link ? (
<Link to={link}>
@@ -8,7 +8,6 @@ import { formatNumber } from '../../lib/format-number';
import Routes from '../routes';
import { TrancheItem } from './tranche-item';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
export interface TrancheTableProps {
tranche: {
@@ -23,7 +22,6 @@ export interface TrancheTableProps {
totalLocked: BigNumber;
onClick: () => void;
disabled?: boolean;
address: string | null;
}
export const Tranche0Table = ({
@@ -67,11 +65,9 @@ export const TrancheTable = ({
totalVested,
totalLocked,
disabled = false,
address,
}: TrancheTableProps) => {
const { t } = useTranslation();
const total = vested.plus(locked);
const { account: connectedAddress } = useWeb3React();
const trancheFullyLocked =
tranche.tranche_start.getTime() > new Date().getTime();
const totalAllTranches = totalVested.plus(totalLocked);
@@ -97,20 +93,13 @@ export const TrancheTable = ({
amount: reduceAmount,
}}
components={{
stakeLink: <Link className="underline" to={Routes.VALIDATORS} />,
disassociateLink: (
<Link className="underline" to={Routes.DISASSOCIATE} />
),
stakeLink: <Link to={`/staking`} />,
disassociateLink: <Link to={`/staking/disassociate`} />,
}}
/>
</div>
);
} else if (
!trancheFullyLocked &&
redeemable &&
connectedAddress &&
address === connectedAddress
) {
} else if (!trancheFullyLocked && redeemable) {
message = (
<Button onClick={onClick} disabled={disabled}>
{t('Redeem unlocked VEGA from tranche {{id}}', {
@@ -1,7 +1,7 @@
import { useBalances } from '../../../lib/balances/balances-store';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link, useParams } from 'react-router-dom';
import { Link, useParams, useOutletContext } from 'react-router-dom';
import { TransactionCallout } from '../../../components/transaction-callout';
import { useContracts } from '../../../contexts/contracts/contracts-context';
@@ -9,36 +9,32 @@ import {
TransactionActionType,
TxState,
} from '../../../hooks/transaction-reducer';
import { useGetUserTrancheBalances } from '../../../hooks/use-get-user-tranche-balances';
import { useRefreshBalances } from '../../../hooks/use-refresh-balances';
import { useTransaction } from '../../../hooks/use-transaction';
import { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number';
import Routes from '../../routes';
import type { RedemptionState } from '../redemption-reducer';
import { TrancheTable } from '../tranche-table';
import { useTranches } from '../../../lib/tranches/tranches-store';
import { useWeb3React } from '@web3-react/core';
import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
import { useUserTrancheBalances } from '../hooks';
import { useAppState } from '../../../contexts/app-state/app-state-context';
export const RedeemFromTranche = () => {
const { account: address } = useWeb3React();
const { state, address } = useOutletContext<{
state: RedemptionState;
address: string;
}>();
const { vesting } = useContracts();
const { t } = useTranslation();
const {
appState: { decimals },
} = useAppState();
const { lien, totalVestedBalance, totalLockedBalance } = useBalances();
const refreshBalances = useRefreshBalances(address || '');
const { tranches, getTranches } = useTranches((state) => ({
tranches: state.tranches,
getTranches: state.getTranches,
}));
const { lien, totalVestedBalance, trancheBalances, totalLockedBalance } =
useBalances();
const refreshBalances = useRefreshBalances(address);
const getUserTrancheBalances = useGetUserTrancheBalances(address, vesting);
const { id } = useParams<{ id: string }>();
const numberId = Number(id);
const { userTranches } = state;
const tranche = React.useMemo(
() => tranches?.find(({ tranche_id }) => tranche_id === numberId) || null,
[numberId, tranches]
() => userTranches.find(({ tranche_id }) => tranche_id === numberId),
[numberId, userTranches]
);
const {
state: txState,
@@ -46,7 +42,7 @@ export const RedeemFromTranche = () => {
dispatch: txDispatch,
} = useTransaction(() => vesting.withdraw_from_tranche(numberId));
const { token } = useContracts();
const trancheBalances = useUserTrancheBalances(address || '');
const redeemedAmount = React.useMemo(() => {
return (
trancheBalances.find(({ id: bId }) => bId.toString() === id?.toString())
@@ -59,10 +55,10 @@ export const RedeemFromTranche = () => {
// If the claim has been committed refetch the new VEGA balance
React.useEffect(() => {
if (txState.txState === TxState.Complete && address) {
getUserTrancheBalances();
refreshBalances();
getTranches(decimals);
}
}, [address, decimals, getTranches, refreshBalances, txState.txState]);
}, [address, getUserTrancheBalances, refreshBalances, txState.txState]);
const trancheBalance = React.useMemo(() => {
return trancheBalances.find(
@@ -70,10 +66,6 @@ export const RedeemFromTranche = () => {
);
}, [id, trancheBalances]);
if (!address) {
return <EthConnectPrompt />;
}
if (
!tranche ||
tranche.total_removed.isEqualTo(tranche.total_added) ||
@@ -155,7 +147,6 @@ export const RedeemFromTranche = () => {
locked={trancheBalance.locked}
vested={trancheBalance.vested}
onClick={perform}
address={address || ''}
/>
)}
</section>
+6 -11
View File
@@ -300,17 +300,12 @@ const routerConfig = [
element: <LazyRedemption name="Vesting" />,
children: [
{
path: ':address',
children: [
{
index: true,
element: <LazyRedemptionIndex />,
},
{
path: ':id',
element: <LazyRedemptionTranche />,
},
],
index: true,
element: <LazyRedemptionIndex />,
},
{
path: ':id',
element: <LazyRedemptionTranche />,
},
],
},
@@ -16,11 +16,10 @@ export const StakingWalletsContainer = ({
if (!account) {
return (
<>
<EthConnectPrompt>
<p>{t('associateInfo1')}</p>
<p>{t('associateInfo2')}</p>
<EthConnectPrompt />
</>
</EthConnectPrompt>
);
}
@@ -363,7 +363,6 @@ export const ConsensusValidatorsTable = ({
customThemeParams={NODE_LIST_GRID_STYLES}
getRowHeight={(params: RowHeightParams) => getRowHeight(params)}
defaultColDef={defaultColDef}
tooltipShowDelay={0}
animateRows={true}
suppressCellFocus={true}
overlayNoRowsTemplate={t('noValidators')}
@@ -59,6 +59,8 @@ export const defaultColDef = {
comparator: (a: string, b: string) => parseFloat(a) - parseFloat(b),
cellStyle: { margin: '10px 0', padding: '0 12px' },
tooltipComponent: TooltipCellComponent,
tooltipShowDelay: 0,
tooltipHideDelay: 0,
};
interface ValidatorRendererProps {
@@ -236,7 +236,6 @@ export const StandbyPendingValidatorsTable = ({
customThemeParams={NODE_LIST_GRID_STYLES}
rowHeight={52}
defaultColDef={defaultColDef}
tooltipShowDelay={0}
animateRows={true}
suppressCellFocus={true}
overlayNoRowsTemplate={t('noValidators')}
@@ -1,145 +0,0 @@
import { render } from '@testing-library/react';
import { AppStateProvider } from '../../../contexts/app-state/app-state-provider';
import { ValidatorTable } from './validator-table';
import { ValidatorStatus } from '@vegaprotocol/types';
import countryData from '../../../components/country-selector/country-data';
import { formatNumber, toBigNum } from '@vegaprotocol/react-helpers';
const mockNode = {
id: 'bb1822715aa86ce0e205aa4c78e9b71cdeaec94596ce72d366f0d50589eb1bf5',
name: 'Marvin',
pubkey: 'f45085a3bbce4e8197910448a0f22e7d5e79f8234336053bc11b177b0d70e785',
infoUrl: 'https://en.wikipedia.org/wiki/Marvin_the_Paranoid_Android',
location: 'AQ',
ethereumAddress: '0x6ae2ff81b4a00f2edbed1fe3551ee0e3d81aa4f4',
stakedByOperator: '3000000000000000000000',
stakedByDelegates: '1280000000000000000',
stakedTotal: '3001280000000000000000',
pendingStake: '0',
epochData: null,
rankingScore: {
rankingScore: '0.3999466965166174',
stakeScore: '0.1999733482583087',
performanceScore: '1',
votingPower: '2000',
status: ValidatorStatus.VALIDATOR_NODE_STATUS_TENDERMINT,
},
};
const mockStakedTotal = '15008.4';
const decimals = 18;
const renderComponent = () =>
render(
<AppStateProvider>
<ValidatorTable node={mockNode} stakedTotal={mockStakedTotal} />
</AppStateProvider>
);
describe('ValidatorTable', () => {
it('should render successfully', () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
});
it('should render a link to the staking guide', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('validator-table-staking-guide-link')).toBeTruthy();
});
it('should render the correct node id', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('validator-id')).toHaveTextContent(mockNode.id);
});
it('should render the validator description url', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('validator-description-url')).toHaveAttribute(
'href',
mockNode.infoUrl
);
});
it('should render the correct validator status', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('validator-status')).toHaveTextContent('Consensus');
});
it('should render a link to the validator forum', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('validator-forum-link')).toBeTruthy();
});
it('should render the pubkey', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('validator-public-key')).toHaveTextContent(
mockNode.pubkey
);
});
it('should render the server location', () => {
const location = countryData.find((c) => c.code === mockNode.location);
const { getByTestId } = renderComponent();
expect(getByTestId('validator-server-location')).toHaveTextContent(
// @ts-ignore - location is not null
location.name
);
});
it('should render the ethereum address', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('validator-eth-address')).toHaveTextContent(
mockNode.ethereumAddress
);
});
it('should render the staked by operator', () => {
const stakedByOperator = formatNumber(
toBigNum(mockNode.stakedByOperator, decimals)
);
const { getByTestId } = renderComponent();
expect(getByTestId('staked-by-operator')).toHaveTextContent(
stakedByOperator
);
});
it('should render the staked by delegates', () => {
const stakedByDelegates = formatNumber(
toBigNum(mockNode.stakedByDelegates, decimals)
);
const { getByTestId } = renderComponent();
expect(getByTestId('staked-by-delegates')).toHaveTextContent(
stakedByDelegates
);
});
it('should render the total stake', () => {
const stakedTotal = formatNumber(toBigNum(mockNode.stakedTotal, decimals));
const { getByTestId } = renderComponent();
expect(getByTestId('total-stake')).toHaveTextContent(stakedTotal);
});
it('should render the pending stake', () => {
const pendingStake = formatNumber(
toBigNum(mockNode.pendingStake, decimals)
);
const { getByTestId } = renderComponent();
expect(getByTestId('pending-stake')).toHaveTextContent(pendingStake);
});
it('should render the values calculated by the helper functions', () => {
// these functions are all tested in shared.spec.ts
const { getByTestId } = renderComponent();
expect(getByTestId('stake-percentage')).toBeInTheDocument();
expect(getByTestId('overstaking-penalty')).toBeInTheDocument();
expect(getByTestId('performance-penalty')).toBeInTheDocument();
expect(getByTestId('total-penalties')).toBeInTheDocument();
expect(getByTestId('unnormalised-voting-power')).toBeInTheDocument();
expect(getByTestId('normalised-voting-power')).toBeInTheDocument();
});
});
@@ -1,25 +1,19 @@
import React, { useMemo } from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import countryData from '../../../components/country-selector/country-data';
import { Link as UTLink, Link } from '@vegaprotocol/ui-toolkit';
import { useEnvironment } from '@vegaprotocol/environment';
import {
createDocsLinks,
ExternalLinks,
toBigNum,
} from '@vegaprotocol/react-helpers';
import * as Schema from '@vegaprotocol/types';
import {
Link as UTLink,
Link,
Tooltip,
KeyValueTable,
KeyValueTableRow,
RoundedWrapper,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number';
import { ExternalLinks, toBigNum } from '@vegaprotocol/react-helpers';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import countryData from '../../../components/country-selector/country-data';
import * as Schema from '@vegaprotocol/types';
import { SubHeading } from '../../../components/heading';
import {
getLastEpochScoreAndPerformance,
@@ -29,7 +23,6 @@ import {
getPerformancePenalty,
getTotalPenalties,
getUnnormalisedVotingPower,
getStakePercentage,
} from '../shared';
import type { ReactNode } from 'react';
import type { StakingNodeFieldsFragment } from './__generated__/Staking';
@@ -69,7 +62,7 @@ export const ValidatorTable = ({
stakedTotal,
previousEpochData,
}: ValidatorTableProps) => {
const { ETHERSCAN_URL, VEGA_DOCS_URL } = useEnvironment();
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
const {
appState: { decimals },
@@ -88,7 +81,10 @@ export const ValidatorTable = ({
node.stakedTotal
);
const stakePercentage = getStakePercentage(total, stakedOnNode);
const stakePercentage =
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
? '-'
: stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%';
const totalPenaltiesAmount = getTotalPenalties(
rawValidatorScore,
@@ -98,207 +94,158 @@ export const ValidatorTable = ({
);
return (
<>
<p className="mb-12">
{t('validatorFormIntro')}{' '}
{VEGA_DOCS_URL && (
<ExternalLink
href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
target="_blank"
data-testid="validator-table-staking-guide-link"
className="text-white"
>
{t('readMoreValidatorForm')}
</ExternalLink>
)}
</p>
<div className="my-12" data-testid="validator-table">
<SubHeading title={t('profile')} />
<RoundedWrapper>
<KeyValueTable data-testid="validator-table-profile">
<KeyValueTableRow>
<span>{t('id')}</span>
<ValidatorTableCell dataTestId="validator-id">
{node.id}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('ABOUT THIS VALIDATOR')}</span>
<span>
<a href={node.infoUrl}>{node.infoUrl}</a>
</span>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>
<strong>{t('STATUS')}</strong>
</span>
<span data-testid="validator-status">
<strong>
{t(statusTranslationKey(node.rankingScore.status))}
</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<div className="my-12" data-testid="validator-table">
<SubHeading title={t('profile')} />
<RoundedWrapper>
<KeyValueTable data-testid="validator-table-profile">
<KeyValueTableRow>
<span>{t('id')}</span>
<ValidatorTableCell dataTestId="validator-id">
{node.id}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('ABOUT THIS VALIDATOR')}</span>
<Tooltip description={t('AboutThisValidatorDescription')}>
<a data-testid="validator-description-url" href={node.infoUrl}>
{node.infoUrl}
</a>
</Tooltip>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>
<strong>{t('STATUS')}</strong>
</span>
<Tooltip description={t('ValidatorStatusDescription')}>
<span data-testid="validator-status">
<strong>
{t(statusTranslationKey(node.rankingScore.status))}
</strong>
</span>
</Tooltip>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<div className="mb-10">
{t('validatorTableIntro')}{' '}
<UTLink
href={ExternalLinks.VALIDATOR_FORUM}
target="_blank"
data-testid="validator-forum-link"
>
{t('onTheForum')}
</UTLink>
</div>
<SubHeading title={t('ADDRESS')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-address">
<KeyValueTableRow>
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
<ValidatorTableCell dataTestId="validator-public-key">
{node.pubkey}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('SERVER LOCATION')}</span>
<ValidatorTableCell dataTestId="validator-server-location">
{countryData.find((c) => c.code === node.location)?.name ||
t('not available')}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>{t('ETHEREUM ADDRESS')}</span>
<span data-testid="validator-eth-address">
<Link
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
target="_blank"
>
{node.ethereumAddress}
</Link>
</span>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<SubHeading title={t('STAKE')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-stake">
<KeyValueTableRow>
<span>{t('STAKED BY OPERATOR')}</span>
<Tooltip description={t('StakedByOperatorDescription')}>
<span data-testid="staked-by-operator">
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
</span>
</Tooltip>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STAKED BY DELEGATES')}</span>
<Tooltip description={t('StakedByDelegatesDescription')}>
<span data-testid="staked-by-delegates">
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
</span>
</Tooltip>
</KeyValueTableRow>
<KeyValueTableRow>
<span>
<strong>{t('TOTAL STAKE')}</strong>
</span>
<span data-testid="total-stake">
<strong>
{formatNumber(toBigNum(node.stakedTotal, decimals))}
</strong>
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PENDING STAKE')}</span>
<Tooltip description={t('PendingStakeDescription')}>
<span data-testid="pending-stake">
{formatNumber(toBigNum(node.pendingStake, decimals))}
</span>
</Tooltip>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>{t('STAKE SHARE')}</span>
<Tooltip description={t('StakeShareDescription')}>
<span data-testid="stake-percentage">{stakePercentage}</span>
</Tooltip>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<SubHeading title={t('PENALTIES')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-penalties">
<KeyValueTableRow>
<span>{t('OVERSTAKED PENALTY')}</span>
<Tooltip description={t('OverstakedPenaltyDescription')}>
<span data-testid="overstaking-penalty">
{getOverstakingPenalty(overstakedAmount, node.stakedTotal)}
</span>
</Tooltip>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PERFORMANCE PENALTY')}</span>
<Tooltip description={t('PerformancePenaltyDescription')}>
<span data-testid="performance-penalty">
{getPerformancePenalty(performanceScore)}
</span>
</Tooltip>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>
<strong>{t('TOTAL PENALTIES')}</strong>
</span>
<span data-testid="total-penalties">
<strong>{totalPenaltiesAmount}</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<SubHeading title={t('VOTING POWER')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-voting-power">
<KeyValueTableRow>
<span>{t('UNNORMALISED VOTING POWER')}</span>
<Tooltip description={t('UnnormalisedVotingPowerDescription')}>
<span data-testid="unnormalised-voting-power">
{getUnnormalisedVotingPower(rawValidatorScore)}
</span>
</Tooltip>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>
<strong>{t('NORMALISED VOTING POWER')}</strong>
</span>
<Tooltip description={t('NormalisedVotingPowerDescription')}>
<strong data-testid="normalised-voting-power">
{getNormalisedVotingPower(node.rankingScore.votingPower)}
</strong>
</Tooltip>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<div className="mb-10">
{t('validatorTableIntro')}{' '}
<UTLink
href={ExternalLinks.VALIDATOR_FORUM}
target="_blank"
data-testid="validator-forum-link"
>
{t('onTheForum')}
</UTLink>
</div>
</>
<SubHeading title={t('ADDRESS')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-address">
<KeyValueTableRow>
<span>{t('VEGA ADDRESS / PUBLIC KEY')}</span>
<ValidatorTableCell dataTestId="validator-public-key">
{node.pubkey}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('SERVER LOCATION')}</span>
<ValidatorTableCell>
{countryData.find((c) => c.code === node.location)?.name ||
t('not available')}
</ValidatorTableCell>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>{t('ETHEREUM ADDRESS')}</span>
<span>
<Link
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/address/${node.ethereumAddress}`}
target="_blank"
>
{node.ethereumAddress}
</Link>
</span>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<SubHeading title={t('STAKE')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-stake">
<KeyValueTableRow>
<span>{t('STAKED BY OPERATOR')}</span>
<span data-testid="staked-by-operator">
{formatNumber(toBigNum(node.stakedByOperator, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('STAKED BY DELEGATES')}</span>
<span data-testid="staked-by-delegates">
{formatNumber(toBigNum(node.stakedByDelegates, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>
<strong>{t('TOTAL STAKE')}</strong>
</span>
<span data-testid="total-stake">
<strong>
{formatNumber(toBigNum(node.stakedTotal, decimals))}
</strong>
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PENDING STAKE')}</span>
<span data-testid="pending-stake">
{formatNumber(toBigNum(node.pendingStake, decimals))}
</span>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>{t('STAKE SHARE')}</span>
<span data-testid="stake-percentage">{stakePercentage}</span>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<SubHeading title={t('PENALTIES')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-penalties">
<KeyValueTableRow>
<span>{t('OVERSTAKED PENALTY')}</span>
<span>
{getOverstakingPenalty(overstakedAmount, node.stakedTotal)}
</span>
</KeyValueTableRow>
<KeyValueTableRow>
<span>{t('PERFORMANCE PENALTY')}</span>
<span>{getPerformancePenalty(performanceScore)}</span>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>
<strong>{t('TOTAL PENALTIES')}</strong>
</span>
<span>
<strong>{totalPenaltiesAmount}</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
<SubHeading title={t('VOTING POWER')} />
<RoundedWrapper marginBottomLarge={true}>
<KeyValueTable data-testid="validator-table-voting-power">
<KeyValueTableRow>
<span>{t('UNNORMALISED VOTING POWER')}</span>
<span>{getUnnormalisedVotingPower(rawValidatorScore)}</span>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<span>
<strong>{t('NORMALISED VOTING POWER')}</strong>
</span>
<span>
<strong>
{getNormalisedVotingPower(node.rankingScore.votingPower)}
</strong>
</span>
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
</div>
);
};
@@ -8,7 +8,6 @@ import {
getFormattedPerformanceScore,
getPerformancePenalty,
getTotalPenalties,
getStakePercentage,
} from './shared';
describe('getLastEpochScoreAndPerformance', () => {
@@ -140,23 +139,3 @@ describe('getTotalPenalties', () => {
expect(getTotalPenalties('0.25', '0.5', '1000', '10000')).toEqual('0.00%');
});
});
describe('getStakePercentage', () => {
it('should return the stake percentage', () => {
expect(
getStakePercentage(new BigNumber('1000'), new BigNumber('100'))
).toEqual('10%');
expect(
getStakePercentage(new BigNumber('1000'), new BigNumber('500'))
).toEqual('50%');
expect(
getStakePercentage(new BigNumber('1000'), new BigNumber('257.5'))
).toEqual('25.75%');
});
it('should return "0%" if the total stake is 0', () => {
expect(getStakePercentage(new BigNumber('0'), new BigNumber('0'))).toEqual(
'0%'
);
});
});
-5
View File
@@ -95,8 +95,3 @@ export const getTotalPenalties = (
2
);
};
export const getStakePercentage = (total: BigNumber, stakedOnNode: BigNumber) =>
total.isEqualTo(0) || stakedOnNode.isEqualTo(0)
? '0%'
: stakedOnNode.dividedBy(total).times(100).dp(2).toString() + '%';
@@ -1,34 +1,35 @@
import { BigNumber } from '../../../lib/bignumber';
import { sumCirculatingTokens } from './token-details-circulating';
import type { Tranche } from '@vegaprotocol/smart-contracts';
it('It sums some easy tranches correctly', () => {
const tranches = [
{ total_added: new BigNumber(100), locked_amount: new BigNumber(0) },
{ total_added: new BigNumber(100), locked_amount: new BigNumber(0) },
{ total_added: new BigNumber(100), locked_amount: new BigNumber(0) },
const tranches: Partial<Tranche>[] = [
{ total_added: new BigNumber('100'), locked_amount: new BigNumber(0) },
{ total_added: new BigNumber('100'), locked_amount: new BigNumber(0) },
{ total_added: new BigNumber('100'), locked_amount: new BigNumber(0) },
];
const result = sumCirculatingTokens(tranches);
const result = sumCirculatingTokens(tranches as Tranche[]);
expect(result.toString()).toEqual('300');
});
it('It sums some longer tranches correctly', () => {
const tranches = [
const tranches: Partial<Tranche>[] = [
{
total_added: new BigNumber(10000000000),
total_added: new BigNumber('10000000000'),
locked_amount: new BigNumber(0),
},
{ total_added: new BigNumber(20), locked_amount: new BigNumber(0) },
{ total_added: new BigNumber(3000), locked_amount: new BigNumber(3020) },
{ total_added: new BigNumber('20'), locked_amount: new BigNumber(0) },
{ total_added: new BigNumber('3000'), locked_amount: new BigNumber(3020) },
];
const result = sumCirculatingTokens(tranches);
const result = sumCirculatingTokens(tranches as Tranche[]);
expect(result.toString()).toEqual('10000000000');
});
it('Handles null tranche array', () => {
const tranches = null;
const result = sumCirculatingTokens(tranches);
const result = sumCirculatingTokens(tranches as unknown as Tranche[]);
expect(result.toString()).toEqual('0');
});
@@ -1,6 +1,6 @@
import { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number';
import type { Tranche } from '../../../lib/tranches/tranches-store';
import type { Tranche } from '@vegaprotocol/smart-contracts';
/**
* Add together the circulating tokens from all tranches
@@ -9,9 +9,7 @@ import type { Tranche } from '../../../lib/tranches/tranches-store';
* @param decimals decimal places for the formatted result
* @return The total circulating tokens from all tranches
*/
export function sumCirculatingTokens(
tranches: { total_added: BigNumber; locked_amount: BigNumber }[] | null
): BigNumber {
export function sumCirculatingTokens(tranches: Tranche[] | null): BigNumber {
let totalCirculating: BigNumber = new BigNumber(0);
tranches?.forEach(
@@ -7,7 +7,7 @@ import {
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { useTranches } from '../../../lib/tranches/tranches-store';
import { useTranches } from '../../../hooks/use-tranches';
import type { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number';
import { TokenDetailsCirculating } from './token-details-circulating';
@@ -25,11 +25,7 @@ export const TokenDetails = ({
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
const { tranches, loading, error } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
const { tranches, loading, error } = useTranches();
const { config } = useEthereumConfig();
const { token } = useContracts();
+2 -6
View File
@@ -4,18 +4,14 @@ import { Outlet } from 'react-router-dom';
import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { useTranches } from '../../hooks/use-tranches';
import type { RouteChildProps } from '..';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useTranches } from '../../lib/tranches/tranches-store';
const TrancheRouter = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const { tranches, error, loading } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
const { tranches, error, loading } = useTranches();
if (!tranches || loading) {
return (
+55 -42
View File
@@ -1,32 +1,48 @@
import {
KeyValueTable,
KeyValueTableRow,
Link,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { Link as RouterLink } from 'react-router-dom';
import type { Tranche as ITranche } from '@vegaprotocol/smart-contracts';
import { Link } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
import { Navigate } from 'react-router-dom';
import { formatNumber } from '@vegaprotocol/react-helpers';
import { useOutletContext } from 'react-router-dom';
import { useEnvironment } from '@vegaprotocol/environment';
import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number';
import { TrancheItem } from '../redemption/tranche-item';
import Routes from '../routes';
import { TrancheLabel } from './tranche-label';
import { useTranches } from '../../lib/tranches/tranches-store';
const TrancheProgressContents = ({
children,
}: {
children: React.ReactNode;
}) => (
<div className="flex justify-between gap-4 font-mono py-2 px-4">
{children}
</div>
);
export const Tranche = () => {
const tranches = useTranches((state) => state.tranches);
const tranches = useOutletContext<ITranche[]>();
const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation();
const { trancheId } = useParams<{ trancheId: string; address: string }>();
const { trancheId } = useParams<{ trancheId: string }>();
const { chainId } = useWeb3React();
const tranche = tranches?.find(
const tranche = tranches.find(
(tranche) => trancheId && parseInt(trancheId) === tranche.tranche_id
);
const lockedData = React.useMemo(() => {
if (!tranche) return null;
const locked = tranche.locked_amount.div(tranche.total_added);
return {
locked,
unlocked: new BigNumber(1).minus(locked),
};
}, [tranche]);
if (!tranche) {
return <Navigate to={Routes.NOT_FOUND} />;
}
@@ -51,36 +67,33 @@ export const Tranche = () => {
</div>
<h2>{t('Holders')}</h2>
{tranche.users.length ? (
<RoundedWrapper>
<KeyValueTable>
<KeyValueTableRow>
<h1>{t('Ethereum Address')}</h1>
<h1>{t('View tranche data')}</h1>
</KeyValueTableRow>
{tranche.users.map((user) => (
<KeyValueTableRow key={user}>
{
<Link
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/address/${user}`}
target="_blank"
>
{user}
</Link>
}
{
<RouterLink
className="underline"
title={t('View vesting information')}
to={`${Routes.REDEEM}/${user}`}
>
{t('View vesting information')}
</RouterLink>
}
</KeyValueTableRow>
))}
</KeyValueTable>
</RoundedWrapper>
<ul role="list">
{tranche.users.map((user, i) => {
const unlocked = user.remaining_tokens.times(
lockedData?.unlocked || 0
);
const locked = user.remaining_tokens.times(lockedData?.locked || 0);
return (
<li className="pb-4" key={i}>
<Link
title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/tx/${user.address}`}
target="_blank"
>
{user.address}
</Link>
<TrancheProgressContents>
<span>{t('Locked')}</span>
<span>{t('Unlocked')}</span>
</TrancheProgressContents>
<TrancheProgressContents>
<span>{formatNumber(locked)}</span>
<span>{formatNumber(unlocked)}</span>
</TrancheProgressContents>
</li>
);
})}
</ul>
) : (
<p>{t('No users')}</p>
)}
+16 -15
View File
@@ -1,3 +1,5 @@
import { useOutletContext } from 'react-router-dom';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -8,8 +10,6 @@ import { TrancheLabel } from './tranche-label';
import { VestingChart } from './vesting-chart';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { useEthereumConfig } from '@vegaprotocol/web3';
import type { Tranche } from '../../lib/tranches/tranches-store';
import { useTranches } from '../../lib/tranches/tranches-store';
const trancheMinimum = 10;
@@ -17,7 +17,7 @@ const shouldShowTranche = (t: Tranche) =>
!t.total_added.isLessThanOrEqualTo(trancheMinimum);
export const Tranches = () => {
const tranches = useTranches((state) => state.tranches);
const tranches = useOutletContext<Tranche[]>();
const [showAll, setShowAll] = React.useState<boolean>(false);
const { t } = useTranslation();
const { chainId } = useWeb3React();
@@ -38,24 +38,25 @@ export const Tranches = () => {
<ul role="list">
{(showAll ? tranches : filteredTranches).map((tranche) => {
return (
<TrancheItem
key={tranche.tranche_id}
link={`${tranche.tranche_id}`}
tranche={tranche}
locked={tranche.locked_amount}
unlocked={tranche.total_added.minus(tranche.locked_amount)}
total={tranche.total_added}
secondaryHeader={
<TrancheLabel chainId={chainId} id={tranche.tranche_id} />
}
/>
<React.Fragment key={tranche.tranche_id}>
<TrancheItem
link={`${tranche.tranche_id}`}
tranche={tranche}
locked={tranche.locked_amount}
unlocked={tranche.total_added.minus(tranche.locked_amount)}
total={tranche.total_added}
secondaryHeader={
<TrancheLabel chainId={chainId} id={tranche.tranche_id} />
}
/>
</React.Fragment>
);
})}
</ul>
) : (
<p>{t('No tranches')}</p>
)}
<section className="text-center mt-4">
<section className="text-center mt-32">
<ButtonLink onClick={() => setShowAll(!showAll)}>
{showAll
? t(
-1
View File
@@ -22,7 +22,6 @@ module.exports = defineConfig({
viewportHeight: 900,
responseTimeout: 50000,
requestTimeout: 20000,
retries: 2,
},
env: {
ETHERSCAN_URL: 'https://sepolia.etherscan.io',
@@ -72,11 +72,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('approve-warning').should(
'contain.text',
`Deposits of ${btcSymbol} not approved`
);
cy.getByTestId('deposit-submit').click();
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('10');
@@ -411,7 +407,7 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.getByTestId('deposit-submit').click();
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('10000');
+6 -132
View File
@@ -1,5 +1,3 @@
import { removeDecimal } from '@vegaprotocol/cypress';
import { ethers } from 'ethers';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import { selectAsset } from '../support/helpers';
@@ -11,7 +9,7 @@ const formFieldError = 'input-error-text';
const ASSET_EURO = 1;
describe('deposit form validation', { tags: '@smoke' }, () => {
function openDepositForm() {
before(() => {
cy.mockWeb3Provider();
cy.mockSubscription();
cy.mockTradingPage();
@@ -22,14 +20,10 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
cy.getByTestId('deposit-button').click();
cy.wait('@Assets');
connectEthereumWallet('MetaMask');
}
before(() => {
openDepositForm();
cy.getByTestId('deposit-submit').click();
});
it('handles empty fields', () => {
cy.getByTestId('deposit-submit').click();
cy.getByTestId(formFieldError).should('contain.text', 'Required');
cy.getByTestId(formFieldError).should('have.length', 2);
});
@@ -50,13 +44,6 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
});
it('invalid amount', () => {
mockWeb3DepositCalls({
allowance: '1000',
depositLifetimeLimit: '1000',
balance: '800',
deposited: '0',
dps: 5,
});
// Deposit amount smaller than minimum viable for selected asset
// Select an amount so that we have a known decimal places value to work with
selectAsset(ASSET_EURO);
@@ -69,126 +56,13 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
});
it('insufficient funds', () => {
mockWeb3DepositCalls({
allowance: '1000',
depositLifetimeLimit: '1000',
balance: '800',
deposited: '0',
dps: 5,
});
// 1001-DEPO-005
// Deposit amount is valid, but less than approved. This will always be the case because our
// CI wallet wont have approved any assets
cy.get(amountField)
.clear()
.type('850')
.type('100')
.next(`[data-testid="${formFieldError}"]`)
.should('have.text', 'Insufficient amount in Ethereum wallet');
});
});
describe('deposit actions', { tags: '@smoke' }, () => {
before(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/');
cy.wait('@MarketsCandles');
cy.getByTestId('dialog-close').click();
});
it('Deposit to trade is visble', () => {
cy.getByTestId('Collateral').click();
cy.contains('[data-testid="deposit"]', 'Deposit to trade').should(
'be.visible'
);
cy.contains('[data-testid="deposit"]', 'Deposit to trade').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('deposit-submit').should('be.visible');
});
});
function mockWeb3DepositCalls({
allowance,
depositLifetimeLimit,
balance,
deposited,
dps,
}: {
allowance: string;
depositLifetimeLimit: string;
balance: string;
deposited: string;
dps: number;
}) {
const assetContractAddress = '0x0158031158bb4df2ad02eaa31e8963e84ea978a4';
const collateralBridgeAddress = '0x7fe27d970bc8afc3b11cc8d9737bfb66b1efd799';
const toResult = (value: string, dps: number) => {
const rawValue = removeDecimal(value, dps);
return ethers.utils.hexZeroPad(
ethers.utils.hexlify(parseInt(rawValue)),
32
);
};
cy.intercept('POST', 'http://localhost:8545', (req) => {
// Mock chainId call
if (req.body.method === 'eth_chainId') {
req.alias = 'eth_chainId';
req.reply({
id: req.body.id,
jsonrpc: req.body.jsonrpc,
result: '0xaa36a7', // 11155111 for sepolia chain id
});
}
// Mock deposited amount
if (req.body.method === 'eth_getStorageAt') {
req.alias = 'eth_getStorageAt';
req.reply({
id: req.body.id,
jsonrpc: req.body.jsonrpc,
result: toResult(deposited, dps),
});
}
if (req.body.method === 'eth_call') {
// Mock approved amount for asset on collateral bridge
if (
req.body.params[0].to === assetContractAddress &&
req.body.params[0].data ===
'0xdd62ed3e000000000000000000000000ee7d375bcb50c26d52e1a4a472d8822a2a22d94f0000000000000000000000007fe27d970bc8afc3b11cc8d9737bfb66b1efd799'
) {
req.alias = 'eth_call_allowance';
req.reply({
id: req.body.id,
jsonrpc: req.body.jsonrpc,
result: toResult(allowance, dps),
});
}
// Mock balance of asset in Ethereum wallet
else if (
req.body.params[0].to === assetContractAddress &&
req.body.params[0].data ===
'0x70a08231000000000000000000000000ee7d375bcb50c26d52e1a4a472d8822a2a22d94f'
) {
req.alias = 'eth_call_balanceOf';
req.reply({
id: req.body.id,
jsonrpc: req.body.jsonrpc,
result: toResult(balance, dps),
});
}
// Mock deposit lifetime limit
else if (
req.body.params[0].to === collateralBridgeAddress &&
req.body.params[0].data ===
'0x354a897a0000000000000000000000000158031158bb4df2ad02eaa31e8963e84ea978a4'
) {
req.alias = 'eth_call_get_deposit_maximum'; // deposit lifetime limit
req.reply({
id: req.body.id,
jsonrpc: req.body.jsonrpc,
result: toResult(depositLifetimeLimit, dps),
});
}
}
});
}
@@ -29,7 +29,6 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(0, 'Mark Price', '0.05749');
validateMarketDataRow(1, 'Best Bid Price', '6.81765 ');
validateMarketDataRow(2, 'Best Offer Price', '6.81769 ');
validateMarketDataRow(3, 'Quote Unit', 'BTC');
});
it('market volume displayed', () => {
@@ -169,9 +168,9 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('liquidity price range displayed', () => {
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
validateMarketDataRow(1, 'Lowest Price', '0.05634 BTC');
validateMarketDataRow(2, 'Highest Price', '0.05864 BTC');
validateMarketDataRow(0, 'Liquidity Price Range', '2.00%');
validateMarketDataRow(1, 'LP Volume Min', '0.05634 tBTC');
validateMarketDataRow(2, 'LP Volume Max', '0.05864 tBTC');
});
it('oracle displayed', () => {
+4 -10
View File
@@ -4,7 +4,6 @@ import { marketsQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
const dialogCloseBtn = 'dialog-close';
const popoverTrigger = 'popover-trigger';
describe('markets table', { tags: '@smoke' }, () => {
beforeEach(() => {
@@ -174,13 +173,8 @@ describe('markets table', { tags: '@smoke' }, () => {
});
function openMarketDropDown() {
cy.getByTestId(dialogCloseBtn).then((button) => {
if (button.is(':visible')) {
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
cy.getByTestId(dialogCloseBtn).click();
}
cy.get('[data-testid^="ask-vol-"]').should('be.visible');
cy.getByTestId(popoverTrigger).click({ force: true });
cy.contains('Loading market data...').should('not.exist');
});
cy.getByTestId(dialogCloseBtn).should('be.visible');
cy.getByTestId(dialogCloseBtn).click();
cy.getByTestId('popover-trigger').click();
cy.contains('Loading market data...').should('not.exist');
}
@@ -1,12 +1,11 @@
import { mockConnectWallet } from '@vegaprotocol/cypress';
beforeEach(() => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.wait('@MarketsCandles');
cy.getByTestId('dialog-close').click();
});
@@ -20,11 +19,11 @@ describe('Desktop view', { tags: '@smoke' }, () => {
cy.getByTestId('navbar')
.find(`[data-testid="navbar-links"] a[data-testid=${link}]`)
.then((element) => {
cy.contains('Loading...').should('not.exist');
cy.wrap(element).click();
cy.wrap(element)
.get('span.absolute.md\\:h-1.w-full')
.should('exist');
cy.contains('No market').should('not.exist');
cy.location('hash').should('equal', hashes[index]);
});
});
@@ -70,6 +69,7 @@ describe('Mobile view', { tags: '@smoke' }, () => {
cy.getByTestId('button-menu-drawer').click();
cy.getByTestId('menu-drawer').within((el) => {
cy.wrap(el).getByTestId('Trading').click();
cy.contains('No market').should('not.exist');
cy.location('hash').should('equal', '#/markets/market-1');
});
});
+4 -35
View File
@@ -17,50 +17,19 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
});
it('can connect', () => {
// Mock authentication
cy.intercept('POST', 'https://wallet.testnet.vega.xyz/api/v1/auth/token', {
body: {
token: 'test-token',
},
});
// Mock getting keys from wallet
cy.intercept('GET', 'https://wallet.testnet.vega.xyz/api/v1/keys', {
body: {
keys: [
{
algorithm: {
name: 'algo',
version: 1,
},
index: 0,
meta: [],
pub: 'HOSTED_PUBKEY',
tainted: false,
},
],
},
});
cy.getByTestId(connectVegaBtn).click();
mockConnectWallet();
cy.contains('Connect Vega wallet');
cy.contains('Hosted Fairground wallet');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-hosted"]')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.getByTestId(form).find('#wallet').click().type('user');
cy.getByTestId(form).find('#passphrase').click().type('pass');
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
cy.wait('@walletReq');
cy.getByTestId(manageVegaBtn).should('exist');
});
it('doesnt connect with invalid credentials', () => {
// Mock incorrect username/password
cy.intercept('POST', 'https://wallet.testnet.vega.xyz/api/v1/auth/token', {
body: {
error: 'No wallet',
},
statusCode: 403, // 403 forbidden invalid crednetials
});
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-hosted"]')
@@ -71,7 +40,7 @@ describe('connect hosted wallet', { tags: '@smoke' }, () => {
cy.getByTestId('form-error').should('have.text', 'Invalid credentials');
});
it('doesnt connect with empty fields', () => {
it('doesnt connect with invalid fields', () => {
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-hosted"]')
@@ -3,14 +3,12 @@ query AccountHistory(
$assetId: ID!
$accountTypes: [AccountType!]
$dateRange: DateRange
$marketIds: [ID!]
) {
balanceChanges(
filter: {
partyIds: [$partyId]
accountTypes: $accountTypes
assetId: $assetId
marketIds: $marketIds
}
dateRange: $dateRange
) {
@@ -8,7 +8,6 @@ export type AccountHistoryQueryVariables = Types.Exact<{
assetId: Types.Scalars['ID'];
accountTypes?: Types.InputMaybe<Array<Types.AccountType> | Types.AccountType>;
dateRange?: Types.InputMaybe<Types.DateRange>;
marketIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
}>;
@@ -24,9 +23,9 @@ export type AccountsWithBalanceQuery = { __typename?: 'Query', balanceChanges: {
export const AccountHistoryDocument = gql`
query AccountHistory($partyId: ID!, $assetId: ID!, $accountTypes: [AccountType!], $dateRange: DateRange, $marketIds: [ID!]) {
query AccountHistory($partyId: ID!, $assetId: ID!, $accountTypes: [AccountType!], $dateRange: DateRange) {
balanceChanges(
filter: {partyIds: [$partyId], accountTypes: $accountTypes, assetId: $assetId, marketIds: $marketIds}
filter: {partyIds: [$partyId], accountTypes: $accountTypes, assetId: $assetId}
dateRange: $dateRange
) {
edges {
@@ -59,7 +58,6 @@ export const AccountHistoryDocument = gql`
* assetId: // value for 'assetId'
* accountTypes: // value for 'accountTypes'
* dateRange: // value for 'dateRange'
* marketIds: // value for 'marketIds'
* },
* });
*/
@@ -6,9 +6,8 @@ import {
} from '@vegaprotocol/react-helpers';
import { useVegaWallet } from '@vegaprotocol/wallet';
import compact from 'lodash/compact';
import uniqBy from 'lodash/uniqBy';
import type { ChangeEvent } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useMemo, useState } from 'react';
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
import * as Schema from '@vegaprotocol/types';
@@ -26,10 +25,8 @@ import {
import { AccountTypeMapping } from '@vegaprotocol/types';
import { PriceChart } from 'pennant';
import 'pennant/dist/style.css';
import type { Account } from '@vegaprotocol/accounts';
import { accountsDataProvider } from '@vegaprotocol/accounts';
import { accountsOnlyDataProvider } from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/react-helpers';
import type { Market } from '@vegaprotocol/market-list';
const DateRange = {
RANGE_1D: '1D',
@@ -100,7 +97,7 @@ const AccountHistoryManager = ({
);
const { data: accounts } = useDataProvider({
dataProvider: accountsDataProvider,
dataProvider: accountsOnlyDataProvider,
variables: variablesForOneTimeQuery,
skip: !pubKey,
});
@@ -121,39 +118,6 @@ const AccountHistoryManager = ({
const [range, setRange] = useState<typeof DateRange[keyof typeof DateRange]>(
DateRange.RANGE_1M
);
const [market, setMarket] = useState<Market | null>(null);
const marketFilterCb = useCallback(
(item: Market) =>
!asset?.id ||
item.tradableInstrument.instrument.product.settlementAsset.id ===
asset?.id,
[asset?.id]
);
const markets = useMemo<Market[] | null>(() => {
const arr =
accounts
?.filter((item: Account) => Boolean(item && item.market))
.map<Market>((item) => item.market as Market) ?? null;
return arr
? uniqBy(arr.filter(marketFilterCb), 'id').sort((a, b) =>
a.tradableInstrument.instrument.code.localeCompare(
b.tradableInstrument.instrument.code
)
)
: null;
}, [accounts, marketFilterCb]);
const resolveMarket = useCallback(
(m: Market) => {
setMarket(m);
const newAssetId =
m.tradableInstrument.instrument.product.settlementAsset.id;
const newAsset = assets.find((item) => item.id === newAssetId);
if ((!asset || (assets && newAssetId !== asset.id)) && newAsset) {
setAsset(newAsset);
}
},
[asset, assets]
);
const variables = useMemo(
() => ({
@@ -162,113 +126,62 @@ const AccountHistoryManager = ({
accountTypes: accountType ? [accountType] : undefined,
dateRange:
range === 'All' ? undefined : { start: calculateStartDate(range) },
marketIds: market?.id ? [market.id] : undefined,
}),
[pubKey, asset, accountType, range, market?.id]
[pubKey, asset, accountType, range]
);
const { data } = useAccountHistoryQuery({
variables,
skip: !asset || !pubKey,
});
const accountTypeMenu = useMemo(() => {
return (
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{accountType
? `${
AccountTypeMapping[
accountType as keyof typeof Schema.AccountType
]
} Account`
: t('Select account type')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{[
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
Schema.AccountType.ACCOUNT_TYPE_BOND,
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
].map((type) => (
<DropdownMenuItem
key={type}
onClick={() => setAccountType(type as Schema.AccountType)}
>
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}, [accountType]);
const assetsMenu = useMemo(() => {
return (
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{asset ? asset.symbol : t('Select asset')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{assets.map((a) => (
<DropdownMenuItem key={a.id} onClick={() => setAsset(a)}>
{a.symbol}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}, [assets, asset]);
const marketsMenu = useMemo(() => {
return accountType === Schema.AccountType.ACCOUNT_TYPE_MARGIN &&
markets?.length ? (
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{market
? market.tradableInstrument.instrument.code
: t('Select market')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{market && (
<DropdownMenuItem key="0" onClick={() => setMarket(null)}>
{t('All markets')}
</DropdownMenuItem>
)}
{markets?.map((m) => (
<DropdownMenuItem key={m.id} onClick={() => resolveMarket(m)}>
{m.tradableInstrument.instrument.code}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
) : null;
}, [markets, market, accountType, resolveMarket]);
useEffect(() => {
if (
accountType !== Schema.AccountType.ACCOUNT_TYPE_MARGIN ||
market?.tradableInstrument.instrument.product.settlementAsset.id !==
asset?.id
) {
setMarket(null);
}
}, [accountType, asset?.id, market]);
return (
<div className="h-full w-full flex flex-col gap-8">
<div className="w-full flex flex-col-reverse lg:flex-row items-start lg:items-center justify-between gap-4 px-2">
<div className="flex items-center gap-4 shrink-0">
<>
{accountTypeMenu}
{assetsMenu}
{marketsMenu}
</>
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{accountType
? `${
AccountTypeMapping[
accountType as keyof typeof Schema.AccountType
]
} Account`
: t('Select account type')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{[
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
Schema.AccountType.ACCOUNT_TYPE_BOND,
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
].map((type) => (
<DropdownMenuItem
key={type}
onClick={() => setAccountType(type as Schema.AccountType)}
>
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu
trigger={
<DropdownMenuTrigger>
{asset ? asset.symbol : t('Select asset')}
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{assets.map((a) => (
<DropdownMenuItem key={a.id} onClick={() => setAsset(a)}>
{a.symbol}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="pt-1 justify-items-end">
<Toggle
@@ -12,7 +12,7 @@ import type { ReactNode } from 'react';
import { AppFailure } from './app-failure';
import { Web3Provider } from './web3-provider';
export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
loading: () => <>Loading...</>,
});
@@ -123,7 +123,7 @@ export const MarketLiquiditySupplied = ({
{showMessage && (
<p className="mt-4">
{t(
'The market has sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.'
'The market is in an auction because there are no priced limit orders, which are required to deploy liquidity commitment pegged orders. This means the order book is empty on one or both sides.'
)}
</p>
)}
+2 -2
View File
@@ -33,7 +33,7 @@ import { Connectors } from '../lib/vega-connectors';
import { ViewingBanner } from '../components/viewing-banner';
import { Banner } from '../components/banner';
import classNames from 'classnames';
import { AppLoader, DynamicLoader } from '../components/app-loader';
import { AppLoader } from '../components/app-loader';
const DEFAULT_TITLE = t('Welcome to Vega trading!');
@@ -115,7 +115,7 @@ function VegaTradingApp(props: AppProps) {
// Prevent HashRouter from being server side rendered as it
// relies on presence of document object
if (status === 'default') {
return <DynamicLoader />;
return null;
}
return (
-2
View File
@@ -12,8 +12,6 @@ export default function Document() {
type="font/woff2"
crossOrigin="anonymous"
/>
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
<link
rel="icon"
type="image/x-icon"
-106
View File
@@ -1,106 +0,0 @@
.pre-loader {
display: flex;
width: 100%;
min-height: 100vh;
justify-content: center;
align-items: center;
}
.pre-loader .loader-item:nth-child(0) {
animation-delay: 0ms;
animation-direction: reverse;
}
.pre-loader .loader-item:first-child {
animation-delay: -0.2s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(2) {
animation-delay: 0.1s;
animation-direction: reverse;
}
.pre-loader .loader-item:nth-child(3) {
animation-delay: -0.15s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(4) {
animation-delay: 1s;
animation-direction: reverse;
}
.pre-loader .loader-item:nth-child(5) {
animation-delay: -0.25s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(6) {
animation-delay: 0.3s;
animation-direction: reverse;
}
.pre-loader .loader-item:nth-child(7) {
animation-delay: -1.05s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(8) {
animation-delay: 0.8s;
animation-direction: reverse;
}
.pre-loader .loader-item:nth-child(9) {
animation-delay: -0.9s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(10) {
animation-delay: 0.5s;
animation-direction: reverse;
}
.pre-loader .loader-item:nth-child(11) {
animation-delay: -2.75s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(12) {
animation-delay: 2.4s;
animation-direction: reverse;
}
.pre-loader .loader-item:nth-child(13) {
animation-delay: -0.65s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(14) {
animation-delay: 0.7s;
animation-direction: reverse;
}
.pre-loader .loader-item:nth-child(15) {
animation-delay: -3s;
animation-direction: alternate;
}
.pre-loader .loader-item:nth-child(16) {
animation-delay: 2.4s;
animation-direction: reverse;
}
.pre-loader .pre-loader-center {
align-items: center;
display: flex;
flex-direction: column;
}
.pre-loader .pre-loader-wrapper {
width: 50px;
height: 50px;
display: flex;
flex-wrap: wrap;
}
.pre-loader .loader-item {
width: 10px;
height: 10px;
background: #000;
animation: flickering 0.4s steps(2, jump-none) infinite alternate;
}
@keyframes flickering {
0% {
opacity: 1;
}
25% {
opacity: 1;
}
26% {
opacity: 0;
}
to {
opacity: 0;
}
}
@@ -58,7 +58,6 @@ export const AssetDetailsDialog = ({
const { data } = useAssetsDataProvider();
const asset = data?.find((a) => a.id === assetId);
const assetSymbol = asset?.symbol || '';
const content = asset ? (
<div className="my-2">
@@ -98,12 +97,6 @@ export const AssetDetailsDialog = ({
}}
>
{content}
<p className="text-sm mb-4">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit.',
[assetSymbol]
)}
</p>
<div className="w-1/4">
<Button
data-testid="close-asset-details-dialog"
@@ -48,7 +48,7 @@ export const DealTicketEstimates = ({
<DataTitle quoteName={quoteName}>{t('Est. Position Size')}</DataTitle>
<ValueTooltipRow
value={notionalSize}
description={constants.NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName || '')}
description={constants.NOTIONAL_SIZE_TOOLTIP_TEXT}
/>
</div>
)}
@@ -66,7 +66,7 @@ export const DealTicketEstimates = ({
<DataTitle quoteName={quoteName}>{t('Est. Margin')}</DataTitle>
<ValueTooltipRow
value={estMargin}
description={constants.EST_MARGIN_TOOLTIP_TEXT(quoteName || '')}
description={constants.EST_MARGIN_TOOLTIP_TEXT}
/>
</div>
)}
@@ -75,7 +75,7 @@ export const DealTicketEstimates = ({
<DataTitle quoteName={quoteName}>{t('Est. Close out')}</DataTitle>
<ValueTooltipRow
value={estCloseOut}
description={constants.EST_CLOSEOUT_TOOLTIP_TEXT(quoteName || '')}
description={constants.EST_CLOSEOUT_TOOLTIP_TEXT}
/>
</div>
)}
@@ -318,37 +318,33 @@ const SummaryMessage = memo(
}
if (!pubKey) {
return (
<div className="mb-4">
<Notification
testId={'deal-ticket-connect-wallet'}
intent={Intent.Warning}
message={
<p className="text-sm pb-2">
You need a{' '}
<ExternalLink href="https://vega.xyz/wallet">
Vega wallet
</ExternalLink>{' '}
with {assetSymbol} to start trading in this market.
</p>
}
buttonProps={{
text: t('Connect wallet'),
action: openVegaWalletDialog,
dataTestId: 'order-connect-wallet',
size: 'md',
}}
/>
</div>
<Notification
testId={'deal-ticket-connect-wallet'}
intent={Intent.Warning}
message={
<p className="text-sm pb-2">
You need a{' '}
<ExternalLink href="https://vega.xyz/wallet">
Vega wallet
</ExternalLink>{' '}
with {assetSymbol} to start trading in this market.
</p>
}
buttonProps={{
text: t('Connect wallet'),
action: openVegaWalletDialog,
dataTestId: 'order-connect-wallet',
size: 'md',
}}
/>
);
}
if (errorMessage === SummaryValidationType.NoCollateral) {
return (
<div className="mb-4">
<ZeroBalanceError
asset={market.tradableInstrument.instrument.product.settlementAsset}
onClickCollateral={onClickCollateral}
/>
</div>
<ZeroBalanceError
asset={market.tradableInstrument.instrument.product.settlementAsset}
onClickCollateral={onClickCollateral}
/>
);
}
@@ -367,11 +363,7 @@ const SummaryMessage = memo(
// If there is no blocking error but user doesn't have enough
// balance render the margin warning, but still allow submission
if (balanceError) {
return (
<div className="mb-4">
<MarginWarning balance={balance} margin={margin} asset={asset} />;
</div>
);
return <MarginWarning balance={balance} margin={margin} asset={asset} />;
}
// Show auction mode warning
@@ -383,15 +375,13 @@ const SummaryMessage = memo(
].includes(marketData.marketTradingMode)
) {
return (
<div className="mb-4">
<Notification
intent={Intent.Warning}
testId={'dealticket-warning-auction'}
message={t(
'Any orders placed now will not trade until the auction ends'
)}
/>
</div>
<Notification
intent={Intent.Warning}
testId={'dealticket-warning-auction'}
message={t(
'Any orders placed now will not trade until the auction ends'
)}
/>
);
}
@@ -9,7 +9,6 @@ import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks } from '@vegaprotocol/react-helpers';
import { compileGridData } from './compile-grid-data';
import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
import BigNumber from 'bignumber.js';
type TradingModeTooltipProps = {
marketId?: string;
@@ -116,21 +115,13 @@ export const TradingModeTooltip = ({
case Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION: {
switch (trigger) {
case Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: {
const notEnoughLiquidity = new BigNumber(
marketData.suppliedStake || 0
).isLessThan(marketData.targetStake || 0);
return (
<section data-testid="trading-mode-tooltip">
<p className={classNames({ 'mb-4': Boolean(compiledGrid) })}>
<span className="mb-2">
{notEnoughLiquidity &&
t(
'This market is in auction until it reaches sufficient liquidity.'
)}
{!notEnoughLiquidity &&
t(
'This market may have sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.'
)}
<span>
{t(
'This market is in auction until it reaches sufficient liquidity.'
)}
</span>{' '}
{VEGA_DOCS_URL && (
<ExternalLink
+9 -19
View File
@@ -1,27 +1,17 @@
import { t } from '@vegaprotocol/react-helpers';
export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
`A fraction of the notional position size, in the market's settlement asset %s, to cover any potential losses that you may incur.
For example, for a notional size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.`,
[settlementAsset]
);
export const EST_MARGIN_TOOLTIP_TEXT = t(
'When opening a position on a futures market, you must post margin to cover any potential losses that you may incur. The margin is typically a fraction of the notional position size. For example, for a notional position size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.'
);
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
);
export const EST_CLOSEOUT_TOOLTIP_TEXT = (quote: string) =>
t(
`If the price drops below this number, measured in the market price quote unit %s, you will be closed out, based on your current position and account balance.`,
[quote]
);
export const NOTIONAL_SIZE_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
`The notional size represents the position size in the settlement asset %s of the futures contract. This is calculated by multiplying the number of contracts by the prices of the contract.
For example 10 contracts traded at a price of $50 has a notional size of $500.`,
[settlementAsset]
);
export const EST_CLOSEOUT_TOOLTIP_TEXT = t(
'Because you only need to post a fraction of your position size as margin when trading futures, it is possible to obtain leverage meaning your notional position size exceeds your account balance. In this scenario, if the market moves against your position, it will sometimes be necessary to force close your position due to insufficient funds. The estimated close out tells you the price at which that would happen based on current position and account balance.'
);
export const NOTIONAL_SIZE_TOOLTIP_TEXT = t(
'The notional size represents the position size in the settlement asset of the futures contract. The notional size is calculated by multiplying the number of contracts by the price of the contract. For example, ten contracts traded at a price of $50 has a notional size of $500.'
);
export const EST_FEES_TOOLTIP_TEXT = t(
'When you execute a new buy or sell order, you must pay a small amount of commission to the network for doing so. This fee is used to provide income to the node operates of the network and market makers who make prices on the futures market you are trading.'
);
@@ -72,13 +72,13 @@ export const useFeeDealTicketDetails = (
return null;
}, [derivedPrice, order.size, market.decimalPlaces]);
const assetSymbol =
const symbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
return useMemo(() => {
return {
market,
assetSymbol,
symbol,
notionalSize,
estMargin,
estCloseOut,
@@ -87,7 +87,7 @@ export const useFeeDealTicketDetails = (
};
}, [
market,
assetSymbol,
symbol,
notionalSize,
estMargin,
estCloseOut,
@@ -98,7 +98,7 @@ export const useFeeDealTicketDetails = (
export interface FeeDetails {
market: Market;
assetSymbol: string;
symbol: string;
notionalSize: string | null;
estMargin: OrderMargin | null;
estCloseOut: string | null;
@@ -106,7 +106,7 @@ export interface FeeDetails {
}
export const getFeeDetailsValues = ({
assetSymbol,
symbol,
notionalSize,
estMargin,
estCloseOut,
@@ -114,7 +114,6 @@ export const getFeeDetailsValues = ({
}: FeeDetails) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const quoteName = market.tradableInstrument.instrument.product.quoteName;
const formatValueWithMarketDp = (
value: string | number | null | undefined
): string => {
@@ -133,8 +132,8 @@ export const getFeeDetailsValues = ({
{
label: t('Notional'),
value: formatValueWithMarketDp(notionalSize),
symbol: assetSymbol,
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol),
symbol,
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT,
},
{
label: t('Fees'),
@@ -145,31 +144,31 @@ export const getFeeDetailsValues = ({
<>
<span>
{t(
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
'The most you would be expected to pay in fees, the actual amount may vary.'
)}
</span>
<FeesBreakdown
fees={estMargin?.fees}
feeFactors={market.fees.factors}
symbol={assetSymbol}
symbol={symbol}
decimals={assetDecimals}
/>
</>
),
symbol: assetSymbol,
symbol,
},
{
label: t('Margin'),
value:
estMargin?.margin && `~${formatValueWithAssetDp(estMargin?.margin)}`,
symbol: assetSymbol,
labelDescription: EST_MARGIN_TOOLTIP_TEXT(assetSymbol),
symbol,
labelDescription: EST_MARGIN_TOOLTIP_TEXT,
},
{
label: t('Liquidation'),
value: estCloseOut && `~${formatValueWithMarketDp(estCloseOut)}`,
symbol: market.tradableInstrument.instrument.product.quoteName,
labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT(quoteName),
labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT,
},
];
};
+11 -74
View File
@@ -4,12 +4,10 @@ import type { DepositFormProps } from './deposit-form';
import { DepositForm } from './deposit-form';
import * as Schema from '@vegaprotocol/types';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
jest.mock('@vegaprotocol/wallet');
jest.mock('@vegaprotocol/web3');
jest.mock('@web3-react/core');
const mockConnector = { deactivate: jest.fn() };
@@ -39,8 +37,6 @@ function generateAsset(): AssetFieldsFragment {
let asset: AssetFieldsFragment;
let props: DepositFormProps;
const MOCK_ETH_ADDRESS = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
const MOCK_VEGA_KEY =
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
beforeEach(() => {
asset = generateAsset();
@@ -93,17 +89,14 @@ describe('Deposit form', () => {
});
});
it('fails when Ethereum wallet not connected', async () => {
(useWeb3React as jest.Mock).mockReturnValue({
isActive: false,
account: '',
});
it('fails when submitted with invalid ethereum address', async () => {
(useWeb3React as jest.Mock).mockReturnValue({ account: '123' });
render(<DepositForm {...props} />);
fireEvent.submit(screen.getByTestId('deposit-form'));
expect(
await screen.findByText('Connect Ethereum wallet')
await screen.findByText('Invalid Ethereum address')
).toBeInTheDocument();
});
@@ -145,7 +138,7 @@ describe('Deposit form', () => {
fireEvent.submit(screen.getByTestId('deposit-form'));
expect(
await screen.findByText('Amount is above deposit limit')
await screen.findByText('Insufficient amount in Ethereum wallet')
).toBeInTheDocument();
});
@@ -166,7 +159,7 @@ describe('Deposit form', () => {
fireEvent.submit(screen.getByTestId('deposit-form'));
expect(
await screen.findByText('Amount is above approved amount.')
await screen.findByText('Amount is above approved amount')
).toBeInTheDocument();
});
@@ -200,9 +193,9 @@ describe('Deposit form', () => {
});
});
it('handles deposit approvals', async () => {
it('handles deposit approvals', () => {
const mockUseVegaWallet = useVegaWallet as jest.Mock;
mockUseVegaWallet.mockReturnValue({ pubKey: MOCK_VEGA_KEY });
mockUseVegaWallet.mockReturnValue({ pubKey: null });
const mockUseWeb3React = useWeb3React as jest.Mock;
mockUseWeb3React.mockReturnValue({
@@ -219,18 +212,13 @@ describe('Deposit form', () => {
/>
);
expect(screen.queryByLabelText('Amount')).not.toBeInTheDocument();
expect(screen.getByTestId('approve-warning')).toHaveTextContent(
`Deposits of ${asset.symbol} not approved`
);
fireEvent.click(
screen.getByRole('button', { name: `Approve ${asset.symbol}` })
screen.getByText(`Approve ${asset.symbol}`, {
selector: '[type="button"]',
})
);
await waitFor(() => {
expect(props.submitApprove).toHaveBeenCalled();
});
expect(props.submitApprove).toHaveBeenCalled();
});
it('handles submitting a deposit', async () => {
@@ -296,55 +284,4 @@ describe('Deposit form', () => {
render(<DepositForm {...props} />);
expect(await screen.queryAllByTestId('view-asset-details')).toHaveLength(0);
});
it('renders a connect button if Ethereum wallet is not connected', () => {
(useWeb3React as jest.Mock).mockReturnValue({
isActive: false,
account: '',
});
render(<DepositForm {...props} />);
expect(screen.getByRole('button', { name: 'Connect' })).toBeInTheDocument();
expect(
screen.queryByLabelText('From (Ethereum address)')
).not.toBeInTheDocument();
});
it('renders a disabled input if Ethereum wallet is connected', () => {
(useWeb3React as jest.Mock).mockReturnValue({
isActive: true,
account: MOCK_ETH_ADDRESS,
});
render(<DepositForm {...props} />);
expect(
screen.queryByRole('button', { name: 'Connect' })
).not.toBeInTheDocument();
const fromInput = screen.getByLabelText('From (Ethereum address)');
expect(fromInput).toHaveValue(MOCK_ETH_ADDRESS);
expect(fromInput).toBeDisabled();
expect(fromInput).toHaveAttribute('readonly');
});
it('prevents submission if you are on the wrong chain', () => {
(useWeb3React as jest.Mock).mockReturnValue({
isActive: true,
account: MOCK_ETH_ADDRESS,
chainId: 1,
});
(useWeb3ConnectStore as unknown as jest.Mock).mockImplementation(
// 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(
/this app only works on/i
);
});
});
+156 -196
View File
@@ -1,8 +1,8 @@
import type { Asset } from '@vegaprotocol/assets';
import { AssetOption } from '@vegaprotocol/assets';
import {
t,
ethereumAddress,
t,
required,
vegaPublicKey,
minSafe,
@@ -14,19 +14,17 @@ import {
import {
Button,
FormGroup,
Icon,
Input,
InputError,
RichSelect,
Notification,
Intent,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useWeb3React } from '@web3-react/core';
import BigNumber from 'bignumber.js';
import type { ButtonHTMLAttributes } from 'react';
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { useMemo } from 'react';
import type { FieldError } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';
import { Controller, useForm, useWatch } from 'react-hook-form';
import { DepositLimits } from './deposit-limits';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import {
@@ -74,8 +72,7 @@ export const DepositForm = ({
isFaucetable,
}: DepositFormProps) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openDialog = useWeb3ConnectStore((store) => store.open);
const { isActive, account } = useWeb3React();
const { account } = useWeb3React();
const { pubKey } = useVegaWallet();
const {
register,
@@ -86,27 +83,26 @@ export const DepositForm = ({
formState: { errors },
} = useForm<FormFields>({
defaultValues: {
from: account,
to: pubKey ? pubKey : undefined,
asset: selectedAsset?.id || '',
},
});
const onSubmit = async (fields: FormFields) => {
const onDeposit = async (fields: FormFields) => {
if (!selectedAsset || selectedAsset.source.__typename !== 'ERC20') {
throw new Error('Invalid asset');
}
if (approved) {
submitDeposit({
assetSource: selectedAsset.source.contractAddress,
amount: fields.amount,
vegaPublicKey: fields.to,
});
} else {
submitApprove();
}
submitDeposit({
assetSource: selectedAsset.source.contractAddress,
amount: fields.amount,
vegaPublicKey: fields.to,
});
};
const amount = useWatch({ name: 'amount', control });
const maxAmount = useMemo(() => {
const maxApproved = allowance ? allowance : new BigNumber(0);
const maxAvailable = balance ? balance : new BigNumber(0);
@@ -137,12 +133,9 @@ export const DepositForm = ({
return minViableAmount;
}, [selectedAsset]);
const approved = allowance && allowance.isGreaterThan(0) ? true : false;
const formState = getFormState(selectedAsset, isActive, approved);
return (
<form
onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(onDeposit)}
noValidate={true}
data-testid="deposit-form"
>
@@ -150,54 +143,19 @@ export const DepositForm = ({
label={t('From (Ethereum address)')}
labelFor="ethereum-address"
>
<Controller
name="from"
control={control}
rules={{
<Input
id="ethereum-address"
{...register('from', {
validate: {
required: (value) => {
if (!value) return t('Connect Ethereum wallet');
return true;
},
required,
ethereumAddress,
},
}}
defaultValue={account}
render={() => {
if (isActive && account) {
return (
<>
<Input
id="ethereum-address"
value={account}
readOnly={true}
disabled={true}
{...register('from', {
validate: {
required,
ethereumAddress,
},
})}
/>
<DisconnectEthereumButton
onDisconnect={() => {
setValue('from', ''); // clear from value so required ethereum connection validation works
}}
/>
</>
);
}
return (
<Button
onClick={openDialog}
variant="primary"
fill={true}
type="button"
data-testid="connect-eth-wallet-btn"
>
{t('Connect')}
</Button>
);
})}
/>
<EthereumButton
clearAddress={() => {
setValue('from', '');
clearErrors('from');
}}
/>
{errors.from?.message && (
@@ -283,136 +241,148 @@ export const DepositForm = ({
deposited={deposited}
balance={balance}
asset={selectedAsset}
allowance={allowance}
/>
</div>
)}
{formState === 'deposit' && (
<FormGroup label={t('Amount')} labelFor="amount">
<Input
type="number"
autoComplete="off"
id="amount"
{...register('amount', {
validate: {
required,
minSafe: (value) => minSafe(new BigNumber(min))(value),
approved: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(maxAmount.approved)) {
return t('Amount is above approved amount');
}
return true;
},
limit: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(maxAmount.limit)) {
return t('Amount is above deposit limit');
}
return true;
},
balance: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(maxAmount.available)) {
return t('Insufficient amount in Ethereum wallet');
}
return true;
},
maxSafe: (v) => {
return maxSafe(maxAmount.amount)(v);
},
<FormGroup label={t('Amount')} labelFor="amount">
<Input
type="number"
autoComplete="off"
id="amount"
{...register('amount', {
validate: {
required,
minSafe: (value) => minSafe(new BigNumber(min))(value),
maxSafe: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(maxAmount.available)) {
return t('Insufficient amount in Ethereum wallet');
} else if (value.isGreaterThan(maxAmount.limit)) {
return t('Amount is above temporary deposit limit');
} else if (value.isGreaterThan(maxAmount.approved)) {
return t('Amount is above approved amount');
}
return maxSafe(maxAmount.amount)(v);
},
})}
/>
{errors.amount?.message && (
<AmountError error={errors.amount} submitApprove={submitApprove} />
)}
{selectedAsset && balance && (
<UseButton
onClick={() => {
setValue('amount', balance.toFixed(selectedAsset.decimals));
clearErrors('amount');
}}
>
{t('Use maximum')}
</UseButton>
)}
</FormGroup>
)}
<FormButton selectedAsset={selectedAsset} formState={formState} />
},
})}
/>
{errors.amount?.message && (
<InputError intent="danger" forInput="amount">
{errors.amount.message}
</InputError>
)}
{selectedAsset && balance && (
<UseButton
onClick={() => {
setValue('amount', balance.toFixed(selectedAsset.decimals));
clearErrors('amount');
}}
>
{t('Use maximum')}
</UseButton>
)}
</FormGroup>
<FormButton
selectedAsset={selectedAsset}
amount={new BigNumber(amount || 0)}
allowance={allowance}
onApproveClick={submitApprove}
/>
</form>
);
};
const AmountError = ({
error,
submitApprove,
}: {
error: FieldError;
submitApprove: () => void;
}) => {
if (error.type === 'approved') {
return (
<InputError intent="danger" forInput="amount">
{error.message}.
<button onClick={submitApprove} className="underline ml-2">
{t('Update approve amount')}
</button>
</InputError>
);
}
return (
<InputError intent="danger" forInput="amount">
{error.message}
</InputError>
);
};
interface FormButtonProps {
selectedAsset?: Asset;
formState: ReturnType<typeof getFormState>;
amount: BigNumber;
allowance: BigNumber | undefined;
onApproveClick: () => void;
}
const FormButton = ({ selectedAsset, formState }: FormButtonProps) => {
const FormButton = ({
selectedAsset,
amount,
allowance,
onApproveClick,
}: FormButtonProps) => {
const { open, desiredChainId } = useWeb3ConnectStore((store) => ({
open: store.open,
desiredChainId: store.desiredChainId,
}));
const { isActive, chainId } = useWeb3React();
const desiredChainId = useWeb3ConnectStore((store) => store.desiredChainId);
const submitText =
formState === 'approve'
? t(`Approve ${selectedAsset ? selectedAsset.symbol : ''}`)
: t('Deposit');
const invalidChain = isActive && chainId !== desiredChainId;
return (
<>
{formState === 'approve' && (
<div className="mb-2">
<Notification
intent={Intent.Warning}
testId="approve-warning"
message={t(`Deposits of ${selectedAsset?.symbol} not approved`)}
/>
</div>
)}
{invalidChain && (
<div className="mb-2">
<Notification
intent={Intent.Danger}
testId="chain-error"
message={t(
`This app only works on ${getChainName(desiredChainId)}.`
)}
/>
</div>
)}
const approved =
allowance && allowance.isGreaterThan(0) && amount.isLessThan(allowance);
let button = null;
let message: ReactNode = '';
if (!isActive) {
button = (
<Button onClick={open} data-testid="connect-eth-wallet-btn">
{t('Connect Ethereum wallet')}
</Button>
);
} else if (chainId !== desiredChainId) {
const chainName = getChainName(desiredChainId);
message = t(`This app only works on ${chainName}.`);
button = (
<Button
type="submit"
data-testid="deposit-submit"
variant={isActive ? 'primary' : 'default'}
variant="primary"
fill={true}
disabled={invalidChain}
disabled={true}
>
{submitText}
{t('Deposit')}
</Button>
</>
);
} else if (!selectedAsset) {
button = (
<Button
type="submit"
data-testid="deposit-submit"
variant="primary"
fill={true}
>
{t('Deposit')}
</Button>
);
} else if (approved) {
message = (
<>
<Icon name="tick" className="mr-2" />
<span>{t('Approved')}</span>
</>
);
button = (
<Button
type="submit"
data-testid="deposit-submit"
variant="primary"
fill={true}
>
{t('Deposit')}
</Button>
);
} else {
message = t(`Deposits of ${selectedAsset.symbol} not approved`);
button = (
<Button
onClick={onApproveClick}
data-testid="deposit-approve-submit"
variant="primary"
fill={true}
>
{t(`Approve ${selectedAsset.symbol}`)}
</Button>
);
}
return (
<div className="flex flex-col gap-4">
{message && <p className="text-center">{message}</p>}
{button}
</div>
);
};
@@ -428,20 +398,21 @@ const UseButton = (props: UseButtonProps) => {
);
};
const DisconnectEthereumButton = ({
onDisconnect,
}: {
onDisconnect: () => void;
}) => {
const { connector } = useWeb3React();
const EthereumButton = ({ clearAddress }: { clearAddress: () => void }) => {
const openDialog = useWeb3ConnectStore((state) => state.open);
const { isActive, connector } = useWeb3React();
const [, , removeEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
if (!isActive) {
return <UseButton onClick={openDialog}>{t('Connect')}</UseButton>;
}
return (
<UseButton
onClick={() => {
connector.deactivate();
clearAddress();
removeEagerConnector();
onDisconnect();
}}
data-testid="disconnect-ethereum-wallet"
>
@@ -449,14 +420,3 @@ const DisconnectEthereumButton = ({
</UseButton>
);
};
const getFormState = (
selectedAsset: Asset | undefined,
isActive: boolean,
approved: boolean
) => {
if (!selectedAsset) return 'deposit';
if (!isActive) return 'deposit';
if (approved) return 'deposit';
return 'approve';
};
-8
View File
@@ -11,7 +11,6 @@ interface DepositLimitsProps {
deposited: BigNumber;
asset: Asset;
balance?: BigNumber;
allowance?: BigNumber;
}
export const DepositLimits = ({
@@ -19,7 +18,6 @@ export const DepositLimits = ({
deposited,
asset,
balance,
allowance,
}: DepositLimitsProps) => {
const limits = [
{
@@ -46,12 +44,6 @@ export const DepositLimits = ({
rawValue: max.minus(deposited),
value: compactNumber(max.minus(deposited), asset.decimals),
},
{
key: 'ALLOWANCE',
label: t('Approved'),
rawValue: allowance,
value: allowance ? compactNumber(allowance, asset.decimals) : '-',
},
];
return (
+1 -2
View File
@@ -45,9 +45,8 @@ export const FillsManager = ({
datasource={{ getRows }}
onBodyScrollEnd={onBodyScrollEnd}
onBodyScroll={onBodyScroll}
noRowsOverlayComponent={() => null}
onMarketClick={onMarketClick}
suppressLoadingOverlay
suppressNoRowsOverlay
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -27,13 +27,12 @@ export const AssetProposalNotification = ({
</>
);
return (
<div className="mb-2">
<Notification
intent={Intent.Warning}
message={message}
testId="asset-proposal-notification"
/>
</div>
<Notification
intent={Intent.Warning}
message={message}
testId="asset-proposal-notification"
className="mb-2"
/>
);
}
@@ -34,6 +34,7 @@ export const MarketProposalNotification = ({
intent={Intent.Warning}
message={message}
testId="market-proposal-notification"
className="px-2 py-1"
/>
</div>
);
@@ -2,8 +2,10 @@ import type { Asset } from '@vegaprotocol/assets';
import { assetsProvider } from '@vegaprotocol/assets';
import type { Market } from '@vegaprotocol/market-list';
import { marketsProvider } from '@vegaprotocol/market-list';
import type { PageInfo } from '@vegaprotocol/react-helpers';
import { makeInfiniteScrollGetRows } from '@vegaprotocol/react-helpers';
import {
defaultAppend as append,
makeDataProvider,
makeDerivedDataProvider,
useDataProvider,
@@ -25,15 +27,13 @@ import type {
import { LedgerEntriesDocument } from './__generated__/LedgerEntries';
export type LedgerEntry = LedgerEntryFragment & {
id: number;
asset: Asset | null | undefined;
marketSender: Market | null | undefined;
marketReceiver: Market | null | undefined;
};
export type AggregatedLedgerEntriesEdge = Schema.AggregatedLedgerEntriesEdge;
export type AggregatedLedgerEntriesNode = AggregatedLedgerEntriesEdge & {
node: LedgerEntry;
};
const getData = (responseData: LedgerEntriesQuery | null) => {
return responseData?.ledgerEntries?.edges || [];
@@ -94,29 +94,33 @@ export const update = (
});
};
const getPageInfo = (responseData: LedgerEntriesQuery): PageInfo | null =>
responseData.ledgerEntries?.pageInfo || null;
const ledgerEntriesOnlyProvider = makeDataProvider({
query: LedgerEntriesDocument,
getData,
getDelta: getData,
update,
pagination: {
getPageInfo,
append,
first: 100,
},
additionalContext: {
isEnlargedTimeout: true,
},
});
export const ledgerEntriesProvider = makeDerivedDataProvider<
AggregatedLedgerEntriesNode[],
AggregatedLedgerEntriesNode[],
(AggregatedLedgerEntriesEdge | null)[],
AggregatedLedgerEntriesEdge[],
LedgerEntriesQueryVariables
>(
[
ledgerEntriesOnlyProvider,
(callback, client) => assetsProvider(callback, client),
marketsProvider,
],
[ledgerEntriesOnlyProvider, assetsProvider, marketsProvider],
([entries, assets, markets]) => {
return entries.map((edge: AggregatedLedgerEntriesEdge) => {
const entry = edge.node;
const entry = edge?.node;
const asset = assets.find((asset: Asset) => asset.id === entry.assetId);
const marketSender = markets.find(
(market: Market) => market.id === entry.fromAccountMarketId
@@ -144,22 +148,21 @@ export const useLedgerEntriesDataProvider = ({
filter,
gridRef,
}: Props) => {
const dataRef = useRef<AggregatedLedgerEntriesEdge[] | null>(null);
const dataRef = useRef<(AggregatedLedgerEntriesEdge | null)[] | null>(null);
const totalCountRef = useRef<number>();
const variables = useMemo<LedgerEntriesQueryVariables>(
() => ({
partyId,
dateRange: filter?.vegaTime?.value,
pagination: {
first: 5000,
},
fromAccountType: filter?.fromAccountType?.value ?? null,
toAccountType: filter?.toAccountType?.value ?? null,
}),
[partyId, filter?.vegaTime?.value]
[partyId, filter]
);
const update = useCallback(
({ data }: { data: AggregatedLedgerEntriesEdge[] | null }) => {
({ data }: { data: (AggregatedLedgerEntriesEdge | null)[] | null }) => {
return updateGridData(dataRef, data, gridRef);
},
[gridRef]
@@ -170,7 +173,7 @@ export const useLedgerEntriesDataProvider = ({
data,
totalCount,
}: {
data: AggregatedLedgerEntriesEdge[] | null;
data: (AggregatedLedgerEntriesEdge | null)[] | null;
totalCount?: number;
}) => {
totalCountRef.current = totalCount;
+29 -31
View File
@@ -3,9 +3,7 @@ import type * as Schema from '@vegaprotocol/types';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { FilterChangedEvent } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { subDays, formatRFC3339 } from 'date-fns';
import type { AggregatedLedgerEntriesNode } from './ledger-entries-data-provider';
import { useCallback, useRef, useState } from 'react';
import { useLedgerEntriesDataProvider } from './ledger-entries-data-provider';
import { LedgerTable } from './ledger-table';
import type * as Types from '@vegaprotocol/types';
@@ -17,43 +15,43 @@ export interface Filter {
fromAccountType?: { value: Types.AccountType[] };
toAccountType?: { value: Types.AccountType[] };
}
const defaultFilter = {
vegaTime: {
value: { start: formatRFC3339(subDays(Date.now(), 7)) },
},
};
export const LedgerManager = ({ partyId }: { partyId: string }) => {
type LedgerManagerProps = { partyId: string };
export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [filter, setFilter] = useState<Filter>(defaultFilter);
const [dataCount, setDataCount] = useState(0);
const [filter, setFilter] = useState<Filter | undefined>();
const { data, error, loading, reload } = useLedgerEntriesDataProvider({
partyId,
filter,
gridRef,
});
const { data, error, loading, getRows, reload } =
useLedgerEntriesDataProvider({
partyId,
filter,
gridRef,
});
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
setFilter(updatedFilter);
}, []);
const extractNodesDecorator = useCallback(
(data: AggregatedLedgerEntriesNode[] | null, loading: boolean) =>
data && !loading ? data.map((item) => item.node) : null,
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
const updatedFilter = event.api.getFilterModel();
if (Object.keys(updatedFilter).length) {
setFilter(updatedFilter);
} else if (filter) {
setFilter(undefined);
}
},
[filter]
);
const getRowId = useCallback(
({ data }: { data: Types.AggregatedLedgerEntry }) =>
`${data.vegaTime}-${data.fromAccountPartyId}-${data.toAccountPartyId}`,
[]
);
const extractedData = extractNodesDecorator(data, loading);
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [extractedData]);
return (
<div className="h-full relative">
<LedgerTable
ref={gridRef}
rowData={extractedData}
rowModelType="infinite"
datasource={{ getRows }}
onFilterChanged={onFilterChanged}
getRowId={getRowId}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -61,7 +59,7 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
error={error}
data={data}
noDataMessage={t('No entries')}
noDataCondition={() => !dataCount}
noDataCondition={(data) => !(data && data.length)}
reload={reload}
/>
</div>

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