Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8af78190e6 | ||
|
|
6b280d8a70 | ||
|
|
891b441427 | ||
|
|
86739dfaad | ||
|
|
d87adfd7d5 | ||
|
|
7364abbbf2 | ||
|
|
1bd56c9bdb | ||
|
|
a985bc7317 | ||
|
|
1cab9abfd5 | ||
|
|
26a82ab2ab | ||
|
|
69a0830e80 | ||
|
|
4eb6e09bd4 | ||
|
|
a02100b079 | ||
|
|
4bb78e9891 | ||
|
|
79f360dc24 | ||
|
|
a3fcd6b7dc | ||
|
|
23ce480daa | ||
|
|
da209d96b0 | ||
|
|
9166a85a55 | ||
|
|
a5ec1f708f | ||
|
|
3afdc0c8da | ||
|
|
757d545d97 | ||
|
|
fc4d40f3cf | ||
|
|
ff47eda692 | ||
|
|
7483d9ce1d | ||
|
|
f9209cf327 | ||
|
|
420a29680d | ||
|
|
19c9e79647 | ||
|
|
4cd856fcb8 | ||
|
|
649cc61280 | ||
|
|
35f47b75ac | ||
|
|
0697acdac9 | ||
|
|
92ec7166bc | ||
|
|
56b5214dbf | ||
|
|
94509a29c5 | ||
|
|
4e2b6b2d04 | ||
|
|
c29087cc96 | ||
|
|
5bde096977 | ||
|
|
923dc09eb6 | ||
|
|
4fbdb337dd | ||
|
|
6f4a5b9097 | ||
|
|
0c26d99ce2 | ||
|
|
5aedeba4ff | ||
|
|
5789d3496b | ||
|
|
d95fdca0d4 | ||
|
|
e4bf61c2e2 | ||
|
|
0ba54cd8a4 | ||
|
|
eee85c231b | ||
|
|
afdb387742 | ||
|
|
63698fbb80 | ||
|
|
58d8f7857e | ||
|
|
0931730582 | ||
|
|
3aefa10a4f | ||
|
|
efeccc7972 | ||
|
|
95f4e489b2 | ||
|
|
0033f3c5f5 | ||
|
|
abf69786ba | ||
|
|
0a5fb9d917 | ||
|
|
8e82bf43fb | ||
|
|
4bb57e9c47 | ||
|
|
ce6d4cb35d | ||
|
|
647f04656f | ||
|
|
67186bf4c0 |
@@ -18,6 +18,12 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
node-version: 16.15.1
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Generate queries
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
node-version: 16.15.1
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Check PR title
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
node-version: 16.15.1
|
||||
- name: Install root dependencies
|
||||
run: yarn install
|
||||
- name: Generate queries
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Publish libs to npm
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
project:
|
||||
description: 'Monorepo project to publish'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- ui-toolkit
|
||||
- react-helpers
|
||||
- tailwindcss-config
|
||||
- types
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Build & Publish - Tag
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: 'read'
|
||||
actions: 'read'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: User Node.js 16
|
||||
id: Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
- name: Restore node_modules from cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build project
|
||||
run: yarn nx build ${{inputs.project}}
|
||||
- name: Publish project to @vegaprotocol
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
package: dist/libs/${{inputs.project}}/package.json
|
||||
access: public
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
id: Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
node-version: 16.15.1
|
||||
- name: Restore node_modules from cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
|
||||
@@ -8,7 +8,7 @@ context('Asset page', { tags: '@regression' }, () => {
|
||||
|
||||
it('should be able to see full assets list', () => {
|
||||
cy.getAssets().then((assets) => {
|
||||
Object.values(assets).forEach((asset) => {
|
||||
assets.forEach((asset) => {
|
||||
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -25,7 +25,7 @@ context('Asset page', { tags: '@regression' }, () => {
|
||||
});
|
||||
|
||||
cy.getAssets().then((assets) => {
|
||||
Object.values(assets).forEach((asset) => {
|
||||
assets.forEach((asset) => {
|
||||
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -33,7 +33,7 @@ context('Asset page', { tags: '@regression' }, () => {
|
||||
|
||||
it('should open details page when clicked on "View details"', () => {
|
||||
cy.getAssets().then((assets) => {
|
||||
Object.values(assets).forEach((asset) => {
|
||||
assets.forEach((asset) => {
|
||||
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
|
||||
.eq(0)
|
||||
.should('contain.text', 'View details');
|
||||
|
||||
@@ -1,303 +1,15 @@
|
||||
context('Validator page', { tags: '@smoke' }, function () {
|
||||
const validatorMenuHeading = 'a[href="/validators"]';
|
||||
const tendermintDataHeader = '[data-testid="tendermint-header"]';
|
||||
const vegaDataHeader = '[data-testid="vega-header"]';
|
||||
const jsonSection = '.language-json';
|
||||
|
||||
before('Visit validators page and obtain data', function () {
|
||||
cy.visit('/');
|
||||
cy.get(validatorMenuHeading).click();
|
||||
cy.get_validators().as('validators');
|
||||
cy.get_nodes().as('nodes');
|
||||
cy.visit('/validators');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
before('Ensure at least two validators are present', function () {
|
||||
assert.isAtLeast(
|
||||
this.validators.length,
|
||||
2,
|
||||
'Ensuring at least two validators exist'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to see validator page sections', function () {
|
||||
cy.get(vegaDataHeader)
|
||||
.contains('Vega data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see relevant validator information in tendermint section', function () {
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection)
|
||||
.invoke('text')
|
||||
.convert_string_json_to_js_object()
|
||||
.then((validatorsInJson) => {
|
||||
this.validators.forEach((validator, index) => {
|
||||
const validatorInJson =
|
||||
validatorsInJson.result.validators[index];
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.address,
|
||||
validator.address,
|
||||
`Checking that validator address shown in json matches system data`
|
||||
);
|
||||
cy.contains(validator.address).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.pub_key.type,
|
||||
validator.pub_key.type,
|
||||
`Checking that validator public key type shown in json matches system data`
|
||||
);
|
||||
cy.contains(validator.pub_key.type).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.pub_key.value,
|
||||
validator.pub_key.value,
|
||||
`Checking that validator public key value shown in json matches system data`
|
||||
);
|
||||
cy.contains(validator.pub_key.value).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
validatorInJson.voting_power,
|
||||
validator.voting_power,
|
||||
`Checking that validator voting power in json matches system data`
|
||||
);
|
||||
cy.contains(validator.voting_power).should('be.visible');
|
||||
|
||||
// Proposer priority can change frequently mid test
|
||||
// Therefore only checking the field name is present.
|
||||
cy.contains('proposer_priority').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test disabled 2022/11/15 during the 0.62.1 upgrade. The JSON structure changed, and
|
||||
// this test failed. Rather than fix it, it will be replaced when the validator view displays
|
||||
// something useful rather than just dumping out the JSON.
|
||||
xit('should be able to see relevant node information in vega data section', function () {
|
||||
cy.get(vegaDataHeader)
|
||||
.contains('Vega data')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection)
|
||||
.invoke('text')
|
||||
.convert_string_json_to_js_object()
|
||||
.then((nodesInJson) => {
|
||||
this.nodes.forEach((node, index) => {
|
||||
const nodeInJson = nodesInJson.edges[index].node;
|
||||
|
||||
// Vegacapsule shows no info or null for following fields:
|
||||
// name, infoURL, avatarUrl, location, epoch data
|
||||
// Therefore, these values remain unchecked.
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.__typename,
|
||||
node.__typename,
|
||||
`Checking that node __typename shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.__typename).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.id,
|
||||
node.id,
|
||||
`Checking that node id shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.id).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.pubkey,
|
||||
node.pubkey,
|
||||
`Checking that node pubkey shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.pubkey).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.tmPubkey,
|
||||
node.tmPubkey,
|
||||
`Checking that node tmPubkey shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.tmPubkey).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.ethereumAddress,
|
||||
node.ethereumAddress,
|
||||
`Checking that node ethereumAddress shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.ethereumAddress).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.stakedByOperator,
|
||||
node.stakedByOperator,
|
||||
`Checking that node stakedByOperator value shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.stakedByOperator).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.stakedByDelegates,
|
||||
node.stakedByDelegates,
|
||||
`Checking that node stakedByDelegates value shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.stakedByDelegates).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.stakedTotal,
|
||||
node.stakedTotal,
|
||||
`Checking that node stakedTotal shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.stakedTotal).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.pendingStake,
|
||||
node.pendingStake,
|
||||
`Checking that node pendingStake shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.pendingStake).should('be.visible');
|
||||
|
||||
assert.equal(
|
||||
nodeInJson.status,
|
||||
node.status,
|
||||
`Checking that node status shown in json matches system data`
|
||||
);
|
||||
cy.contains(node.status).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see validator page displayed on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(validatorMenuHeading).click();
|
||||
cy.get(vegaDataHeader)
|
||||
.contains('Vega data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.and('is.visible')
|
||||
.next()
|
||||
.within(() => {
|
||||
cy.get(jsonSection).should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(tendermintDataHeader)
|
||||
.contains('Tendermint data')
|
||||
.next()
|
||||
.within(() => {
|
||||
this.validators.forEach((validator) => {
|
||||
cy.contains(validator.address).should('be.visible');
|
||||
cy.contains(validator.pub_key.type).should('be.visible');
|
||||
cy.contains(validator.pub_key.value).should('be.visible');
|
||||
cy.contains(validator.voting_power).should('be.visible');
|
||||
// Proposer priority can change frequently mid test
|
||||
// Therefore only checking the field name is present.
|
||||
cy.contains('proposer_priority').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to switch validator page between light and dark mode', function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
const jsonFields = '.hljs';
|
||||
const sideMenuBackground = '.absolute';
|
||||
|
||||
// Engage dark mode if not allready set
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.then((background_color) => {
|
||||
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
|
||||
cy.get(themeSwitcher).click();
|
||||
});
|
||||
|
||||
// Engage white mode
|
||||
cy.get(themeSwitcher).click();
|
||||
|
||||
// White Mode
|
||||
cy.get(validatorMenuHeading)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSideMenuBackgroundColor);
|
||||
|
||||
// Dark Mode
|
||||
cy.get(themeSwitcher).click();
|
||||
cy.get(validatorMenuHeading)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSideMenuBackgroundColor);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_validators', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `http://localhost:26617/validators`,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
.its(`body.result.validators`)
|
||||
.then(function (response) {
|
||||
let validators = [];
|
||||
response.forEach((account, index) => {
|
||||
validators[index] = account;
|
||||
});
|
||||
return validators;
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_nodes', () => {
|
||||
const mutation =
|
||||
'{nodesConnection { edges { node { id name infoUrl avatarUrl pubkey tmPubkey ethereumAddress \
|
||||
location stakedByOperator stakedByDelegates stakedTotal pendingStake \
|
||||
epochData { total offline online __typename } status name __typename}}}}';
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `http://localhost:3028/query`,
|
||||
body: {
|
||||
query: mutation,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
})
|
||||
.its(`body.data.nodesConnection.edges`)
|
||||
.then(function (response) {
|
||||
let nodes = [];
|
||||
response.forEach((node) => {
|
||||
nodes.push(node);
|
||||
});
|
||||
return nodes;
|
||||
it('should be able to see validator tiles', function () {
|
||||
cy.getNodes().then((nodes) => {
|
||||
nodes.forEach((node) => {
|
||||
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
|
||||
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/query
|
||||
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
|
||||
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.xyz
|
||||
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.xyz/rest
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.validators-testnet.vega.xyz/websocket/
|
||||
@@ -19,12 +19,12 @@ const EmptyList = ({ heading, label }: EmptyListProps) => {
|
||||
|
||||
<div className="mt-4">
|
||||
{heading ? (
|
||||
<h1 className="font-alpha text-xl uppercase text-center leading-relaxed">
|
||||
<h1 className="font-alpha calt text-xl uppercase text-center leading-relaxed">
|
||||
{heading}
|
||||
</h1>
|
||||
) : null}
|
||||
{label ? (
|
||||
<p className="font-alpha text-gray-500 text-center">{label}</p>
|
||||
<p className="font-alpha calt text-gray-500 text-center">{label}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
query ExplorerEpoch($id: ID!) {
|
||||
epoch(id: $id) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
firstBlock
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerFutureEpoch {
|
||||
networkParameter(key: "validators.epoch.length") {
|
||||
value
|
||||
}
|
||||
|
||||
epoch {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# query ExplorerEpoch($id: ID!) {
|
||||
#
|
||||
##### This could be useful for calculating roughly when a future epoch will
|
||||
##### occur, but epoch not exist results in a total error
|
||||
# networkParameter(key: "validators.epoch.length") {
|
||||
# value
|
||||
# }
|
||||
#
|
||||
##### This could be useful for relating where we are in time, but as above
|
||||
##### the total failure caused by epoch(id) not existing
|
||||
##### means this is useful
|
||||
# currentEpoch: epoch {
|
||||
# id
|
||||
# }
|
||||
#
|
||||
# epoch(id: $id) {
|
||||
# id
|
||||
# timestamps {
|
||||
# start
|
||||
# end
|
||||
# firstBlock
|
||||
# lastBlock
|
||||
# }
|
||||
# }
|
||||
#}
|
||||
@@ -0,0 +1,99 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerEpochQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerEpochQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, firstBlock: string, lastBlock?: string | null } } };
|
||||
|
||||
export type ExplorerFutureEpochQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerFutureEpochQuery = { __typename?: 'Query', networkParameter?: { __typename?: 'NetworkParameter', value: string } | null, epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null } } };
|
||||
|
||||
|
||||
export const ExplorerEpochDocument = gql`
|
||||
query ExplorerEpoch($id: ID!) {
|
||||
epoch(id: $id) {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
end
|
||||
firstBlock
|
||||
lastBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerEpochQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerEpochQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerEpochQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerEpochQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerEpochQuery(baseOptions: Apollo.QueryHookOptions<ExplorerEpochQuery, ExplorerEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerEpochQuery, ExplorerEpochQueryVariables>(ExplorerEpochDocument, options);
|
||||
}
|
||||
export function useExplorerEpochLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerEpochQuery, ExplorerEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerEpochQuery, ExplorerEpochQueryVariables>(ExplorerEpochDocument, options);
|
||||
}
|
||||
export type ExplorerEpochQueryHookResult = ReturnType<typeof useExplorerEpochQuery>;
|
||||
export type ExplorerEpochLazyQueryHookResult = ReturnType<typeof useExplorerEpochLazyQuery>;
|
||||
export type ExplorerEpochQueryResult = Apollo.QueryResult<ExplorerEpochQuery, ExplorerEpochQueryVariables>;
|
||||
export const ExplorerFutureEpochDocument = gql`
|
||||
query ExplorerFutureEpoch {
|
||||
networkParameter(key: "validators.epoch.length") {
|
||||
value
|
||||
}
|
||||
epoch {
|
||||
id
|
||||
timestamps {
|
||||
start
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerFutureEpochQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerFutureEpochQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerFutureEpochQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerFutureEpochQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerFutureEpochQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>(ExplorerFutureEpochDocument, options);
|
||||
}
|
||||
export function useExplorerFutureEpochLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>(ExplorerFutureEpochDocument, options);
|
||||
}
|
||||
export type ExplorerFutureEpochQueryHookResult = ReturnType<typeof useExplorerFutureEpochQuery>;
|
||||
export type ExplorerFutureEpochLazyQueryHookResult = ReturnType<typeof useExplorerFutureEpochLazyQuery>;
|
||||
export type ExplorerFutureEpochQueryResult = Apollo.QueryResult<ExplorerFutureEpochQuery, ExplorerFutureEpochQueryVariables>;
|
||||
@@ -0,0 +1,56 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { IconForEpoch } from './epoch';
|
||||
|
||||
const THE_PAST = 'Monday, 17 February 2022 11:44:09';
|
||||
const THE_FUTURE = 'Monday, 17 February 3023 11:44:09';
|
||||
|
||||
describe('IconForEpoch', () => {
|
||||
it('Handles malformed dates', () => {
|
||||
const start = 'This is n0t a d4te';
|
||||
const end = '📅';
|
||||
const screen = render(<IconForEpoch start={start} end={end} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'calendar icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to a calendar icon', () => {
|
||||
const start = null as unknown as string;
|
||||
const end = null as unknown as string;
|
||||
const screen = render(<IconForEpoch start={start} end={end} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'calendar icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('if start and end are both in the future, stick with calendar', () => {
|
||||
const screen = render(<IconForEpoch start={THE_FUTURE} end={THE_FUTURE} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'calendar icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('if start is in the past and end is in the future, this is currently active', () => {
|
||||
const screen = render(<IconForEpoch start={THE_PAST} end={THE_FUTURE} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'circle icon'
|
||||
);
|
||||
});
|
||||
|
||||
it('if start and end are in the paste, this is done', () => {
|
||||
const screen = render(<IconForEpoch start={THE_PAST} end={THE_PAST} />);
|
||||
|
||||
expect(screen.getByRole('img')).toHaveAttribute(
|
||||
'aria-label',
|
||||
'tick-circle icon'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import EpochMissingOverview, { calculateEpochData } from './epoch-missing';
|
||||
import { getSecondsFromInterval } from '@vegaprotocol/react-helpers';
|
||||
const START_DATE_PAST = 'Monday, 17 February 2022 11:44:09';
|
||||
|
||||
describe('getSecondsFromInterval', () => {
|
||||
it('returns 0 for bad data', () => {
|
||||
expect(getSecondsFromInterval(null as unknown as string)).toEqual(0);
|
||||
expect(getSecondsFromInterval('')).toEqual(0);
|
||||
expect(getSecondsFromInterval('🧙')).toEqual(0);
|
||||
expect(getSecondsFromInterval(2 as unknown as string)).toEqual(0);
|
||||
});
|
||||
|
||||
it('parses out months from a capital M', () => {
|
||||
expect(getSecondsFromInterval('2M')).toEqual(5184000);
|
||||
});
|
||||
|
||||
it('parses out days from a capital D', () => {
|
||||
expect(getSecondsFromInterval('1D')).toEqual(86400);
|
||||
});
|
||||
|
||||
it('parses out hours from a lower case h', () => {
|
||||
expect(getSecondsFromInterval('11h')).toEqual(39600);
|
||||
});
|
||||
|
||||
it('parses out minutes from a lower case m', () => {
|
||||
expect(getSecondsFromInterval('10m')).toEqual(600);
|
||||
});
|
||||
|
||||
it('parses out seconds from a lower case s', () => {
|
||||
expect(getSecondsFromInterval('99s')).toEqual(99);
|
||||
});
|
||||
|
||||
it('parses complex examples', () => {
|
||||
expect(getSecondsFromInterval('24h')).toEqual(86400);
|
||||
expect(getSecondsFromInterval('1h30m')).toEqual(5400);
|
||||
expect(getSecondsFromInterval('1D1h30m1s')).toEqual(91801);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateEpochData', () => {
|
||||
it('Handles bad data', () => {
|
||||
const currentEpochId = null as unknown as string;
|
||||
const missingEpochId = null as unknown as string;
|
||||
const epochStart = null as unknown as string;
|
||||
const epochLength = null as unknown as string;
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
expect(res).toHaveProperty('label', 'Missing data');
|
||||
expect(res).toHaveProperty('isInFuture', false);
|
||||
});
|
||||
|
||||
it('Calculates that a bigger epoch number is in the future from basic data', () => {
|
||||
const currentEpochId = '10';
|
||||
const missingEpochId = '20';
|
||||
const epochStart = '';
|
||||
const epochLength = '';
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
expect(res).toHaveProperty('isInFuture', true);
|
||||
});
|
||||
|
||||
it('If it has an epoch length and a start time, it provides an estimate', () => {
|
||||
const currentEpochId = '10';
|
||||
const missingEpochId = '20';
|
||||
const epochStart = START_DATE_PAST;
|
||||
const epochLength = '1s';
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
// 'Estimate: 17/02/2022, 11:44:19 - in less than a minute')
|
||||
expect(res).toHaveProperty('label');
|
||||
expect(res.label).toMatch(/^Estimate/);
|
||||
expect(res.label).toMatch(/in less than a minute$/);
|
||||
});
|
||||
|
||||
it('Provide decent string for past', () => {
|
||||
const currentEpochId = '20';
|
||||
const missingEpochId = '10';
|
||||
const epochStart = START_DATE_PAST;
|
||||
const epochLength = '1s';
|
||||
const res = calculateEpochData(
|
||||
currentEpochId,
|
||||
missingEpochId,
|
||||
epochStart,
|
||||
epochLength
|
||||
);
|
||||
|
||||
// 'Estimate: 17/02/2022, 11:44:19 - in less than a minute')
|
||||
expect(res).toHaveProperty('label');
|
||||
expect(res.label).toMatch(/^Estimate/);
|
||||
expect(res.label).toMatch(/less than a minute ago$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EpochMissingOverview', () => {
|
||||
function renderComponent(missingEpochId: string) {
|
||||
return render(
|
||||
<MockedProvider>
|
||||
<EpochMissingOverview missingEpochId={missingEpochId} />
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
it('renders a - if no id is provided', () => {
|
||||
const n = null as unknown as string;
|
||||
const screen = renderComponent(n);
|
||||
expect(screen.getByTestId('empty')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import { useExplorerFutureEpochQuery } from './__generated__/Epoch';
|
||||
|
||||
import addSeconds from 'date-fns/addSeconds';
|
||||
import formatDistance from 'date-fns/formatDistance';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import isFuture from 'date-fns/isFuture';
|
||||
import { isValidDate } from '@vegaprotocol/react-helpers';
|
||||
import { getSecondsFromInterval } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export type EpochMissingOverviewProps = {
|
||||
missingEpochId?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a set of details for an epoch that has no representation in the
|
||||
* data node. This is primarily for one of two reasons:
|
||||
*
|
||||
* 1. The epoch hasn't happened yet
|
||||
* 2. The epoch happened before a snapshot, and thus the details don't exist
|
||||
*
|
||||
* This component is used when the API has responded with no data for an epoch
|
||||
* by ID, so we already know that we can't display start time/block etc.
|
||||
*
|
||||
* We can detect 1 if the epoch is a higher number than the current epoch
|
||||
* We can detect 2 if the epoch is in the past, but we still get no response.
|
||||
*/
|
||||
const EpochMissingOverview = ({
|
||||
missingEpochId,
|
||||
}: EpochMissingOverviewProps) => {
|
||||
const { data, error, loading } = useExplorerFutureEpochQuery();
|
||||
|
||||
// This should not happen, but it's easily handled
|
||||
if (!missingEpochId) {
|
||||
return <span data-testid="empty">-</span>;
|
||||
}
|
||||
|
||||
// No data should also not happen - we've requested the current epoch. This
|
||||
// could happen at chain restart, but shouldn't. If it does, fallback.
|
||||
if (!data || loading || error) {
|
||||
return <span data-testid="empty">{missingEpochId}</span>;
|
||||
}
|
||||
|
||||
// If we have enough information to predict a future or past block time, let's do it
|
||||
if (
|
||||
!missingEpochId ||
|
||||
!data.epoch.id ||
|
||||
!data.epoch.timestamps.start ||
|
||||
!data?.networkParameter?.value
|
||||
) {
|
||||
return <span data-testid="empty">{missingEpochId}</span>;
|
||||
}
|
||||
|
||||
const { label, isInFuture } = calculateEpochData(
|
||||
data.epoch.id,
|
||||
missingEpochId,
|
||||
data.epoch.timestamps.start,
|
||||
data.networkParameter.value
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip description={<p className="text-xs m-2">{label}</p>}>
|
||||
<p>
|
||||
{isInFuture ? (
|
||||
<Icon name="calendar" className="mr-1" />
|
||||
) : (
|
||||
<Icon name="outdated" className="mr-1" />
|
||||
)}
|
||||
{missingEpochId}
|
||||
</p>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export function calculateEpochData(
|
||||
currentEpochId: string,
|
||||
missingEpochId: string,
|
||||
epochStart: string,
|
||||
epochLength: string
|
||||
) {
|
||||
// Blank string will be return 0 seconds from getSecondsFromInterval
|
||||
const epochLengthInSeconds = getSecondsFromInterval(epochLength);
|
||||
|
||||
if (!epochStart || !epochLength) {
|
||||
// Let's just take a guess
|
||||
return {
|
||||
label: 'Missing data',
|
||||
isInFuture: parseInt(missingEpochId) > parseInt(currentEpochId),
|
||||
};
|
||||
}
|
||||
|
||||
const startFrom = new Date(epochStart);
|
||||
|
||||
const diff = parseInt(missingEpochId) - parseInt(currentEpochId);
|
||||
const futureDate = addSeconds(startFrom, diff * epochLengthInSeconds);
|
||||
|
||||
const label =
|
||||
isValidDate(futureDate) && isValidDate(startFrom)
|
||||
? `Estimate: ${futureDate.toLocaleString()} - ${formatDistance(
|
||||
futureDate,
|
||||
startFrom,
|
||||
{ addSuffix: true }
|
||||
)}`
|
||||
: 'Missing data';
|
||||
|
||||
return {
|
||||
label,
|
||||
isInFuture: isFuture(futureDate),
|
||||
};
|
||||
}
|
||||
|
||||
export default EpochMissingOverview;
|
||||
@@ -0,0 +1,127 @@
|
||||
import { useExplorerEpochQuery } from './__generated__/Epoch';
|
||||
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { BlockLink } from '../links';
|
||||
import { Time } from '../time';
|
||||
import { TimeAgo } from '../time-ago';
|
||||
import EpochMissingOverview from './epoch-missing';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import isPast from 'date-fns/isPast';
|
||||
|
||||
const borderClass =
|
||||
'border-solid border-2 border-vega-dark-200 border-collapse';
|
||||
|
||||
export type EpochOverviewProps = {
|
||||
id?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays detailed information about an epoch, given an ID. This
|
||||
* works for past epochs and current epochs - future epochs, and a
|
||||
* few other situations (see epoch-missing) will not return us
|
||||
* enough information to render this.
|
||||
*
|
||||
* The details are hidden in a tooltip, behind the epoch number
|
||||
*/
|
||||
const EpochOverview = ({ id }: EpochOverviewProps) => {
|
||||
const { data, error, loading } = useExplorerEpochQuery({
|
||||
variables: { id: id || '' },
|
||||
});
|
||||
|
||||
const ti = data?.epoch.timestamps;
|
||||
if (
|
||||
error?.message &&
|
||||
error.message.includes('no resource corresponding to this id')
|
||||
) {
|
||||
return <EpochMissingOverview missingEpochId={id} />;
|
||||
}
|
||||
|
||||
if (!ti || loading || error) {
|
||||
return <span>{id}</span>;
|
||||
}
|
||||
|
||||
const description = (
|
||||
<table className="text-xs m-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Block')}</th>
|
||||
<th className={`text-center ${borderClass}`}>{t('Time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('Start')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.firstBlock ? <BlockLink height={ti.firstBlock} /> : '-'}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
<Time date={ti.start} />
|
||||
<br />
|
||||
<TimeAgo date={ti.start} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={`px-2 ${borderClass}`}>{t('End')}</th>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.lastBlock ? (
|
||||
<BlockLink height={ti.lastBlock} />
|
||||
) : (
|
||||
t('In progress')
|
||||
)}
|
||||
</td>
|
||||
<td className={`px-2 ${borderClass}`}>
|
||||
{ti.end ? (
|
||||
<>
|
||||
<Time date={ti.end} />
|
||||
<br />
|
||||
<TimeAgo date={ti.end} />
|
||||
</>
|
||||
) : (
|
||||
<span>{t('-')}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip description={description}>
|
||||
<p>
|
||||
<IconForEpoch start={ti.start} end={ti.end} />
|
||||
{id}
|
||||
</p>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export type IconForEpochProps = {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Chooses an icon to display next to the epoch number, representing
|
||||
* when the epoch is relative to now (i.e. not yet started, started,
|
||||
* finished)
|
||||
*/
|
||||
export function IconForEpoch({ start, end }: IconForEpochProps) {
|
||||
const startHasPassed = start ? isPast(new Date(start)) : false;
|
||||
const endHasPassed = end ? isPast(new Date(end)) : false;
|
||||
|
||||
let i: IconProps['name'] = 'calendar';
|
||||
|
||||
if (!startHasPassed && !endHasPassed) {
|
||||
i = 'calendar';
|
||||
} else if (startHasPassed && !endHasPassed) {
|
||||
i = 'circle';
|
||||
} else if (startHasPassed && endHasPassed) {
|
||||
i = 'tick-circle';
|
||||
}
|
||||
|
||||
return <Icon name={i} className="mr-2" />;
|
||||
}
|
||||
|
||||
export default EpochOverview;
|
||||
@@ -11,14 +11,14 @@ export const InfoBlock = ({ title, subtitle, tooltipInfo }: InfoBlockProps) => {
|
||||
return (
|
||||
<div className="flex flex-col text-center ">
|
||||
<h3 className="text-4xl">{title}</h3>
|
||||
<p className="text-zinc-800 dark:text-zinc-300">
|
||||
<p className="text-vega-dark-100 dark:text-vega-light-200">
|
||||
{subtitle}
|
||||
{tooltipInfo ? (
|
||||
<Tooltip description={tooltipInfo} align="center">
|
||||
<span>
|
||||
<Icon
|
||||
name="info-sign"
|
||||
className="ml-2 text-zinc-400 dark:text-zinc-600"
|
||||
className="ml-2 text-vega-light-300 dark:text-vega-dark-300"
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -44,12 +44,12 @@ export const InfoPanel = ({
|
||||
text={id}
|
||||
startChars={visibleChars}
|
||||
endChars={visibleChars}
|
||||
className="text-black dark:text-zinc-200"
|
||||
className="text-vega-dark-100 dark:text-vega-light-200"
|
||||
/>
|
||||
) : (
|
||||
<p
|
||||
title={id}
|
||||
className="text-black dark:text-zinc-200 truncate ..."
|
||||
className="text-vega-dark-100 dark:text-vega-light-200 truncate ..."
|
||||
>
|
||||
{id}
|
||||
</p>
|
||||
@@ -70,7 +70,7 @@ export const InfoPanel = ({
|
||||
</div>
|
||||
{copy && (
|
||||
<CopyWithTooltip text={id}>
|
||||
<button className="bg-zinc-100 dark:bg-zinc-900 rounded-sm py-2 px-3">
|
||||
<button className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-sm py-2 px-3">
|
||||
<Icon name="duplicate" />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Routes } from '../../../routes/route-names';
|
||||
export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
|
||||
assetId: string;
|
||||
asDialog?: boolean;
|
||||
showAssetSymbol?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -18,12 +19,22 @@ export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
|
||||
* with a link to the assets modal. If the name does not come back
|
||||
* it will use the ID instead.
|
||||
*/
|
||||
export const AssetLink = ({ assetId, asDialog, ...props }: AssetLinkProps) => {
|
||||
export const AssetLink = ({
|
||||
assetId,
|
||||
asDialog,
|
||||
showAssetSymbol = false,
|
||||
...props
|
||||
}: AssetLinkProps) => {
|
||||
const { data: asset } = useAssetDataProvider(assetId);
|
||||
|
||||
const open = useAssetDetailsDialogStore((state) => state.open);
|
||||
const navigate = useNavigate();
|
||||
const label = asset?.name ? asset.name : assetId;
|
||||
const label = asset
|
||||
? showAssetSymbol
|
||||
? asset?.symbol
|
||||
: asset?.name
|
||||
: assetId;
|
||||
|
||||
return (
|
||||
<ButtonLink
|
||||
data-testid="asset-link"
|
||||
|
||||
@@ -48,7 +48,7 @@ const MarketLink = ({
|
||||
<Link
|
||||
className="underline"
|
||||
{...props}
|
||||
to={`/${Routes.MARKETS}#${id}`}
|
||||
to={`/${Routes.MARKETS}/${id}`}
|
||||
title={id}
|
||||
>
|
||||
{label}
|
||||
@@ -56,7 +56,7 @@ const MarketLink = ({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
|
||||
<Link className="underline" {...props} to={`/${Routes.MARKETS}/${id}`}>
|
||||
<Hash text={id} />
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import PartyLink from './party-link';
|
||||
|
||||
describe('PartyLink', () => {
|
||||
it('renders Network for 000.000 party', () => {
|
||||
const zeroes =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
const screen = render(<PartyLink id={zeroes} />);
|
||||
expect(screen.getByText('Network')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Network for network party', () => {
|
||||
const screen = render(<PartyLink id="network" />);
|
||||
expect(screen.getByText('Network')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders ID with no link for invalid party', () => {
|
||||
const screen = render(<PartyLink id="this-party-is-not-valid" />);
|
||||
expect(screen.getByTestId('invalid-party')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('links a valid party to the party page', () => {
|
||||
const aValidParty =
|
||||
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e';
|
||||
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<PartyLink id={aValidParty} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const el = screen.getByText(aValidParty);
|
||||
expect(el).toBeInTheDocument();
|
||||
// The text should be a link that points to the party's page
|
||||
expect(el.parentElement?.tagName).toEqual('A');
|
||||
expect(el.parentElement?.getAttribute('href')).toContain(aValidParty);
|
||||
});
|
||||
});
|
||||
@@ -3,19 +3,47 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import Hash from '../hash';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { isValidPartyId } from '../../../routes/parties/id/components/party-id-error';
|
||||
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const SPECIAL_CASE_NETWORK_ID =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000';
|
||||
export const SPECIAL_CASE_NETWORK = 'network';
|
||||
|
||||
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
truncate?: boolean;
|
||||
};
|
||||
|
||||
const PartyLink = ({ id, ...props }: PartyLinkProps) => {
|
||||
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
|
||||
// Some transactions will involve the 'network' party, which is alias for '000...000'
|
||||
// The party page does not handle this nicely, so in this case we render the word 'Network'
|
||||
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
|
||||
return (
|
||||
<span className="font-mono" data-testid="network">
|
||||
{t('Network')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// If the party doesn't look correct, there's no point in linking to id. Just render
|
||||
// the ID as it was given to us
|
||||
if (!isValidPartyId(id)) {
|
||||
return (
|
||||
<span className="font-mono" data-testid="invalid-party">
|
||||
{id}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
{...props}
|
||||
to={`/${Routes.PARTIES}/${id}`}
|
||||
>
|
||||
<Hash text={id} />
|
||||
<Hash text={truncate ? truncateMiddle(id) : id} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,8 +22,7 @@ export const MarketDetails = ({
|
||||
}: {
|
||||
market: MarketInfoNoCandlesQuery['market'];
|
||||
}) => {
|
||||
const assetSymbol =
|
||||
market?.tradableInstrument.instrument.product?.settlementAsset.symbol;
|
||||
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
|
||||
const assetId = useMemo(
|
||||
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
|
||||
[market]
|
||||
@@ -39,6 +38,10 @@ export const MarketDetails = ({
|
||||
const assetDecimals =
|
||||
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
||||
|
||||
const liquidityPriceRange = formatNumberPercentage(
|
||||
new BigNumber(market.lpPriceRange).times(100)
|
||||
);
|
||||
|
||||
const panels = [
|
||||
{
|
||||
title: t('Key details'),
|
||||
@@ -179,32 +182,40 @@ export const MarketDetails = ({
|
||||
{
|
||||
title: t('Liquidity price range'),
|
||||
content: (
|
||||
<MarketInfoTable
|
||||
noBorder={false}
|
||||
data={{
|
||||
liquidityPriceRange: formatNumberPercentage(
|
||||
new BigNumber(market.lpPriceRange).times(100)
|
||||
),
|
||||
LPVolumeMin:
|
||||
market.data?.midPrice &&
|
||||
`${addDecimalsFormatNumber(
|
||||
new BigNumber(1)
|
||||
.minus(market.lpPriceRange)
|
||||
.times(market.data.midPrice)
|
||||
.toString(),
|
||||
market.decimalPlaces
|
||||
)} ${assetSymbol}`,
|
||||
LPVolumeMax:
|
||||
market.data?.midPrice &&
|
||||
`${addDecimalsFormatNumber(
|
||||
new BigNumber(1)
|
||||
.plus(market.lpPriceRange)
|
||||
.times(market.data.midPrice)
|
||||
.toString(),
|
||||
market.decimalPlaces
|
||||
)} ${assetSymbol}`,
|
||||
}}
|
||||
></MarketInfoTable>
|
||||
<>
|
||||
<p className="text-xs mb-4">
|
||||
{`For liquidity orders to count towards a commitment, they must be
|
||||
within the liquidity monitoring bounds.`}
|
||||
</p>
|
||||
<p className="text-xs mb-4">
|
||||
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
|
||||
price.`}
|
||||
</p>
|
||||
<MarketInfoTable
|
||||
noBorder={false}
|
||||
data={{
|
||||
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
|
||||
lowestPrice:
|
||||
market.data?.midPrice &&
|
||||
`${addDecimalsFormatNumber(
|
||||
new BigNumber(1)
|
||||
.minus(market.lpPriceRange)
|
||||
.times(market.data.midPrice)
|
||||
.toString(),
|
||||
market.decimalPlaces
|
||||
)} ${quoteUnit}`,
|
||||
highestPrice:
|
||||
market.data?.midPrice &&
|
||||
`${addDecimalsFormatNumber(
|
||||
new BigNumber(1)
|
||||
.plus(market.lpPriceRange)
|
||||
.times(market.data.midPrice)
|
||||
.toString(),
|
||||
market.decimalPlaces
|
||||
)} ${quoteUnit}`,
|
||||
}}
|
||||
></MarketInfoTable>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -237,7 +248,7 @@ export const MarketDetails = ({
|
||||
<>
|
||||
{panels.map((p) => (
|
||||
<div className="mb-3">
|
||||
<h2 className="font-alpha text-xl">{p.title}</h2>
|
||||
<h2 className="font-alpha calt text-xl">{p.title}</h2>
|
||||
{p.content}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -83,7 +83,7 @@ const NestedDataListItem = ({
|
||||
});
|
||||
|
||||
const titleClasses = classNames({
|
||||
'text-xl pl-4 border-l-4 font-alpha': hasChildren,
|
||||
'text-xl pl-4 border-l-4 font-alpha calt': hasChildren,
|
||||
'text-base font-medium whitespace-nowrap': !hasChildren,
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface DeterministicOrderDetailsProps {
|
||||
}
|
||||
|
||||
export const wrapperClasses =
|
||||
'grid lg:grid-cols-1 flex items-center max-w-xl border border-zinc-200 dark:border-zinc-800 rounded-md pv-2 ph-5 mb-5';
|
||||
'grid lg:grid-cols-1 flex items-center max-w-xl border border-vega-light-200 dark:border-vega-dark-150 rounded-md pv-2 ph-5 mb-5';
|
||||
|
||||
/**
|
||||
* This component renders the *current* details for an order
|
||||
@@ -42,7 +42,7 @@ const DeterministicOrderDetails = ({
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
{t('Order not found')}
|
||||
</h2>
|
||||
<p className="text-gray-500 mb-12">
|
||||
<p className="text-vega-light-400 mb-12">
|
||||
{t('No order created from this transaction')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const PageHeader = ({
|
||||
copy = false,
|
||||
className,
|
||||
}: PageHeaderProps) => {
|
||||
const titleClasses = 'text-4xl xl:text-5xl uppercase font-alpha';
|
||||
const titleClasses = 'text-4xl xl:text-5xl uppercase font-alpha calt';
|
||||
return (
|
||||
<header className={className}>
|
||||
<span className={`${titleClasses} block`}>{prefix}</span>
|
||||
@@ -38,7 +38,7 @@ export const PageHeader = ({
|
||||
</h2>
|
||||
{copy && (
|
||||
<CopyWithTooltip data-testid="copy-to-clipboard" text={title}>
|
||||
<button className="bg-zinc-100 dark:bg-zinc-900 rounded-sm py-2 px-3">
|
||||
<button className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-sm py-2 px-3">
|
||||
<Icon name="duplicate" className="" />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './page-actions';
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export const PageActions = ({
|
||||
children,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className="flex flex-row items-start gap-1" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,45 @@
|
||||
import classNames from 'classnames';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { RouteTitle } from '../route-title';
|
||||
import { PageActions } from './page-actions';
|
||||
|
||||
type PageTitleProps = {
|
||||
/**
|
||||
* The page title
|
||||
* (also sets the document title unless overwritten by
|
||||
* `documentTitle` property)
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* The react node that consists of CTA buttons, links, etc.
|
||||
*/
|
||||
actions?: ReactNode;
|
||||
/**
|
||||
* Overwrites the document title
|
||||
*/
|
||||
documentTitle?: Parameters<typeof useDocumentTitle>[0];
|
||||
} & Omit<HTMLAttributes<HTMLHeadingElement>, 'children'>;
|
||||
|
||||
export const PageTitle = ({
|
||||
title,
|
||||
actions,
|
||||
documentTitle,
|
||||
className,
|
||||
...props
|
||||
}: PageTitleProps) => {
|
||||
useDocumentTitle(
|
||||
documentTitle && documentTitle.length > 0 ? documentTitle : [title]
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col md:flex-row gap-1 justify-between content-start mb-8"
|
||||
data-testid="page-title"
|
||||
>
|
||||
<RouteTitle className={classNames('mb-1', className)} {...props}>
|
||||
{title}
|
||||
</RouteTitle>
|
||||
{actions && <PageActions>{actions}</PageActions>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ interface PanelProps {
|
||||
export const Panel = ({ children, className }: PanelProps) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'border border-zinc-200 dark:border-zinc-800 rounded-md p-5 mb-5',
|
||||
'border border-vega-light-150 dark:border-vega-dark-150 rounded-md p-5 mb-5',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -13,7 +13,7 @@ export const RouteTitle = ({
|
||||
...props
|
||||
}: RouteTitleProps) => {
|
||||
const classes = classnames(
|
||||
'font-alpha',
|
||||
'font-alpha calt',
|
||||
'text-4xl',
|
||||
'uppercase',
|
||||
'mb-8',
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||
import { AssetLink } from '../links';
|
||||
|
||||
export type DecimalSource = 'ASSET';
|
||||
|
||||
export type SizeInAssetProps = {
|
||||
assetId: string;
|
||||
size?: string | number;
|
||||
decimalSource?: DecimalSource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a market ID and an order size it will fetch the market
|
||||
* order size, and format the size accordingly
|
||||
*/
|
||||
const SizeInAsset = ({
|
||||
assetId,
|
||||
size,
|
||||
decimalSource = 'ASSET',
|
||||
}: SizeInAssetProps) => {
|
||||
const { data } = useAssetDataProvider(assetId);
|
||||
if (!size) {
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
let label = size;
|
||||
|
||||
if (data) {
|
||||
if (decimalSource === 'ASSET' && data.decimals) {
|
||||
label = addDecimalsFormatNumber(size, data.decimals);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<p>
|
||||
<span>{label}</span>
|
||||
<AssetLink assetId={assetId} showAssetSymbol={true} asDialog={true} />
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default SizeInAsset;
|
||||
@@ -3,7 +3,7 @@ import { useExplorerMarketQuery } from '../links/market-link/__generated__/Marke
|
||||
|
||||
export type DecimalSource = 'MARKET';
|
||||
|
||||
export type PriceInMarketProps = {
|
||||
export type SizeInMarketProps = {
|
||||
marketId: string;
|
||||
size?: string | number;
|
||||
decimalSource?: DecimalSource;
|
||||
@@ -17,7 +17,7 @@ const SizeInMarket = ({
|
||||
marketId,
|
||||
size,
|
||||
decimalSource = 'MARKET',
|
||||
}: PriceInMarketProps) => {
|
||||
}: SizeInMarketProps) => {
|
||||
const { data } = useExplorerMarketQuery({
|
||||
variables: { id: marketId },
|
||||
fetchPolicy: 'cache-first',
|
||||
|
||||
@@ -11,7 +11,7 @@ export const StatusMessage = ({
|
||||
className,
|
||||
...props
|
||||
}: StatusMessageProps) => {
|
||||
const classes = classnames('font-alpha text-2xl mb-28', className);
|
||||
const classes = classnames('font-alpha calt text-2xl mb-28', className);
|
||||
return (
|
||||
<h3 className={classes} {...props}>
|
||||
{children}
|
||||
|
||||
@@ -12,7 +12,7 @@ export const SubHeading = ({
|
||||
...props
|
||||
}: SubHeadingProps) => {
|
||||
const classes = classnames(
|
||||
'font-alpha',
|
||||
'font-alpha calt',
|
||||
'text-2xl',
|
||||
'uppercase',
|
||||
'mt-8 mb-2',
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
type Deposit = components['schemas']['vegaBuiltinAssetDeposit'];
|
||||
|
||||
const fullMock: Deposit = {
|
||||
partyId: 'party123',
|
||||
partyId: '0000000000000000000000000000000000000000000000000000000000000001',
|
||||
vegaAssetId: 'asset123',
|
||||
amount: 'amount123',
|
||||
};
|
||||
|
||||
+3
-2
@@ -10,7 +10,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
type Withdrawal = components['schemas']['vegaBuiltinAssetWithdrawal'];
|
||||
|
||||
const fullMock: Withdrawal = {
|
||||
partyId: 'party123',
|
||||
partyId: '0000000000000000000000000000000000000000000000000000000000000001',
|
||||
vegaAssetId: 'asset123',
|
||||
amount: 'amount123',
|
||||
};
|
||||
@@ -67,11 +67,12 @@ describe('Chain Event: Builtin asset withdrawal', () => {
|
||||
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
|
||||
const partyLink = screen.getByText(`${fullMock.partyId}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
if (!partyLink.parentElement) {
|
||||
throw new Error('Party link does not exist');
|
||||
}
|
||||
|
||||
expect(partyLink.parentElement.tagName).toEqual('A');
|
||||
expect(partyLink.parentElement.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.partyId}`
|
||||
|
||||
@@ -10,10 +10,12 @@ import { TxDetailsChainEventDeposit } from './tx-erc20-deposit';
|
||||
type Deposit = components['schemas']['vegaERC20Deposit'];
|
||||
|
||||
const fullMock: Deposit = {
|
||||
vegaAssetId: 'asset123',
|
||||
vegaAssetId:
|
||||
'0000000000000000000000000000000000000000000000000000000000000002',
|
||||
amount: 'amount123',
|
||||
sourceEthereumAddress: 'eth123',
|
||||
targetPartyId: 'vega123',
|
||||
targetPartyId:
|
||||
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||
};
|
||||
|
||||
describe('Chain Event: ERC20 asset deposit', () => {
|
||||
|
||||
@@ -13,7 +13,8 @@ const fullMock: Deposit = {
|
||||
amount: 'amount123',
|
||||
blockTime: 'block123',
|
||||
ethereumAddress: 'eth123',
|
||||
vegaPublicKey: 'vega123',
|
||||
vegaPublicKey:
|
||||
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||
};
|
||||
|
||||
describe('Chain Event: Stake deposit', () => {
|
||||
|
||||
@@ -13,7 +13,8 @@ const fullMock: Remove = {
|
||||
amount: 'amount123',
|
||||
blockTime: 'block123',
|
||||
ethereumAddress: 'eth123',
|
||||
vegaPublicKey: 'vega123',
|
||||
vegaPublicKey:
|
||||
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||
};
|
||||
|
||||
describe('Chain Event: Stake remove', () => {
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ export const ChainResponseCode = ({
|
||||
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
|
||||
|
||||
return (
|
||||
<div title={`Response code: ${code} - ${label}`}>
|
||||
<div title={`Response code: ${code} - ${label}`} className="inline-block">
|
||||
<span
|
||||
className="mr-2"
|
||||
aria-label={isSuccess ? 'Success' : 'Warning'}
|
||||
|
||||
@@ -14,10 +14,14 @@ interface TxDetailsSharedProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
|
||||
// A transitional property used in some complex TX types to display more detailed type information
|
||||
// than the shared component can derive
|
||||
hideTypeRow?: boolean;
|
||||
}
|
||||
|
||||
// Applied to all header cells
|
||||
const sharedHeaderProps = {
|
||||
export const sharedHeaderProps = {
|
||||
// Ensures that multi line contents still have the header aligned to the first line
|
||||
className: 'align-top',
|
||||
};
|
||||
@@ -31,6 +35,7 @@ export const TxDetailsShared = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
hideTypeRow = false,
|
||||
}: TxDetailsSharedProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
@@ -41,10 +46,12 @@ export const TxDetailsShared = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{txData.type}</TableCell>
|
||||
</TableRow>
|
||||
{hideTypeRow === false ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{txData.type}</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
|
||||
<TableCell>
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { PartyLink } from '../../../../links';
|
||||
import {
|
||||
SPECIAL_CASE_NETWORK,
|
||||
SPECIAL_CASE_NETWORK_ID,
|
||||
} from '../../../../links/party-link/party-link';
|
||||
import SizeInAsset from '../../../../size-in-asset/size-in-asset';
|
||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { headerClasses, wrapperClasses } from '../transfer-details';
|
||||
import type { Transfer } from '../transfer-details';
|
||||
|
||||
interface TransferParticipantsProps {
|
||||
transfer: Transfer;
|
||||
from: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a box containing the To, From and amount of a
|
||||
* transfer. This is shown for all transfers, including
|
||||
* recurring and reward transfers.
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
* @param from The sender is not in the transaction, but comes from the Transaction submitter
|
||||
*/
|
||||
export function TransferParticipants({
|
||||
transfer,
|
||||
from,
|
||||
}: TransferParticipantsProps) {
|
||||
// This mapping is required as the global account types require a type to be set, while
|
||||
// the underlying protobufs allow for every field to be undefined.
|
||||
const fromAcct =
|
||||
transfer.fromAccountType &&
|
||||
transfer.fromAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
|
||||
? AccountType[transfer.fromAccountType]
|
||||
: AccountType.ACCOUNT_TYPE_GENERAL;
|
||||
const fromAccountTypeLabel = transfer.fromAccountType
|
||||
? AccountTypeMapping[fromAcct]
|
||||
: 'Unknown';
|
||||
|
||||
const toAcct =
|
||||
transfer.toAccountType &&
|
||||
transfer.toAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
|
||||
? AccountType[transfer.toAccountType]
|
||||
: AccountType.ACCOUNT_TYPE_GENERAL;
|
||||
const toAccountTypeLabel = transfer.fromAccountType
|
||||
? AccountTypeMapping[toAcct]
|
||||
: 'Unknown';
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Transfer')}</h2>
|
||||
<div className="relative block rounded-lg py-6 text-center">
|
||||
<PartyLink id={from} truncate={true} />
|
||||
<Tooltip
|
||||
description={
|
||||
<p>{`${t('From account')}: ${fromAccountTypeLabel}`}</p>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Icon className="ml-3" name={'bank-account'} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<br />
|
||||
|
||||
{/* This block of divs is used to render the inset arrow containing the transfer amount */}
|
||||
<div className="bg-vega-light-200 dark:vega-dark-200 flex items-center justify-center my-4 relative">
|
||||
<div className="bg-vega-light-200 dark:bg-vega-dark-200 border w-full pt-5 pb-3 px-3 border-vega-light-200 dark:border-vega-dark-150 relative">
|
||||
<div className="text-xs z-20 relative leading-none">
|
||||
{transfer.asset ? (
|
||||
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Empty divs for the top arrow and the bottom arrow of the transfer inset */}
|
||||
<div className="z-10 absolute top-[-1px] left-1/2 w-4 h-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 9"
|
||||
className="fill-vega-light-100 dark:fill-black"
|
||||
>
|
||||
<path d="M0,0L8,9l8,-9Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="z-10 absolute bottom-[-16px] left-1/2 w-4 h-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 9"
|
||||
className="fill-vega-light-100 dark:fill-vega-dark-200"
|
||||
>
|
||||
<path d="M0,0L8,9l8,-9Z" />
|
||||
</svg>
|
||||
</div>
|
||||
{/*
|
||||
<div className="z-10 absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 rotate-45 w-4 h-4 dark:border-vega-dark-200 border-vega-light-200 bg-white dark:bg-black border-r border-b"></div>
|
||||
<div className="z-10 absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-1/2 rotate-45 w-4 h-4 border-vega-light-200 dark:border-vega-dark-200 bg-vega-light-200 dark:bg-vega-dark-200 border-r border-b"></div>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TransferRecurringRecipient to={transfer.to} />
|
||||
<Tooltip
|
||||
description={<p>{`${t('To account')}: ${toAccountTypeLabel}`}</p>}
|
||||
>
|
||||
<span>
|
||||
<Icon className="ml-3" name={'bank-account'} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TransferRecurringRecipientProps {
|
||||
to?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the transfer is to 000...000, then this is a transfer to the
|
||||
* Rewards Pool rather than the network. This component saves this
|
||||
* logic from complicating the To section of the participants block
|
||||
*
|
||||
* @param markets String[] IDs of markets for this dispatch strategy
|
||||
*/
|
||||
export function TransferRecurringRecipient({
|
||||
to,
|
||||
}: TransferRecurringRecipientProps) {
|
||||
if (to === SPECIAL_CASE_NETWORK || to === SPECIAL_CASE_NETWORK_ID) {
|
||||
return <span>{t('Rewards pool')}</span>;
|
||||
} else if (to) {
|
||||
return <PartyLink id={to} truncate={true} />;
|
||||
}
|
||||
|
||||
// Fallback should not happen
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import EpochOverview from '../../../../epoch-overview/epoch';
|
||||
import { useExplorerFutureEpochQuery } from '../../../../epoch-overview/__generated__/Epoch';
|
||||
import { headerClasses, wrapperClasses } from '../transfer-details';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Recurring } from '../transfer-details';
|
||||
|
||||
interface TransferRepeatProps {
|
||||
recurring: Recurring;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer for a transfer. These can vary quite
|
||||
* widely, essentially every field can be null.
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferRepeat({ recurring }: TransferRepeatProps) {
|
||||
const { data } = useExplorerFutureEpochQuery();
|
||||
|
||||
if (!recurring) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Active epochs')}</h2>
|
||||
<div className="relative block rounded-lg py-6 text-center p-6">
|
||||
<p>
|
||||
<EpochOverview id={recurring.startEpoch} />
|
||||
</p>
|
||||
<p className="leading-10 my-2">
|
||||
<IconForEpoch
|
||||
start={recurring.startEpoch}
|
||||
end={recurring.endEpoch}
|
||||
current={data?.epoch.id}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{recurring.endEpoch ? (
|
||||
<EpochOverview id={recurring.endEpoch} />
|
||||
) : (
|
||||
<span>{t('Forever')}</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export type IconForTransferProps = {
|
||||
current?: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pick an icon rto represent the state of the repetition for this recurring
|
||||
* transfer. It can be unstarted, in progress, or complete.
|
||||
*
|
||||
* @param start The epoch in which the transfer first occurs
|
||||
* @param end The last epoch in which the transfer occurs
|
||||
* @param current The current epoch
|
||||
*/
|
||||
function IconForEpoch({ start, end, current }: IconForTransferProps) {
|
||||
let i: IconProps['name'] = 'repeat';
|
||||
|
||||
if (current && start && end) {
|
||||
const startEpoch = parseInt(start);
|
||||
const endEpoch = parseInt(end);
|
||||
const currentEpoch = parseInt(current);
|
||||
|
||||
if (currentEpoch > endEpoch) {
|
||||
// If we've finished
|
||||
i = 'updated';
|
||||
} else if (startEpoch > currentEpoch) {
|
||||
// If we haven't yet started
|
||||
i = 'time';
|
||||
} else if (startEpoch < currentEpoch && endEpoch > currentEpoch) {
|
||||
i = 'repeat';
|
||||
}
|
||||
}
|
||||
|
||||
return <Icon name={i} className="mr-2" />;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { AssetLink, MarketLink } from '../../../../links';
|
||||
import { headerClasses, wrapperClasses } from '../transfer-details';
|
||||
import type { components } from '../../../../../../types/explorer';
|
||||
import type { Recurring } from '../transfer-details';
|
||||
import { DispatchMetricLabels } from '@vegaprotocol/types';
|
||||
|
||||
export type Metric = components['schemas']['vegaDispatchMetric'];
|
||||
export type Strategy = components['schemas']['vegaDispatchStrategy'];
|
||||
|
||||
const metricLabels = {
|
||||
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
|
||||
...DispatchMetricLabels,
|
||||
};
|
||||
|
||||
interface TransferRewardsProps {
|
||||
recurring: Recurring;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer for a transfer. These can vary quite
|
||||
* widely, essentially every field can be null.
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferRewards({ recurring }: TransferRewardsProps) {
|
||||
const metric =
|
||||
recurring?.dispatchStrategy?.metric || 'DISPATCH_METRIC_UNSPECIFIED';
|
||||
|
||||
if (!recurring || !recurring.dispatchStrategy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<h2 className={headerClasses}>{t('Reward metrics')}</h2>
|
||||
<ul className="relative block rounded-lg py-6 text-center p-6">
|
||||
{recurring.dispatchStrategy.assetForMetric ? (
|
||||
<li>
|
||||
<strong>{t('Asset')}</strong>:{' '}
|
||||
<AssetLink assetId={recurring.dispatchStrategy.assetForMetric} />
|
||||
</li>
|
||||
) : null}
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>: {metricLabels[metric]}
|
||||
</li>
|
||||
{recurring.dispatchStrategy.markets &&
|
||||
recurring.dispatchStrategy.markets.length > 0 ? (
|
||||
<li>
|
||||
<strong>{t('Markets in scope')}</strong>:
|
||||
<ul>
|
||||
{recurring.dispatchStrategy.markets.map((m) => (
|
||||
<li key={m}>
|
||||
<MarketLink id={m} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
) : null}
|
||||
<li>
|
||||
<strong>{t('Factor')}</strong>: {recurring.factor}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TransferRecurringStrategyProps {
|
||||
strategy: Strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple renderer for a dispatch strategy in a recurring transfer
|
||||
*
|
||||
* @param strategy Dispatch strategy object
|
||||
*/
|
||||
export function TransferRecurringStrategy({
|
||||
strategy,
|
||||
}: TransferRecurringStrategyProps) {
|
||||
if (!strategy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{strategy.assetForMetric ? (
|
||||
<li>
|
||||
<strong>{t('Asset for metric')}</strong>:{' '}
|
||||
<AssetLink assetId={strategy.assetForMetric} />
|
||||
</li>
|
||||
) : null}
|
||||
<li>
|
||||
<strong>{t('Metric')}</strong>: {strategy.metric}
|
||||
</li>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { TransferRepeat } from './blocks/transfer-repeat';
|
||||
import { TransferRewards } from './blocks/transfer-rewards';
|
||||
import { TransferParticipants } from './blocks/transfer-participants';
|
||||
|
||||
export type Recurring = components['schemas']['v1RecurringTransfer'];
|
||||
export type Metric = components['schemas']['vegaDispatchMetric'];
|
||||
|
||||
export const wrapperClasses =
|
||||
'border border-vega-light-150 dark:border-vega-dark-200 rounded-md pv-2 mb-5 w-full sm:w-1/4 min-w-[200px] ';
|
||||
export const headerClasses =
|
||||
'bg-solid bg-vega-light-150 dark:bg-vega-dark-150 border-vega-light-150 text-center text-xl py-2 font-alpha calt';
|
||||
|
||||
export type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
interface TransferDetailsProps {
|
||||
transfer: Transfer;
|
||||
from: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderer for a transfer. These can vary quite
|
||||
* widely, essentially every field can be null.
|
||||
*
|
||||
* @param transfer A recurring transfer object
|
||||
*/
|
||||
export function TransferDetails({ transfer, from }: TransferDetailsProps) {
|
||||
const recurring = transfer.recurring;
|
||||
|
||||
return (
|
||||
<div className="flex gap-5 flex-wrap">
|
||||
<TransferParticipants from={from} transfer={transfer} />
|
||||
{recurring ? <TransferRepeat recurring={transfer.recurring} /> : null}
|
||||
{recurring && recurring.dispatchStrategy ? (
|
||||
<TransferRewards recurring={transfer.recurring} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
|
||||
import { TxDetailsIssueSignatures } from './tx-issue-signatures';
|
||||
import { TxDetailsNodeAnnounce } from './tx-node-announce';
|
||||
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
|
||||
import { TxDetailsTransfer } from './tx-transfer';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -108,6 +109,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsUndelegate;
|
||||
case 'State Variable Proposal':
|
||||
return TxDetailsStateVariable;
|
||||
case 'Transfer Funds':
|
||||
return TxDetailsTransfer;
|
||||
default:
|
||||
return TxDetailsGeneric;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableRow, TableCell, TableWithTbody } from '../../table';
|
||||
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import { PartyLink } from '../../links';
|
||||
import SizeInAsset from '../../size-in-asset/size-in-asset';
|
||||
import { TransferDetails } from './transfer/transfer-details';
|
||||
import {
|
||||
SPECIAL_CASE_NETWORK,
|
||||
SPECIAL_CASE_NETWORK_ID,
|
||||
} from '../../links/party-link/party-link';
|
||||
|
||||
type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
interface TxDetailsNodeAnnounceProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the details of a transfer. Broadly there are three distinct
|
||||
* types of transfer, listed below in order of complexity:
|
||||
*
|
||||
* - A one off transfer
|
||||
* - A recurring transfer
|
||||
* - A recurring rewards pool transfer
|
||||
*
|
||||
* One off transfers are simple, really the important data is the amount
|
||||
* and who sent it to whom. This is rendered as one distinct box.
|
||||
*
|
||||
* A recurring transfer has two components - the same as above, and an
|
||||
* additional box that shows details about how it repeats. This is defined
|
||||
* as a start epoch and and end epoch. The Epoch/MissingEpoch components
|
||||
* render slightly differently depending on if the epoch is in the past,
|
||||
* current or in the future.
|
||||
*
|
||||
* Finally rewards pool transfers get the two boxes above, and an additional
|
||||
* one that describes how the reward is distributed.
|
||||
*
|
||||
* The information is split up in to three boxes to allow for the reuse across
|
||||
* all the types of transfer above.
|
||||
*/
|
||||
export const TxDetailsTransfer = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsNodeAnnounceProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const transfer: Transfer = txData.command.transfer;
|
||||
if (!transfer) {
|
||||
return <>{t('Transfer data missing')}</>;
|
||||
}
|
||||
|
||||
const from = txData.submitter;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{getTypeLabelForTransfer(transfer)}</TableCell>
|
||||
</TableRow>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
hideTypeRow={true}
|
||||
/>
|
||||
{from ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('From')}</TableCell>
|
||||
<TableCell>
|
||||
<PartyLink id={from} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
{transfer.to ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('To')}</TableCell>
|
||||
<TableCell>
|
||||
<PartyLink id={transfer.to} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
{transfer.asset && transfer.amount ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Amount')}</TableCell>
|
||||
<TableCell>
|
||||
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
<TransferDetails from={from} transfer={transfer} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a string description of this transfer
|
||||
* @param txData A full transfer
|
||||
* @returns string Transfer label
|
||||
*/
|
||||
export function getTypeLabelForTransfer(tx: Transfer) {
|
||||
if (tx.to === SPECIAL_CASE_NETWORK || tx.to === SPECIAL_CASE_NETWORK_ID) {
|
||||
if (tx.recurring && tx.recurring.dispatchStrategy) {
|
||||
return 'Reward top up transfer';
|
||||
}
|
||||
// Else: we don't know that it's a reward transfer, so let's not guess
|
||||
} else if (tx.recurring) {
|
||||
return 'Recurring transfer';
|
||||
} else if (tx.oneOff) {
|
||||
// Currently redundant, but could be used to indicate something more specific
|
||||
return 'Transfer';
|
||||
}
|
||||
|
||||
return 'Transfer';
|
||||
}
|
||||
@@ -14,14 +14,17 @@ interface StringMap {
|
||||
// Using https://github.com/vegaprotocol/protos/blob/e0f646ce39aab1fc66a9200ceec0262306d3beb3/commands/transaction.go#L93 as a reference
|
||||
const displayString: StringMap = {
|
||||
OrderSubmission: 'Order Submission',
|
||||
'Submit Order': 'Order',
|
||||
OrderCancellation: 'Order Cancellation',
|
||||
OrderAmendment: 'Order Amendment',
|
||||
VoteSubmission: 'Vote Submission',
|
||||
WithdrawSubmission: 'Withdraw Submission',
|
||||
Withdraw: 'Withdraw Request',
|
||||
LiquidityProvisionSubmission: 'Liquidity Provision',
|
||||
LiquidityProvisionCancellation: 'Liquidity Cancellation',
|
||||
LiquidityProvisionAmendment: 'Liquidity Amendment',
|
||||
LiquidityProvisionSubmission: 'LP order',
|
||||
'Liquidity Provision Order': 'LP order',
|
||||
LiquidityProvisionCancellation: 'LP cancel',
|
||||
LiquidityProvisionAmendment: 'LP update',
|
||||
'Amend LiquidityProvision Order': 'Amend LP',
|
||||
ProposalSubmission: 'Governance Proposal',
|
||||
AnnounceNode: 'Node Announcement',
|
||||
NodeVote: 'Node Vote',
|
||||
@@ -31,10 +34,11 @@ const displayString: StringMap = {
|
||||
DelegateSubmission: 'Delegation',
|
||||
UndelegateSubmission: 'Undelegation',
|
||||
KeyRotateSubmission: 'Key Rotation',
|
||||
StateVariableProposal: 'State Variable Proposal',
|
||||
StateVariableProposal: 'State Variable',
|
||||
Transfer: 'Transfer',
|
||||
CancelTransfer: 'Cancel Transfer',
|
||||
ValidatorHeartbeat: 'Validator Heartbeat',
|
||||
ValidatorHeartbeat: 'Heartbeat',
|
||||
'Batch Market Instructions': 'Batch',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -132,7 +136,8 @@ export function getLabelForChainEvent(
|
||||
export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
||||
let type = displayString[orderType] || orderType;
|
||||
|
||||
let colours = 'text-white dark:text-white bg-zinc-800 dark:bg-zinc-800';
|
||||
let colours =
|
||||
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-150';
|
||||
|
||||
// This will get unwieldy and should probably produce a different colour of tag
|
||||
if (type === 'Chain Event' && !!command?.chainEvent) {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { getTypeLabelForTransfer } from './details/tx-transfer';
|
||||
import type { components } from '../../../types/explorer';
|
||||
|
||||
type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
describe('TX: Transfer: getLabelForTransfer', () => {
|
||||
it('renders reward top up label if the TO party is 000', () => {
|
||||
const mock: Transfer = {
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
recurring: {
|
||||
dispatchStrategy: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer');
|
||||
});
|
||||
|
||||
it('renders reward top up label if the TO party is network', () => {
|
||||
const mock = {
|
||||
to: 'network',
|
||||
recurring: {
|
||||
dispatchStrategy: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Reward top up transfer');
|
||||
});
|
||||
|
||||
it('renders recurring label if the tx has a recurring property', () => {
|
||||
const mock: Transfer = {
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000001',
|
||||
recurring: {
|
||||
startEpoch: '0',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Recurring transfer');
|
||||
});
|
||||
|
||||
it('renders one off label if the tx has a oneOff property', () => {
|
||||
const mock: Transfer = {
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000001',
|
||||
oneOff: {
|
||||
deliverOn: '0',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Transfer');
|
||||
});
|
||||
|
||||
it('renders one off label otherwise', () => {
|
||||
const mock: Transfer = {
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000001',
|
||||
};
|
||||
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Transfer');
|
||||
});
|
||||
});
|
||||
@@ -37,7 +37,9 @@ export const TxsInfiniteListItem = ({
|
||||
className="text-sm col-span-10 xl:col-span-3 leading-none"
|
||||
data-testid="tx-hash"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">ID: </span>
|
||||
<span className="xl:hidden uppercase text-vega-dark-300">
|
||||
ID:
|
||||
</span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.TX}/${toHex(hash)}`}
|
||||
text={hash}
|
||||
@@ -49,7 +51,9 @@ export const TxsInfiniteListItem = ({
|
||||
className="text-sm col-span-10 xl:col-span-3 leading-none"
|
||||
data-testid="pub-key"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">By: </span>
|
||||
<span className="xl:hidden uppercase text-vega-dark-300">
|
||||
By:
|
||||
</span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.PARTIES}/${submitter}`}
|
||||
text={submitter}
|
||||
@@ -64,7 +68,9 @@ export const TxsInfiniteListItem = ({
|
||||
className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"
|
||||
data-testid="tx-block"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">Block: </span>
|
||||
<span className="xl:hidden uppercase text-vega-dark-300">
|
||||
Block:
|
||||
</span>
|
||||
<TruncatedLink
|
||||
to={`/${Routes.BLOCKS}/${block}`}
|
||||
text={block}
|
||||
@@ -76,7 +82,7 @@ export const TxsInfiniteListItem = ({
|
||||
className="text-sm col-span-2 xl:col-span-1 leading-none flex items-center"
|
||||
data-testid="tx-success"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">
|
||||
<span className="xl:hidden uppercase text-vega-dark-300">
|
||||
Success:
|
||||
</span>
|
||||
{isNumber(code) ? (
|
||||
|
||||
@@ -94,7 +94,7 @@ export const TxsInfiniteList = ({
|
||||
|
||||
return (
|
||||
<div className={className} data-testid="transactions-list">
|
||||
<div className="xl:grid grid-cols-10 w-full mb-3 hidden text-zinc-500 uppercase">
|
||||
<div className="xl:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
|
||||
<div className="col-span-3">
|
||||
<span className="hidden xl:inline">Transaction </span>
|
||||
<span>ID</span>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { DATA_SOURCES } from '../config';
|
||||
|
||||
type PubKey = {
|
||||
type: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type Validator = {
|
||||
address: string;
|
||||
pub_key: PubKey;
|
||||
voting_power: string;
|
||||
proposer_priority: string;
|
||||
};
|
||||
|
||||
type Result = {
|
||||
block_height: string;
|
||||
validators: Validator[];
|
||||
count: string;
|
||||
total: string;
|
||||
};
|
||||
|
||||
type TendermintValidatorsResponse = {
|
||||
jsonrpc: string;
|
||||
id: number;
|
||||
result: Result;
|
||||
};
|
||||
|
||||
export const useTendermintValidators = (pollInterval?: number) => {
|
||||
const {
|
||||
state: { data, loading, error },
|
||||
refetch,
|
||||
} = useFetch<TendermintValidatorsResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/validators`
|
||||
);
|
||||
|
||||
const ref = useRef<TendermintValidatorsResponse | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
if (data) ref.current = data;
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval =
|
||||
pollInterval &&
|
||||
setInterval(() => {
|
||||
refetch();
|
||||
}, pollInterval);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [pollInterval, refetch]);
|
||||
|
||||
return { data: ref.current, loading, error, refetch };
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
@@ -8,6 +7,7 @@ import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
|
||||
import { useState } from 'react';
|
||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||
|
||||
export const AssetPage = () => {
|
||||
useDocumentTitle(['Assets']);
|
||||
@@ -22,18 +22,25 @@ export const AssetPage = () => {
|
||||
return (
|
||||
<>
|
||||
<section className="relative">
|
||||
<RouteTitle data-testid="asset-header">{title}</RouteTitle>
|
||||
<PageTitle
|
||||
data-testid="asset-header"
|
||||
title={title}
|
||||
actions={
|
||||
<Button
|
||||
disabled={!data}
|
||||
size="xs"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
{t('View JSON')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<AsyncRenderer
|
||||
noDataMessage={t('Asset not found')}
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
>
|
||||
<div className="absolute top-0 right-0">
|
||||
<Button size="xs" onClick={() => setDialogOpen(true)}>
|
||||
{t('View JSON')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-full relative">
|
||||
<AssetDetailsTable asset={data as AssetFieldsFragment} />
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,12 @@ import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { MarketDetails } from '../../components/markets/market-details';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import compact from 'lodash/compact';
|
||||
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
|
||||
import { marketInfoNoCandlesDataProvider } from '@vegaprotocol/market-info';
|
||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||
|
||||
export const MarketPage = () => {
|
||||
useScrollToLocation();
|
||||
@@ -40,20 +40,25 @@ export const MarketPage = () => {
|
||||
return (
|
||||
<>
|
||||
<section className="relative">
|
||||
<RouteTitle data-testid="markets-heading">
|
||||
{data?.market?.tradableInstrument.instrument.name}
|
||||
</RouteTitle>
|
||||
<PageTitle
|
||||
data-testid="markets-heading"
|
||||
title={data?.market?.tradableInstrument.instrument.name || ''}
|
||||
actions={
|
||||
<Button
|
||||
disabled={!data?.market}
|
||||
size="xs"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
{t('View JSON')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<AsyncRenderer
|
||||
noDataMessage={t('This chain has no markets')}
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
>
|
||||
<div className="absolute top-0 right-0">
|
||||
<Button size="xs" onClick={() => setDialogOpen(true)}>
|
||||
{t('View JSON')}
|
||||
</Button>
|
||||
</div>
|
||||
<MarketDetails market={data?.market} />
|
||||
</AsyncRenderer>
|
||||
</section>
|
||||
|
||||
@@ -68,7 +68,7 @@ const Party = () => {
|
||||
return (
|
||||
<section>
|
||||
<h1
|
||||
className="font-alpha uppercase font-xl mb-4 text-zinc-800 dark:text-zinc-200"
|
||||
className="font-alpha calt uppercase font-xl mb-4 text-vega-dark-100 dark:text-vega-light-100"
|
||||
data-testid="parties-header"
|
||||
>
|
||||
{t('Public key')}
|
||||
|
||||
@@ -9,7 +9,7 @@ import Party from './parties';
|
||||
import { Parties } from './parties/home';
|
||||
import { Party as PartySingle } from './parties/id';
|
||||
import Txs from './txs';
|
||||
import Validators from './validators';
|
||||
import { ValidatorsPage } from './validators';
|
||||
import Genesis from './genesis';
|
||||
import { Block } from './blocks/id';
|
||||
import { Blocks } from './blocks/home';
|
||||
@@ -125,7 +125,7 @@ const validators: Route[] = flags.validators
|
||||
path: Routes.VALIDATORS,
|
||||
name: 'Validators',
|
||||
text: t('Validators'),
|
||||
element: <Validators />,
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
@@ -1,47 +1 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { SubHeading } from '../../components/sub-heading';
|
||||
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { DATA_SOURCES } from '../../config';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type { TendermintValidatorsResponse } from './tendermint-validator-response';
|
||||
import { useExplorerNodesQuery } from './__generated__/Nodes';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
|
||||
const Validators = () => {
|
||||
const {
|
||||
state: { data: validators },
|
||||
} = useFetch<TendermintValidatorsResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/validators`
|
||||
);
|
||||
|
||||
useDocumentTitle(['Validators']);
|
||||
|
||||
const { data } = useExplorerNodesQuery();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="validators-header">{t('Validators')}</RouteTitle>
|
||||
{data ? (
|
||||
<>
|
||||
<SubHeading data-testid="vega-header">{t('Vega data')}</SubHeading>
|
||||
<SyntaxHighlighter data-testid="vega-data" data={data} />
|
||||
</>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
{validators ? (
|
||||
<>
|
||||
<SubHeading data-testid="tendermint-header">
|
||||
{t('Tendermint data')}
|
||||
</SubHeading>
|
||||
<SyntaxHighlighter data-testid="tendermint-data" data={validators} />
|
||||
</>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Validators;
|
||||
export * from './validators-page';
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
export interface PubKey {
|
||||
type: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Validator {
|
||||
address: string;
|
||||
pub_key: PubKey;
|
||||
voting_power: string;
|
||||
proposer_priority: string;
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
block_height: string;
|
||||
validators: Validator[];
|
||||
count: string;
|
||||
total: string;
|
||||
}
|
||||
|
||||
export interface TendermintValidatorsResponse {
|
||||
jsonrpc: string;
|
||||
id: number;
|
||||
result: Result;
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
import { countryCodeToFlagEmoji, t } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
Button,
|
||||
CopyWithTooltip,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
Tooltip,
|
||||
truncateMiddle,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useExplorerNodesQuery } from './__generated__/Nodes';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import compact from 'lodash/compact';
|
||||
import { useTendermintValidators } from '../../hooks/use-tendermint-validators';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
|
||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {
|
||||
ContractAddressLink,
|
||||
DApp,
|
||||
TOKEN_VALIDATOR,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import classNames from 'classnames';
|
||||
import { NodeStatus, NodeStatusMapping } from '@vegaprotocol/types';
|
||||
|
||||
type RateProps = {
|
||||
value: BigNumber | number | undefined;
|
||||
className?: string;
|
||||
colour?: 'green' | 'blue' | 'pink' | 'orange';
|
||||
asPoint?: boolean;
|
||||
zero?: boolean;
|
||||
};
|
||||
const Rate = ({
|
||||
value,
|
||||
className,
|
||||
colour = 'blue',
|
||||
asPoint = false,
|
||||
}: RateProps) => {
|
||||
const val =
|
||||
typeof value === 'undefined'
|
||||
? new BigNumber(0)
|
||||
: typeof value === 'number'
|
||||
? new BigNumber(value)
|
||||
: value;
|
||||
const bar = asPoint
|
||||
? {
|
||||
right: `${val.times(100).toFixed(2)}%`,
|
||||
}
|
||||
: { width: `${val.times(100).toFixed(2)}%` };
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'overflow-hidden rounded h-[9px] flex w-full bg-vega-light-100 dark:bg-vega-dark-150',
|
||||
{ 'pl-[9px]': asPoint },
|
||||
{
|
||||
'bg-gradient-to-l to-vega-orange-500 dark:to-vega-orange-550 from-vega-light-100 dark:from-vega-dark-150':
|
||||
asPoint && colour === 'orange',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="relative w-full">
|
||||
<div
|
||||
className={classNames(
|
||||
'w-[9px] h-[9px] absolute top-0 right-0 transition-all rounded',
|
||||
{
|
||||
'bg-vega-green-550 dark:bg-vega-green-500': colour === 'green',
|
||||
'bg-vega-blue-550 dark:bg-vega-blue-500': colour === 'blue',
|
||||
'bg-vega-pink-550 dark:bg-vega-pink-500': colour === 'pink',
|
||||
'bg-vega-orange-550 dark:bg-vega-orange-500': colour === 'orange',
|
||||
},
|
||||
'bg-vega',
|
||||
className
|
||||
)}
|
||||
style={bar}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ValidatorsPage = () => {
|
||||
useDocumentTitle(['Validators']);
|
||||
|
||||
const { data: tmData } = useTendermintValidators(5000);
|
||||
const { data, loading, error, refetch } = useExplorerNodesQuery();
|
||||
|
||||
const validators = compact(data?.nodesConnection.edges?.map((e) => e?.node));
|
||||
|
||||
// voting power
|
||||
const powers = compact(tmData?.result.validators).map(
|
||||
(v) => new BigNumber(v.voting_power)
|
||||
);
|
||||
const totalVotingPower = BigNumber.sum(...powers);
|
||||
|
||||
// proposer priority
|
||||
const priorities = compact(tmData?.result.validators).map(
|
||||
(v) => new BigNumber(v.proposer_priority)
|
||||
);
|
||||
const absoluteProposerPriority = BigNumber.max(
|
||||
...priorities.map((p) => p.abs())
|
||||
);
|
||||
|
||||
const tmValidators = useMemo(() => {
|
||||
return tmData?.result.validators.map((v) => {
|
||||
const data = {
|
||||
key: v.pub_key.value,
|
||||
votingPower: new BigNumber(v.voting_power),
|
||||
proposerPriority: new BigNumber(v.proposer_priority),
|
||||
};
|
||||
return {
|
||||
...data,
|
||||
votingPowerRatio: data.votingPower.dividedBy(totalVotingPower),
|
||||
proposerPriorityRatio: absoluteProposerPriority
|
||||
.plus(data.proposerPriority)
|
||||
.dividedBy(absoluteProposerPriority.times(2)),
|
||||
};
|
||||
});
|
||||
}, [tmData?.result.validators, totalVotingPower, absoluteProposerPriority]);
|
||||
|
||||
const totalStaked = BigNumber.sum(
|
||||
...validators.map((v) => new BigNumber(v.stakedTotal))
|
||||
);
|
||||
|
||||
const [vegaDialog, setVegaDialog] = useState<boolean>(false);
|
||||
const [tmDialog, setTmDialog] = useState<boolean>(false);
|
||||
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
<PageTitle
|
||||
title={t('Validators')}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
disabled={Boolean(!data)}
|
||||
size="xs"
|
||||
onClick={() => setVegaDialog(true)}
|
||||
>
|
||||
{t('View JSON')}
|
||||
</Button>
|
||||
{
|
||||
<Button
|
||||
disabled={Boolean(!tmData)}
|
||||
size="xs"
|
||||
onClick={() => setTmDialog(true)}
|
||||
>
|
||||
{t('View tendermint as JSON')}
|
||||
</Button>
|
||||
}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<AsyncRenderer
|
||||
data={validators}
|
||||
loading={loading}
|
||||
error={error}
|
||||
reload={refetch}
|
||||
>
|
||||
<ul className="md:columns-2">
|
||||
{validators.map((v) => {
|
||||
const tm = tmValidators?.find((tmv) => tmv.key === v.tmPubkey);
|
||||
const stakedRatio = new BigNumber(v.stakedTotal).dividedBy(
|
||||
totalStaked
|
||||
);
|
||||
const validatorPage = tokenLink(
|
||||
TOKEN_VALIDATOR.replace(':id', v.id)
|
||||
);
|
||||
const validatorName =
|
||||
v.name && v.name.length > 0 ? v.name : truncateMiddle(v.id);
|
||||
return (
|
||||
<li className="mb-5" key={v.id}>
|
||||
<div
|
||||
data-testid="validator-tile"
|
||||
validator-id={v.id}
|
||||
className="border border-vega-light-200 dark:border-vega-dark-200 rounded p-2 overflow-hidden relative flex gap-2 items-start justify-between"
|
||||
>
|
||||
{v.avatarUrl && (
|
||||
<div className="w-20">
|
||||
<ExternalLink href={validatorPage}>
|
||||
<img
|
||||
className="w-full"
|
||||
src={v.avatarUrl}
|
||||
alt={validatorName}
|
||||
/>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full">
|
||||
<h2 className="font-alpha text-2xl">
|
||||
<ExternalLink href={validatorPage}>
|
||||
{validatorName}
|
||||
</ExternalLink>
|
||||
</h2>
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
<div>{t('ID')}</div>
|
||||
<div className="break-all text-xs">{v.id}</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Status')}</div>
|
||||
<div className="break-all text-xs">
|
||||
<span
|
||||
className={classNames('mr-1', {
|
||||
'text-vega-green-550 dark:vega-green-500':
|
||||
v.status === NodeStatus.NODE_STATUS_VALIDATOR,
|
||||
'text-vega-pink-550 dark:vega-pink-500':
|
||||
v.status ===
|
||||
NodeStatus.NODE_STATUS_NON_VALIDATOR,
|
||||
})}
|
||||
>
|
||||
<Icon name="tick-circle" size={3} />
|
||||
</span>
|
||||
{NodeStatusMapping[v.status]}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Location')}</div>
|
||||
<div>
|
||||
{countryCodeToFlagEmoji(v.location)}{' '}
|
||||
<span className="text-[10px]">{v.location}</span>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Public key')}</div>
|
||||
<div className="break-all text-xs">{v.pubkey}</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Ethereum address')}</div>
|
||||
<div className="break-all text-xs">
|
||||
<ContractAddressLink address={v.ethereumAddress} />{' '}
|
||||
<CopyWithTooltip text={v.ethereumAddress}>
|
||||
<button title={t('Copy address to clipboard')}>
|
||||
<Icon size={3} name="duplicate" />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Tendermint public key')}</div>
|
||||
<div className="break-all text-xs">{v.tmPubkey}</div>
|
||||
</KeyValueTableRow>
|
||||
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Voting power')}</div>
|
||||
<div className="w-44 text-right">
|
||||
<Rate value={tm?.votingPowerRatio} />
|
||||
<div className="text-[10px] leading-3">
|
||||
{tm?.votingPowerRatio.times(100).toFixed(2)}
|
||||
{'% '}({tm?.votingPower.toString()})
|
||||
</div>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Proposer priority')}</div>
|
||||
<div className="w-44 text-right">
|
||||
<Rate
|
||||
value={tm?.proposerPriorityRatio}
|
||||
colour="orange"
|
||||
asPoint={true}
|
||||
zero={true}
|
||||
/>
|
||||
<div className="text-[10px] leading-3">
|
||||
{tm?.proposerPriority.toString()}
|
||||
</div>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Stake share')}</div>
|
||||
<div className="w-44 text-right">
|
||||
<Rate value={stakedRatio} colour="green" />
|
||||
<div className="text-[10px] leading-3">
|
||||
<Tooltip
|
||||
description={
|
||||
<KeyValueTable
|
||||
numerical={true}
|
||||
className="mb-1"
|
||||
>
|
||||
<KeyValueTableRow
|
||||
className="text-xs"
|
||||
noBorder={true}
|
||||
>
|
||||
<div>{t('Staked by operator')}</div>
|
||||
<div>{v.stakedByOperator}</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow
|
||||
className="text-xs"
|
||||
noBorder={true}
|
||||
>
|
||||
<div>{t('Staked by delegates')}</div>
|
||||
<div>{v.stakedByDelegates}</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow
|
||||
className="text-xs"
|
||||
noBorder={true}
|
||||
>
|
||||
<div>{t('Staked (total)')}</div>
|
||||
<div>{v.stakedTotal}</div>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{stakedRatio.times(100).toFixed(2)}%
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</AsyncRenderer>
|
||||
</section>
|
||||
<JsonViewerDialog
|
||||
open={vegaDialog}
|
||||
onChange={(isOpen) => setVegaDialog(isOpen)}
|
||||
title={t('Vega Validators')}
|
||||
content={data}
|
||||
/>
|
||||
<JsonViewerDialog
|
||||
open={tmDialog}
|
||||
onChange={(isOpen) => setTmDialog(isOpen)}
|
||||
title={t('Tendermint Validators')}
|
||||
content={tmData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+62
-21
@@ -15,6 +15,7 @@ type OneOf<T extends any[]> = T extends [infer Only]
|
||||
? OneOf<[XOR<A, B>, ...Rest]>
|
||||
: never;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface paths {
|
||||
'/info': {
|
||||
/**
|
||||
@@ -40,8 +41,6 @@ export interface paths {
|
||||
};
|
||||
}
|
||||
|
||||
export type webhooks = Record<string, never>;
|
||||
|
||||
export interface components {
|
||||
schemas: {
|
||||
/**
|
||||
@@ -66,7 +65,7 @@ export interface components {
|
||||
| 'OPERATOR_LESS_THAN'
|
||||
| 'OPERATOR_LESS_THAN_OR_EQUAL';
|
||||
/**
|
||||
* The supported Oracle sources
|
||||
* The supported oracle sources
|
||||
* @description - ORACLE_SOURCE_UNSPECIFIED: The default value
|
||||
* - ORACLE_SOURCE_OPEN_ORACLE: Specifies that the payload will be base64 encoded JSON conforming to the Open Oracle standard
|
||||
* - ORACLE_SOURCE_JSON: Specifies that the payload will be base64 encoded JSON, but does not specify the shape of the data
|
||||
@@ -174,7 +173,7 @@ export interface components {
|
||||
readonly '@type'?: string;
|
||||
[key: string]: unknown | undefined;
|
||||
};
|
||||
/** Used announce a node as a new pending validator */
|
||||
/** Used to announce a node as a new pending validator */
|
||||
readonly v1AnnounceNode: {
|
||||
/** AvatarURL of the validator */
|
||||
readonly avatarUrl?: string;
|
||||
@@ -269,13 +268,13 @@ export interface components {
|
||||
readonly v1ETHAddress: {
|
||||
readonly address?: string;
|
||||
};
|
||||
/** A transaction to allow validator to rotate their ethereum keys */
|
||||
/** A transaction to allow a validator to rotate their ethereum keys */
|
||||
readonly v1EthereumKeyRotateSubmission: {
|
||||
/** Currently used public address */
|
||||
readonly currentAddress?: string;
|
||||
/** Signature that can be verified using the new ethereum address */
|
||||
readonly ethereumSignature?: components['schemas']['v1Signature'];
|
||||
/** The new adress to rotate to */
|
||||
/** The new address to rotate to */
|
||||
readonly newAddress?: string;
|
||||
/** Ethereum public key to use as a submitter to allow automatic signature generation */
|
||||
readonly submitterAddress?: string;
|
||||
@@ -309,7 +308,9 @@ export interface components {
|
||||
readonly version?: string;
|
||||
};
|
||||
readonly v1InputData: {
|
||||
/** A command used by a node operator to announce its node as a pending validator */
|
||||
readonly announceNode?: components['schemas']['v1AnnounceNode'];
|
||||
/** A command to submit a batch of order instructions to a market */
|
||||
readonly batchMarketInstructions?: components['schemas']['v1BatchMarketInstructions'];
|
||||
/**
|
||||
* Format: uint64
|
||||
@@ -323,17 +324,35 @@ export interface components {
|
||||
* `block_height` prevents replay attacks in conjunction with `nonce` (see above).
|
||||
*/
|
||||
readonly blockHeight?: string;
|
||||
/** A command to request cancelling a recurring transfer */
|
||||
readonly cancelTransfer?: components['schemas']['v1CancelTransfer'];
|
||||
/**
|
||||
* Command used by a validator to submit an event forwarded to the Vega network to provide information
|
||||
* on events happening on other networks, to be used by a foreign chain
|
||||
* to recognise a decision taken by the Vega network
|
||||
*/
|
||||
readonly chainEvent?: components['schemas']['v1ChainEvent'];
|
||||
/** Command to delegate tokens to a validator */
|
||||
readonly delegateSubmission?: components['schemas']['v1DelegateSubmission'];
|
||||
/** Command used by a validator to allow given validator to rotate their Ethereum keys */
|
||||
readonly ethereumKeyRotateSubmission?: components['schemas']['v1EthereumKeyRotateSubmission'];
|
||||
/** Command used by a validator to submit signatures to a smart contract */
|
||||
readonly issueSignatures?: components['schemas']['v1IssueSignatures'];
|
||||
/** Command used by a validator to allow given validator to rotate their Vega keys */
|
||||
readonly keyRotateSubmission?: components['schemas']['v1KeyRotateSubmission'];
|
||||
/** Command to request amending a liquidity commitment */
|
||||
readonly liquidityProvisionAmendment?: components['schemas']['v1LiquidityProvisionAmendment'];
|
||||
/** Command to request cancelling a liquidity commitment */
|
||||
readonly liquidityProvisionCancellation?: components['schemas']['v1LiquidityProvisionCancellation'];
|
||||
/** Command to submit a liquidity commitment */
|
||||
readonly liquidityProvisionSubmission?: components['schemas']['v1LiquidityProvisionSubmission'];
|
||||
/** Command used by a validator to submit a signature, to be used by a foreign chain to recognise a decision taken by the Vega network */
|
||||
readonly nodeSignature?: components['schemas']['v1NodeSignature'];
|
||||
/** Validator commands */
|
||||
/**
|
||||
* Validator commands
|
||||
* Command used by a validator when a node votes for validating that a given resource exists or is valid,
|
||||
* for example, an ERC20 deposit is valid and exists on ethereum
|
||||
*/
|
||||
readonly nodeVote?: components['schemas']['v1NodeVote'];
|
||||
/**
|
||||
* Format: uint64
|
||||
@@ -349,30 +368,50 @@ export interface components {
|
||||
* slightly differently, causing a different hash.
|
||||
*/
|
||||
readonly nonce?: string;
|
||||
/** Oracles */
|
||||
/**
|
||||
* Oracles
|
||||
* Command to submit new oracle data from third party providers
|
||||
*/
|
||||
readonly oracleDataSubmission?: components['schemas']['v1OracleDataSubmission'];
|
||||
/** Command to amend an order */
|
||||
readonly orderAmendment?: components['schemas']['v1OrderAmendment'];
|
||||
/**
|
||||
* User commands
|
||||
* Command to cancel an order
|
||||
*/
|
||||
readonly orderCancellation?: components['schemas']['v1OrderCancellation'];
|
||||
/** User commands */
|
||||
/** A command for submitting an order */
|
||||
readonly orderSubmission?: components['schemas']['v1OrderSubmission'];
|
||||
/** Command to submit a governance proposal */
|
||||
readonly proposalSubmission?: components['schemas']['v1ProposalSubmission'];
|
||||
/** Command used by a validator to propose a protocol upgrade */
|
||||
readonly protocolUpgradeProposal?: components['schemas']['v1ProtocolUpgradeProposal'];
|
||||
/** Command used by a validator to submit a floating point value */
|
||||
readonly stateVariableProposal?: components['schemas']['v1StateVariableProposal'];
|
||||
/** Command to submit a transfer */
|
||||
readonly transfer?: components['schemas']['commandsv1Transfer'];
|
||||
/** Command to remove tokens delegated to a validator */
|
||||
readonly undelegateSubmission?: components['schemas']['v1UndelegateSubmission'];
|
||||
/**
|
||||
* Command used by a validator to signal they are still online and validating blocks
|
||||
* or ready to validate blocks when they are still a pending validator
|
||||
*/
|
||||
readonly validatorHeartbeat?: components['schemas']['v1ValidatorHeartbeat'];
|
||||
/** Command to submit a vote on a governance proposal */
|
||||
readonly voteSubmission?: components['schemas']['v1VoteSubmission'];
|
||||
/** Command to submit a withdrawal */
|
||||
readonly withdrawSubmission?: components['schemas']['v1WithdrawSubmission'];
|
||||
};
|
||||
/** A transaction for a validator to submit signatures to a smart contract */
|
||||
readonly v1IssueSignatures: {
|
||||
/** The kind of signatures to generate, namely for whether a signer is being added or removed */
|
||||
readonly kind?: components['schemas']['v1NodeSignatureKind'];
|
||||
/** The ethereum address which will submit the signatures to the smart-contract */
|
||||
/** The ethereum address which will submit the signatures to the smart contract */
|
||||
readonly submitter?: string;
|
||||
/** The ID of the node that will be signed in or out of the smartcontract */
|
||||
/** The ID of the node that will be signed in or out of the smart contract */
|
||||
readonly validatorNodeId?: string;
|
||||
};
|
||||
/** A transaction to allow validator to rotate their Vega keys */
|
||||
/** A transaction to allow a validator to rotate their Vega keys */
|
||||
readonly v1KeyRotateSubmission: {
|
||||
/** Hash of currently used public key */
|
||||
readonly currentPubKeyHash?: string;
|
||||
@@ -496,8 +535,8 @@ export interface components {
|
||||
/** Specific details for a one off transfer */
|
||||
readonly v1OneOffTransfer: {
|
||||
/**
|
||||
* A unix timestamp in second. Time at which the
|
||||
* transfer should be delivered in the to account
|
||||
* A unix timestamp in seconds. Time at which the
|
||||
* transfer should be delivered into the To account
|
||||
* Format: int64
|
||||
*/
|
||||
readonly deliverOn?: string;
|
||||
@@ -512,7 +551,7 @@ export interface components {
|
||||
readonly payload?: string;
|
||||
/**
|
||||
* @description The source from which the data is coming from. Must be base64 encoded.
|
||||
* Oracle data a type of external data source data.
|
||||
* Oracle data is a type of external data source data.
|
||||
*/
|
||||
readonly source?: components['schemas']['OracleDataSubmissionOracleSource'];
|
||||
};
|
||||
@@ -603,7 +642,7 @@ export interface components {
|
||||
/** Type for the order, required field - See `Order.Type` */
|
||||
readonly type?: components['schemas']['vegaOrderType'];
|
||||
};
|
||||
/** @description PropertyKey describes the property key contained in an data source data. */
|
||||
/** @description PropertyKey describes the property key contained in data source data. */
|
||||
readonly v1PropertyKey: {
|
||||
/** @description name is the name of the property. */
|
||||
readonly name?: string;
|
||||
@@ -650,6 +689,7 @@ export interface components {
|
||||
/** Proposal configuration and the actual change that is meant to be executed when proposal is enacted */
|
||||
readonly terms?: components['schemas']['vegaProposalTerms'];
|
||||
};
|
||||
/** A transaction for a validator to suggest a protocol upgrade */
|
||||
readonly v1ProtocolUpgradeProposal: {
|
||||
/**
|
||||
* The block height at which to perform the upgrade
|
||||
@@ -708,6 +748,7 @@ export interface components {
|
||||
*/
|
||||
readonly pubKey?: components['schemas']['v1PubKey'];
|
||||
};
|
||||
/** A transaction for a validator to submit a floating point value */
|
||||
readonly v1StateVariableProposal: {
|
||||
/** The state value proposal details */
|
||||
readonly proposal?: components['schemas']['vegaStateValueProposal'];
|
||||
@@ -969,7 +1010,7 @@ export interface components {
|
||||
readonly sourceEthereumAddress?: string;
|
||||
/** The Vega network internal identifier of the asset */
|
||||
readonly vegaAssetId?: string;
|
||||
/** The updated withdraw threshold */
|
||||
/** The updated withdrawal threshold */
|
||||
readonly withdrawThreshold?: string;
|
||||
};
|
||||
/** An asset allow-listing for an ERC20 token */
|
||||
@@ -1064,7 +1105,7 @@ export interface components {
|
||||
/** The ethereum address of the old signer */
|
||||
readonly oldSigner?: string;
|
||||
};
|
||||
/** The threshold have been updated on the multisigcontrol */
|
||||
/** The threshold has been updated on the multisig control */
|
||||
readonly vegaERC20ThresholdSet: {
|
||||
/**
|
||||
* Format: int64
|
||||
@@ -1078,13 +1119,13 @@ export interface components {
|
||||
* Format: int64
|
||||
*/
|
||||
readonly newThreshold?: number;
|
||||
/** The nonce create by the vega network */
|
||||
/** The nonce created by the Vega network */
|
||||
readonly nonce?: string;
|
||||
};
|
||||
readonly vegaERC20Update: {
|
||||
/**
|
||||
* The lifetime limits deposit per address.
|
||||
* This is will be interpreted against the asset decimals.
|
||||
* This will be interpreted against the asset decimals.
|
||||
* note: this is a temporary measure that can be changed by governance
|
||||
*/
|
||||
readonly lifetimeLimit?: string;
|
||||
@@ -1237,7 +1278,7 @@ export interface components {
|
||||
* price levels over which automated liquidity provision orders will be deployed
|
||||
*/
|
||||
readonly lpPriceRange?: string;
|
||||
/** Optional new market meta data, tags */
|
||||
/** Optional new market metadata, tags */
|
||||
readonly metadata?: readonly string[];
|
||||
/**
|
||||
* Decimal places for order sizes, sets what size the smallest order / position on the market can be
|
||||
|
||||
@@ -8,7 +8,7 @@ export function Dashboard() {
|
||||
<>
|
||||
<div className="px-16 pt-20 pb-12 bg-greys-light-100">
|
||||
<div className="max-w-screen-xl mx-auto">
|
||||
<h1 className="font-alpha uppercase text-5xl mb-8">
|
||||
<h1 className="font-alpha calt uppercase text-5xl mb-8">
|
||||
{t('Top liquidity opportunities')}
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ type Network = 'testnet' | 'mainnet';
|
||||
export const Intro = ({ network = 'testnet' }: { network?: Network }) => {
|
||||
return (
|
||||
<div>
|
||||
<p className="font-alpha text-2xl font-medium mb-2">
|
||||
<p className="font-alpha calt text-2xl font-medium mb-2">
|
||||
{t(
|
||||
'Become a liquidity provider and earn a cut of the fees paid during trading.'
|
||||
)}
|
||||
|
||||
@@ -92,7 +92,7 @@ export const Detail = () => {
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-alpha text-2xl mb-4">
|
||||
<h2 className="font-alpha calt text-2xl mb-4">
|
||||
{t('Current Liquidity Provision')}
|
||||
</h2>
|
||||
<LPProvidersGrid
|
||||
|
||||
@@ -14,13 +14,13 @@ export const Header = ({
|
||||
<div className="mb-6">
|
||||
<Link to="/">
|
||||
<Icon name="chevron-left" className="mr-2" />
|
||||
<span className="underline font-alpha text-lg font-medium">
|
||||
<span className="underline font-alpha calt text-lg font-medium">
|
||||
{t('Liquidity opportunities')}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
<h1 className="font-alpha text-5xl mb-6">{name}</h1>
|
||||
<p className="font-alpha text-4xl">{symbol}</p>
|
||||
<h1 className="font-alpha calt text-5xl mb-6">{name}</h1>
|
||||
<p className="font-alpha calt text-4xl">{symbol}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Market = ({
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr
|
||||
className="text-sm text-greys-light-400 text-left font-alpha"
|
||||
className="text-sm text-greys-light-400 text-left font-alpha calt"
|
||||
style={{ fontFeatureSettings: "'liga' off, 'calt' off" }}
|
||||
>
|
||||
<th className="font-medium px-4">{t('Volume (24h)')}</th>
|
||||
|
||||
@@ -36,7 +36,7 @@ export const Grid = ({ isRowClickable, children, ...props }: Props) => {
|
||||
|
||||
return (
|
||||
<AgGridReact
|
||||
className={classNames('ag-theme-alpine h-full font-alpha', {
|
||||
className={classNames('ag-theme-alpine h-full font-alpha calt', {
|
||||
'row-hover': isRowClickable,
|
||||
})}
|
||||
rowHeight={92}
|
||||
|
||||
@@ -13,7 +13,7 @@ const Remainder = () => (
|
||||
);
|
||||
|
||||
const COPY_CLASS =
|
||||
'text-sm font-medium whitespace-nowrap text-white font-alpha';
|
||||
'text-sm font-medium whitespace-nowrap text-white font-alpha calt';
|
||||
|
||||
const Tooltip = ({
|
||||
children,
|
||||
|
||||
+6
-8
@@ -59,10 +59,10 @@ const ROWS = [
|
||||
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||
return (
|
||||
<Dialog size="medium" open={isOpen} onChange={onChange}>
|
||||
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase liga-0-calt-0">
|
||||
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase">
|
||||
{t('Health')}
|
||||
</h1>
|
||||
<p className="text-lg font-medium font-alpha mb-8 liga-0-calt-0">
|
||||
<p className="text-lg font-medium font-alpha mb-8">
|
||||
{t(
|
||||
'Market health is a representation of market and liquidity status and how close that market is to moving from one fee level to another.'
|
||||
)}
|
||||
@@ -70,10 +70,10 @@ export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||
|
||||
<table className="table-fixed">
|
||||
<thead className="border-b border-greys-light-300">
|
||||
<th className="w-1/2 text-left font-medium font-alpha text-base pb-4 uppercase liga-0-calt-0">
|
||||
<th className="w-1/2 text-left font-medium font-alpha text-base pb-4 uppercase">
|
||||
{t('Market status')}
|
||||
</th>
|
||||
<th className="w-1/2 text-lef font-medium font-alpha text-base pb-4 uppercase liga-0-calt-0">
|
||||
<th className="w-1/2 text-lef font-medium font-alpha text-base pb-4 uppercase">
|
||||
{t('Liquidity status')}
|
||||
</th>
|
||||
</thead>
|
||||
@@ -85,12 +85,10 @@ export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||
<td
|
||||
className={classNames('pr-4 pb-10', { 'pt-8': isFirstRow })}
|
||||
>
|
||||
<h2 className="font-medium font-alpha uppercase text-base liga-0-calt-0">
|
||||
<h2 className="font-medium font-alpha uppercase text-base">
|
||||
{t(r.title)}
|
||||
</h2>
|
||||
<p className="font-medium font-alpha text-lg liga-0-calt-0">
|
||||
{t(r.copy)}
|
||||
</p>
|
||||
<p className="font-medium font-alpha text-lg">{t(r.copy)}</p>
|
||||
</td>
|
||||
<td
|
||||
className={classNames('pl-4 pb-10', { 'pt-8': isFirstRow })}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -0,0 +1,111 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #000;
|
||||
}
|
||||
.pre-loader .pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader .pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(0) {
|
||||
animation-delay: 0ms;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:first-child {
|
||||
animation-delay: -50ms;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(3) {
|
||||
animation-delay: -0.6s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(4) {
|
||||
animation-delay: 0.4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(5) {
|
||||
animation-delay: -0.5s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(6) {
|
||||
animation-delay: 0.3s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(7) {
|
||||
animation-delay: -1.4s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(8) {
|
||||
animation-delay: 2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(9) {
|
||||
animation-delay: -0.9s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(10) {
|
||||
animation-delay: 1.5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(11) {
|
||||
animation-delay: -0.55s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(12) {
|
||||
animation-delay: 1.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(13) {
|
||||
animation-delay: -0.65s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(14) {
|
||||
animation-delay: 0.7s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(15) {
|
||||
animation-delay: -3.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(16) {
|
||||
animation-delay: 1.6s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
animation: flickering 0.4s linear infinite alternate;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
html.dark .pre-loader .loader-item {
|
||||
background: #fff;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -0,0 +1,9 @@
|
||||
(function () {
|
||||
var storedTheme = window.localStorage.getItem('theme');
|
||||
if (
|
||||
storedTheme === 'dark' ||
|
||||
(!storedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hosts": ["https://api.validators-testnet.vega.xyz/graphql"]
|
||||
}
|
||||
@@ -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_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -201,12 +201,21 @@ context('Staking Page - verify elements on page', function () {
|
||||
cy.get(stakeShare)
|
||||
.invoke('text')
|
||||
.then(($stakePercentage) => {
|
||||
if ($stakePercentage != '-') {
|
||||
cy.wrap($stakePercentage).should(
|
||||
'match',
|
||||
/\b(?<!\.)(?!0+(?:\.0+)?%)(?:\d|[1-9]\d|100)(?:(?<!100)\.\d+)?%/
|
||||
);
|
||||
}
|
||||
// The pattern must start at a word boundary (\b).
|
||||
// The pattern cannot be immediately preceded by a dot ((?<!\.)).
|
||||
// The pattern can be one of the following:
|
||||
// A percentage value of zero (0%), or
|
||||
// A non-zero percentage value that can be:
|
||||
// A single digit (\d) between 0 and 9, or
|
||||
// A two-digit number between 0 and 99 (\d{1,2}), or
|
||||
// The number 100.
|
||||
// The pattern can optionally include a decimal point and one or more digits after the decimal point ((?:(?<!100)\.\d+)?). However, if the number is 100, it cannot have a decimal point.
|
||||
// The pattern must end with a percentage sign (%).
|
||||
|
||||
cy.wrap($stakePercentage).should(
|
||||
'match',
|
||||
/\b(?<!\.)(?:0+(?:\.0+)?%|(?:\d|\d{1,2}|100)(?:(?<!100)\.\d+)?)%/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const connectPrompt = '[data-testid="eth-connect-prompt"]';
|
||||
const connectButton = '[data-testid="connect-to-eth-btn"]';
|
||||
|
||||
context(
|
||||
@@ -18,7 +17,7 @@ context(
|
||||
});
|
||||
|
||||
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 () {
|
||||
|
||||
@@ -13,4 +13,4 @@ last 2 Edge major versions
|
||||
last 2 Safari major version
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
|
||||
@@ -12,6 +12,7 @@ NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -15,6 +15,7 @@ NX_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -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_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
|
||||
@@ -10,3 +10,4 @@ NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
||||
@@ -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_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
@@ -7,3 +7,4 @@ NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
@@ -10,3 +10,4 @@ NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
|
||||
@@ -0,0 +1,10 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
|
||||
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/query
|
||||
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
@@ -1,17 +1,17 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useAppState } from '../../contexts/app-state/app-state-context';
|
||||
import { useContracts } from '../../contexts/contracts/contracts-context';
|
||||
import { useGetAssociationBreakdown } from '../../hooks/use-get-association-breakdown';
|
||||
import { useGetUserTrancheBalances } from '../../hooks/use-get-user-tranche-balances';
|
||||
import { useGetUserBalances } from '../../hooks/use-get-user-balances';
|
||||
import { useBalances } from '../../lib/balances/balances-store';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
|
||||
import { useTranches } from '../../lib/tranches/tranches-store';
|
||||
import { useUserTrancheBalances } from '../../routes/redemption/hooks';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
|
||||
interface BalanceManagerProps {
|
||||
children: ReactElement;
|
||||
@@ -24,7 +24,13 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = 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 numberOfConfirmations = config?.confirmations || 0;
|
||||
@@ -41,10 +47,10 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
|
||||
numberOfConfirmations
|
||||
);
|
||||
|
||||
const getUserTrancheBalances = useGetUserTrancheBalances(
|
||||
account || '',
|
||||
contracts?.vesting
|
||||
);
|
||||
const getTranches = useTranches((state) => state.getTranches);
|
||||
useEffect(() => {
|
||||
getTranches(decimals);
|
||||
}, [decimals, getTranches]);
|
||||
const getAssociationBreakdown = useGetAssociationBreakdown(
|
||||
account || '',
|
||||
contracts?.staking,
|
||||
@@ -54,50 +60,14 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
|
||||
// update balances on connect to Ethereum
|
||||
useEffect(() => {
|
||||
const updateBalances = async () => {
|
||||
if (!account || !config) return;
|
||||
try {
|
||||
const [b, w, stats, a] = await Promise.all([
|
||||
contracts.vesting.user_total_all_tranches(account),
|
||||
contracts.token.balanceOf(account),
|
||||
contracts.vesting.user_stats(account),
|
||||
contracts.token.allowance(
|
||||
account,
|
||||
config.staking_bridge_contract.address
|
||||
),
|
||||
]);
|
||||
|
||||
const balance = toBigNum(b, decimals);
|
||||
const walletBalance = toBigNum(w, decimals);
|
||||
const lien = toBigNum(stats.lien, decimals);
|
||||
const allowance = toBigNum(a, decimals);
|
||||
|
||||
updateStoreBalances({
|
||||
balanceFormatted: balance,
|
||||
walletBalance,
|
||||
lien,
|
||||
allowance,
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
const balances = await getUserBalances();
|
||||
if (balances) {
|
||||
updateStoreBalances(balances);
|
||||
}
|
||||
};
|
||||
|
||||
updateBalances();
|
||||
}, [
|
||||
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]);
|
||||
}, [getUserBalances, updateStoreBalances]);
|
||||
|
||||
useEffect(() => {
|
||||
if (account) {
|
||||
|
||||
@@ -6,27 +6,22 @@ import {
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
|
||||
interface EthConnectPrompProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const EthConnectPrompt = ({ children }: EthConnectPrompProps) => {
|
||||
export const EthConnectPrompt = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<Button
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
data-testid="connect-to-eth-btn"
|
||||
>
|
||||
{t('connectEthWallet')}
|
||||
</Button>
|
||||
</>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
fill={true}
|
||||
data-testid="connect-to-eth-btn"
|
||||
>
|
||||
{t('connectEthWallet')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -48,6 +48,10 @@ export const ContractAddresses: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
},
|
||||
VALIDATOR_TESTNET: {
|
||||
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
|
||||
lockedAddress: '0x0', // TODO not deployed to this env
|
||||
},
|
||||
MAINNET: {
|
||||
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
|
||||
lockedAddress: '0x78344c7305d73a7a0ac3c94cd9960f4449a1814e',
|
||||
@@ -58,6 +62,7 @@ const envName = windowOrDefault('NX_VEGA_ENV') ?? 'local';
|
||||
|
||||
export const ENV = {
|
||||
// Environment
|
||||
tranchesServiceUrl: windowOrDefault('NX_TRANCHES_SERVICE_URL'),
|
||||
dsn: windowOrDefault('NX_SENTRY_DSN'),
|
||||
urlConnect: TRUTHY.includes(windowOrDefault('NX_ETH_URL_CONNECT')),
|
||||
explorerUrl: windowOrDefault('NX_VEGA_EXPLORER'),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Tranche } from '@vegaprotocol/smart-contracts';
|
||||
import React from 'react';
|
||||
|
||||
import type { BigNumber } from '../../lib/bignumber';
|
||||
@@ -19,21 +18,7 @@ export interface VegaKey {
|
||||
meta: Array<{ key: string; value: string }> | null;
|
||||
}
|
||||
|
||||
export interface UserTrancheBalance {
|
||||
/** ID of tranche */
|
||||
id: number;
|
||||
|
||||
/** Users vesting tokens on tranche */
|
||||
locked: BigNumber;
|
||||
|
||||
/** Users vested tokens on tranche */
|
||||
vested: BigNumber;
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
/** Array of tranche objects */
|
||||
tranches: Tranche[] | null;
|
||||
|
||||
/** Number of decimal places of the VEGA token (18 on Mainnet, 5 on Testnet) */
|
||||
decimals: number;
|
||||
|
||||
@@ -52,9 +37,6 @@ export interface AppState {
|
||||
/** Whether or not the connect to Ethereum wallet overlay is open */
|
||||
ethConnectOverlay: boolean;
|
||||
|
||||
/** The error if one was thrown during retrieval of tranche data */
|
||||
trancheError: Error | null;
|
||||
|
||||
/** Whether or not the mobile drawer is open. Only relevant on screens smaller than 960 */
|
||||
drawerOpen: boolean;
|
||||
|
||||
@@ -71,12 +53,10 @@ export enum AppStateActionType {
|
||||
SET_TOKEN,
|
||||
SET_ALLOWANCE,
|
||||
REFRESH_BALANCES,
|
||||
SET_TRANCHE_DATA,
|
||||
SET_VEGA_WALLET_OVERLAY,
|
||||
SET_VEGA_WALLET_MANAGE_OVERLAY,
|
||||
SET_ETH_WALLET_OVERLAY,
|
||||
SET_DRAWER,
|
||||
SET_TRANCHE_ERROR,
|
||||
REFRESH_ASSOCIATED_BALANCES,
|
||||
SET_ASSOCIATION_BREAKDOWN,
|
||||
SET_TRANSACTION_OVERLAY,
|
||||
@@ -90,14 +70,6 @@ export type AppStateAction =
|
||||
totalSupply: BigNumber;
|
||||
totalAssociated: BigNumber;
|
||||
}
|
||||
| {
|
||||
type: AppStateActionType.SET_TRANCHE_DATA;
|
||||
tranches: Tranche[];
|
||||
}
|
||||
| {
|
||||
type: AppStateActionType.SET_TRANCHE_ERROR;
|
||||
error: Error | null;
|
||||
}
|
||||
| {
|
||||
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY;
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -14,11 +14,9 @@ const initialAppState: AppState = {
|
||||
totalAssociated: new BigNumber(0),
|
||||
decimals: 0,
|
||||
totalSupply: new BigNumber(0),
|
||||
tranches: null,
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
trancheError: null,
|
||||
drawerOpen: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
@@ -34,17 +32,6 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||
totalAssociated: action.totalAssociated,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_TRANCHE_DATA:
|
||||
return {
|
||||
...state,
|
||||
tranches: action.tranches,
|
||||
};
|
||||
case AppStateActionType.SET_TRANCHE_ERROR: {
|
||||
return {
|
||||
...state,
|
||||
trancheError: action.error,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_VEGA_WALLET_OVERLAY: {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -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;
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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]);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user