feat(token): 1823 tranches service (#2742)

This commit is contained in:
Dexter Edwards 2023-02-24 14:53:30 +00:00 committed by GitHub
parent 56b5214dbf
commit 92ec7166bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 547 additions and 140020 deletions

View File

@ -1,32 +0,0 @@
name: Generate tranches
on:
schedule:
- cron: '0 */6 * * *'
jobs:
master:
name: Generate Queries
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.15.1
- name: Install root dependencies
run: yarn install
- name: Generate queries
run: node ./scripts/get-tranches.js
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: 'chore: update tranches'
commit_options: '--no-verify --signoff'
skip_fetch: true
skip_checkout: true

View File

@ -1 +0,0 @@
[]

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -1 +0,0 @@
[]

View File

@ -12,6 +12,7 @@ NX_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit suppl
NX_LOCAL_PROVIDER_URL=http://localhost:8545/ NX_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_VEGA_WALLET_URL=http://localhost:1789 NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
#Test configuration variables #Test configuration variables
CYPRESS_FAIRGROUND=false CYPRESS_FAIRGROUND=false

View File

@ -1,4 +1,3 @@
const connectPrompt = '[data-testid="eth-connect-prompt"]';
const connectButton = '[data-testid="connect-to-eth-btn"]'; const connectButton = '[data-testid="connect-to-eth-btn"]';
context( context(
@ -18,7 +17,7 @@ context(
}); });
it('should have connect Eth wallet info', function () { it('should have connect Eth wallet info', function () {
cy.get(connectPrompt).should('be.visible'); cy.get(connectButton).should('be.visible');
}); });
it('should have connect Eth wallet button', function () { it('should have connect Eth wallet button', function () {

View File

@ -12,6 +12,7 @@ NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
#Test configuration variables #Test configuration variables
CYPRESS_FAIRGROUND=false CYPRESS_FAIRGROUND=false

View File

@ -15,6 +15,7 @@ NX_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_VEGA_WALLET_URL=http://localhost:1789 NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
#Test configuration variables #Test configuration variables
CYPRESS_FAIRGROUND=false CYPRESS_FAIRGROUND=false

View File

@ -9,3 +9,4 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz

View File

@ -10,3 +10,4 @@ NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996 NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz

View File

@ -6,3 +6,4 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz

View File

@ -7,3 +7,4 @@ NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz

View File

@ -10,3 +10,4 @@ NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_DELEGATIONS_PAGINATION=50 NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz

View File

@ -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 { useWeb3React } from '@web3-react/core';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useAppState } from '../../contexts/app-state/app-state-context'; import { useAppState } from '../../contexts/app-state/app-state-context';
import { useContracts } from '../../contexts/contracts/contracts-context'; import { useContracts } from '../../contexts/contracts/contracts-context';
import { useGetAssociationBreakdown } from '../../hooks/use-get-association-breakdown'; import { useGetAssociationBreakdown } from '../../hooks/use-get-association-breakdown';
import { useGetUserTrancheBalances } from '../../hooks/use-get-user-tranche-balances'; import { useGetUserBalances } from '../../hooks/use-get-user-balances';
import { useBalances } from '../../lib/balances/balances-store'; import { useBalances } from '../../lib/balances/balances-store';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events'; 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 { interface BalanceManagerProps {
children: ReactElement; children: ReactElement;
@ -24,7 +24,13 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
const { const {
appState: { decimals }, appState: { decimals },
} = useAppState(); } = useAppState();
const { updateBalances: updateStoreBalances } = useBalances(); 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 { config } = useEthereumConfig(); const { config } = useEthereumConfig();
const numberOfConfirmations = config?.confirmations || 0; const numberOfConfirmations = config?.confirmations || 0;
@ -41,10 +47,10 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
numberOfConfirmations numberOfConfirmations
); );
const getUserTrancheBalances = useGetUserTrancheBalances( const getTranches = useTranches((state) => state.getTranches);
account || '', useEffect(() => {
contracts?.vesting getTranches(decimals);
); }, [decimals, getTranches]);
const getAssociationBreakdown = useGetAssociationBreakdown( const getAssociationBreakdown = useGetAssociationBreakdown(
account || '', account || '',
contracts?.staking, contracts?.staking,
@ -54,50 +60,14 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
// update balances on connect to Ethereum // update balances on connect to Ethereum
useEffect(() => { useEffect(() => {
const updateBalances = async () => { const updateBalances = async () => {
if (!account || !config) return; const balances = await getUserBalances();
try { if (balances) {
const [b, w, stats, a] = await Promise.all([ updateStoreBalances(balances);
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(); 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(() => { useEffect(() => {
if (account) { if (account) {

View File

@ -6,27 +6,22 @@ import {
useAppState, useAppState,
} from '../../contexts/app-state/app-state-context'; } from '../../contexts/app-state/app-state-context';
interface EthConnectPrompProps { export const EthConnectPrompt = () => {
children?: React.ReactNode;
}
export const EthConnectPrompt = ({ children }: EthConnectPrompProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appDispatch } = useAppState(); const { appDispatch } = useAppState();
return ( return (
<>
{children}
<Button <Button
variant="primary"
onClick={() => onClick={() =>
appDispatch({ appDispatch({
type: AppStateActionType.SET_ETH_WALLET_OVERLAY, type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
isOpen: true, isOpen: true,
}) })
} }
fill={true}
data-testid="connect-to-eth-btn" data-testid="connect-to-eth-btn"
> >
{t('connectEthWallet')} {t('connectEthWallet')}
</Button> </Button>
</>
); );
}; };

View File

@ -58,6 +58,7 @@ const envName = windowOrDefault('NX_VEGA_ENV') ?? 'local';
export const ENV = { export const ENV = {
// Environment // Environment
tranchesServiceUrl: windowOrDefault('NX_TRANCHES_SERVICE_URL'),
dsn: windowOrDefault('NX_SENTRY_DSN'), dsn: windowOrDefault('NX_SENTRY_DSN'),
urlConnect: TRUTHY.includes(windowOrDefault('NX_ETH_URL_CONNECT')), urlConnect: TRUTHY.includes(windowOrDefault('NX_ETH_URL_CONNECT')),
explorerUrl: windowOrDefault('NX_VEGA_EXPLORER'), explorerUrl: windowOrDefault('NX_VEGA_EXPLORER'),

View File

@ -1,4 +1,3 @@
import type { Tranche } from '@vegaprotocol/smart-contracts';
import React from 'react'; import React from 'react';
import type { BigNumber } from '../../lib/bignumber'; import type { BigNumber } from '../../lib/bignumber';
@ -19,21 +18,7 @@ export interface VegaKey {
meta: Array<{ key: string; value: string }> | null; 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 { export interface AppState {
/** Array of tranche objects */
tranches: Tranche[] | null;
/** Number of decimal places of the VEGA token (18 on Mainnet, 5 on Testnet) */ /** Number of decimal places of the VEGA token (18 on Mainnet, 5 on Testnet) */
decimals: number; decimals: number;
@ -52,9 +37,6 @@ export interface AppState {
/** Whether or not the connect to Ethereum wallet overlay is open */ /** Whether or not the connect to Ethereum wallet overlay is open */
ethConnectOverlay: boolean; 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 */ /** Whether or not the mobile drawer is open. Only relevant on screens smaller than 960 */
drawerOpen: boolean; drawerOpen: boolean;
@ -71,12 +53,10 @@ export enum AppStateActionType {
SET_TOKEN, SET_TOKEN,
SET_ALLOWANCE, SET_ALLOWANCE,
REFRESH_BALANCES, REFRESH_BALANCES,
SET_TRANCHE_DATA,
SET_VEGA_WALLET_OVERLAY, SET_VEGA_WALLET_OVERLAY,
SET_VEGA_WALLET_MANAGE_OVERLAY, SET_VEGA_WALLET_MANAGE_OVERLAY,
SET_ETH_WALLET_OVERLAY, SET_ETH_WALLET_OVERLAY,
SET_DRAWER, SET_DRAWER,
SET_TRANCHE_ERROR,
REFRESH_ASSOCIATED_BALANCES, REFRESH_ASSOCIATED_BALANCES,
SET_ASSOCIATION_BREAKDOWN, SET_ASSOCIATION_BREAKDOWN,
SET_TRANSACTION_OVERLAY, SET_TRANSACTION_OVERLAY,
@ -90,14 +70,6 @@ export type AppStateAction =
totalSupply: BigNumber; totalSupply: BigNumber;
totalAssociated: BigNumber; totalAssociated: BigNumber;
} }
| {
type: AppStateActionType.SET_TRANCHE_DATA;
tranches: Tranche[];
}
| {
type: AppStateActionType.SET_TRANCHE_ERROR;
error: Error | null;
}
| { | {
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY; type: AppStateActionType.SET_VEGA_WALLET_OVERLAY;
isOpen: boolean; isOpen: boolean;

View File

@ -14,11 +14,9 @@ const initialAppState: AppState = {
totalAssociated: new BigNumber(0), totalAssociated: new BigNumber(0),
decimals: 0, decimals: 0,
totalSupply: new BigNumber(0), totalSupply: new BigNumber(0),
tranches: null,
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false, ethConnectOverlay: false,
trancheError: null,
drawerOpen: false, drawerOpen: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',
@ -34,17 +32,6 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
totalAssociated: action.totalAssociated, 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: { case AppStateActionType.SET_VEGA_WALLET_OVERLAY: {
return { return {
...state, ...state,

View File

@ -1,666 +0,0 @@
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;

View File

@ -1,21 +0,0 @@
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 };
}

View File

@ -0,0 +1,41 @@
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]);
};

View File

@ -1,70 +0,0 @@
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]);
};

View File

@ -1,70 +0,0 @@
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,
};
}

View File

@ -1,21 +0,0 @@
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));
}

View File

@ -1,6 +1,11 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { create } from 'zustand'; import { create } from 'zustand';
import type { UserTrancheBalance } from '../../contexts/app-state/app-state-context';
interface UserTrancheBalance {
id: number;
locked: BigNumber;
vested: BigNumber;
}
export interface AssociationBreakdown { export interface AssociationBreakdown {
stakingAssociations: { [vegaKey: string]: BigNumber }; stakingAssociations: { [vegaKey: string]: BigNumber };
@ -13,8 +18,8 @@ export type BalancesStore = {
balanceFormatted: BigNumber; balanceFormatted: BigNumber;
totalVestedBalance: BigNumber; totalVestedBalance: BigNumber;
totalLockedBalance: BigNumber; totalLockedBalance: BigNumber;
walletBalance: BigNumber;
trancheBalances: UserTrancheBalance[]; trancheBalances: UserTrancheBalance[];
walletBalance: BigNumber;
lien: BigNumber; lien: BigNumber;
walletAssociatedBalance: BigNumber; walletAssociatedBalance: BigNumber;
vestingAssociatedBalance: BigNumber; vestingAssociatedBalance: BigNumber;
@ -38,8 +43,8 @@ export const useBalances = create<BalancesStore>((set) => ({
stakingAssociations: {}, stakingAssociations: {},
vestingAssociations: {}, vestingAssociations: {},
}, },
trancheBalances: [],
allowance: new BigNumber(0), allowance: new BigNumber(0),
trancheBalances: [],
totalVestedBalance: new BigNumber(0), totalVestedBalance: new BigNumber(0),
totalLockedBalance: new BigNumber(0), totalLockedBalance: new BigNumber(0),
balanceFormatted: new BigNumber(0), balanceFormatted: new BigNumber(0),

View File

@ -0,0 +1,80 @@
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,
});
}
},
}));

View File

@ -4,7 +4,6 @@ import { format } from 'date-fns';
import React from 'react'; import React from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit'; import { KeyValueTable, KeyValueTableRow } from '@vegaprotocol/ui-toolkit';
import { useContracts } from '../../contexts/contracts/contracts-context'; import { useContracts } from '../../contexts/contracts/contracts-context';
@ -22,6 +21,7 @@ import { TrancheNotFound } from './tranche-not-found';
import { UntargetedClaim } from './untargeted-claim'; import { UntargetedClaim } from './untargeted-claim';
import { Verifying } from './verifying'; import { Verifying } from './verifying';
import type { ClaimAction, ClaimState } from './claim-reducer'; import type { ClaimAction, ClaimState } from './claim-reducer';
import type { Tranche } from '../../lib/tranches/tranches-store';
interface ClaimFlowProps { interface ClaimFlowProps {
state: ClaimState; state: ClaimState;

View File

@ -1,8 +1,8 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { format } from 'date-fns'; import { format } from 'date-fns';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { DATE_FORMAT_LONG } from '../../lib/date-formats'; import { DATE_FORMAT_LONG } from '../../lib/date-formats';
import type { Tranche } from '../../lib/tranches/tranches-store';
interface ClaimInfoProps { interface ClaimInfoProps {
tranche: Tranche; tranche: Tranche;

View File

@ -1,7 +1,5 @@
import React from 'react'; import React from 'react';
import { useAppState } from '../../contexts/app-state/app-state-context'; 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 { useRefreshBalances } from '../../hooks/use-refresh-balances';
import { useSearchParams } from '../../hooks/use-search-params'; import { useSearchParams } from '../../hooks/use-search-params';
import { ClaimError } from './claim-error'; import { ClaimError } from './claim-error';
@ -13,7 +11,7 @@ import {
initialClaimState, initialClaimState,
} from './claim-reducer'; } from './claim-reducer';
import type { Tranche } from '@vegaprotocol/smart-contracts'; import type { Tranche } from '../../lib/tranches/tranches-store';
const Claim = ({ const Claim = ({
address, address,
@ -23,10 +21,8 @@ const Claim = ({
tranches: Tranche[]; tranches: Tranche[];
}) => { }) => {
const params = useSearchParams(); const params = useSearchParams();
const { vesting } = useContracts();
const { appState } = useAppState(); const { appState } = useAppState();
const [state, dispatch] = React.useReducer(claimReducer, initialClaimState); const [state, dispatch] = React.useReducer(claimReducer, initialClaimState);
const getUserTrancheBalances = useGetUserTrancheBalances(address, vesting);
const refreshBalances = useRefreshBalances(address); const refreshBalances = useRefreshBalances(address);
React.useEffect(() => { React.useEffect(() => {
@ -48,10 +44,9 @@ const Claim = ({
// If the claim has been committed refetch the new VEGA balance // If the claim has been committed refetch the new VEGA balance
React.useEffect(() => { React.useEffect(() => {
if (state.claimStatus === ClaimStatus.Finished && address) { if (state.claimStatus === ClaimStatus.Finished && address) {
getUserTrancheBalances();
refreshBalances(); refreshBalances();
} }
}, [address, getUserTrancheBalances, refreshBalances, state.claimStatus]); }, [address, refreshBalances, state.claimStatus]);
if (state.error) { if (state.error) {
return <ClaimError />; return <ClaimError />;

View File

@ -5,7 +5,7 @@ import { EthConnectPrompt } from '../../components/eth-connect-prompt';
import { Heading } from '../../components/heading'; import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader'; import { SplashLoader } from '../../components/splash-loader';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import { useTranches } from '../../hooks/use-tranches'; import { useTranches } from '../../lib/tranches/tranches-store';
import type { RouteChildProps } from '..'; import type { RouteChildProps } from '..';
import Claim from './claim'; import Claim from './claim';
import { ClaimRestricted } from './claim-restricted'; import { ClaimRestricted } from './claim-restricted';
@ -16,7 +16,11 @@ const ClaimIndex = ({ name }: RouteChildProps) => {
useDocumentTitle(name); useDocumentTitle(name);
const { t } = useTranslation(); const { t } = useTranslation();
const { account } = useWeb3React(); const { account } = useWeb3React();
const { tranches, loading, error } = useTranches(); const { tranches, loading, error } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
if (loading || !tranches) { if (loading || !tranches) {
return ( return (
@ -38,13 +42,14 @@ const ClaimIndex = ({ name }: RouteChildProps) => {
if (!account) { if (!account) {
content = ( content = (
<EthConnectPrompt> <>
<p data-testid="eth-connect-prompt"> <p data-testid="eth-connect-prompt">
{t( {t(
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas." "Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas."
)} )}
</p> </p>
</EthConnectPrompt> <EthConnectPrompt />
</>
); );
} else { } else {
content = isRestricted() ? ( content = isRestricted() ? (

View File

@ -51,11 +51,9 @@ const mockAppState: AppState = {
totalAssociated: new BigNumber('50063005'), totalAssociated: new BigNumber('50063005'),
decimals: 18, decimals: 18,
totalSupply: new BigNumber(65000000), totalSupply: new BigNumber(65000000),
tranches: null,
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false, ethConnectOverlay: false,
trancheError: null,
drawerOpen: false, drawerOpen: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',

View File

@ -14,11 +14,9 @@ const mockAppState: AppState = {
totalAssociated: new BigNumber('50063005'), totalAssociated: new BigNumber('50063005'),
decimals: 18, decimals: 18,
totalSupply: mockTotalSupply, totalSupply: mockTotalSupply,
tranches: null,
vegaWalletOverlay: false, vegaWalletOverlay: false,
vegaWalletManageOverlay: false, vegaWalletManageOverlay: false,
ethConnectOverlay: false, ethConnectOverlay: false,
trancheError: null,
drawerOpen: false, drawerOpen: false,
transactionOverlay: false, transactionOverlay: false,
bannerMessage: '', bannerMessage: '',

View File

@ -1,56 +1,81 @@
import { Callout, Intent } from '@vegaprotocol/ui-toolkit'; import { Callout, Intent } from '@vegaprotocol/ui-toolkit';
import { useBalances } from '../../../lib/balances/balances-store'; import { useEffect, useMemo, useState } from 'react';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Link, useNavigate, useOutletContext } from 'react-router-dom'; import { Link, useNavigate, useParams } from 'react-router-dom';
import { AddLockedTokenAddress } from '../../../components/add-locked-token'; import { AddLockedTokenAddress } from '../../../components/add-locked-token';
import { formatNumber } from '../../../lib/format-number'; import { formatNumber } from '../../../lib/format-number';
import { truncateMiddle } from '../../../lib/truncate-middle'; import { truncateMiddle } from '../../../lib/truncate-middle';
import Routes from '../../routes'; import Routes from '../../routes';
import type { RedemptionState } from '../redemption-reducer';
import { Tranche0Table, TrancheTable } from '../tranche-table'; import { Tranche0Table, TrancheTable } from '../tranche-table';
import { VestingTable } from './vesting-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 = () => { export const RedemptionInformation = () => {
const { state, account } = useOutletContext<{
state: RedemptionState;
account: string;
}>();
const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const { const navigate = useNavigate();
balanceFormatted, const tranches = useTranches((state) => state.tranches);
lien, const { address } = useParams<{ address: string }>();
totalVestedBalance, const [userBalances, setUserBalances] = useState<null | UserBalances>();
totalLockedBalance, const getUsersBalances = useGetUserBalances(address);
trancheBalances, useEffect(() => {
} = useBalances(); getUsersBalances().then(setUserBalances);
}, [getUsersBalances]);
const { userTranches } = state; const userTrancheBalances = useUserTrancheBalances(address);
const filteredTranches = useMemo(
const filteredTranches = React.useMemo(
() => () =>
userTranches.filter((tr) => { tranches?.filter((tr) => {
const balance = trancheBalances.find( const balance = userTrancheBalances.find(
({ id }) => id.toString() === tr.tranche_id.toString() ({ id }) => id.toString() === tr.tranche_id.toString()
); );
return ( return (
balance?.locked.isGreaterThan(0) || balance?.vested.isGreaterThan(0) balance?.locked.isGreaterThan(0) || balance?.vested.isGreaterThan(0)
); );
}), }) || [],
[trancheBalances, userTranches] [userTrancheBalances, tranches]
); );
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 = React.useMemo(() => { const zeroTranche = useMemo(() => {
const zeroTranche = trancheBalances.find((t) => t.id === 0); const zeroTranche = userTrancheBalances.find((t) => t.id === 0);
if (zeroTranche && zeroTranche.locked.isGreaterThan(0)) { if (zeroTranche && zeroTranche.locked.isGreaterThan(0)) {
return zeroTranche; return zeroTranche;
} }
return null; return null;
}, [trancheBalances]); }, [userTrancheBalances]);
if (!filteredTranches.length) { 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) {
return ( return (
<section data-testid="redemption-page"> <section data-testid="redemption-page">
<div className="mb-8"> <div className="mb-8">
@ -79,17 +104,17 @@ export const RedemptionInformation = () => {
{t( {t(
'{{address}} has {{balance}} VEGA tokens in {{tranches}} tranches of the vesting contract.', '{{address}} has {{balance}} VEGA tokens in {{tranches}} tranches of the vesting contract.',
{ {
address: truncateMiddle(account), address: truncateMiddle(address),
balance: formatNumber(balanceFormatted), balance: formatNumber(userBalances.balanceFormatted),
tranches: filteredTranches.length, tranches: filteredTranches.length,
} }
)} )}
</p> </p>
<div className="mb-24"> <div className="mb-24">
<VestingTable <VestingTable
associated={lien} associated={userBalances.lien}
locked={totalLockedBalance} locked={totalLocked}
vested={totalVestedBalance} vested={totalVested}
/> />
</div> </div>
{filteredTranches.length ? <h2>{t('Tranche breakdown')}</h2> : null} {filteredTranches.length ? <h2>{t('Tranche breakdown')}</h2> : null}
@ -98,7 +123,7 @@ export const RedemptionInformation = () => {
trancheId={0} trancheId={0}
total={ total={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
trancheBalances.find( userTrancheBalances.find(
({ id }) => id.toString() === zeroTranche.id.toString() ({ id }) => id.toString() === zeroTranche.id.toString()
)!.locked )!.locked
} }
@ -108,22 +133,25 @@ export const RedemptionInformation = () => {
<TrancheTable <TrancheTable
key={tr.tranche_id} key={tr.tranche_id}
tranche={tr} tranche={tr}
lien={lien} lien={userBalances.lien}
locked={ locked={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
trancheBalances.find( userTrancheBalances.find(
({ id }) => id.toString() === tr.tranche_id.toString() ({ id }) => id.toString() === tr.tranche_id.toString()
)!.locked )!.locked
} }
vested={ vested={
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
trancheBalances.find( userTrancheBalances.find(
({ id }) => id.toString() === tr.tranche_id.toString() ({ id }) => id.toString() === tr.tranche_id.toString()
)!.vested )!.vested
} }
totalVested={totalVestedBalance} totalVested={totalVested}
totalLocked={totalLockedBalance} totalLocked={totalLocked}
onClick={() => navigate(`/vesting/${tr.tranche_id}`)} onClick={() =>
navigate(`${Routes.REDEEM}/${address}/${tr.tranche_id}`)
}
address={address}
/> />
))} ))}
<Callout <Callout
@ -132,7 +160,7 @@ export const RedemptionInformation = () => {
intent={Intent.Warning} intent={Intent.Warning}
> >
<p>{t('Find out more about Staking.')}</p> <p>{t('Find out more about Staking.')}</p>
<Link to="/staking" className="underline text-white"> <Link to={Routes.VALIDATORS} className="underline text-white">
{t('Stake VEGA tokens')} {t('Stake VEGA tokens')}
</Link> </Link>
</Callout> </Callout>

View File

@ -12,7 +12,7 @@ export interface VestingTableProps {
} }
const VestingTableIndicatorSquare = ({ colour }: { colour: string }) => ( const VestingTableIndicatorSquare = ({ colour }: { colour: string }) => (
<span className={`bg-${colour} inline-block h-12 w-12 mr-4`} /> <span className={`bg-${colour} inline-block h-4 w-4 mr-1`} />
); );
export const VestingTable = ({ export const VestingTable = ({
@ -65,17 +65,17 @@ export const VestingTable = ({
</KeyValueTable> </KeyValueTable>
<div className="flex border-white border"> <div className="flex border-white border">
<div <div
className="bg-vega-pink h-16" className="bg-vega-pink h-4"
style={{ flex: lockedPercentage.toNumber() }} style={{ flex: lockedPercentage.toNumber() }}
/> />
<div <div
className="bg-vega-green h-16" className="bg-vega-green h-4"
style={{ flex: vestedPercentage.toNumber() }} style={{ flex: vestedPercentage.toNumber() }}
/> />
</div> </div>
<div className="flex h-4 mt-4"> <div className="flex h-1 mt-1">
<div <div
className="bg-vega-yellow h-4" className="bg-vega-yellow h-1"
style={{ flex: stakedPercentage.toNumber() }} style={{ flex: stakedPercentage.toNumber() }}
/> />
<div <div

View File

@ -0,0 +1,54 @@
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;
};

View File

@ -1,38 +0,0 @@
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,
};
}
}

View File

@ -1,52 +1,63 @@
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit'; import {
Button,
Callout,
FormGroup,
Input,
InputError,
Intent,
Splash,
} from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import React from 'react'; import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Outlet } from 'react-router-dom'; import { Outlet, useNavigate, useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { EthConnectPrompt } from '../../components/eth-connect-prompt'; import { EthConnectPrompt } from '../../components/eth-connect-prompt';
import { SplashLoader } from '../../components/splash-loader'; import { SplashLoader } from '../../components/splash-loader';
import { useTranches } from '../../hooks/use-tranches'; import { useTranches } from '../../lib/tranches/tranches-store';
import { useBalances } from '../../lib/balances/balances-store';
import RoutesConfig from '../routes'; import RoutesConfig from '../routes';
import {
initialRedemptionState, interface FormFields {
RedemptionActionType, address: string;
redemptionReducer, }
} from './redemption-reducer';
const RedemptionRouter = () => { const RedemptionRouter = () => {
const { address } = useParams<{ address: string }>();
const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const [state, dispatch] = React.useReducer( const validatePubkey = useCallback(
redemptionReducer, (value: string) => {
initialRedemptionState 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 { trancheBalances } = useBalances();
const { account } = useWeb3React(); const { account } = useWeb3React();
const { tranches, error, loading } = useTranches(); const { tranches, error, loading } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormFields>();
React.useEffect(() => { const onSubmit = useCallback(
const run = (address: string) => { (fields: FormFields) => {
const userTranches = tranches?.filter((t) => navigate(`${RoutesConfig.REDEEM}/${fields.address}`);
t.users.some( },
({ address: a }) => a.toLowerCase() === address.toLowerCase() [navigate]
)
); );
if (userTranches) {
dispatch({
type: RedemptionActionType.SET_USER_TRANCHES,
userTranches,
});
}
};
if (account) {
run(account);
}
}, [account, tranches]);
if (error) { if (error) {
return ( return (
<Callout intent={Intent.Danger} title={t('errorLoadingTranches')}> <Callout intent={Intent.Danger} title={t('errorLoadingTranches')}>
@ -63,30 +74,48 @@ const RedemptionRouter = () => {
); );
} }
if (!account) { if (!address) {
return ( return (
<EthConnectPrompt> <div className="max-w-md">
<p data-testid="eth-connect-prompt"> {!account ? (
{t( <EthConnectPrompt />
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas." ) : (
<Button
fill={true}
variant="primary"
onClick={() => navigate(`${RoutesConfig.REDEEM}/${account}`)}
>
{t('View connected Eth Wallet')}
</Button>
)} )}
</p> <p className="py-4 flex justify-center">{t('OR')}</p>
</EthConnectPrompt> <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>
); );
} }
if (!trancheBalances.length) { return <Outlet />;
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; export default RedemptionRouter;

View File

@ -33,10 +33,10 @@ export const TrancheItem = ({
}: TrancheItemProps) => { }: TrancheItemProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const labelClasses = const labelClasses =
'inline-block uppercase bg-white text-black py-4 px-8 font-mono'; 'inline-block uppercase bg-white text-black py-1 px-2 font-mono';
return ( return (
<section data-testid="tranche-item" className="mb-40"> <section data-testid="tranche-item" className="mb-8">
<div className="flex border-b"> <div className="flex border-b">
{link ? ( {link ? (
<Link to={link}> <Link to={link}>

View File

@ -8,6 +8,7 @@ import { formatNumber } from '../../lib/format-number';
import Routes from '../routes'; import Routes from '../routes';
import { TrancheItem } from './tranche-item'; import { TrancheItem } from './tranche-item';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { useWeb3React } from '@web3-react/core';
export interface TrancheTableProps { export interface TrancheTableProps {
tranche: { tranche: {
@ -22,6 +23,7 @@ export interface TrancheTableProps {
totalLocked: BigNumber; totalLocked: BigNumber;
onClick: () => void; onClick: () => void;
disabled?: boolean; disabled?: boolean;
address: string | null;
} }
export const Tranche0Table = ({ export const Tranche0Table = ({
@ -65,9 +67,11 @@ export const TrancheTable = ({
totalVested, totalVested,
totalLocked, totalLocked,
disabled = false, disabled = false,
address,
}: TrancheTableProps) => { }: TrancheTableProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const total = vested.plus(locked); const total = vested.plus(locked);
const { account: connectedAddress } = useWeb3React();
const trancheFullyLocked = const trancheFullyLocked =
tranche.tranche_start.getTime() > new Date().getTime(); tranche.tranche_start.getTime() > new Date().getTime();
const totalAllTranches = totalVested.plus(totalLocked); const totalAllTranches = totalVested.plus(totalLocked);
@ -93,13 +97,20 @@ export const TrancheTable = ({
amount: reduceAmount, amount: reduceAmount,
}} }}
components={{ components={{
stakeLink: <Link to={`/staking`} />, stakeLink: <Link className="underline" to={Routes.VALIDATORS} />,
disassociateLink: <Link to={`/staking/disassociate`} />, disassociateLink: (
<Link className="underline" to={Routes.DISASSOCIATE} />
),
}} }}
/> />
</div> </div>
); );
} else if (!trancheFullyLocked && redeemable) { } else if (
!trancheFullyLocked &&
redeemable &&
connectedAddress &&
address === connectedAddress
) {
message = ( message = (
<Button onClick={onClick} disabled={disabled}> <Button onClick={onClick} disabled={disabled}>
{t('Redeem unlocked VEGA from tranche {{id}}', { {t('Redeem unlocked VEGA from tranche {{id}}', {

View File

@ -1,7 +1,7 @@
import { useBalances } from '../../../lib/balances/balances-store'; import { useBalances } from '../../../lib/balances/balances-store';
import React from 'react'; import React from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Link, useParams, useOutletContext } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { TransactionCallout } from '../../../components/transaction-callout'; import { TransactionCallout } from '../../../components/transaction-callout';
import { useContracts } from '../../../contexts/contracts/contracts-context'; import { useContracts } from '../../../contexts/contracts/contracts-context';
@ -9,32 +9,36 @@ import {
TransactionActionType, TransactionActionType,
TxState, TxState,
} from '../../../hooks/transaction-reducer'; } from '../../../hooks/transaction-reducer';
import { useGetUserTrancheBalances } from '../../../hooks/use-get-user-tranche-balances';
import { useRefreshBalances } from '../../../hooks/use-refresh-balances'; import { useRefreshBalances } from '../../../hooks/use-refresh-balances';
import { useTransaction } from '../../../hooks/use-transaction'; import { useTransaction } from '../../../hooks/use-transaction';
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number'; import { formatNumber } from '../../../lib/format-number';
import Routes from '../../routes'; import Routes from '../../routes';
import type { RedemptionState } from '../redemption-reducer';
import { TrancheTable } from '../tranche-table'; 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 = () => { export const RedeemFromTranche = () => {
const { state, address } = useOutletContext<{ const { account: address } = useWeb3React();
state: RedemptionState;
address: string;
}>();
const { vesting } = useContracts(); const { vesting } = useContracts();
const { t } = useTranslation(); const { t } = useTranslation();
const { lien, totalVestedBalance, trancheBalances, totalLockedBalance } = const {
useBalances(); appState: { decimals },
const refreshBalances = useRefreshBalances(address); } = useAppState();
const getUserTrancheBalances = useGetUserTrancheBalances(address, vesting); const { lien, totalVestedBalance, totalLockedBalance } = useBalances();
const refreshBalances = useRefreshBalances(address || '');
const { tranches, getTranches } = useTranches((state) => ({
tranches: state.tranches,
getTranches: state.getTranches,
}));
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const numberId = Number(id); const numberId = Number(id);
const { userTranches } = state;
const tranche = React.useMemo( const tranche = React.useMemo(
() => userTranches.find(({ tranche_id }) => tranche_id === numberId), () => tranches?.find(({ tranche_id }) => tranche_id === numberId) || null,
[numberId, userTranches] [numberId, tranches]
); );
const { const {
state: txState, state: txState,
@ -42,7 +46,7 @@ export const RedeemFromTranche = () => {
dispatch: txDispatch, dispatch: txDispatch,
} = useTransaction(() => vesting.withdraw_from_tranche(numberId)); } = useTransaction(() => vesting.withdraw_from_tranche(numberId));
const { token } = useContracts(); const { token } = useContracts();
const trancheBalances = useUserTrancheBalances(address || '');
const redeemedAmount = React.useMemo(() => { const redeemedAmount = React.useMemo(() => {
return ( return (
trancheBalances.find(({ id: bId }) => bId.toString() === id?.toString()) trancheBalances.find(({ id: bId }) => bId.toString() === id?.toString())
@ -55,10 +59,10 @@ export const RedeemFromTranche = () => {
// If the claim has been committed refetch the new VEGA balance // If the claim has been committed refetch the new VEGA balance
React.useEffect(() => { React.useEffect(() => {
if (txState.txState === TxState.Complete && address) { if (txState.txState === TxState.Complete && address) {
getUserTrancheBalances();
refreshBalances(); refreshBalances();
getTranches(decimals);
} }
}, [address, getUserTrancheBalances, refreshBalances, txState.txState]); }, [address, decimals, getTranches, refreshBalances, txState.txState]);
const trancheBalance = React.useMemo(() => { const trancheBalance = React.useMemo(() => {
return trancheBalances.find( return trancheBalances.find(
@ -66,6 +70,10 @@ export const RedeemFromTranche = () => {
); );
}, [id, trancheBalances]); }, [id, trancheBalances]);
if (!address) {
return <EthConnectPrompt />;
}
if ( if (
!tranche || !tranche ||
tranche.total_removed.isEqualTo(tranche.total_added) || tranche.total_removed.isEqualTo(tranche.total_added) ||
@ -147,6 +155,7 @@ export const RedeemFromTranche = () => {
locked={trancheBalance.locked} locked={trancheBalance.locked}
vested={trancheBalance.vested} vested={trancheBalance.vested}
onClick={perform} onClick={perform}
address={address || ''}
/> />
)} )}
</section> </section>

View File

@ -298,6 +298,9 @@ const routerConfig = [
{ {
path: Routes.REDEEM, path: Routes.REDEEM,
element: <LazyRedemption name="Vesting" />, element: <LazyRedemption name="Vesting" />,
children: [
{
path: ':address',
children: [ children: [
{ {
index: true, index: true,
@ -309,6 +312,8 @@ const routerConfig = [
}, },
], ],
}, },
],
},
{ path: 'associate', element: <LazyStakingAssociate /> }, { path: 'associate', element: <LazyStakingAssociate /> },
{ path: 'disassociate', element: <LazyStakingDisassociate /> }, { path: 'disassociate', element: <LazyStakingDisassociate /> },
], ],

View File

@ -16,10 +16,11 @@ export const StakingWalletsContainer = ({
if (!account) { if (!account) {
return ( return (
<EthConnectPrompt> <>
<p>{t('associateInfo1')}</p> <p>{t('associateInfo1')}</p>
<p>{t('associateInfo2')}</p> <p>{t('associateInfo2')}</p>
</EthConnectPrompt> <EthConnectPrompt />
</>
); );
} }

View File

@ -1,35 +1,34 @@
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import { sumCirculatingTokens } from './token-details-circulating'; import { sumCirculatingTokens } from './token-details-circulating';
import type { Tranche } from '@vegaprotocol/smart-contracts';
it('It sums some easy tranches correctly', () => { it('It sums some easy tranches correctly', () => {
const tranches: Partial<Tranche>[] = [ 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) }, { 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 as Tranche[]); const result = sumCirculatingTokens(tranches);
expect(result.toString()).toEqual('300'); expect(result.toString()).toEqual('300');
}); });
it('It sums some longer tranches correctly', () => { it('It sums some longer tranches correctly', () => {
const tranches: Partial<Tranche>[] = [ const tranches = [
{ {
total_added: new BigNumber('10000000000'), total_added: new BigNumber(10000000000),
locked_amount: new BigNumber(0), locked_amount: new BigNumber(0),
}, },
{ total_added: new BigNumber('20'), 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(3000), locked_amount: new BigNumber(3020) },
]; ];
const result = sumCirculatingTokens(tranches as Tranche[]); const result = sumCirculatingTokens(tranches);
expect(result.toString()).toEqual('10000000000'); expect(result.toString()).toEqual('10000000000');
}); });
it('Handles null tranche array', () => { it('Handles null tranche array', () => {
const tranches = null; const tranches = null;
const result = sumCirculatingTokens(tranches as unknown as Tranche[]); const result = sumCirculatingTokens(tranches);
expect(result.toString()).toEqual('0'); expect(result.toString()).toEqual('0');
}); });

View File

@ -1,6 +1,6 @@
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number'; import { formatNumber } from '../../../lib/format-number';
import type { Tranche } from '@vegaprotocol/smart-contracts'; import type { Tranche } from '../../../lib/tranches/tranches-store';
/** /**
* Add together the circulating tokens from all tranches * Add together the circulating tokens from all tranches
@ -9,7 +9,9 @@ import type { Tranche } from '@vegaprotocol/smart-contracts';
* @param decimals decimal places for the formatted result * @param decimals decimal places for the formatted result
* @return The total circulating tokens from all tranches * @return The total circulating tokens from all tranches
*/ */
export function sumCirculatingTokens(tranches: Tranche[] | null): BigNumber { export function sumCirculatingTokens(
tranches: { total_added: BigNumber; locked_amount: BigNumber }[] | null
): BigNumber {
let totalCirculating: BigNumber = new BigNumber(0); let totalCirculating: BigNumber = new BigNumber(0);
tranches?.forEach( tranches?.forEach(

View File

@ -7,7 +7,7 @@ import {
KeyValueTableRow, KeyValueTableRow,
RoundedWrapper, RoundedWrapper,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useTranches } from '../../../hooks/use-tranches'; import { useTranches } from '../../../lib/tranches/tranches-store';
import type { BigNumber } from '../../../lib/bignumber'; import type { BigNumber } from '../../../lib/bignumber';
import { formatNumber } from '../../../lib/format-number'; import { formatNumber } from '../../../lib/format-number';
import { TokenDetailsCirculating } from './token-details-circulating'; import { TokenDetailsCirculating } from './token-details-circulating';
@ -25,7 +25,11 @@ export const TokenDetails = ({
const { ETHERSCAN_URL } = useEnvironment(); const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { tranches, loading, error } = useTranches(); const { tranches, loading, error } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
const { config } = useEthereumConfig(); const { config } = useEthereumConfig();
const { token } = useContracts(); const { token } = useContracts();

View File

@ -4,14 +4,18 @@ import { Outlet } from 'react-router-dom';
import { Heading } from '../../components/heading'; import { Heading } from '../../components/heading';
import { SplashLoader } from '../../components/splash-loader'; import { SplashLoader } from '../../components/splash-loader';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
import { useTranches } from '../../hooks/use-tranches';
import type { RouteChildProps } from '..'; import type { RouteChildProps } from '..';
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit'; import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useTranches } from '../../lib/tranches/tranches-store';
const TrancheRouter = ({ name }: RouteChildProps) => { const TrancheRouter = ({ name }: RouteChildProps) => {
useDocumentTitle(name); useDocumentTitle(name);
const { t } = useTranslation(); const { t } = useTranslation();
const { tranches, error, loading } = useTranches(); const { tranches, error, loading } = useTranches((state) => ({
loading: state.loading,
error: state.error,
tranches: state.tranches,
}));
if (!tranches || loading) { if (!tranches || loading) {
return ( return (

View File

@ -1,48 +1,32 @@
import type { Tranche as ITranche } from '@vegaprotocol/smart-contracts'; import {
import { Link } from '@vegaprotocol/ui-toolkit'; KeyValueTable,
KeyValueTableRow,
Link,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { Link as RouterLink } from 'react-router-dom';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
import { formatNumber } from '@vegaprotocol/react-helpers';
import { useOutletContext } from 'react-router-dom';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { BigNumber } from '../../lib/bignumber';
import { formatNumber } from '../../lib/format-number';
import { TrancheItem } from '../redemption/tranche-item'; import { TrancheItem } from '../redemption/tranche-item';
import Routes from '../routes'; import Routes from '../routes';
import { TrancheLabel } from './tranche-label'; 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 = () => { export const Tranche = () => {
const tranches = useOutletContext<ITranche[]>(); const tranches = useTranches((state) => state.tranches);
const { ETHERSCAN_URL } = useEnvironment(); const { ETHERSCAN_URL } = useEnvironment();
const { t } = useTranslation(); const { t } = useTranslation();
const { trancheId } = useParams<{ trancheId: string }>(); const { trancheId } = useParams<{ trancheId: string; address: string }>();
const { chainId } = useWeb3React(); const { chainId } = useWeb3React();
const tranche = tranches.find( const tranche = tranches?.find(
(tranche) => trancheId && parseInt(trancheId) === tranche.tranche_id (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) { if (!tranche) {
return <Navigate to={Routes.NOT_FOUND} />; return <Navigate to={Routes.NOT_FOUND} />;
} }
@ -67,33 +51,36 @@ export const Tranche = () => {
</div> </div>
<h2>{t('Holders')}</h2> <h2>{t('Holders')}</h2>
{tranche.users.length ? ( {tranche.users.length ? (
<ul role="list"> <RoundedWrapper>
{tranche.users.map((user, i) => { <KeyValueTable>
const unlocked = user.remaining_tokens.times( <KeyValueTableRow>
lockedData?.unlocked || 0 <h1>{t('Ethereum Address')}</h1>
); <h1>{t('View tranche data')}</h1>
const locked = user.remaining_tokens.times(lockedData?.locked || 0); </KeyValueTableRow>
return ( {tranche.users.map((user) => (
<li className="pb-4" key={i}> <KeyValueTableRow key={user}>
{
<Link <Link
title={t('View on Etherscan (opens in a new tab)')} title={t('View on Etherscan (opens in a new tab)')}
href={`${ETHERSCAN_URL}/tx/${user.address}`} href={`${ETHERSCAN_URL}/address/${user}`}
target="_blank" target="_blank"
> >
{user.address} {user}
</Link> </Link>
<TrancheProgressContents> }
<span>{t('Locked')}</span> {
<span>{t('Unlocked')}</span> <RouterLink
</TrancheProgressContents> className="underline"
<TrancheProgressContents> title={t('View vesting information')}
<span>{formatNumber(locked)}</span> to={`${Routes.REDEEM}/${user}`}
<span>{formatNumber(unlocked)}</span> >
</TrancheProgressContents> {t('View vesting information')}
</li> </RouterLink>
); }
})} </KeyValueTableRow>
</ul> ))}
</KeyValueTable>
</RoundedWrapper>
) : ( ) : (
<p>{t('No users')}</p> <p>{t('No users')}</p>
)} )}

View File

@ -1,5 +1,3 @@
import { useOutletContext } from 'react-router-dom';
import type { Tranche } from '@vegaprotocol/smart-contracts';
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -10,6 +8,8 @@ import { TrancheLabel } from './tranche-label';
import { VestingChart } from './vesting-chart'; import { VestingChart } from './vesting-chart';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { useEthereumConfig } from '@vegaprotocol/web3'; import { useEthereumConfig } from '@vegaprotocol/web3';
import type { Tranche } from '../../lib/tranches/tranches-store';
import { useTranches } from '../../lib/tranches/tranches-store';
const trancheMinimum = 10; const trancheMinimum = 10;
@ -17,7 +17,7 @@ const shouldShowTranche = (t: Tranche) =>
!t.total_added.isLessThanOrEqualTo(trancheMinimum); !t.total_added.isLessThanOrEqualTo(trancheMinimum);
export const Tranches = () => { export const Tranches = () => {
const tranches = useOutletContext<Tranche[]>(); const tranches = useTranches((state) => state.tranches);
const [showAll, setShowAll] = React.useState<boolean>(false); const [showAll, setShowAll] = React.useState<boolean>(false);
const { t } = useTranslation(); const { t } = useTranslation();
const { chainId } = useWeb3React(); const { chainId } = useWeb3React();
@ -38,8 +38,8 @@ export const Tranches = () => {
<ul role="list"> <ul role="list">
{(showAll ? tranches : filteredTranches).map((tranche) => { {(showAll ? tranches : filteredTranches).map((tranche) => {
return ( return (
<React.Fragment key={tranche.tranche_id}>
<TrancheItem <TrancheItem
key={tranche.tranche_id}
link={`${tranche.tranche_id}`} link={`${tranche.tranche_id}`}
tranche={tranche} tranche={tranche}
locked={tranche.locked_amount} locked={tranche.locked_amount}
@ -49,14 +49,13 @@ export const Tranches = () => {
<TrancheLabel chainId={chainId} id={tranche.tranche_id} /> <TrancheLabel chainId={chainId} id={tranche.tranche_id} />
} }
/> />
</React.Fragment>
); );
})} })}
</ul> </ul>
) : ( ) : (
<p>{t('No tranches')}</p> <p>{t('No tranches')}</p>
)} )}
<section className="text-center mt-32"> <section className="text-center mt-4">
<ButtonLink onClick={() => setShowAll(!showAll)}> <ButtonLink onClick={() => setShowAll(!showAll)}>
{showAll {showAll
? t( ? t(

View File

@ -2,51 +2,15 @@ import type BigNumber from 'bignumber.js';
export interface Tranche { export interface Tranche {
tranche_id: number; tranche_id: number;
tranche_start: Date; users: string[];
tranche_end: Date; initial_balance: number;
total_added: BigNumber; current_balance: number;
total_removed: BigNumber; cliff_start: number;
locked_amount: BigNumber; duration: number;
deposits: Array<TrancheDeposit>;
withdrawals: Array<TrancheWithdrawal>;
users: Array<TrancheUser>;
} }
export interface TrancheDeposit { export interface TrancheServiceResponse {
amount: BigNumber; tranches: Tranche[];
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 { export interface IVegaClaimData {
@ -67,9 +31,3 @@ export interface IClaimTokenParams {
signature: IVegaClaimSignature; signature: IVegaClaimSignature;
country: string | null; country: string | null;
} }
export interface EpochDetails {
id: string;
startSeconds: BigNumber;
endSeconds: BigNumber;
}

View File

@ -1,986 +0,0 @@
const ethers = require('ethers');
const BigNumber = require('bignumber.js');
const uniq = require('lodash/uniq');
const fs = require('fs');
const MAX_ATTEMPTS = 5;
const CONFIG = [
{
env: 'mainnet',
contract: '0x23d1bfe8fa50a167816fbd79d7932577c06011f4',
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 = [
{
inputs: [
{
internalType: 'address',
name: 'token_v1_address',
type: 'address',
},
{
internalType: 'address',
name: 'token_v2_address',
type: 'address',
},
{
internalType: 'address[]',
name: 'old_addresses',
type: 'address[]',
},
{
internalType: 'address[]',
name: 'new_addresses',
type: 'address[]',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'new_controller',
type: 'address',
},
],
name: 'Controller_Set',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'issuer',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'Issuer_Permitted',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'issuer',
type: 'address',
},
],
name: 'Issuer_Revoked',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
indexed: true,
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'Stake_Deposited',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
indexed: true,
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'Stake_Removed',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: true,
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'Stake_Transferred',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: true,
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'Tranche_Balance_Added',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'user',
type: 'address',
},
{
indexed: true,
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'Tranche_Balance_Removed',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
indexed: false,
internalType: 'uint256',
name: 'cliff_start',
type: 'uint256',
},
{
indexed: false,
internalType: 'uint256',
name: 'duration',
type: 'uint256',
},
],
name: 'Tranche_Created',
type: 'event',
},
{
inputs: [],
name: 'accuracy_scale',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'address_migration',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
internalType: 'address',
name: 'target',
type: 'address',
},
],
name: 'assisted_withdraw_from_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'controller',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'cliff_start',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'duration',
type: 'uint256',
},
],
name: 'create_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'default_tranche_id',
outputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
],
name: 'get_tranche_balance',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
],
name: 'get_vested_for_tranche',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'issue_into_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'move_into_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'issuer',
type: 'address',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'permit_issuer',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'permitted_issuance',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'remove_stake',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'issuer',
type: 'address',
},
],
name: 'revoke_issuer',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'new_controller',
type: 'address',
},
],
name: 'set_controller',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
{
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'stake_balance',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
internalType: 'bytes32',
name: 'vega_public_key',
type: 'bytes32',
},
],
name: 'stake_tokens',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'staking_token',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'total_locked',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'total_staked',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'tranche_count',
outputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
name: 'tranches',
outputs: [
{
internalType: 'uint256',
name: 'cliff_start',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'duration',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'user_stats',
outputs: [
{
internalType: 'uint256',
name: 'total_in_all_tranches',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lien',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'user',
type: 'address',
},
],
name: 'user_total_all_tranches',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'v1_address',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'v1_migrated',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'v2_address',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint8',
name: 'tranche_id',
type: 'uint8',
},
],
name: 'withdraw_from_tranche',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];
function addDecimal(value, decimals) {
return value.dividedBy(Math.pow(10, decimals)).decimalPlaces(decimals);
}
function createUserTransactions(events, decimals) {
return events.map((event) => {
return {
amount: addDecimal(
new BigNumber(event.args?.amount.toString()),
decimals
),
user: event.args?.user,
tranche_id: event.args?.tranche_id,
tx: event.transactionHash,
};
});
}
function getUsersInTranche(
balanceAddedEvents,
balanceRemovedEvents,
addresses,
decimals
) {
return addresses.map((address) => {
const userDeposits = balanceAddedEvents.filter(
(event) => event.args?.user === address
);
const userWithdraws = balanceRemovedEvents.filter(
(event) => event.args?.user === address
);
const deposits = createUserTransactions(userDeposits, decimals);
const withdrawals = createUserTransactions(userWithdraws, decimals);
const total_tokens = deposits.reduce(
(pre, cur) => pre.plus(cur.amount),
new BigNumber(0)
);
const withdrawn_tokens = withdrawals.reduce(
(pre, cur) => pre.plus(cur.amount),
new BigNumber(0)
);
const remaining_tokens = total_tokens.minus(withdrawn_tokens);
return {
address,
deposits,
withdrawals,
total_tokens,
withdrawn_tokens,
remaining_tokens,
};
});
}
function sumFromEvents(events, decimals) {
const amounts = events.map((e) =>
addDecimal(new BigNumber(e.args?.amount.toString()), decimals)
);
// Start with a 0 so if there are none there is no NaN
return BigNumber.sum.apply(null, [new BigNumber(0), ...amounts]);
}
function getLockedAmount(totalAdded, cliffStart, trancheDuration) {
let amount = new BigNumber(0);
const ts = Math.round(new Date().getTime() / 1000);
const tranche_progress = (ts - cliffStart) / trancheDuration;
if (tranche_progress < 0) {
amount = totalAdded;
} else if (tranche_progress < 1) {
amount = totalAdded.times(1 - tranche_progress);
}
return amount;
}
function createTransactions(events, decimals) {
return events.map((event) => {
return {
amount: addDecimal(
new BigNumber(event.args?.amount.toString()),
decimals
),
user: event.args?.user,
tx: event.transactionHash,
};
});
}
function getTranchesFromHistory(
createEvents,
addEvents,
removeEvents,
decimals
) {
return createEvents.map((event) => {
const tranche_id = event.args?.tranche_id;
const balanceAddedEvents = addEvents.filter(
(e) =>
e.event === 'Tranche_Balance_Added' && e.args?.tranche_id === tranche_id
);
const balanceRemovedEvents = removeEvents.filter(
(e) =>
e.event === 'Tranche_Balance_Removed' &&
e.args?.tranche_id === tranche_id
);
//get tranche start and end dates
const tranche_duration = event.args?.duration;
const cliff_start = event.args?.cliff_start;
const tranche_start = new Date(cliff_start.mul(1000).toNumber());
const tranche_end = new Date(
cliff_start.add(tranche_duration).mul(1000).toNumber()
);
// get added and removed values
const total_added = sumFromEvents(balanceAddedEvents, decimals);
const total_removed = sumFromEvents(balanceRemovedEvents, decimals);
// get locked amount
const locked_amount = getLockedAmount(
total_added,
cliff_start,
tranche_duration
);
// get all deposits and withdrawals
const deposits = createTransactions(balanceAddedEvents, decimals);
const withdrawals = createTransactions(balanceRemovedEvents, decimals);
// get all users
const uniqueAddresses = uniq(
balanceAddedEvents.map((event) => event.args?.user)
);
const users = getUsersInTranche(
balanceAddedEvents,
balanceRemovedEvents,
uniqueAddresses,
decimals
);
return {
tranche_id: parseInt(tranche_id),
tranche_start,
tranche_end,
total_added,
total_removed,
locked_amount,
deposits,
withdrawals,
users,
};
});
}
const getBatched = async (filter, contract, provider, startblock) => {
const batchSize = 100000;
const currentBlockNumber = await provider.getBlockNumber();
const batches = Math.ceil(currentBlockNumber / batchSize);
const startIndex = Math.floor(startblock / batchSize);
console.log(
`Processing batches of size ${batches} starting at batch ${startIndex}`
);
let res = [];
for (let index = startIndex; index < batches; index++) {
let events = [];
let attempts = 1;
// Sometimes the queries fail for timeout even on small batch sizes. Some basic retry logic.
while (attempts < MAX_ATTEMPTS) {
try {
events = await contract.queryFilter(
filter,
index * batchSize,
(index + 1) * batchSize - 1
);
break;
} catch {
console.log('retry batch', index);
++attempts;
if (attempts >= MAX_ATTEMPTS) {
throw new Error('Could not get in 4 attempts');
}
}
}
res = [...events, ...res];
console.log(
`Processed blocks ${index * batchSize} to ${
(index + 1) * batchSize - 1
} as part of batch ${index}`
);
}
return res;
};
const run = async (c) => {
const provider = new ethers.providers.JsonRpcProvider({
url: c.provider,
timeout: 1000 * 60 * 1000,
});
const contract = new ethers.Contract(c.contract, vestingAbi, provider);
const [created, added, removed] = await Promise.all([
getBatched(
contract.filters.Tranche_Created(),
contract,
provider,
c.startBlock
),
getBatched(
contract.filters.Tranche_Balance_Added(),
contract,
provider,
c.startBlock
),
getBatched(
contract.filters.Tranche_Balance_Removed(),
contract,
provider,
c.startBlock
),
]);
fs.writeFileSync(
`./apps/static/src/assets/${c.env}-tranches.json`,
JSON.stringify(
getTranchesFromHistory(created, added, removed, 18),
null,
2
),
{ encoding: 'UTF-8' }
);
};
CONFIG.forEach((c) => run(c));