Compare commits

..
89 changed files with 139311 additions and 139048 deletions
@@ -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
@@ -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,
};
}
@@ -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>
);
}
@@ -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',
@@ -178,9 +178,9 @@ function openMarketDropDown() {
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.get('[data-testid^="ask-vol-"]').should('be.visible');
cy.getByTestId(popoverTrigger).click({ force: true });
cy.contains('Loading market data...').should('not.exist');
});
}
@@ -1,6 +1,6 @@
import { mockConnectWallet } from '@vegaprotocol/cypress';
beforeEach(() => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
@@ -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...</>,
});
+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;
}
}
+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
@@ -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>
+3 -13
View File
@@ -22,7 +22,6 @@ import {
} from '@vegaprotocol/types';
import type { LedgerEntry } from './ledger-entries-data-provider';
import { forwardRef } from 'react';
import { formatRFC3339, subDays } from 'date-fns';
export const TransferTooltipCellComponent = ({
value,
@@ -36,11 +35,6 @@ export const TransferTooltipCellComponent = ({
);
};
const defaultRangeFilter = { start: formatRFC3339(subDays(Date.now(), 7)) };
const dateRangeFilterParams = {
maxNextDays: 0,
defaultRangeFilter,
};
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
@@ -48,20 +42,17 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No entries')}
ref={ref}
getRowId={({ data }) => data.id}
tooltipShowDelay={500}
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
tooltipComponent: TransferTooltipCellComponent,
filterParams: {
...dateRangeFilterParams,
buttons: ['reset'],
},
filterParams: { buttons: ['reset'] },
}}
suppressLoadingOverlay
suppressNoRowsOverlay
{...props}
>
<AgGridColumn
@@ -210,7 +201,6 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-'
}
filterParams={dateRangeFilterParams}
filter={DateRangeFilter}
/>
</AgGrid>
@@ -70,6 +70,7 @@ export const OrderbookRow = React.memo(
relativeAsk={cumulativeRelativeAsk}
relativeBid={cumulativeRelativeBid}
indicativeVolume={indicativeVolume}
className="pr-4 text-black dark:text-white"
/>
</>
);
+31 -6
View File
@@ -201,8 +201,26 @@ const OrderbookDebugInfo = ({
midPrice?: string;
}) => (
<Fragment>
<div className="absolute top-1/2 left-0 border-t border-t-black w-full" />
<div className="text-xs p-2 bg-black/80 text-white absolute left-0 bottom-6 font-mono">
<div
style={{
position: 'absolute',
top: '50%',
left: '0',
borderTop: '1px solid rgba(255,0,0,0.5)',
background: 'black',
width: '100%',
transform: 'translateY(-50%)',
}}
></div>
<div
className="absolute left-0 bottom-0 font-mono"
style={{
fontSize: '10px',
color: '#FFF',
background: '#000',
padding: '2px',
}}
>
<pre>
{JSON.stringify(
{
@@ -475,7 +493,12 @@ export const Orderbook = ({
const tableBody =
data && data.length !== 0 ? (
<div className="grid grid-cols-4 gap-1 text-right auto-rows-[17px]">
<div
className="grid grid-cols-4 gap-[0.3125rem] text-right"
style={{
gridAutoRows: '17px',
}}
>
{data.map((data, i) => (
<OrderbookRow
key={data.price}
@@ -536,13 +559,14 @@ export const Orderbook = ({
onDoubleClick={() => setDebug(!debug)}
>
<div
className="absolute top-0 grid grid-cols-4 auto-rows-[17px] gap-2 text-right border-b pt-2 bg-white dark:bg-black z-10 border-default w-full"
className="absolute top-0 grid grid-cols-4 gap-2 text-right border-b pt-2 bg-white dark:bg-black z-10 border-default w-full"
style={{ gridAutoRows: '17px' }}
ref={headerElement}
>
<div>{t('Bid vol')}</div>
<div>{t('Ask vol')}</div>
<div>{t('Price')}</div>
<div className="pr-1 whitespace-nowrap overflow-hidden text-ellipsis">
<div className="pr-[2px] whitespace-nowrap overflow-hidden text-ellipsis">
{t('Cumulative vol')}
</div>
</div>
@@ -581,7 +605,8 @@ export const Orderbook = ({
)}
</div>
<div
className="absolute bottom-0 grid grid-cols-4 gap-2 border-t border-default mt-2 z-10 bg-white dark:bg-black w-full"
className="absolute bottom-0 grid grid-cols-4 gap-2 border-t-[1px] border-default mt-2 z-10 bg-white dark:bg-black w-full"
style={{ gridAutoRows: '20px' }}
ref={footerElement}
>
<div className="col-span-2">
@@ -74,7 +74,7 @@ export const FeesBreakdown = ({
: '-';
};
return (
<dl className="grid grid-cols-6">
<dl className="grid grid-cols-5">
<dt className="col-span-2">{t('Infrastructure fee')}</dt>
{feeFactors && (
<dd className="text-right col-span-1">
@@ -83,7 +83,7 @@ export const FeesBreakdown = ({
)}
</dd>
)}
<dd className="text-right col-span-3">
<dd className="text-right col-span-2">
{formatValue(fees.infrastructureFee)} {symbol || ''}
</dd>
<dt className="col-span-2">{t('Liquidity fee')}</dt>
@@ -94,7 +94,7 @@ export const FeesBreakdown = ({
)}
</dd>
)}
<dd className="text-right col-span-3">
<dd className="text-right col-span-2">
{formatValue(fees.liquidityFee)} {symbol || ''}
</dd>
<dt className="col-span-2">{t('Maker fee')}</dt>
@@ -105,7 +105,7 @@ export const FeesBreakdown = ({
)}
</dd>
)}
<dd className="text-right col-span-3">
<dd className="text-right col-span-2">
{formatValue(fees.makerFee)} {symbol || ''}
</dd>
<dt className="col-span-2">{t('Total fees')}</dt>
@@ -114,7 +114,7 @@ export const FeesBreakdown = ({
{totalFeesPercentage(feeFactors)}
</dd>
)}
<dd className="text-right col-span-3">
<dd className="text-right col-span-2">
{formatValue(totalFees)} {symbol || ''}
</dd>
</dl>
@@ -152,8 +152,6 @@ export const OrderListManager = ({
isReadOnly={isReadOnly}
hasActiveOrder={hasActiveOrder}
blockLoadDebounceMillis={100}
suppressLoadingOverlay
suppressNoRowsOverlay
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -323,7 +323,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
field="realisedPNL"
type="rightAligned"
cellClassRules={signedNumberCssClassRules}
cellClass="text-right font-mono"
filter="agNumberColumnFilter"
valueGetter={({
data,
@@ -348,7 +347,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
field="unrealisedPNL"
type="rightAligned"
cellClassRules={signedNumberCssClassRules}
cellClass="text-right font-mono"
filter="agNumberColumnFilter"
valueGetter={({
data,
-1
View File
@@ -1,5 +1,4 @@
/* eslint-disable */
process.env.TZ = 'UTC';
export default {
displayName: 'react-helpers',
preset: '../../jest.preset.js',
@@ -1,6 +1,7 @@
import { memo } from 'react';
import React from 'react';
import type { ICellRendererParams } from 'ag-grid-community';
import classNames from 'classnames';
import { BID_COLOR, ASK_COLOR } from './vol-cell';
import { NumericCell } from './numeric-cell';
import { addDecimalsFormatNumber } from '../format';
export interface CumulativeVolProps {
@@ -14,7 +15,11 @@ export interface CumulativeVolProps {
positionDecimalPlaces: number;
}
export const CumulativeVol = memo(
export interface ICumulativeVolCellProps extends ICellRendererParams {
value: CumulativeVolProps;
}
export const CumulativeVol = React.memo(
({
relativeAsk,
relativeBid,
@@ -22,6 +27,7 @@ export const CumulativeVol = memo(
bid,
indicativeVolume,
testId,
className,
positionDecimalPlaces,
}: CumulativeVolProps) => {
const askBar = relativeAsk ? (
@@ -34,7 +40,7 @@ export const CumulativeVol = memo(
backgroundColor: ASK_COLOR,
opacity: 0.6,
}}
/>
></div>
) : null;
const bidBar = relativeBid ? (
<div
@@ -47,48 +53,25 @@ export const CumulativeVol = memo(
backgroundColor: BID_COLOR,
opacity: 0.6,
}}
/>
></div>
) : null;
const volume = indicativeVolume ? (
<span>
(
<NumericCell
value={Number(indicativeVolume)}
valueFormatted={addDecimalsFormatNumber(
indicativeVolume,
positionDecimalPlaces ?? 0
)}
/>
<span className="relative">
({addDecimalsFormatNumber(indicativeVolume, positionDecimalPlaces ?? 0)}
)
</span>
) : (
<span>
{ask ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFormatNumber(
ask,
positionDecimalPlaces ?? 0
)}
/>
) : null}
<span className="relative">
{ask ? addDecimalsFormatNumber(ask, positionDecimalPlaces ?? 0) : null}
{ask && bid ? '/' : null}
{bid ? (
<NumericCell
value={ask}
valueFormatted={addDecimalsFormatNumber(
bid,
positionDecimalPlaces ?? 0
)}
/>
) : null}
{bid ? addDecimalsFormatNumber(bid, positionDecimalPlaces ?? 0) : null}
</span>
);
return (
<div
className="relative font-mono pr-1"
className={classNames('h-full relative', className)}
data-testid={testId || 'cumulative-vol'}
>
{askBar}
@@ -100,3 +83,9 @@ export const CumulativeVol = memo(
);
CumulativeVol.displayName = 'CumulativeVol';
export const CumulativeVolCell = ({ value }: ICumulativeVolCellProps) => (
<CumulativeVol {...value} />
);
CumulativeVolCell.displayName = 'CumulativeVolCell';
@@ -1,29 +0,0 @@
import { render, screen } from '@testing-library/react';
import type { DateRangeFilterProps } from './date-range-filter';
import { DateRangeFilter } from './date-range-filter';
const commonProps = {
filterChangedCallback: jest.fn(),
};
describe('DateRangeFilter', () => {
it('should be properly rendered', async () => {
const defaultRangeFilter = {
start: '2023-02-14T13:53:01+01:00',
end: '2023-02-21T13:53:01+01:00',
};
const displayStartValue = '2023-02-14T12:53:01.000';
const displayEndValue = '2023-02-21T12:53:01.000';
render(
<DateRangeFilter
{...(commonProps as unknown as DateRangeFilterProps)}
defaultRangeFilter={defaultRangeFilter}
/>
);
expect(screen.getByLabelText('Start')).toHaveValue(displayStartValue);
expect(screen.getByLabelText('End')).toHaveValue(displayEndValue);
expect(commonProps.filterChangedCallback).toHaveBeenCalled();
});
});
@@ -1,259 +1,113 @@
import type { ChangeEvent } from 'react';
import { useEffect, useMemo } from 'react';
import { useEffect } from 'react';
import type * as Schema from '@vegaprotocol/types';
import { forwardRef, useImperativeHandle, useState } from 'react';
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
import {
isBefore,
subDays,
addDays,
differenceInDays,
formatRFC3339,
min,
max,
isValid,
} from 'date-fns';
import { t } from '../i18n';
import { formatForInput } from '../format/date';
import { t } from '../i18n';
const defaultFilterValue: Schema.DateRange = {};
export interface DateRangeFilterProps extends IFilterParams {
defaultRangeFilter?: Schema.DateRange;
maxSubDays?: number;
maxNextDays?: number;
maxDaysRange?: number;
}
export const DateRangeFilter = forwardRef(
(props: DateRangeFilterProps, ref) => {
const defaultDates = props?.defaultRangeFilter || defaultFilterValue;
const [value, setValue] = useState<Schema.DateRange>(defaultDates);
const [error, setError] = useState<string>('');
const [minStartDate, maxStartDate, minEndDate, maxEndDate] = useMemo(() => {
const minStartDate =
props?.maxSubDays !== undefined
? formatForInput(subDays(Date.now(), props.maxSubDays))
: '';
const maxStartDate =
props?.maxNextDays !== undefined
? formatForInput(addDays(Date.now(), props.maxNextDays))
: '';
const minEndDate =
value.start && props?.maxDaysRange !== undefined
? formatForInput(new Date(value.start))
: minStartDate || value.start
? formatForInput(new Date(value.start))
: '';
const endDateCandidates = [];
if (props.maxNextDays !== undefined) {
endDateCandidates.push(addDays(new Date(), props.maxNextDays));
}
if (props.maxDaysRange !== undefined && value.start) {
endDateCandidates.push(
addDays(new Date(value.start), props.maxDaysRange)
);
}
const maxEndDate = endDateCandidates.length
? formatForInput(min(endDateCandidates))
: maxStartDate;
return [minStartDate, maxStartDate, minEndDate, maxEndDate];
}, [props.maxSubDays, props.maxDaysRange, props.maxNextDays, value.start]);
// expose AG Grid Filter Lifecycle callbacks
useImperativeHandle(ref, () => {
return {
doesFilterPass(params: IDoesFilterPassParams) {
const { api, colDef, column, columnApi, context } = props;
const { node } = params;
const rowValue = props.valueGetter({
api,
colDef,
column,
columnApi,
context,
data: node.data,
getValue: (field) => node.data[field],
node,
});
if (
value.start &&
rowValue &&
new Date(rowValue) <= new Date(value.start)
) {
return false;
}
if (
value.end &&
rowValue &&
new Date(rowValue) >= new Date(value.end)
) {
return false;
}
return true;
},
export const DateRangeFilter = forwardRef((props: IFilterParams, ref) => {
const [value, setValue] = useState<Schema.DateRange>(defaultFilterValue);
isFilterActive() {
return value.start || value.end;
},
getModel() {
if (!this.isFilterActive()) {
return null;
}
return { value };
},
setModel(model?: { value: Schema.DateRange } | null) {
setValue(
model?.value || props?.defaultRangeFilter || defaultFilterValue
);
},
};
});
const validate = (
name: string,
timeValue: Date,
update?: Schema.DateRange
) => {
if (
props.maxSubDays !== undefined &&
isBefore(new Date(timeValue), subDays(Date.now(), props.maxSubDays + 1))
) {
setError(
t(
'The earliest data that can be queried is %s days ago.',
String(props.maxSubDays)
)
);
return false;
}
if (props?.maxDaysRange !== undefined) {
const contrvalue =
name === 'start'
? update?.end || value.end
: update?.start || value.start;
// expose AG Grid Filter Lifecycle callbacks
useImperativeHandle(ref, () => {
return {
doesFilterPass(params: IDoesFilterPassParams) {
const { api, colDef, column, columnApi, context } = props;
const { node } = params;
const rowValue = props.valueGetter({
api,
colDef,
column,
columnApi,
context,
data: node.data,
getValue: (field) => node.data[field],
node,
});
if (
Math.abs(
differenceInDays(new Date(timeValue), new Date(contrvalue))
) > props.maxDaysRange
value.start &&
rowValue &&
new Date(rowValue) <= new Date(value.start)
) {
setError(
t(
'The maximum time range that can be queried is %s days.',
String(props.maxDaysRange)
)
);
return false;
}
}
setError('');
return true;
};
if (
value.end &&
rowValue &&
new Date(rowValue) >= new Date(value.end)
) {
return false;
}
return true;
},
const checkForEndDate = (
endDate: Date | undefined,
startDate: Date | undefined
) => {
const endDateCandidates: Date[] = [];
if (props.maxDaysRange !== undefined && isValid(startDate)) {
endDateCandidates.push(addDays(startDate as Date, props.maxDaysRange));
}
if (props.maxNextDays !== undefined) {
endDateCandidates.push(addDays(Date.now(), props.maxNextDays));
}
if (isValid(endDate)) {
endDateCandidates.push(endDate as Date);
}
return endDate && startDate
? formatRFC3339(max([startDate, min(endDateCandidates)]))
: undefined;
};
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
const { value: dateValue, name } = event.target;
const date = new Date(dateValue || defaultDates[name as 'start' | 'end']);
let update = { [name]: isValid(date) ? formatRFC3339(date) : undefined };
const startCheckDate = name === 'start' ? date : new Date(value.start);
const endCheckDate =
name === 'start'
? new Date(value.end)
: isValid(date)
? date
: new Date(maxEndDate);
const endDate = isValid(endCheckDate) ? endCheckDate : undefined;
const startDate = isValid(startCheckDate) ? startCheckDate : undefined;
update = { ...update, end: checkForEndDate(endDate, startDate) };
isFilterActive() {
return value.start || value.end;
},
if (validate(name, date, update)) {
setValue((curr) => ({
...curr,
...update,
}));
}
};
useEffect(() => {
props?.filterChangedCallback();
}, [value, props]);
getModel() {
if (!this.isFilterActive()) {
return null;
}
const notification = useMemo(() => {
const not = error ? (
<div
className="text-sm flex items-center first-letter:uppercase mt-2 border-danger text-danger"
role="alert"
return { value };
},
setModel(model?: { value: Schema.DateRange } | null) {
setValue(model?.value || defaultFilterValue);
},
};
});
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
setValue({
...value,
[event.target.name]:
event.target.value &&
new Date(event.target.value).toISOString().replace('Z', '000000Z'),
});
};
useEffect(() => {
props?.filterChangedCallback();
}, [value, props]);
const start = (value.start && formatForInput(new Date(value.start))) || '';
const end = (value.end && formatForInput(new Date(value.end))) || '';
return (
<div className="ag-filter-body-wrapper">
<fieldset className="ag-simple-filter-body-wrapper">
<label className="block" key="start">
<span className="block">{t('Start')}</span>
<input
type="datetime-local"
name="start"
value={start}
onChange={onChange}
/>
</label>
<label className="block" key="end">
<span className="block">{t('End')}</span>
<input
type="datetime-local"
name="end"
value={end}
onChange={onChange}
/>
</label>
</fieldset>
<div className="ag-filter-apply-panel">
<button
type="button"
className="ag-standard-button ag-filter-apply-panel-button"
onClick={() => setValue(defaultFilterValue)}
>
{error}
</div>
) : null;
return (
<div className="ag-filter-apply-panel flex min-h-[2rem]">{not}</div>
);
}, [error]);
const start = (value.start && formatForInput(new Date(value.start))) || '';
const end = (value.end && formatForInput(new Date(value.end))) || '';
return (
<div className="ag-filter-body-wrapper inline-block min-w-fit">
{notification}
<div className="ag-filter-apply-panel">
<fieldset className="ag-simple-filter-body-wrapper">
<label className="block" key="start">
<span className="block mb-1">{t('Start')}</span>
<input
type="datetime-local"
name="start"
value={start || ''}
onChange={onChange}
min={minStartDate}
max={maxStartDate}
/>
</label>
</fieldset>
<fieldset className="ag-simple-filter-body-wrapper">
<label className="block" key="end">
<span className="block mb-1">{t('End')}</span>
<input
type="datetime-local"
name="end"
value={end || ''}
onChange={onChange}
min={minEndDate}
max={maxEndDate}
/>
</label>
</fieldset>
</div>
<div className="ag-filter-apply-panel">
<button
type="button"
className="ag-standard-button ag-filter-apply-panel-button"
onClick={() => {
setError('');
setValue(defaultDates);
}}
>
{t('Reset')}
</button>
</div>
Reset
</button>
</div>
);
}
);
</div>
);
});
-1
View File
@@ -8,4 +8,3 @@ export * from './summary-rows';
export * from './vol-cell';
export * from './set-filter';
export * from './date-range-filter';
export * from './numeric-cell';
@@ -1,31 +0,0 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { NumericCell } from './numeric-cell';
describe('NumericCell', () => {
const testId = 'cell';
it('Displays formatted value', () => {
const valueFormatted = '111.00';
render(
<NumericCell
value={111}
valueFormatted={valueFormatted}
testId={testId}
/>
);
expect(screen.getByTestId(testId)).toHaveTextContent(valueFormatted);
expect(screen.getByText('00')).toBeInTheDocument();
expect(screen.getByText('00')).toHaveClass('opacity-60');
});
it('Displays 0', () => {
render(<NumericCell value={0} valueFormatted="0.00" testId={testId} />);
expect(screen.getByTestId(testId)).toHaveTextContent('0.00');
});
it('Displays - if value is not a number', () => {
render(<NumericCell value={null} valueFormatted="" testId={testId} />);
expect(screen.getByTestId(testId)).toHaveTextContent('-');
});
});
@@ -1,44 +0,0 @@
import { forwardRef } from 'react';
import { getDecimalSeparator, isNumeric } from '../format';
interface NumericCellProps {
value: number | bigint | null | undefined;
valueFormatted: string;
testId?: string;
}
/**
* Renders a numeric value in a consistent way for data grid
* use, right aligned, monospace and decimals deemphasised
*/
export const NumericCell = forwardRef<HTMLSpanElement, NumericCellProps>(
({ value, valueFormatted, testId }, ref) => {
if (!isNumeric(value)) {
return (
<span ref={ref} data-testid={testId}>
-
</span>
);
}
const decimalSeparator = getDecimalSeparator();
const valueSplit: string[] = decimalSeparator
? valueFormatted.split(decimalSeparator).map((v) => `${v}`)
: [`${value}`];
return (
<span
ref={ref}
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
data-testid={testId}
title={valueFormatted}
>
{valueSplit[0]}
{valueSplit[1] ? decimalSeparator : null}
{valueSplit[1] ? (
<span className="opacity-60">{valueSplit[1]}</span>
) : null}
</span>
);
}
);
@@ -1,15 +1,15 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom/extend-expect';
import * as React from 'react';
import { PriceCell } from './price-cell';
describe('PriceCell', () => {
describe('<PriceCell />', () => {
it('Displays formatted value', () => {
render(<PriceCell value={100} valueFormatted="100.00" />);
expect(screen.getByTestId('price')).toHaveTextContent('100.00');
expect(screen.getByTestId('price')).toHaveAttribute('title', '100.00');
});
it('Displays 0', () => {
render(<PriceCell value={0} valueFormatted="0.00" />);
expect(screen.getByTestId('price')).toHaveTextContent('0.00');
@@ -19,14 +19,4 @@ describe('PriceCell', () => {
render(<PriceCell value={null} valueFormatted="" />);
expect(screen.getByTestId('price')).toHaveTextContent('-');
});
it('can be clicked if given an onClick handler', async () => {
const mockOnClick = jest.fn();
const value = 100;
render(
<PriceCell value={value} valueFormatted="100.00" onClick={mockOnClick} />
);
await userEvent.click(screen.getByTestId('price'));
expect(mockOnClick).toHaveBeenCalledWith(value);
});
});
+30 -13
View File
@@ -1,6 +1,5 @@
import { memo, forwardRef } from 'react';
import { isNumeric } from '../format';
import { NumericCell } from './numeric-cell';
import { getDecimalSeparator, isNumeric } from '../format';
export interface IPriceCellProps {
value: number | bigint | null | undefined;
valueFormatted: string;
@@ -18,23 +17,41 @@ export const PriceCell = memo(
</span>
);
}
const decimalSeparator = getDecimalSeparator();
const valueSplit: string[] = decimalSeparator
? valueFormatted.split(decimalSeparator).map((v) => `${v}`)
: [`${value}`];
return onClick ? (
<button
onClick={() => onClick(value)}
className="hover:dark:bg-neutral-800 hover:bg-neutral-200 text-right"
className="hover:dark:bg-neutral-800 hover:bg-neutral-200"
>
<NumericCell
value={value}
valueFormatted={valueFormatted}
testId={testId || 'price'}
/>
<span
ref={ref}
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
data-testid={testId || 'price'}
title={valueFormatted}
>
{valueSplit[0]}
{valueSplit[1] ? decimalSeparator : null}
{valueSplit[1] ? (
<span className="opacity-60">{valueSplit[1]}</span>
) : null}
</span>
</button>
) : (
<NumericCell
value={value}
valueFormatted={valueFormatted}
testId={testId || 'price'}
/>
<span
ref={ref}
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
data-testid={testId || 'price'}
title={valueFormatted}
>
{valueSplit[0]}
{valueSplit[1] ? decimalSeparator : null}
{valueSplit[1] ? (
<span className="opacity-60">{valueSplit[1]}</span>
) : null}
</span>
);
}
)
+13 -15
View File
@@ -6,7 +6,6 @@ import React, {
useState,
} from 'react';
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
import { t } from '../i18n';
export const SetFilter = forwardRef((props: IFilterParams, ref) => {
const [value, setValue] = useState<string[]>([]);
@@ -17,19 +16,18 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
doesFilterPass(params: IDoesFilterPassParams) {
const { api, colDef, column, columnApi, context } = props;
const { node } = params;
const getValue = props.valueGetter({
api,
colDef,
column,
columnApi,
context,
data: node.data,
getValue: (field) => node.data[field],
node,
});
return Array.isArray(value)
? value.includes(getValue)
: getValue === value;
return (
props.valueGetter({
api,
colDef,
column,
columnApi,
context,
data: node.data,
getValue: (field) => node.data[field],
node,
}) === value
);
},
isFilterActive() {
@@ -85,7 +83,7 @@ export const SetFilter = forwardRef((props: IFilterParams, ref) => {
className="ag-standard-button ag-filter-apply-panel-button"
onClick={() => setValue([])}
>
{t('Reset')}
Reset
</button>
</div>
</div>
+9 -3
View File
@@ -1,8 +1,8 @@
import React from 'react';
import type { ICellRendererParams } from 'ag-grid-community';
import { PriceCell } from './price-cell';
import classNames from 'classnames';
import * as tailwind from '@vegaprotocol/tailwindcss-config';
import { NumericCell } from './numeric-cell';
export enum VolumeType {
bid,
@@ -43,11 +43,17 @@ export const Vol = React.memo(
backgroundColor: type === VolumeType.bid ? BID_COLOR : ASK_COLOR,
opacity: type === VolumeType.bid ? 0.6 : 0.6,
}}
/>
<NumericCell value={value} valueFormatted={valueFormatted} />
></div>
<PriceCell value={value} valueFormatted={valueFormatted} />
</div>
);
}
);
Vol.displayName = 'Vol';
export const VolCell = ({ value, valueFormatted }: IVolCellProps) => (
<Vol value={value} {...valueFormatted} />
);
VolCell.displayName = 'VolCell';
@@ -2,15 +2,51 @@ import type BigNumber from 'bignumber.js';
export interface Tranche {
tranche_id: number;
users: string[];
initial_balance: number;
current_balance: number;
cliff_start: number;
duration: number;
tranche_start: Date;
tranche_end: Date;
total_added: BigNumber;
total_removed: BigNumber;
locked_amount: BigNumber;
deposits: Array<TrancheDeposit>;
withdrawals: Array<TrancheWithdrawal>;
users: Array<TrancheUser>;
}
export interface TrancheServiceResponse {
tranches: Tranche[];
export interface TrancheDeposit {
amount: BigNumber;
user: string;
tx: string;
}
export interface TrancheWithdrawal {
amount: BigNumber;
user: string;
tx: string;
}
export interface TrancheUser {
address: string;
deposits: Array<{
amount: BigNumber;
user: string;
tx: string;
tranche_id: number;
}>;
withdrawals: Array<{
amount: BigNumber;
user: string;
tx: string;
tranche_id: number;
}>;
total_tokens: BigNumber;
withdrawn_tokens: BigNumber;
remaining_tokens: BigNumber;
}
export enum TrancheEvents {
Created = 'Tranche_Created',
BalanceAdded = 'Tranche_Balance_Added',
BalanceRemoved = 'Tranche_Balance_Removed',
}
export interface IVegaClaimData {
@@ -31,3 +67,9 @@ export interface IClaimTokenParams {
signature: IVegaClaimSignature;
country: string | null;
}
export interface EpochDetails {
id: string;
startSeconds: BigNumber;
endSeconds: BigNumber;
}
+21 -16
View File
@@ -1,20 +1,16 @@
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { forwardRef } from 'react';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/ui-toolkit';
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import {
addDecimal,
addDecimalsFormatNumber,
getDateTimeFormat,
NumericCell,
t,
} from '@vegaprotocol/react-helpers';
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
import type { CellClassParams } from 'ag-grid-community';
import type { CellClassParams, ValueFormatterParams } from 'ag-grid-community';
import type { AgGridReactProps } from 'ag-grid-react';
import type { Trade } from './trades-data-provider';
import { Side } from '@vegaprotocol/types';
@@ -48,15 +44,21 @@ interface Props extends AgGridReactProps {
onClick?: (price?: string) => void;
}
type TradesTableValueFormatterParams = Omit<
ValueFormatterParams,
'data' | 'value'
> & {
data: Trade | null;
};
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%', background: 'red' }}
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No trades')}
getRowId={({ data }) => data.id}
ref={ref}
defaultColDef={{
flex: 1,
resizable: true,
}}
{...props}
@@ -70,8 +72,10 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Trade, 'price'>) => {
if (!value || !data?.market) {
}: TradesTableValueFormatterParams & {
value: Trade['price'];
}) => {
if (!data?.market) {
return null;
}
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
@@ -106,8 +110,10 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
valueFormatter={({
value,
data,
}: VegaValueFormatterParams<Trade, 'size'>) => {
if (!value || !data?.market) {
}: TradesTableValueFormatterParams & {
value: Trade['size'];
}) => {
if (!data?.market) {
return null;
}
return addDecimalsFormatNumber(
@@ -115,17 +121,16 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
data.market.positionDecimalPlaces
);
}}
cellRenderer={NumericCell}
/>
<AgGridColumn
headerName={t('Created at')}
field="createdAt"
type="rightAligned"
width={170}
cellClass="text-right"
valueFormatter={({
value,
}: VegaValueFormatterParams<Trade, 'createdAt'>) => {
}: TradesTableValueFormatterParams & {
value: Trade['createdAt'];
}) => {
return value && getDateTimeFormat().format(new Date(value));
}}
/>
-4
View File
@@ -2761,16 +2761,12 @@ export type PositionUpdate = {
__typename?: 'PositionUpdate';
/** Average entry price for this position */
averageEntryPrice: Scalars['String'];
/** The total amount of profit and loss that was not transferred due to loss socialisation */
lossSocializationAmount: Scalars['String'];
/** Market relating to this position */
marketId: Scalars['ID'];
/** Open volume (int64) */
openVolume: Scalars['String'];
/** The party holding this position */
partyId: Scalars['ID'];
/** Enum set if the position was closed out or orders were removed because party was distressed */
positionStatus: PositionStatus;
/** Realised Profit and Loss (int64) */
realisedPNL: Scalars['String'];
/** Unrealised Profit and Loss (int64) */
@@ -1,13 +1,5 @@
import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
const pseudoRandom = (seed: number) => {
let value = seed;
return () => {
value = (value * 16807) % 2147483647;
return value / 1000000000;
};
};
import { useEffect, useState } from 'react';
export interface LoaderProps {
size?: 'small' | 'large';
@@ -43,8 +35,6 @@ export const Loader = ({
size === 'small' ? 'w-[15px] h-[15px]' : 'w-[50px] h-[50px]';
const items = size === 'small' ? 9 : 16;
const generate = useMemo(() => pseudoRandom(1), []);
return (
<div
className="flex flex-col items-center pre-loader-center"
@@ -57,7 +47,7 @@ export const Loader = ({
className={itemClasses}
key={i}
style={{
opacity: generate() > 1.5 ? 1 : 0,
opacity: Math.random() > 0.75 ? 1 : 0,
}}
/>
);
+1 -1
View File
@@ -28,7 +28,7 @@ export const Tabs = ({ children, active: activeDefaultId }: TabsProps) => {
if (!isValidElement(child) || child.props.hidden) return null;
const isActive = child.props.id === activeTab;
const triggerClass = classNames(
'relative px-4 py-1 border-r border-default',
'relative px-4 py-2 border-r border-default',
'uppercase',
{
'cursor-default': isActive,
+18
View File
@@ -12,6 +12,24 @@ const CONFIG = [
provider: 'https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
startBlock: 12834524,
},
{
env: 'testnet',
contract: '0xe2deBB240b43EDfEBc9c38B67c0894B9A92Bf07c',
provider: 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
startBlock: 11340808,
},
{
env: 'stagnet3',
contract: '0x9F10cBeEf03A564Fb914c2010c0Cd55E9BB11406',
provider: 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
startBlock: 11790009,
},
{
env: 'devnet',
contract: '0xd1216AAb948f5FC706Df73df6d71c64CcaA8550a',
provider: 'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
startBlock: 11790003,
},
];
const vestingAbi = [