Compare commits
106
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca1300d13a | ||
|
|
fc3b1bd9ab | ||
|
|
090d850647 | ||
|
|
95280f5b26 | ||
|
|
101ae00a57 | ||
|
|
613cce3683 | ||
|
|
41fa18ed45 | ||
|
|
c941893cdd | ||
|
|
d049b471d8 | ||
|
|
010ffc0f46 | ||
|
|
dd56e4083b | ||
|
|
09d1ba68ba | ||
|
|
3959daa2b7 | ||
|
|
c599b02c89 | ||
|
|
e5ebaf02d2 | ||
|
|
ae54616c1f | ||
|
|
8f3cf390c5 | ||
|
|
64cf0c90a7 | ||
|
|
3edd91eadb | ||
|
|
676aeb07a4 | ||
|
|
52ee998bf0 | ||
|
|
727e2cd2ff | ||
|
|
06b2dd18f9 | ||
|
|
a189f38cdc | ||
|
|
d18681a5f0 | ||
|
|
9e2d03853a | ||
|
|
18c36d5e6b | ||
|
|
dcfb8f9527 | ||
|
|
a64acecf4b | ||
|
|
0998952196 | ||
|
|
2fd4d379cf | ||
|
|
c99d055b24 | ||
|
|
f1fa13e0bd | ||
|
|
ae020a8dca | ||
|
|
fa871a49c7 | ||
|
|
4205dd471a | ||
|
|
1040077c2b | ||
|
|
9d346d7846 | ||
|
|
b68136ba3f | ||
|
|
f5ad2de5a5 | ||
|
|
b3bfb90804 | ||
|
|
327ebf3496 | ||
|
|
1098accb84 | ||
|
|
52183555a9 | ||
|
|
51187029aa | ||
|
|
f7f7be59d3 | ||
|
|
49e3baf094 | ||
|
|
a575b4c502 | ||
|
|
af2e52d59c | ||
|
|
2725bef159 | ||
|
|
070f1905e2 | ||
|
|
6705eb4398 | ||
|
|
9d3fc04597 | ||
|
|
d3929b8d4a | ||
|
|
8a5579b1cc | ||
|
|
197f2e8097 | ||
|
|
8f5a2276de | ||
|
|
46513685c8 | ||
|
|
e66e96f12d | ||
|
|
31c8365812 | ||
|
|
460f534c7a | ||
|
|
24cd080dd4 | ||
|
|
f10c33748f | ||
|
|
ac163d0194 | ||
|
|
e7616e64f8 | ||
|
|
adca4600c2 | ||
|
|
a8eef1cb53 | ||
|
|
58d4bd1459 | ||
|
|
26020b5f71 | ||
|
|
16d345eb4a | ||
|
|
3b3fcab4f4 | ||
|
|
f54a629179 | ||
|
|
56f48671cc | ||
|
|
a65ee358ff | ||
|
|
7b8d814724 | ||
|
|
cd237dc491 | ||
|
|
9ad00adc5a | ||
|
|
fab055bb34 | ||
|
|
d66956d080 | ||
|
|
9848c8ffff | ||
|
|
b0e8ed62d2 | ||
|
|
289ff3b5b3 | ||
|
|
1d965187dc | ||
|
|
8bf3615001 | ||
|
|
1b77800b62 | ||
|
|
979dc2904b | ||
|
|
0fec3bcf3c | ||
|
|
4542e25ec9 | ||
|
|
acd96fbf21 | ||
|
|
96bf12d4dd | ||
|
|
47b81188d4 | ||
|
|
efada06256 | ||
|
|
311ef41138 | ||
|
|
5dd0cafeca | ||
|
|
f3d6aa1129 | ||
|
|
93dbc32f22 | ||
|
|
fd1827ef67 | ||
|
|
9c9948e4c1 | ||
|
|
79b0868ff7 | ||
|
|
a8401d4c6f | ||
|
|
c030264211 | ||
|
|
068f7b3fc3 | ||
|
|
4430f0c5f6 | ||
|
|
0a14000708 | ||
|
|
9ca6fe3cbf | ||
|
|
4f96472375 |
@@ -87,7 +87,7 @@ jobs:
|
||||
run: ls -alsh /home/runner/.vegacapsule/testnet/logs/
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: logs-${{ matrix.project }}
|
||||
path: /home/runner/.vegacapsule/testnet/logs
|
||||
|
||||
@@ -3,7 +3,7 @@ NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_URL=http://localhost:3028/query
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/capsule-network.json
|
||||
NX_VEGA_CONFIG_URL=
|
||||
|
||||
CYPRESS_VEGA_TENDERMINT_URL=http://localhost:26617
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"governance.proposal.updateAsset.minProposerBalance",
|
||||
"governance.proposal.updateAsset.minVoterBalance",
|
||||
"governance.proposal.updateAsset.requiredParticipation",
|
||||
"governance.proposal.updateMarket.minProposerEquityLikeShare",
|
||||
"market.fee.factors.infrastructureFee",
|
||||
"market.fee.factors.makerFee",
|
||||
"market.liquidity.bondPenaltyParameter",
|
||||
@@ -77,6 +76,7 @@
|
||||
"governance.proposal.updateMarket.requiredParticipationLP",
|
||||
"governance.proposal.updateNetParam.requiredMajority",
|
||||
"governance.proposal.updateNetParam.requiredParticipation",
|
||||
"governance.proposal.updateMarket.minProposerEquityLikeShare",
|
||||
"validators.vote.required"
|
||||
],
|
||||
"duration": [
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
context('Blocks page', { tags: '@regression' }, function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
const blockNavigation = 'a[href="/blocks"]';
|
||||
beforeEach(() => {
|
||||
cy.visit('/blocks');
|
||||
});
|
||||
const blockHeight = '[data-testid="block-height"]';
|
||||
const blockTime = '[data-testid="block-time"]';
|
||||
const blockHeader = '[data-testid="block-header"]';
|
||||
const previousBlockBtn = '[data-testid="previous-block"]';
|
||||
const infiniteScrollWrapper = '[data-testid="infinite-scroll-wrapper"]';
|
||||
|
||||
beforeEach('navigate to blocks page', function () {
|
||||
cy.get(blockNavigation).click();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed', function () {
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(blockNavigation).click();
|
||||
cy.switchToMobile();
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Block validator page is displayed', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
|
||||
cy.get('[data-testid="block-validator"]').should('not.be.empty');
|
||||
cy.get(blockTime).should('not.be.empty');
|
||||
//TODO: Add assertion for transactions when txs are added
|
||||
@@ -35,7 +29,7 @@ context('Blocks page', { tags: '@regression' }, function () {
|
||||
|
||||
it('Navigate to previous block', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
cy.get(blockHeader)
|
||||
.invoke('text')
|
||||
.then(($blockHeaderTxt) => {
|
||||
|
||||
@@ -2,17 +2,14 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
const networkParametersNavigation = 'a[href="/network-parameters"]';
|
||||
beforeEach(() => {
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
|
||||
const networkParametersHeader = '[data-testid="network-param-header"]';
|
||||
const tableRows = '[data-testid="key-value-table-row"]';
|
||||
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.visit('/');
|
||||
cy.get(networkParametersNavigation).click();
|
||||
});
|
||||
|
||||
it('should show network parameter heading at top of page', function () {
|
||||
cy.get(networkParametersHeader)
|
||||
.should('have.text', 'Network Parameters')
|
||||
@@ -201,55 +198,8 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to switch network parameter 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 already 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(networkParametersNavigation)
|
||||
.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(networkParametersNavigation)
|
||||
.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);
|
||||
});
|
||||
|
||||
it.skip('should be able to see network parameters - on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(networkParametersNavigation).click();
|
||||
it('should be able to see network parameters - on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
network_parameters = Object.entries(network_parameters);
|
||||
network_parameters.forEach((network_parameter) => {
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
|
||||
NX_VEGA_NETWORKS='{"SANDBOX":"https://sandbox.explorer.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
# App flags
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/tm
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
|
||||
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet1-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
|
||||
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_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
|
||||
NX_VEGA_ENV=MIRROR
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=SANDBOX
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_NETWORKS={}
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
|
||||
|
||||
@@ -1,12 +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/graphql
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
|
||||
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||
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
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
NX_TENDERMINT_URL=http://localhost:26607/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -1,16 +1,20 @@
|
||||
import classnames from 'classnames';
|
||||
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
|
||||
import { Nav } from './components/nav';
|
||||
import { Header } from './components/header';
|
||||
import { Main } from './components/main';
|
||||
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
|
||||
import { Footer } from './components/footer/footer';
|
||||
import { AnnouncementBanner, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AssetDetailsDialog,
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
|
||||
@@ -25,35 +29,58 @@ const DialogsContainer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function App() {
|
||||
const layoutClasses = classnames(
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)_auto] md:grid-cols-[300px_1fr]',
|
||||
'min-h-[100vh] mx-auto my-0',
|
||||
'border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r',
|
||||
'bg-white dark:bg-black',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
);
|
||||
const MainnetSimAd = () => {
|
||||
const [shouldDisplayBanner, setShouldDisplayBanner] = useState<boolean>(true);
|
||||
|
||||
// Return an empty div so that the grid layout in _app.page.ts
|
||||
// renders correctly
|
||||
if (!shouldDisplayBanner) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBanner>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
|
||||
<button
|
||||
className="flex items-center"
|
||||
onClick={() => setShouldDisplayBanner(false)}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">Mainnet sim 3 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">Learn more</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
);
|
||||
};
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<TendermintWebsocketProvider>
|
||||
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
||||
<AnnouncementBanner>
|
||||
<div className="font-alpha calt uppercase text-center text-lg text-white">
|
||||
<span className="pr-4">Mainnet sim 2 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">
|
||||
Come help stress test the network
|
||||
</ExternalLink>
|
||||
<div
|
||||
className={classNames(
|
||||
'max-w-[1500px] min-h-[100vh]',
|
||||
'mx-auto my-0',
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-1',
|
||||
'border-vega-light-200 dark:border-vega-dark-200 lg:border-l lg:border-r',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Header />
|
||||
<MainnetSimAd />
|
||||
</div>
|
||||
<div>
|
||||
<Main />
|
||||
</div>
|
||||
<div>
|
||||
<Footer />
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
|
||||
<div className={layoutClasses}>
|
||||
<Header />
|
||||
<Nav />
|
||||
<Main />
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
<DialogsContainer />
|
||||
</NetworkLoader>
|
||||
</TendermintWebsocketProvider>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const Footer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-neutral-700 dark:border-neutral-300">
|
||||
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
|
||||
<div className="flex justify-between gap-2 align-middle">
|
||||
{GIT_COMMIT_HASH && (
|
||||
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
|
||||
@@ -19,12 +19,10 @@ const renderComponent = () => (
|
||||
);
|
||||
|
||||
describe('Header', () => {
|
||||
it('should render heading', () => {
|
||||
it('should render navigation', () => {
|
||||
render(renderComponent());
|
||||
|
||||
expect(screen.getByTestId('explorer-header')).toHaveTextContent(
|
||||
'Vega Explorer'
|
||||
);
|
||||
expect(screen.getByTestId('navigation')).toHaveTextContent('Explorer');
|
||||
});
|
||||
it('should render search', () => {
|
||||
render(renderComponent());
|
||||
|
||||
@@ -1,43 +1,108 @@
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ThemeSwitcher, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
ThemeSwitcher,
|
||||
Navigation,
|
||||
NavigationList,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
NavigationBreakpoint,
|
||||
NavigationTrigger,
|
||||
NavigationContent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Search } from '../search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import { useNavStore } from '../nav';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { Search } from '../search';
|
||||
|
||||
const routeToNavigationItem = (r: Navigable) => (
|
||||
<NavigationItem key={r.name}>
|
||||
<NavigationLink to={r.path}>{r.text}</NavigationLink>
|
||||
</NavigationItem>
|
||||
);
|
||||
|
||||
export const Header = () => {
|
||||
const [open, toggle] = useNavStore((state) => [state.open, state.toggle]);
|
||||
const headerClasses = classnames(
|
||||
'md:col-span-2',
|
||||
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
|
||||
'p-4 gap-2 md:gap-4',
|
||||
'border-b border-neutral-700 dark:border-neutral-300 bg-black',
|
||||
'dark text-white'
|
||||
const mainItems = compact(
|
||||
[Routes.TX, Routes.BLOCKS, Routes.ORACLES, Routes.VALIDATORS].map((n) =>
|
||||
routerConfig.find((r) => r.path === n)
|
||||
)
|
||||
);
|
||||
|
||||
const groupedItems = compact(
|
||||
[
|
||||
Routes.PARTIES,
|
||||
Routes.ASSETS,
|
||||
Routes.MARKETS,
|
||||
Routes.GOVERNANCE,
|
||||
Routes.NETWORK_PARAMETERS,
|
||||
Routes.GENESIS,
|
||||
].map((n) => routerConfig.find((r) => r.path === n))
|
||||
);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
/**
|
||||
* Because the grouped items are displayed in a sub menu under an "Other" item
|
||||
* we need to determine whether any underlying item is active to highlight the
|
||||
* trigger in the same fashion as any other top-level `NavigationLink`.
|
||||
* This function checks whether the current location pathname is one of the
|
||||
* underlying NavigationLinks.
|
||||
*/
|
||||
const isOnOther = useMemo(() => {
|
||||
for (const path of groupedItems.map((r) => r.path)) {
|
||||
const matched = matchPath(`${path}/*`, pathname);
|
||||
if (matched) return true;
|
||||
}
|
||||
return false;
|
||||
}, [groupedItems, pathname]);
|
||||
|
||||
return (
|
||||
<header className={headerClasses}>
|
||||
<div className="flex h-full items-center sm:items-stretch gap-4">
|
||||
<Link to={Routes.HOME}>
|
||||
<h1
|
||||
className="text-white text-3xl font-alpha uppercase calt mb-0"
|
||||
data-testid="explorer-header"
|
||||
>
|
||||
{t('Vega Explorer')}
|
||||
</h1>
|
||||
</Link>
|
||||
<NetworkSwitcher />
|
||||
</div>
|
||||
<button
|
||||
data-testid="open-menu"
|
||||
className="md:hidden text-white"
|
||||
onClick={() => toggle()}
|
||||
<Navigation
|
||||
appName="Explorer"
|
||||
theme="system"
|
||||
breakpoints={[490, 900]}
|
||||
actions={
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
<Search />
|
||||
</>
|
||||
}
|
||||
onResize={(width, el) => {
|
||||
if (width < 1157) {
|
||||
// switch to magnifying glass trigger when width < 1157
|
||||
el.classList.remove('nav-search-full');
|
||||
el.classList.add('nav-search-compact');
|
||||
} else {
|
||||
el.classList.remove('nav-search-compact');
|
||||
el.classList.add('nav-search-full');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<NavigationList hide={[NavigationBreakpoint.Small]}>
|
||||
<NavigationItem>
|
||||
<NetworkSwitcher />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Small, NavigationBreakpoint.Narrow]}
|
||||
>
|
||||
<Icon name={open ? 'cross' : 'menu'} />
|
||||
</button>
|
||||
<Search />
|
||||
<ThemeSwitcher className="-my-4" />
|
||||
</header>
|
||||
{mainItems.map(routeToNavigationItem)}
|
||||
{groupedItems && (
|
||||
<NavigationItem>
|
||||
<NavigationTrigger isActive={Boolean(isOnOther)}>
|
||||
{t('Other')}
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList>
|
||||
{groupedItems.map(routeToNavigationItem)}
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
)}
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,13 +4,14 @@ import { ENV } from '../../../config/env';
|
||||
import Hash from '../hash';
|
||||
export type ProposalLinkProps = {
|
||||
id: string;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a proposal ID, generates an external link over to
|
||||
* the Governance page for more information
|
||||
*/
|
||||
const ProposalLink = ({ id }: ProposalLinkProps) => {
|
||||
const ProposalLink = ({ id, text }: ProposalLinkProps) => {
|
||||
const { data } = useExplorerProposalQuery({
|
||||
variables: { id },
|
||||
});
|
||||
@@ -20,7 +21,7 @@ const ProposalLink = ({ id }: ProposalLinkProps) => {
|
||||
|
||||
return (
|
||||
<ExternalLink href={`${base}/proposals/${id}`}>
|
||||
<Hash text={label} />
|
||||
{text ? text : <Hash text={label} />}
|
||||
</ExternalLink>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,9 +4,8 @@ import {
|
||||
getMarketExpiryDateFormatted,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { MarketInfoNoCandlesQuery } from '@vegaprotocol/market-info';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
|
||||
import { MarketInfoTable } from '@vegaprotocol/market-info';
|
||||
import pick from 'lodash/pick';
|
||||
import {
|
||||
MarketStateMapping,
|
||||
MarketTradingModeMapping,
|
||||
@@ -17,11 +16,7 @@ import BigNumber from 'bignumber.js';
|
||||
import { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const MarketDetails = ({
|
||||
market,
|
||||
}: {
|
||||
market: MarketInfoNoCandlesQuery['market'];
|
||||
}) => {
|
||||
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
|
||||
const assetId = useMemo(
|
||||
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
|
||||
@@ -32,7 +27,9 @@ export const MarketDetails = ({
|
||||
if (!market) return null;
|
||||
|
||||
const keyDetails = {
|
||||
...pick(market, 'decimalPlaces', 'positionDecimalPlaces', 'tradingMode'),
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
tradingMode: market.tradingMode,
|
||||
state: MarketStateMapping[market.state],
|
||||
};
|
||||
const assetDecimals =
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './nav';
|
||||
@@ -1,181 +0,0 @@
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import classnames from 'classnames';
|
||||
import { create } from 'zustand';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import first from 'lodash/first';
|
||||
import last from 'lodash/last';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
|
||||
type NavStore = {
|
||||
open: boolean;
|
||||
toggle: () => void;
|
||||
hide: () => void;
|
||||
};
|
||||
|
||||
export const useNavStore = create<NavStore>((set, get) => ({
|
||||
open: false,
|
||||
toggle: () => set({ open: !get().open }),
|
||||
hide: () => set({ open: false }),
|
||||
}));
|
||||
|
||||
const NavLinks = ({ links }: { links: Navigable[] }) => {
|
||||
const navLinks = links.map((r) => (
|
||||
<li key={r.name}>
|
||||
<NavLink
|
||||
to={r.path}
|
||||
className={({ isActive }) =>
|
||||
classnames(
|
||||
'block mb-2 px-2',
|
||||
'text-lg hover:bg-vega-pink dark:hover:bg-vega-yellow hover:text-white dark:hover:text-black',
|
||||
{
|
||||
'bg-vega-pink text-white dark:bg-vega-yellow dark:text-black':
|
||||
isActive,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
{r.text}
|
||||
</NavLink>
|
||||
</li>
|
||||
));
|
||||
|
||||
return <ul className="pr-8 md:pr-0">{navLinks}</ul>;
|
||||
};
|
||||
|
||||
export const Nav = () => {
|
||||
const [open, hide] = useNavStore((state) => [state.open, state.hide]);
|
||||
const location = useLocation();
|
||||
|
||||
const navRef = useRef<HTMLElement>(null);
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const focusable = useMemo(
|
||||
() =>
|
||||
navRef.current
|
||||
? [
|
||||
...(navRef.current.querySelectorAll(
|
||||
'a, button'
|
||||
) as NodeListOf<HTMLElement>),
|
||||
]
|
||||
: [],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[navRef.current] // do not remove `navRef.current` from deps
|
||||
);
|
||||
|
||||
const closeNav = useCallback(() => {
|
||||
hide();
|
||||
console.log(focusable);
|
||||
focusable.forEach((fe) =>
|
||||
fe.setAttribute(
|
||||
'tabindex',
|
||||
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
|
||||
)
|
||||
);
|
||||
}, [focusable, hide]);
|
||||
|
||||
// close navigation when location changes
|
||||
useEffect(() => {
|
||||
closeNav();
|
||||
}, [closeNav, location]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (open) {
|
||||
focusable.forEach((fe) => fe.setAttribute('tabindex', '0'));
|
||||
}
|
||||
|
||||
document.body.style.overflow = open ? 'hidden' : '';
|
||||
const offset =
|
||||
document.querySelector('header')?.getBoundingClientRect().top || 0;
|
||||
if (navRef.current) {
|
||||
navRef.current.style.height = `calc(100vh - ${offset}px)`;
|
||||
}
|
||||
|
||||
// focus current by default
|
||||
if (navRef.current && open) {
|
||||
(navRef.current.querySelector('a[aria-current]') as HTMLElement)?.focus();
|
||||
}
|
||||
|
||||
const closeOnEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeNav();
|
||||
}
|
||||
};
|
||||
|
||||
// tabbing loop
|
||||
const focusLast = (e: FocusEvent) => {
|
||||
e.preventDefault();
|
||||
const isNavElement =
|
||||
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
|
||||
if (!isNavElement && open) {
|
||||
last(focusable)?.focus();
|
||||
}
|
||||
};
|
||||
const focusFirst = (e: FocusEvent) => {
|
||||
e.preventDefault();
|
||||
const isNavElement =
|
||||
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
|
||||
if (!isNavElement && open) {
|
||||
first(focusable)?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const resetOnDesktop = () => {
|
||||
focusable.forEach((fe) =>
|
||||
fe.setAttribute(
|
||||
'tabindex',
|
||||
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resetOnDesktop);
|
||||
|
||||
first(focusable)?.addEventListener('focusout', focusLast);
|
||||
last(focusable)?.addEventListener('focusout', focusFirst);
|
||||
|
||||
document.addEventListener('keydown', closeOnEsc);
|
||||
return () => {
|
||||
window.removeEventListener('resize', resetOnDesktop);
|
||||
document.removeEventListener('keydown', closeOnEsc);
|
||||
first(focusable)?.removeEventListener('focusout', focusLast);
|
||||
last(focusable)?.removeEventListener('focusout', focusFirst);
|
||||
};
|
||||
}, [closeNav, focusable, open]);
|
||||
|
||||
return (
|
||||
<nav
|
||||
ref={navRef}
|
||||
className={classnames(
|
||||
'absolute top-0 z-20 overflow-y-auto',
|
||||
'transition-[right]',
|
||||
{
|
||||
'right-[-200vw] h-full': !open,
|
||||
'right-0 h-[100vh]': open,
|
||||
},
|
||||
'w-full p-4 border-neutral-700 dark:border-neutral-300',
|
||||
'bg-white dark:bg-black',
|
||||
'md:static md:border-r'
|
||||
)}
|
||||
>
|
||||
<NavLinks links={routerConfig} />
|
||||
<button
|
||||
ref={btnRef}
|
||||
className="absolute top-0 right-0 p-4 md:hidden"
|
||||
onClick={() => {
|
||||
closeNav();
|
||||
}}
|
||||
>
|
||||
<Icon name="cross" />
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
interface CancelSummaryProps {
|
||||
orderId?: string;
|
||||
marketId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple component for rendering a reasonable string from an order cancellation
|
||||
*/
|
||||
export const CancelSummary = ({ orderId, marketId }: CancelSummaryProps) => {
|
||||
return <span className="font-bold">{getLabel(orderId, marketId)}</span>;
|
||||
};
|
||||
|
||||
export function getLabel(
|
||||
orderId: string | undefined,
|
||||
marketId: string | undefined
|
||||
): string {
|
||||
if (!orderId && !marketId) {
|
||||
return t('All orders');
|
||||
} else if (marketId && !orderId) {
|
||||
return t('All in market');
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
@@ -5,11 +5,31 @@ import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getSearchType, SearchTypes, toHex } from './detect-search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface FormFields {
|
||||
search: string;
|
||||
}
|
||||
|
||||
const MagnifyingGlass = () => (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="12.8202"
|
||||
y1="13.1798"
|
||||
x2="17.0629"
|
||||
y2="17.4224"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<circle cx="8" cy="8" r="7.5" stroke="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Search = () => {
|
||||
const { register, handleSubmit } = useForm<FormFields>();
|
||||
const navigate = useNavigate();
|
||||
@@ -49,39 +69,95 @@ export const Search = () => {
|
||||
[navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full md:max-w-[620px] justify-self-end"
|
||||
>
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<div className="flex items-stretch gap-2">
|
||||
<div className="flex grow relative">
|
||||
<Input
|
||||
{...register('search')}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className="text-white"
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t(
|
||||
'Enter block number, public key or transaction hash'
|
||||
)}
|
||||
/>
|
||||
{error?.message && (
|
||||
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b text-black">
|
||||
<InputError data-testid="search-error" intent="danger">
|
||||
{error.message}
|
||||
</InputError>
|
||||
</div>
|
||||
const searchForm = (
|
||||
<form className="block min-w-[290px]" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex relative items-stretch gap-2 text-xs">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<button
|
||||
className={classNames(
|
||||
'absolute top-[50%] translate-y-[-50%] left-2',
|
||||
'text-vega-light-300 dark:text-vega-dark-300'
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit" size="sm" data-testid="search-button">
|
||||
>
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
<Input
|
||||
{...register('search')}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className={classNames(
|
||||
'peer',
|
||||
'pl-8 py-2 text-xs',
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200'
|
||||
)}
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t('Enter block number, public key or transaction hash')}
|
||||
/>
|
||||
{error?.message && (
|
||||
<div
|
||||
className={classNames(
|
||||
'hidden peer-focus:block',
|
||||
'bg-white dark:bg-black',
|
||||
'border rounded-b border-t-0 border-vega-light-200 dark:border-vega-dark-200',
|
||||
'absolute top-[100%] flex-1 w-full pb-2 px-2 text-black dark:text-white'
|
||||
)}
|
||||
>
|
||||
<InputError
|
||||
data-testid="search-error"
|
||||
intent="danger"
|
||||
className="text-xs"
|
||||
>
|
||||
{error.message}
|
||||
</InputError>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className="hidden [.search-dropdown_&]:block"
|
||||
type="submit"
|
||||
size="xs"
|
||||
data-testid="search-button"
|
||||
>
|
||||
{t('Search')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
const searchTrigger = (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="text-vega-light-300 dark:text-vega-dark-300 data-open:text-black dark:data-open:text-white flex items-center">
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className={classNames(
|
||||
'search-dropdown',
|
||||
'p-2 min-w-[290px] z-20',
|
||||
'text-vega-light-300 dark:text-vega-dark-300',
|
||||
'bg-white dark:bg-black',
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200',
|
||||
'shadow-[8px_8px_16px_0_rgba(0,0,0,0.4)]'
|
||||
)}
|
||||
align="end"
|
||||
sideOffset={10}
|
||||
>
|
||||
{searchForm}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden [.nav-search-full_&]:block">{searchForm}</div>
|
||||
<div className="hidden [.nav-search-compact_&]:block">
|
||||
{searchTrigger}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { BatchCancellationInstruction } from '../../../../routes/types/bloc
|
||||
import { TxOrderType } from '../../tx-order-type';
|
||||
import { MarketLink } from '../../../links';
|
||||
import OrderSummary from '../../../order-summary/order-summary';
|
||||
import { CancelSummary } from '../../../order-summary/order-cancellation';
|
||||
|
||||
interface BatchCancelProps {
|
||||
index: number;
|
||||
@@ -19,7 +20,14 @@ export const BatchCancel = ({ index, submission }: BatchCancelProps) => {
|
||||
<TxOrderType orderType={'OrderCancellation'} />
|
||||
</td>
|
||||
<td>
|
||||
<OrderSummary id={submission.orderId} modifier="cancelled" />
|
||||
{submission.orderId ? (
|
||||
<OrderSummary id={submission.orderId} modifier="cancelled" />
|
||||
) : (
|
||||
<CancelSummary
|
||||
orderId={submission.orderId}
|
||||
marketId={submission.marketId}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<MarketLink id={submission.marketId} />
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { components } from '../../../../../types/explorer';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
|
||||
import { getBlockTime } from './lib/get-block-time';
|
||||
|
||||
type Added = components['schemas']['vegaERC20SignerAdded'];
|
||||
type Removed = components['schemas']['vegaERC20SignerRemoved'];
|
||||
@@ -61,10 +60,7 @@ describe('Chain Event: multisig signer change', () => {
|
||||
expect(screen.getByText(t('Add signer'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${addedMock.newSigner}`)).toBeInTheDocument();
|
||||
|
||||
const expectedDate = getBlockTime(mockBlockTime);
|
||||
|
||||
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedDate)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
@@ -93,9 +89,6 @@ describe('Chain Event: multisig signer change', () => {
|
||||
expect(screen.getByText(t('Remove signer'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${removedMock.oldSigner}`)).toBeInTheDocument();
|
||||
|
||||
const expectedDate = getBlockTime(mockBlockTime);
|
||||
|
||||
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedDate)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
-4
@@ -6,7 +6,6 @@ import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
|
||||
import omit from 'lodash/omit';
|
||||
import { getBlockTime } from './lib/get-block-time';
|
||||
|
||||
type Threshold =
|
||||
components['schemas']['vegaERC20MultiSigEvent']['thresholdSet'];
|
||||
@@ -74,9 +73,6 @@ describe('Chain Event: multisig threshold change', () => {
|
||||
expect(screen.getByText(t('Threshold'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`66.7%`)).toBeInTheDocument();
|
||||
|
||||
const expectedDate = getBlockTime(mockBlockTime);
|
||||
|
||||
expect(screen.getByText(t('Threshold change date'))).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedDate)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
query ExplorerProposalStatus($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
query ExplorerNewAssetSignatureBundle($id: ID!) {
|
||||
erc20ListAssetBundle(assetId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
source {
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerUpdateAssetSignatureBundle($id: ID!) {
|
||||
erc20SetAssetLimitsBundle(proposalId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
source {
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerProposalStatusQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
|
||||
|
||||
|
||||
export const ExplorerProposalStatusDocument = gql`
|
||||
query ExplorerProposalStatus($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerProposalStatusQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerProposalStatusQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerProposalStatusQuery` 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 } = useExplorerProposalStatusQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerProposalStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>(ExplorerProposalStatusDocument, options);
|
||||
}
|
||||
export function useExplorerProposalStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>(ExplorerProposalStatusDocument, options);
|
||||
}
|
||||
export type ExplorerProposalStatusQueryHookResult = ReturnType<typeof useExplorerProposalStatusQuery>;
|
||||
export type ExplorerProposalStatusLazyQueryHookResult = ReturnType<typeof useExplorerProposalStatusLazyQuery>;
|
||||
export type ExplorerProposalStatusQueryResult = Apollo.QueryResult<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>;
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerNewAssetSignatureBundleQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerNewAssetSignatureBundleQuery = { __typename?: 'Query', erc20ListAssetBundle?: { __typename?: 'Erc20ListAssetBundle', signatures: string, nonce: string } | null, asset?: { __typename?: 'Asset', status: Types.AssetStatus, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } } | null };
|
||||
|
||||
export type ExplorerUpdateAssetSignatureBundleQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerUpdateAssetSignatureBundleQuery = { __typename?: 'Query', erc20SetAssetLimitsBundle: { __typename?: 'ERC20SetAssetLimitsBundle', signatures: string, nonce: string }, asset?: { __typename?: 'Asset', status: Types.AssetStatus, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } } | null };
|
||||
|
||||
|
||||
export const ExplorerNewAssetSignatureBundleDocument = gql`
|
||||
query ExplorerNewAssetSignatureBundle($id: ID!) {
|
||||
erc20ListAssetBundle(assetId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
source {
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerNewAssetSignatureBundleQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerNewAssetSignatureBundleQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerNewAssetSignatureBundleQuery` 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 } = useExplorerNewAssetSignatureBundleQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerNewAssetSignatureBundleQuery(baseOptions: Apollo.QueryHookOptions<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>(ExplorerNewAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export function useExplorerNewAssetSignatureBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>(ExplorerNewAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export type ExplorerNewAssetSignatureBundleQueryHookResult = ReturnType<typeof useExplorerNewAssetSignatureBundleQuery>;
|
||||
export type ExplorerNewAssetSignatureBundleLazyQueryHookResult = ReturnType<typeof useExplorerNewAssetSignatureBundleLazyQuery>;
|
||||
export type ExplorerNewAssetSignatureBundleQueryResult = Apollo.QueryResult<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>;
|
||||
export const ExplorerUpdateAssetSignatureBundleDocument = gql`
|
||||
query ExplorerUpdateAssetSignatureBundle($id: ID!) {
|
||||
erc20SetAssetLimitsBundle(proposalId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
source {
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerUpdateAssetSignatureBundleQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerUpdateAssetSignatureBundleQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerUpdateAssetSignatureBundleQuery` 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 } = useExplorerUpdateAssetSignatureBundleQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerUpdateAssetSignatureBundleQuery(baseOptions: Apollo.QueryHookOptions<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>(ExplorerUpdateAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export function useExplorerUpdateAssetSignatureBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>(ExplorerUpdateAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export type ExplorerUpdateAssetSignatureBundleQueryHookResult = ReturnType<typeof useExplorerUpdateAssetSignatureBundleQuery>;
|
||||
export type ExplorerUpdateAssetSignatureBundleLazyQueryHookResult = ReturnType<typeof useExplorerUpdateAssetSignatureBundleLazyQuery>;
|
||||
export type ExplorerUpdateAssetSignatureBundleQueryResult = Apollo.QueryResult<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>;
|
||||
@@ -0,0 +1,70 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
|
||||
type Terms = components['schemas']['vegaProposalTerms'];
|
||||
|
||||
export function format(date: string | undefined, def: string) {
|
||||
if (!date) {
|
||||
return def;
|
||||
}
|
||||
|
||||
return new Date().toLocaleDateString() || def;
|
||||
}
|
||||
|
||||
export function getDate(
|
||||
data: ExplorerProposalStatusQuery | undefined,
|
||||
terms: Terms
|
||||
): string {
|
||||
const DEFAULT = t('Unknown');
|
||||
if (!data?.proposal?.state) {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
switch (data.proposal.state) {
|
||||
case 'STATE_DECLINED':
|
||||
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
|
||||
case 'STATE_ENACTED':
|
||||
return `${t('Vote passed on')}: ${format(
|
||||
terms.enactmentTimestamp,
|
||||
DEFAULT
|
||||
)}`;
|
||||
case 'STATE_FAILED':
|
||||
return `${t('Failed on')}: ${format(terms.validationTimestamp, DEFAULT)}`;
|
||||
case 'STATE_OPEN':
|
||||
return `${t('Open until')}: ${format(terms.closingTimestamp, DEFAULT)}`;
|
||||
case 'STATE_PASSED':
|
||||
return `${t('Passed on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
|
||||
case 'STATE_REJECTED':
|
||||
return `${t('Rejected on submission')}`;
|
||||
case 'STATE_WAITING_FOR_NODE_VOTE':
|
||||
return `${t('Opening')}...`;
|
||||
default:
|
||||
return DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
interface ProposalDateProps {
|
||||
id: string;
|
||||
terms: Terms;
|
||||
}
|
||||
/**
|
||||
* Shows the most relevant date for the proposal summary view. Depending on the
|
||||
* state returned by GraphQL, we show either the validation, closing or enactment
|
||||
* timestamp
|
||||
*/
|
||||
export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
|
||||
const { data } = useExplorerProposalStatusQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Lozenge className="font-sans text-xs float-right">
|
||||
{getDate(data, terms)}
|
||||
</Lozenge>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import type * as Apollo from '@apollo/client';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
type ProposalQueryResult = Apollo.QueryResult<
|
||||
ExplorerProposalStatusQuery,
|
||||
Types.Exact<{
|
||||
id: string;
|
||||
}>
|
||||
>;
|
||||
|
||||
interface ProposalStatusIconProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
type IconAndLabel = {
|
||||
icon: IconProps['name'];
|
||||
label: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Select an icon for a given query result. Tolerates queries that don't return
|
||||
* any data
|
||||
*
|
||||
* @param data a data result from useExplorerProposalStatusQuery
|
||||
* @returns Icon name
|
||||
*/
|
||||
export function getIconAndLabelForStatus(
|
||||
res: ProposalQueryResult
|
||||
): IconAndLabel {
|
||||
const DEFAULT: IconAndLabel = {
|
||||
icon: 'error',
|
||||
label: t('Proposal state unknown'),
|
||||
};
|
||||
|
||||
if (res.loading) {
|
||||
return {
|
||||
icon: 'more',
|
||||
label: t('Loading data'),
|
||||
};
|
||||
}
|
||||
|
||||
if (!res?.data?.proposal || res.error) {
|
||||
return {
|
||||
icon: 'error',
|
||||
label: res.error?.message || DEFAULT.label,
|
||||
};
|
||||
}
|
||||
|
||||
switch (res.data.proposal.state) {
|
||||
case 'STATE_DECLINED':
|
||||
return {
|
||||
icon: 'stop',
|
||||
label: t('Proposal did not have enough participation to be valid'),
|
||||
};
|
||||
case 'STATE_ENACTED':
|
||||
return {
|
||||
icon: 'tick-circle',
|
||||
label: t('Vote passed and the proposal has been enacted'),
|
||||
};
|
||||
case 'STATE_FAILED':
|
||||
return {
|
||||
icon: 'thumbs-down',
|
||||
label: t('Proposal became invalid and was not processed'),
|
||||
};
|
||||
case 'STATE_OPEN':
|
||||
return {
|
||||
// A checklist to indicate in progress
|
||||
icon: 'form',
|
||||
label: t('Voting is in progress'),
|
||||
};
|
||||
case 'STATE_PASSED':
|
||||
return {
|
||||
icon: 'thumbs-up',
|
||||
label: t(
|
||||
'Voting is complete and this proposal was approved. It is not yet enacted.'
|
||||
),
|
||||
};
|
||||
case 'STATE_REJECTED':
|
||||
return {
|
||||
icon: 'disable',
|
||||
label: t('The proposal was invalid'),
|
||||
};
|
||||
case 'STATE_WAITING_FOR_NODE_VOTE':
|
||||
return {
|
||||
// A sparkly thing indicating it's new
|
||||
icon: 'clean',
|
||||
label: t('Proposal is being checked by validators'),
|
||||
};
|
||||
default:
|
||||
return DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
|
||||
const { icon, label } = getIconAndLabelForStatus(
|
||||
useExplorerProposalStatusQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="float-left mr-3">
|
||||
<Tooltip description={<p>{label}</p>}>
|
||||
<div>
|
||||
<Icon name={icon} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalTerms } from '../tx-proposal';
|
||||
import { BundleError } from './signature-bundle/bundle-error';
|
||||
import { BundleExists } from './signature-bundle/bundle-exists';
|
||||
import { useExplorerNewAssetSignatureBundleQuery } from './__generated__/SignatureBundle';
|
||||
|
||||
export interface ProposalSignatureBundleByTypeProps {
|
||||
id: string;
|
||||
tx?: ProposalTerms['newAsset'] | ProposalTerms['updateAsset'];
|
||||
}
|
||||
|
||||
/**
|
||||
* If a proposal needs a signature bundle, AND that signature bundle exists
|
||||
* AND that proposal is a New Asset Proposal, render an overview of the
|
||||
* signature bundle. Or an error.
|
||||
*
|
||||
* There is an almost identical component, ProposalSignatureBundleUpdateAsset
|
||||
*/
|
||||
export const ProposalSignatureBundleNewAsset = ({
|
||||
id,
|
||||
tx,
|
||||
}: ProposalSignatureBundleByTypeProps) => {
|
||||
const { data, error, loading } = useExplorerNewAssetSignatureBundleQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="w-auto max-w-lg p-5 mt-5">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!tx?.changes?.erc20 ||
|
||||
!tx?.changes?.erc20 ||
|
||||
!('contractAddress' in tx.changes.erc20) ||
|
||||
tx.changes.erc20.contractAddress === undefined
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data?.erc20ListAssetBundle?.signatures) {
|
||||
return (
|
||||
<BundleExists
|
||||
signatures={data.erc20ListAssetBundle.signatures}
|
||||
nonce={data.erc20ListAssetBundle.nonce}
|
||||
assetAddress={tx.changes.erc20.contractAddress}
|
||||
status={data.asset?.status}
|
||||
proposalId={id}
|
||||
tx={tx}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <BundleError status={data?.asset?.status} error={error} />;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalSignatureBundleByTypeProps } from './signature-bundle-new';
|
||||
import { BundleError } from './signature-bundle/bundle-error';
|
||||
import { BundleExists } from './signature-bundle/bundle-exists';
|
||||
import { useExplorerUpdateAssetSignatureBundleQuery } from './__generated__/SignatureBundle';
|
||||
|
||||
/**
|
||||
* If a proposal needs a signature bundle, AND that signature bundle exists
|
||||
* AND that proposal is a Asset Limits Proposal, render an overview of the
|
||||
* signature bundle. Or an error.
|
||||
*
|
||||
* There is an almost identical component, ProposalSignatureBundleNewAsset
|
||||
*/
|
||||
export const ProposalSignatureBundleUpdateAsset = ({
|
||||
id,
|
||||
tx,
|
||||
}: ProposalSignatureBundleByTypeProps) => {
|
||||
const { data, error, loading } = useExplorerUpdateAssetSignatureBundleQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (data?.asset?.source?.__typename !== 'ERC20') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data?.erc20SetAssetLimitsBundle?.signatures) {
|
||||
return (
|
||||
<BundleExists
|
||||
signatures={data.erc20SetAssetLimitsBundle.signatures}
|
||||
nonce={data.erc20SetAssetLimitsBundle.nonce}
|
||||
assetAddress={data.asset.source.contractAddress}
|
||||
status={data.asset?.status}
|
||||
proposalId={id}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <BundleError status={data?.asset?.status} error={error} />;
|
||||
}
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
query ExplorerBundleSigners {
|
||||
networkParameter(key: "blockchains.ethereumConfig") {
|
||||
value
|
||||
}
|
||||
nodesConnection(pagination: { first: 25 }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
status
|
||||
ethereumAddress
|
||||
pubkey
|
||||
tmPubkey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerBundleSignersQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerBundleSignersQuery = { __typename?: 'Query', networkParameter?: { __typename?: 'NetworkParameter', value: string } | null, nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, status: Types.NodeStatus, ethereumAddress: string, pubkey: string, tmPubkey: string } } | null> | null } };
|
||||
|
||||
|
||||
export const ExplorerBundleSignersDocument = gql`
|
||||
query ExplorerBundleSigners {
|
||||
networkParameter(key: "blockchains.ethereumConfig") {
|
||||
value
|
||||
}
|
||||
nodesConnection(pagination: {first: 25}) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
status
|
||||
ethereumAddress
|
||||
pubkey
|
||||
tmPubkey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerBundleSignersQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerBundleSignersQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerBundleSignersQuery` 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 } = useExplorerBundleSignersQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerBundleSignersQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerBundleSignersQuery, ExplorerBundleSignersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerBundleSignersQuery, ExplorerBundleSignersQueryVariables>(ExplorerBundleSignersDocument, options);
|
||||
}
|
||||
export function useExplorerBundleSignersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerBundleSignersQuery, ExplorerBundleSignersQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerBundleSignersQuery, ExplorerBundleSignersQueryVariables>(ExplorerBundleSignersDocument, options);
|
||||
}
|
||||
export type ExplorerBundleSignersQueryHookResult = ReturnType<typeof useExplorerBundleSignersQuery>;
|
||||
export type ExplorerBundleSignersLazyQueryHookResult = ReturnType<typeof useExplorerBundleSignersLazyQuery>;
|
||||
export type ExplorerBundleSignersQueryResult = Apollo.QueryResult<ExplorerBundleSignersQuery, ExplorerBundleSignersQueryVariables>;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AssetStatus } from '@vegaprotocol/types';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { BundleError } from './bundle-error';
|
||||
|
||||
describe('Bundle Error', () => {
|
||||
const NON_ENABLED_STATUS: AssetStatus[] = [
|
||||
AssetStatus.STATUS_PENDING_LISTING,
|
||||
];
|
||||
|
||||
const NOT_SHOWN_STATUS: AssetStatus[] = [
|
||||
AssetStatus.STATUS_PROPOSED,
|
||||
AssetStatus.STATUS_REJECTED,
|
||||
];
|
||||
|
||||
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
|
||||
it.each(NOT_SHOWN_STATUS)(
|
||||
'does not render for proposed or rejected bundles',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleError
|
||||
error={{ message: 'test-error-message' } as ApolloError}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
);
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'shows the apollo error in a syntax highlighter if not enabled and a message is provided',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleError
|
||||
error={{ message: 'test-error-message' } as ApolloError}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('No signature bundle')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'shows some fallback error message if the bundle has not been used and there is no error',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleError status={status} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('No signature bundle')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(ENABLED_STATUS)(
|
||||
'hides ProposalLink if the status is enabled',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleError
|
||||
error={{ message: 'irrelevant ' } as ApolloError}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Asset already enabled')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
});
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import type { AssetStatus } from '@vegaprotocol/types';
|
||||
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { IconForBundleStatus } from './bundle-icon';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export interface BundleErrorProps {
|
||||
status?: AssetStatus;
|
||||
error?: ApolloError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders if a proposal signature bundle cannot be found.
|
||||
* It is also possible that a data node has dropped the bundle
|
||||
* from its retention so there is a backup case where we check
|
||||
* the status - if it's already enabled, pretend this isn't an error
|
||||
*/
|
||||
export const BundleError = ({ status, error }: BundleErrorProps) => {
|
||||
if (!status || status === 'STATUS_PROPOSED' || status === 'STATUS_REJECTED') {
|
||||
// If there is no status, there is no asset and no bundle - ProposalDetails will make it clear why.
|
||||
// If the asset exists but is just proposed, or rejected, there won't be a signature bundle yet
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
|
||||
<IconForBundleStatus status={status} />
|
||||
<h1 className="text-xl pb-1">{t('No signature bundle')}</h1>
|
||||
|
||||
<p className="my-4">
|
||||
{t(
|
||||
'No signature bundle was generated as a result of this proposal, or the signature bundle could not be found.'
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
{status === 'STATUS_ENABLED' ? (
|
||||
t('Asset already enabled')
|
||||
) : (
|
||||
<details>
|
||||
<summary>{t('Show server error message')}</summary>
|
||||
|
||||
<SyntaxHighlighter data={error} size="smaller" />
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AssetStatus } from '@vegaprotocol/types';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { BundleExists } from './bundle-exists';
|
||||
|
||||
describe('Bundle Exists', () => {
|
||||
const NON_ENABLED_STATUS: AssetStatus[] = [
|
||||
AssetStatus.STATUS_PENDING_LISTING,
|
||||
AssetStatus.STATUS_PROPOSED,
|
||||
AssetStatus.STATUS_REJECTED,
|
||||
];
|
||||
|
||||
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
|
||||
|
||||
const MOCK_SIGNATURES =
|
||||
'0x1760ce4efec01d5bb9fe57c0660a39a27e0b5a1bd39b4d3fc0452bf142a4ef733baaf4dbedd74cd676c759f96ac7f412ec05e136bbff8a81d9f0c2428df8e099004c4d166ece0527d86e202386e8758580790d0a46f6429baaa268b4bf5c11c9e3402e175a9022ae8295e543853c01383ef17cd58458ddfc9c706fca0ecffa0d3d0160eb5234e63a78b9649428ca7659eb693fdde86edfc6c2707ef2e8fbf4c76a9f0eebe183da571a58837e2fa995d7b10955ecf04c138dc0ce964f17c3de1f5c6d0060ec132cc515cf974ead1da38e2ff8d4b870335865e8d4d8c982182bddea18bb513e2d37fd32de7fedc0c4f694e6bcdcf20f8547f19e7d9d25ce32c6ca9ea51e01ad3b92475778d9da7251d3943071f59107c9cd7ad9dd1923c06e88d3352869d919a591bf30732bad2c3fcf30a9f664dbb7a9c65a64875a48cbeb5741bd0a853901';
|
||||
const MOCK_NONCE =
|
||||
'18250011763873610289536200551900545467959221115607409799241178172533618346952';
|
||||
const MOCK_PROPOSAL_ID =
|
||||
'285923fed8c66ffb416b163e8ec72d3a87b9b8e2570e7ee7fe97d7092a918bc8';
|
||||
|
||||
const PROPOSAL_LINK_TEXT = 'Visit our Governance site to submit this';
|
||||
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'shows a handy ProposalLink if the status is anything except enabled',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleExists
|
||||
nonce={MOCK_NONCE}
|
||||
proposalId={MOCK_PROPOSAL_ID}
|
||||
signatures={MOCK_SIGNATURES}
|
||||
assetAddress={'0x123413423'}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText(PROPOSAL_LINK_TEXT)).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(ENABLED_STATUS)(
|
||||
'hides ProposalLink if the status is enabled',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleExists
|
||||
nonce={MOCK_NONCE}
|
||||
proposalId={MOCK_PROPOSAL_ID}
|
||||
signatures={MOCK_SIGNATURES}
|
||||
assetAddress={'0x123413423'}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.queryAllByText(PROPOSAL_LINK_TEXT)).toEqual([]);
|
||||
}
|
||||
);
|
||||
});
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import ProposalLink from '../../../../links/proposal-link/proposal-link';
|
||||
import { IconForBundleStatus } from './bundle-icon';
|
||||
import type { AssetStatus } from '@vegaprotocol/types';
|
||||
import type { ProposalTerms } from '../../tx-proposal';
|
||||
import { BundleSigners } from './bundle-signers';
|
||||
|
||||
export interface BundleExistsProps {
|
||||
signatures: string;
|
||||
nonce: string;
|
||||
status?: AssetStatus;
|
||||
assetAddress: string;
|
||||
proposalId: string;
|
||||
tx?: ProposalTerms['newAsset'] | ProposalTerms['updateAsset'];
|
||||
}
|
||||
|
||||
/**
|
||||
* If a proposal needs a signature bundle, AND that signature bundle exists,
|
||||
* this component renders that signature bundle.
|
||||
*
|
||||
*/
|
||||
export const BundleExists = ({
|
||||
signatures,
|
||||
nonce,
|
||||
status,
|
||||
proposalId,
|
||||
assetAddress,
|
||||
tx,
|
||||
}: BundleExistsProps) => {
|
||||
// Note if this is wrong, the wrong decoder will be used which will give incorrect data
|
||||
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
|
||||
<IconForBundleStatus status={status} />
|
||||
<h1 className="text-xl pb-1">
|
||||
{status === 'STATUS_ENABLED'
|
||||
? t('Asset added to bridge')
|
||||
: t('Signature bundle generated')}
|
||||
</h1>
|
||||
|
||||
<details className="mt-5">
|
||||
<summary>{t('Signature bundle details')}</summary>
|
||||
|
||||
<div className="ml-4">
|
||||
<h2 className="text-lg mt-2 mb-2">{t('Signatures')}</h2>
|
||||
<p>
|
||||
<textarea
|
||||
className="font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]"
|
||||
readOnly={true}
|
||||
rows={12}
|
||||
cols={120}
|
||||
value={signatures}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg mt-5 mb-2">{t('Nonce')}</h2>
|
||||
|
||||
<p>
|
||||
<textarea
|
||||
className="font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]"
|
||||
readOnly={true}
|
||||
rows={2}
|
||||
cols={120}
|
||||
value={nonce}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<BundleSigners
|
||||
signatures={signatures}
|
||||
nonce={nonce}
|
||||
tx={tx}
|
||||
id={proposalId}
|
||||
assetAddress={assetAddress}
|
||||
/>
|
||||
|
||||
{status !== 'STATUS_ENABLED' ? (
|
||||
<p className="mt-5">
|
||||
<ProposalLink
|
||||
id={proposalId}
|
||||
text={t('Visit our Governance site to submit this')}
|
||||
/>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { AssetStatus } from '@vegaprotocol/types';
|
||||
import { getIcon } from './bundle-icon';
|
||||
|
||||
describe('Bundle status icon', () => {
|
||||
const NON_ENABLED_STATUS: AssetStatus[] = [
|
||||
AssetStatus.STATUS_PENDING_LISTING,
|
||||
AssetStatus.STATUS_PROPOSED,
|
||||
];
|
||||
|
||||
const ERROR_STATUS: AssetStatus[] = [AssetStatus.STATUS_REJECTED];
|
||||
|
||||
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
|
||||
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'show a sparkle icon if the bundle is unused',
|
||||
(status) => {
|
||||
expect(getIcon(status)).toEqual('clean');
|
||||
}
|
||||
);
|
||||
|
||||
it.each(ERROR_STATUS)(
|
||||
'show an error icon if the bundle is unavailable',
|
||||
(status) => {
|
||||
expect(getIcon(status)).toEqual('disable');
|
||||
}
|
||||
);
|
||||
|
||||
it.each(ENABLED_STATUS)(
|
||||
'shows a tick if the bundle is already used',
|
||||
(status) => {
|
||||
expect(getIcon(status)).toEqual('tick-circle');
|
||||
}
|
||||
);
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import type { AssetStatus } from '@vegaprotocol/types';
|
||||
import type { IconName } from '@vegaprotocol/ui-toolkit';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export interface IconForBundleStatusProps {
|
||||
status?: AssetStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Naively select an icon for an asset. If it is enabled, we show a tick - anything
|
||||
* else is assumed to be 'in progress'. There should only be a signature bundle or the
|
||||
* asset should not exist
|
||||
*/
|
||||
export const IconForBundleStatus = ({ status }: IconForBundleStatusProps) => {
|
||||
const i = getIcon(status);
|
||||
|
||||
return (
|
||||
<Icon
|
||||
className="float-left mt-2 mr-3"
|
||||
name={i}
|
||||
data-testid={i}
|
||||
ariaLabel={status}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export function getIcon(status?: AssetStatus): IconName {
|
||||
switch (status) {
|
||||
case 'STATUS_ENABLED':
|
||||
return 'tick-circle';
|
||||
case undefined:
|
||||
case 'STATUS_REJECTED':
|
||||
return 'disable';
|
||||
default:
|
||||
return 'clean';
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import type { EncodeListAssetParameters } from '../../../../../lib/encoders/abis/list-asset';
|
||||
import type { BridgeFunction } from './bundle-signers';
|
||||
import {
|
||||
getBridgeAddressFromNetworkParameter,
|
||||
getSigners,
|
||||
} from './bundle-signers';
|
||||
|
||||
describe('Bundle Signers helpers', () => {
|
||||
it('getBridgeAddressFromNetworkParameter handles invalid json', () => {
|
||||
expect(getBridgeAddressFromNetworkParameter('hi')).toEqual(null);
|
||||
expect(getBridgeAddressFromNetworkParameter('{hi]')).toEqual(null);
|
||||
expect(getBridgeAddressFromNetworkParameter('{"hi"}')).toEqual(null);
|
||||
expect(
|
||||
getBridgeAddressFromNetworkParameter(false as unknown as string)
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('getBridgeAddressFromNetworkParameter returns null if bridge adderss is not in expected place', () => {
|
||||
expect(
|
||||
getBridgeAddressFromNetworkParameter(`{
|
||||
"NetworkParamter": false
|
||||
}`)
|
||||
).toEqual(null);
|
||||
|
||||
expect(
|
||||
getBridgeAddressFromNetworkParameter(`{
|
||||
"network_id": "11155111",
|
||||
"chain_id": "11155111",
|
||||
"confirmations": 3,
|
||||
"staking_bridge_contract": {
|
||||
"address": "0xFFb0A0d4806502ceF491aF1141f66669A1Bd0D03",
|
||||
"deployment_block_height": 2011705
|
||||
},
|
||||
"token_vesting_contract": {
|
||||
"address": "0x680fF88252FA7071CAce7398e77872d54D781d0B",
|
||||
"deployment_block_height": 2011709
|
||||
},
|
||||
"multisig_control_contract": {
|
||||
"address": "0x6eBc32d66277D94DB8FF2ccF86E36f37F29a52D3",
|
||||
"deployment_block_height": 2011699
|
||||
}
|
||||
}`)
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('getBridgeAddressFromNetworkParameter returns address if the collateral_bridge_contract has an address', () => {
|
||||
expect(
|
||||
getBridgeAddressFromNetworkParameter(`{
|
||||
"network_id": "11155111",
|
||||
"chain_id": "11155111",
|
||||
"confirmations": 3,
|
||||
"collateral_bridge_contract": {
|
||||
"address": "0x7fe27d970bc8Afc3B11Cc8d9737bfB66B1efd799"
|
||||
},
|
||||
"staking_bridge_contract": {
|
||||
"address": "0xFFb0A0d4806502ceF491aF1141f66669A1Bd0D03",
|
||||
"deployment_block_height": 2011705
|
||||
},
|
||||
"token_vesting_contract": {
|
||||
"address": "0x680fF88252FA7071CAce7398e77872d54D781d0B",
|
||||
"deployment_block_height": 2011709
|
||||
},
|
||||
"multisig_control_contract": {
|
||||
"address": "0x6eBc32d66277D94DB8FF2ccF86E36f37F29a52D3",
|
||||
"deployment_block_height": 2011699
|
||||
}
|
||||
}`)
|
||||
).toEqual('0x7fe27d970bc8Afc3B11Cc8d9737bfB66B1efd799');
|
||||
});
|
||||
|
||||
it('getSigners to return [] in the case of bad inputs', () => {
|
||||
expect(
|
||||
getSigners('list_asset', '123', '', {
|
||||
assetERC20: '123',
|
||||
assetId: '456',
|
||||
limit: 'bad',
|
||||
threshold: 'data',
|
||||
nonce: 'here',
|
||||
})
|
||||
).toEqual([]);
|
||||
|
||||
expect(
|
||||
getSigners('nothing' as unknown as BridgeFunction, '123', '', {
|
||||
nonce: 'here',
|
||||
} as unknown as EncodeListAssetParameters)
|
||||
).toEqual([]);
|
||||
|
||||
expect(
|
||||
getSigners('set_asset_limits', '0x123', '0x456', {
|
||||
nonce: 'here',
|
||||
} as unknown as EncodeListAssetParameters)
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
import { encodeListAssetBridgeTx } from '../../../../../lib/encoders/abis/list-asset';
|
||||
import { recoverAddress } from 'ethers/lib/utils';
|
||||
import { useExplorerBundleSignersQuery } from './__generated__/BundleSigners';
|
||||
import type { ProposalTerms } from '../../tx-proposal';
|
||||
import { DApp, TOKEN_VALIDATOR, useLinks } from '@vegaprotocol/environment';
|
||||
import { ExternalLink, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { encodeUpdateAssetBridgeTx } from '../../../../../lib/encoders/abis/update-asset';
|
||||
import { prepend0x } from '@vegaprotocol/smart-contracts';
|
||||
import type { EncodeListAssetParameters } from '../../../../../lib/encoders/abis/list-asset';
|
||||
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
export type BridgeFunction = 'list_asset' | 'set_asset_limits';
|
||||
|
||||
export interface BundleSignersProps {
|
||||
signatures: string;
|
||||
assetAddress: string;
|
||||
nonce: string;
|
||||
tx?: ProposalTerms['updateAsset'] | ProposalTerms['newAsset'];
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A logic-heavy component that takes in a signature bundle and returns
|
||||
* the list of validators that signed the bundle. To do this it requires
|
||||
* data from quite a few places - a network parameter, the signature bundle,
|
||||
* the asset that has been modified
|
||||
*/
|
||||
export const BundleSigners = ({
|
||||
signatures,
|
||||
nonce,
|
||||
assetAddress,
|
||||
tx,
|
||||
id,
|
||||
}: BundleSignersProps) => {
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
|
||||
const bridgeFunction: BridgeFunction =
|
||||
tx?.changes?.erc20 && 'contractAddress' in tx.changes.erc20
|
||||
? 'list_asset'
|
||||
: 'set_asset_limits';
|
||||
|
||||
const { data } = useExplorerBundleSignersQuery();
|
||||
|
||||
const bridgeAddress = getBridgeAddressFromNetworkParameter(
|
||||
data?.networkParameter?.value
|
||||
);
|
||||
|
||||
const allEthereumKeys =
|
||||
data?.nodesConnection?.edges
|
||||
?.filter((n) => n?.node.status === 'NODE_STATUS_VALIDATOR')
|
||||
.map((s) => s?.node) || [];
|
||||
|
||||
if (!tx || !tx.changes?.erc20) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { lifetimeLimit, withdrawThreshold } = tx.changes.erc20;
|
||||
|
||||
if (
|
||||
!id ||
|
||||
allEthereumKeys.length === 0 ||
|
||||
!bridgeAddress ||
|
||||
!lifetimeLimit ||
|
||||
!withdrawThreshold
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const signersLowerCase = getSigners(
|
||||
bridgeFunction,
|
||||
bridgeAddress,
|
||||
signatures,
|
||||
{
|
||||
assetERC20: assetAddress,
|
||||
assetId: prepend0x(id),
|
||||
limit: lifetimeLimit,
|
||||
threshold: withdrawThreshold,
|
||||
nonce,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="mt-4 mb-2 text-lg">{t('Signed by validators')}</h2>
|
||||
<ul>
|
||||
{allEthereumKeys?.map((n) => {
|
||||
if (!n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const validatorPage = tokenLink(TOKEN_VALIDATOR.replace(':id', n.id));
|
||||
return signersLowerCase?.indexOf(
|
||||
n?.ethereumAddress.toLowerCase() || '??'
|
||||
) !== -1 ? (
|
||||
<li key={n?.pubkey}>
|
||||
<ExternalLink href={validatorPage}>
|
||||
<Icon name={IconNames.ENDORSED} className="ml-1 mr-2" />
|
||||
{n?.name}
|
||||
<Icon size={3} name={IconNames.SHARE} className="ml-2" />
|
||||
</ExternalLink>
|
||||
</li>
|
||||
) : (
|
||||
<li>
|
||||
<ExternalLink href={validatorPage}>
|
||||
<Icon name={IconNames.MINUS} className="ml-1 mr-2" />
|
||||
{n?.name}
|
||||
<Icon size={3} name={IconNames.SHARE} className="ml-2" />
|
||||
</ExternalLink>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given all of the collated information, this function creates an equivalent unsigned bundle
|
||||
* and recovers the signers from it, In the case of an error, it returns an empty array.
|
||||
*
|
||||
* @param bridgeFunction Decides which data goes in to the digest
|
||||
* @param bridgeAddress ERC20 bridge address
|
||||
* @param signatures Long string of signatures
|
||||
* @param params The object containing all data that the bridge requires for New or Updating assets
|
||||
* @returns String[] Empty if there was an error or no signers were recovered, otherwise lowercased ETH addresses
|
||||
*/
|
||||
export function getSigners(
|
||||
bridgeFunction: BridgeFunction,
|
||||
bridgeAddress: string,
|
||||
signatures: string,
|
||||
params: EncodeListAssetParameters
|
||||
): string[] {
|
||||
try {
|
||||
if (bridgeFunction === 'list_asset') {
|
||||
const digest = encodeListAssetBridgeTx(params, bridgeAddress);
|
||||
|
||||
// Recover Address from digest can return null, which is handled as an empty array
|
||||
return recoverAddressesFromDigest(digest, signatures) || [];
|
||||
} else {
|
||||
// The params bundles are so similar, rather than force the component to make two different
|
||||
// styles, just delete the one different property
|
||||
const p = omit(params, 'assetId');
|
||||
const digest = encodeUpdateAssetBridgeTx(p, bridgeAddress);
|
||||
return recoverAddressesFromDigest(digest, signatures) || [];
|
||||
}
|
||||
} catch (e) {
|
||||
// In the worst case, no signing addresses are recovered. This means that all nodes will
|
||||
// be rendered as if they had not signed the bundle.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Querying for the network parameter value gets us all of the contract details for this network
|
||||
* encoded as a JSON object. This function pulls out the address for the bridge, or returns null
|
||||
* in any of the many cases where it may fail
|
||||
*
|
||||
* @param networkParameterAsString the stringified JSON object
|
||||
* @returns null or bridge address as a string
|
||||
*/
|
||||
export function getBridgeAddressFromNetworkParameter(
|
||||
networkParameterAsString: string | undefined
|
||||
): string | null {
|
||||
if (!networkParameterAsString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const networkParameter = JSON.parse(networkParameterAsString);
|
||||
return networkParameter.collateral_bridge_contract.address;
|
||||
} catch (e) {
|
||||
// There is no good recovery state so return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function recoverAddressesFromDigest(
|
||||
digest: string,
|
||||
unprefixedBundle: string
|
||||
) {
|
||||
// Remove 0x from bundle, then split it in to signatures
|
||||
const sigs = unprefixedBundle.substring(2).match(/.{1,130}/g);
|
||||
|
||||
// Convert each of the signatures from hex to a string
|
||||
const hexSigs = sigs?.map((s) => `0x${s.toString()}`);
|
||||
|
||||
if (!hexSigs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// toLowerCase is a hack - something somewhere is lowercasing some
|
||||
// pubkeys
|
||||
return hexSigs.map((h) => recoverAddress(digest, h).toLowerCase());
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { ProposalTerms } from '../tx-proposal';
|
||||
import { useState } from 'react';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
|
||||
import ProposalLink from '../../../links/proposal-link/proposal-link';
|
||||
import truncate from 'lodash/truncate';
|
||||
import { ProposalStatusIcon } from './proposal-status-icon';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { ProposalDate } from './proposal-date';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
type Rationale = components['schemas']['vegaProposalRationale'];
|
||||
|
||||
type ProposalTermsDialog = {
|
||||
open: boolean;
|
||||
title: string;
|
||||
content: unknown;
|
||||
};
|
||||
|
||||
interface ProposalSummaryProps {
|
||||
id: string;
|
||||
rationale?: Rationale;
|
||||
terms?: ProposalTerms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectively a 'preview' for what the proposal is about, and a link to the
|
||||
* Token site for full breakdown of votes
|
||||
*/
|
||||
export const ProposalSummary = ({
|
||||
id,
|
||||
rationale,
|
||||
terms,
|
||||
}: ProposalSummaryProps) => {
|
||||
const [dialog, setDialog] = useState<ProposalTermsDialog>({
|
||||
open: false,
|
||||
title: '',
|
||||
content: null,
|
||||
});
|
||||
|
||||
const openDialog = () => {
|
||||
if (!terms) return;
|
||||
|
||||
setDialog({
|
||||
open: true,
|
||||
title: rationale?.title || t('Proposal details'),
|
||||
content: terms ? terms : {},
|
||||
});
|
||||
};
|
||||
|
||||
const md =
|
||||
rationale && rationale.description
|
||||
? truncate(rationale.description, {
|
||||
// Limits the description to roughly 5 lines, maximum
|
||||
length: 350,
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
|
||||
{id && <ProposalStatusIcon id={id} />}
|
||||
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
|
||||
{rationale?.description && (
|
||||
<div className="pt-2 text-sm leading-tight">
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
skipHtml={true}
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{md}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
<div className="pt-5">
|
||||
<button className="underline max-md:hidden mr-5" onClick={openDialog}>
|
||||
{t('View terms')}
|
||||
</button>{' '}
|
||||
<ProposalLink id={id} text={t('Full details')} />
|
||||
{terms && <ProposalDate terms={terms} id={id} />}
|
||||
</div>
|
||||
<JsonViewerDialog
|
||||
open={dialog.open}
|
||||
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
|
||||
title={dialog.title}
|
||||
content={dialog.content}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -55,7 +55,7 @@ export const TxDetailsShared = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
|
||||
<TableCell>
|
||||
<Hash text={txData.hash} />
|
||||
<Hash text={txData.hash.toLowerCase()} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -24,6 +24,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 { TxProposal } from './tx-proposal';
|
||||
import { TxDetailsTransfer } from './tx-transfer';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
@@ -88,6 +89,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsOrderAmend;
|
||||
case 'Validator Heartbeat':
|
||||
return TxDetailsHeartbeat;
|
||||
case 'Proposal':
|
||||
return TxProposal;
|
||||
case 'Vote on Proposal':
|
||||
return TxProposalVote;
|
||||
case 'Batch Market Instructions':
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
|
||||
import { CancelSummary } from '../../order-summary/order-cancellation';
|
||||
import Hash from '../../links/hash';
|
||||
|
||||
interface TxDetailsOrderCancelProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -24,8 +26,8 @@ export const TxDetailsOrderCancel = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const marketId: string = txData.command.orderCancellation.marketId || '-';
|
||||
const orderId: string = txData.command.orderCancellation.orderId || '-';
|
||||
const marketId: string = txData.command.orderCancellation.marketId;
|
||||
const orderId: string = txData.command.orderCancellation.orderId;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -38,18 +40,24 @@ export const TxDetailsOrderCancel = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Order')}</TableCell>
|
||||
<TableCell>
|
||||
<code>{orderId}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
{orderId ? (
|
||||
<Hash text={orderId} />
|
||||
) : (
|
||||
<CancelSummary orderId={orderId} marketId={marketId} />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{marketId ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
{orderId !== '-' ? <DeterministicOrderDetails id={orderId} /> : null}
|
||||
{orderId ? <DeterministicOrderDetails id={orderId} /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
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 { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import has from 'lodash/has';
|
||||
import { ProposalSummary } from './proposal/summary';
|
||||
import Hash from '../../links/hash';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ProposalSignatureBundleNewAsset } from './proposal/signature-bundle-new';
|
||||
import { ProposalSignatureBundleUpdateAsset } from './proposal/signature-bundle-update';
|
||||
|
||||
export type Proposal = components['schemas']['v1ProposalSubmission'];
|
||||
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
|
||||
|
||||
interface TxProposalProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a given proposal requires a signature bundle to be emitted by the validators for
|
||||
* the proposal to be completed after it is accepted by the governance vote
|
||||
*
|
||||
* @param t The details of the proposal
|
||||
* @returns boolean True if a signature bundle is required. Used to fetch a signature bundle
|
||||
*/
|
||||
export function proposalRequiresSignatureBundle(proposal?: Proposal): boolean {
|
||||
if (!proposal?.terms) {
|
||||
return false;
|
||||
}
|
||||
return !!['newAsset', 'updateAsset'].filter((requiredIfExists) =>
|
||||
has(proposal.terms, requiredIfExists)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a proposal, returns a nice text label for the proposal type
|
||||
*
|
||||
* @param terms The details of the proposal
|
||||
* @returns string Proposal type
|
||||
*/
|
||||
export function proposalTypeLabel(terms?: ProposalTerms): string {
|
||||
if (has(terms, 'newAsset')) {
|
||||
return t('New asset proposal');
|
||||
} else if (has(terms, 'updateAsset')) {
|
||||
return t('Update asset proposal');
|
||||
} else if (has(terms, 'newMarket')) {
|
||||
return t('New market proposal');
|
||||
} else if (has(terms, 'updateMarket')) {
|
||||
return t('Update market proposal');
|
||||
} else if (has(terms, 'updateNetworkParameter')) {
|
||||
return t('Update network parameter');
|
||||
} else if (has(terms, 'newFreeform')) {
|
||||
return t('Freeform proposal');
|
||||
}
|
||||
|
||||
// The list above contains all currently known types. This will be triggered if a new
|
||||
// unrecognised proposal type is added.
|
||||
return t('Governance proposal');
|
||||
}
|
||||
|
||||
/**
|
||||
* A proposal. It's more simplistic than man other views because there are already other, better
|
||||
* ways to view things about a proposal!
|
||||
*
|
||||
*/
|
||||
export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
|
||||
if (!txData || !txData.command.proposalSubmission) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
let deterministicId = '';
|
||||
|
||||
const proposal: Proposal = txData.command.proposalSubmission;
|
||||
const sig = txData?.signature?.value;
|
||||
if (sig) {
|
||||
deterministicId = txSignatureToDeterministicId(sig);
|
||||
}
|
||||
|
||||
const tx = proposal.terms?.newAsset || proposal.terms?.updateAsset;
|
||||
|
||||
const SignatureBundleComponent = proposal.terms?.newAsset
|
||||
? ProposalSignatureBundleNewAsset
|
||||
: ProposalSignatureBundleUpdateAsset;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{proposalTypeLabel(proposal.terms)}</TableCell>
|
||||
</TableRow>
|
||||
{/* TODO: Disable type row */}
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
hideTypeRow={true}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal ID')}</TableCell>
|
||||
<TableCell>
|
||||
<Hash text={deterministicId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<ProposalSummary
|
||||
id={deterministicId}
|
||||
rationale={proposal.rationale}
|
||||
terms={proposal?.terms}
|
||||
/>
|
||||
{proposalRequiresSignatureBundle(proposal) && (
|
||||
<SignatureBundleComponent id={deterministicId} tx={tx} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
// The subset of ABI types that we use in relevant types
|
||||
export type AbiType = 'address' | 'bytes' | 'bytes32' | 'uint256' | 'string';
|
||||
@@ -0,0 +1,31 @@
|
||||
import { keccak256, defaultAbiCoder, isAddress } from 'ethers/lib/utils';
|
||||
import type { AbiType } from './abi-types';
|
||||
|
||||
export const BRIDGE_COMMAND: AbiType[] = [
|
||||
// The abi encoded bytes of the message
|
||||
'bytes',
|
||||
// The address of the bridge
|
||||
'address',
|
||||
];
|
||||
|
||||
/**
|
||||
* ABI encode values for a bridge call, getting back its digest
|
||||
*
|
||||
* @param bytes The packed bytes of the command for the bridge
|
||||
* @param address the Ethereum address of the ERC20 bridge
|
||||
* @param raw defaults to false. If set, does not keccak256 the output
|
||||
*/
|
||||
export function encodeBridgeCommand(
|
||||
bytes: string,
|
||||
address: string,
|
||||
raw = false
|
||||
) {
|
||||
if (!isAddress(address)) {
|
||||
throw new Error('Bridge address must be a hex value');
|
||||
}
|
||||
|
||||
const values = [bytes, address];
|
||||
|
||||
const value = defaultAbiCoder.encode(BRIDGE_COMMAND, values);
|
||||
return raw === true ? value : keccak256(value);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { encodeBridgeCommand } from './bridge-command';
|
||||
|
||||
describe('Bridge command encoder', () => {
|
||||
const VALID_BYTES =
|
||||
'0x000000000000000000000000b063f5504610ba4b8db230d9f884bfadc1e31da00b87ac58d4af7fc11c8b417153fcb62631cfd9643835ef28db3f5a1caef0b3790000000000000000000000000000000000000000000000487a9a30453944000000000000000000000000000000000000000000000000000000000000000000010b87ac58d4af7fc11c8b417153fcb62631cfd9643835ef28db3f5a1caef0b37900000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000a6c6973745f617373657400000000000000000000000000000000000000000000';
|
||||
const VALID_ADDRESS = '0x7fe27d970bc8Afc3B11Cc8d9737bfB66B1efd799';
|
||||
|
||||
it('rejects non valid bridge addresses', () => {
|
||||
expect(() => {
|
||||
encodeBridgeCommand(VALID_BYTES, '456789');
|
||||
}).toThrowError('Bridge address must be a hex value');
|
||||
});
|
||||
|
||||
it('throws if the bytes are not bytes-like', () => {
|
||||
expect(() => {
|
||||
encodeBridgeCommand('hello', VALID_ADDRESS);
|
||||
}).toThrowError(/invalid/);
|
||||
});
|
||||
|
||||
it('keccac256s the value by default', () => {
|
||||
const res = encodeBridgeCommand(VALID_BYTES, VALID_ADDRESS);
|
||||
// Magic number: Known output, including 0x
|
||||
expect(res.length).toEqual(66);
|
||||
});
|
||||
|
||||
it('Does not keccac256 the value if third param is set', () => {
|
||||
const res = encodeBridgeCommand(VALID_BYTES, VALID_ADDRESS, true);
|
||||
// Magic number: Known output
|
||||
expect(res.length).toEqual(706);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { encodeListAsset, encodeListAssetBridgeTx } from './list-asset';
|
||||
|
||||
describe('List Asset ABI encoder', () => {
|
||||
it('throws if asset erc20 address is invalid', () => {
|
||||
expect(() => {
|
||||
encodeListAsset({
|
||||
assetERC20: '123',
|
||||
assetId: '0x456',
|
||||
limit: '1',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
});
|
||||
}).toThrowError('Asset ERC20 and assetID must be hex values');
|
||||
});
|
||||
|
||||
it('throws if assetId is not hex encoded', () => {
|
||||
expect(() => {
|
||||
encodeListAsset({
|
||||
assetERC20: '0x123',
|
||||
assetId: '456',
|
||||
limit: '1',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
});
|
||||
}).toThrowError('Asset ERC20 and assetID must be hex values');
|
||||
});
|
||||
|
||||
it('throws if values to not match expected format', () => {
|
||||
expect(() => {
|
||||
encodeListAsset({
|
||||
assetERC20: '0xb063f5504610ba4b8db230d9f884bfadc1e31da0',
|
||||
assetId: '0xb063f5504610ba4b8db230d9f884bfadc1e31da0',
|
||||
limit: 'not a valid number',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
});
|
||||
}).toThrowError(/incorrect data length/);
|
||||
});
|
||||
|
||||
it('returns an ABI encoded value if inputs are valid', () => {
|
||||
const EXPECTED_OUTPUT =
|
||||
'0x000000000000000000000000b063f5504610ba4b8db230d9f884bfadc1e31da00b87ac58d4af7fc11c8b417153fcb62631cfd9643835ef28db3f5a1caef0b37900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000a6c6973745f617373657400000000000000000000000000000000000000000000';
|
||||
|
||||
const res = encodeListAsset({
|
||||
assetERC20: '0xb063f5504610ba4b8db230d9f884bfadc1e31da0',
|
||||
assetId:
|
||||
'0x0b87ac58d4af7fc11c8b417153fcb62631cfd9643835ef28db3f5a1caef0b379',
|
||||
limit: '1',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
});
|
||||
|
||||
expect(res).toEqual(EXPECTED_OUTPUT);
|
||||
});
|
||||
|
||||
it('encodeListAssetBridge returns a keccak256 hash of the bridge tx', () => {
|
||||
const EXPECTED_OUTPUT =
|
||||
'0xe0e62b27fe4490025d312bb2e37486f56935a3d9442dc34c2b918b2a28a386f2';
|
||||
|
||||
const res = encodeListAssetBridgeTx(
|
||||
{
|
||||
assetERC20: '0xb063f5504610ba4b8db230d9f884bfadc1e31da0',
|
||||
assetId:
|
||||
'0x0b87ac58d4af7fc11c8b417153fcb62631cfd9643835ef28db3f5a1caef0b379',
|
||||
limit: '1',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
},
|
||||
'0xb063f5504610ba4b8db230d9f884bfadc1e31da0'
|
||||
);
|
||||
|
||||
// Magic number: keccak256 hash length + '0x'
|
||||
expect(res.length).toEqual(66);
|
||||
expect(res).toEqual(EXPECTED_OUTPUT);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { defaultAbiCoder, isAddress, isHexString } from 'ethers/lib/utils';
|
||||
import { encodeBridgeCommand } from './bridge-command';
|
||||
import type { AbiType } from './abi-types';
|
||||
|
||||
export const METHOD_NAME = 'list_asset';
|
||||
|
||||
export const LIST_ASSET_ABI: AbiType[] = [
|
||||
// Asset address
|
||||
'address',
|
||||
// Asset ID on Vega
|
||||
'bytes32',
|
||||
// Lifetime limit
|
||||
'uint256',
|
||||
// Withdraw threshold
|
||||
'uint256',
|
||||
// Nonce
|
||||
'uint256',
|
||||
// Contract method name
|
||||
'string',
|
||||
];
|
||||
|
||||
export interface EncodeListAssetParameters {
|
||||
// The ETH address of the ERC20 asset
|
||||
assetERC20: string;
|
||||
// The Vega ID of the asset, 0x prefixed
|
||||
assetId: string;
|
||||
// The number as a string of the asset
|
||||
limit: string;
|
||||
// THe number-as-a-string of the withdraw threshold
|
||||
threshold: string;
|
||||
// The n-once supplied to the contract
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ABI encoded function call to list an asset. This is
|
||||
* used in the Signature Bundle view on some proposals to recover
|
||||
* which validators signed a multisig bundle. It does this by recovering
|
||||
* the ERC20 addresses of the signers, then comparing those to the list
|
||||
* of signers on the bundle. In order to do this, we recreate the signed
|
||||
* data from the values we know from the transaction. That last part
|
||||
* is what this function does.
|
||||
*
|
||||
* @param EncodeListAssetParameters The arguments for the ABI call
|
||||
* @returns string encoded message
|
||||
*/
|
||||
export function encodeListAsset({
|
||||
assetERC20,
|
||||
assetId,
|
||||
limit,
|
||||
threshold,
|
||||
nonce,
|
||||
}: EncodeListAssetParameters) {
|
||||
if (!isAddress(assetERC20) || !isHexString(assetId)) {
|
||||
throw new Error('Asset ERC20 and assetID must be hex values');
|
||||
}
|
||||
|
||||
const values = [assetERC20, assetId, limit, threshold, nonce, METHOD_NAME];
|
||||
|
||||
return defaultAbiCoder.encode(LIST_ASSET_ABI, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that encodes and packs the message as it is encoded by the
|
||||
* validators in a multisig bundle
|
||||
*
|
||||
* @param params Parameters for the List Asset call
|
||||
* @param bridgeAddress Bridge address for the appropiate network
|
||||
* @returns keccak256 encoded message digest
|
||||
*/
|
||||
export function encodeListAssetBridgeTx(
|
||||
params: EncodeListAssetParameters,
|
||||
bridgeAddress: string
|
||||
) {
|
||||
return encodeBridgeCommand(encodeListAsset(params), bridgeAddress);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { encodeUpdateAsset, encodeUpdateAssetBridgeTx } from './update-asset';
|
||||
|
||||
describe('Update Asset ABI encoder', () => {
|
||||
it('throws if asset erc20 address is invalid', () => {
|
||||
expect(() => {
|
||||
encodeUpdateAsset({
|
||||
assetERC20: '123',
|
||||
limit: '1',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
});
|
||||
}).toThrowError('Asset ERC20 must be a valid address');
|
||||
});
|
||||
|
||||
it('throws if an input is invalid', () => {
|
||||
expect(() => {
|
||||
encodeUpdateAsset({
|
||||
assetERC20: '0xb063f5504610ba4b8db230d9f884bfadc1e31da0',
|
||||
limit: 'hello',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
});
|
||||
}).toThrowError(/invalid BigNumber/);
|
||||
});
|
||||
|
||||
it('returns an ABI encoded value if inputs are valid', () => {
|
||||
const EXPECTED_OUTPUT =
|
||||
'0x000000000000000000000000b063f5504610ba4b8db230d9f884bfadc1e31da000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000107365745f61737365745f6c696d69747300000000000000000000000000000000';
|
||||
|
||||
const res = encodeUpdateAsset({
|
||||
assetERC20: '0xb063f5504610ba4b8db230d9f884bfadc1e31da0',
|
||||
limit: '1',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
});
|
||||
|
||||
expect(res).toEqual(EXPECTED_OUTPUT);
|
||||
});
|
||||
|
||||
it('encodeUpdateAssetBridge returns a keccak256 hash of the bridge tx', () => {
|
||||
const EXPECTED_OUTPUT =
|
||||
'0xeb240131c4558aebfab3da0ddbea1ac0447b9f5670899af2d78795867631d877';
|
||||
|
||||
const res = encodeUpdateAssetBridgeTx(
|
||||
{
|
||||
assetERC20: '0xb063f5504610ba4b8db230d9f884bfadc1e31da0',
|
||||
limit: '1',
|
||||
threshold: '1',
|
||||
nonce: '1',
|
||||
},
|
||||
'0xb063f5504610ba4b8db230d9f884bfadc1e31da0'
|
||||
);
|
||||
|
||||
// Magic number: keccak256 hash length + '0x'
|
||||
expect(res.length).toEqual(66);
|
||||
expect(res).toEqual(EXPECTED_OUTPUT);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { defaultAbiCoder, isAddress } from 'ethers/lib/utils';
|
||||
import { encodeBridgeCommand } from './bridge-command';
|
||||
import type { AbiType } from './abi-types';
|
||||
|
||||
export const METHOD_NAME = 'set_asset_limits';
|
||||
|
||||
export const LIST_ASSET_ABI: AbiType[] = [
|
||||
// Asset address
|
||||
'address',
|
||||
// Lifetime limit
|
||||
'uint256',
|
||||
// Withdraw threshold
|
||||
'uint256',
|
||||
// Nonce
|
||||
'uint256',
|
||||
// Contract method name
|
||||
'string',
|
||||
];
|
||||
|
||||
export interface EncodeUpdateAssetParameters {
|
||||
// The ETH address of the ERC20 asset
|
||||
assetERC20: string;
|
||||
// The number as a string of the asset
|
||||
limit: string;
|
||||
// THe number-as-a-string of the withdraw threshold
|
||||
threshold: string;
|
||||
// The n-once supplied to the contract
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ABI encoded function call to list an asset
|
||||
*
|
||||
* @param EncodeListAssetParameters The arguments for the ABI call
|
||||
* @returns string encoded message
|
||||
*/
|
||||
export function encodeUpdateAsset({
|
||||
assetERC20,
|
||||
limit,
|
||||
threshold,
|
||||
nonce,
|
||||
}: EncodeUpdateAssetParameters) {
|
||||
if (!isAddress(assetERC20)) {
|
||||
throw new Error('Asset ERC20 must be a valid address');
|
||||
}
|
||||
|
||||
const values = [assetERC20, limit, threshold, nonce, METHOD_NAME];
|
||||
|
||||
return defaultAbiCoder.encode(LIST_ASSET_ABI, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that encodes and packs the message as it is encoded by the
|
||||
* validators in a multisig bundle
|
||||
*
|
||||
* @param params Parameters for the List Asset call
|
||||
* @param bridgeAddress Bridge address for the appropiate network
|
||||
* @returns keccak256 encoded message digest
|
||||
*/
|
||||
export function encodeUpdateAssetBridgeTx(
|
||||
params: EncodeUpdateAssetParameters,
|
||||
bridgeAddress: string
|
||||
) {
|
||||
return encodeBridgeCommand(encodeUpdateAsset(params), bridgeAddress);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export const Proposals = () => {
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: proposalsDataProvider,
|
||||
variables: {},
|
||||
});
|
||||
|
||||
useDocumentTitle([t('Governance Proposals')]);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { MarketDetails } from '../../components/markets/market-details';
|
||||
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 { marketInfoProvider } from '@vegaprotocol/market-info';
|
||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||
|
||||
export const MarketPage = () => {
|
||||
@@ -16,24 +16,17 @@ export const MarketPage = () => {
|
||||
|
||||
const { marketId } = useParams<{ marketId: string }>();
|
||||
|
||||
const variables = useMemo(
|
||||
() => ({
|
||||
marketId,
|
||||
}),
|
||||
[marketId]
|
||||
);
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: marketInfoNoCandlesDataProvider,
|
||||
dataProvider: marketInfoProvider,
|
||||
skipUpdates: true,
|
||||
variables,
|
||||
variables: {
|
||||
marketId: marketId || '',
|
||||
skip: !marketId,
|
||||
},
|
||||
});
|
||||
|
||||
useDocumentTitle(
|
||||
compact([
|
||||
'Market details',
|
||||
data?.market?.tradableInstrument.instrument.name,
|
||||
])
|
||||
compact(['Market details', data?.tradableInstrument.instrument.name])
|
||||
);
|
||||
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
@@ -43,10 +36,10 @@ export const MarketPage = () => {
|
||||
<section className="relative">
|
||||
<PageTitle
|
||||
data-testid="markets-heading"
|
||||
title={data?.market?.tradableInstrument.instrument.name || ''}
|
||||
title={data?.tradableInstrument.instrument.name || ''}
|
||||
actions={
|
||||
<Button
|
||||
disabled={!data?.market}
|
||||
disabled={!data}
|
||||
size="xs"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
@@ -60,14 +53,14 @@ export const MarketPage = () => {
|
||||
loading={loading}
|
||||
error={error}
|
||||
>
|
||||
<MarketDetails market={data?.market} />
|
||||
{data && <MarketDetails market={data} />}
|
||||
</AsyncRenderer>
|
||||
</section>
|
||||
<JsonViewerDialog
|
||||
open={dialogOpen}
|
||||
onChange={(isOpen) => setDialogOpen(isOpen)}
|
||||
title={data?.market?.tradableInstrument.instrument.name || ''}
|
||||
content={data?.market}
|
||||
title={data?.tradableInstrument.instrument.name || ''}
|
||||
content={data}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ export const MarketsPage = () => {
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: marketsProvider,
|
||||
variables: undefined,
|
||||
skipUpdates: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ const PERCENTAGE_PARAMS = [
|
||||
'governance.proposal.updateMarket.requiredParticipationLP',
|
||||
'governance.proposal.updateNetParam.requiredMajority',
|
||||
'governance.proposal.updateNetParam.requiredParticipation',
|
||||
'governance.proposal.updateMarket.minProposerEquityLikeShare',
|
||||
'validators.vote.required',
|
||||
];
|
||||
|
||||
|
||||
@@ -23,14 +23,18 @@ import { NetworkParameters } from './network-parameters';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { MarketPage, MarketsPage } from './markets';
|
||||
|
||||
export type Navigable = { path: string; name: string; text: string };
|
||||
export type Navigable = {
|
||||
path: string;
|
||||
name: string;
|
||||
text: string;
|
||||
};
|
||||
type Route = RouteObject & Navigable;
|
||||
|
||||
const partiesRoutes: Route[] = flags.parties
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
name: 'Parties',
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
element: <Party />,
|
||||
children: [
|
||||
@@ -52,7 +56,7 @@ const assetsRoutes: Route[] = flags.assets
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
text: t('Assets'),
|
||||
name: 'Assets',
|
||||
name: t('Assets'),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -71,7 +75,7 @@ const genesisRoutes: Route[] = flags.genesis
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
name: 'Genesis',
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
element: <Genesis />,
|
||||
},
|
||||
@@ -82,7 +86,7 @@ const governanceRoutes: Route[] = flags.governance
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
name: 'Governance proposals',
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
element: <Proposals />,
|
||||
},
|
||||
@@ -93,7 +97,7 @@ const marketsRoutes: Route[] = flags.markets
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
name: 'Markets',
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
children: [
|
||||
{
|
||||
@@ -113,17 +117,18 @@ const networkParametersRoutes: Route[] = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
name: 'NetworkParameters',
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const validators: Route[] = flags.validators
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
name: 'Validators',
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
@@ -133,14 +138,14 @@ const validators: Route[] = flags.validators
|
||||
const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
name: 'Home',
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
element: <Home />,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
name: 'Txs',
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
element: <Txs />,
|
||||
children: [
|
||||
@@ -160,7 +165,7 @@ const routerConfig: Route[] = [
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
name: 'Blocks',
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
@@ -176,7 +181,7 @@ const routerConfig: Route[] = [
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: 'Oracles',
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
// Note: Long enough that there is a truncated output and a full output
|
||||
const pubKey =
|
||||
'67755549e43e95f0697f83b2bf419c6ccc18eee32a8a61b8ba6f59471b86fbef';
|
||||
const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283';
|
||||
const hash = '7416753a30622a9e24a06f0172d6c33a95186b36806d96345c6dc5a23fa3f283';
|
||||
const height = '52987';
|
||||
|
||||
const txData: BlockExplorerTransactionResult = {
|
||||
|
||||
@@ -176,25 +176,14 @@ export const ValidatorsPage = () => {
|
||||
const validatorName =
|
||||
v.name && v.name.length > 0 ? v.name : truncateMiddle(v.id);
|
||||
return (
|
||||
<li className="mb-5" key={v.id}>
|
||||
<li className="mb-5 relative" 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"
|
||||
className="border border-vega-light-200 dark:border-vega-dark-200 rounded p-2 overflow-hidden"
|
||||
>
|
||||
{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">
|
||||
<h2 className="font-alpha text-2xl leading-[60px]">
|
||||
<ExternalLink href={validatorPage}>
|
||||
{validatorName}
|
||||
</ExternalLink>
|
||||
@@ -315,6 +304,23 @@ export const ValidatorsPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
{v.avatarUrl && (
|
||||
<KeyValueTableRow>
|
||||
<div>{t('Avatar')}</div>
|
||||
<div>
|
||||
<ExternalLink
|
||||
href={validatorPage}
|
||||
className="mx-auto"
|
||||
>
|
||||
<img
|
||||
className="max-w-[75px] md:max-w-[200px] max-h-[80px]"
|
||||
src={v.avatarUrl}
|
||||
alt={validatorName}
|
||||
/>
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
</KeyValueTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,3 +3,13 @@
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
Object.defineProperty(window, 'ResizeObserver', {
|
||||
writable: false,
|
||||
value: jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -2,3 +2,12 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.react-markdown-container a {
|
||||
color: #ff077f;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.react-markdown-container a:before {
|
||||
content: '🔗 ';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
export const trancheData = {
|
||||
'0': {
|
||||
tranche_id: 0,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'1': {
|
||||
tranche_id: 1,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 1670422330,
|
||||
duration: 1670453883,
|
||||
},
|
||||
'107': {
|
||||
tranche_id: 107,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'110': {
|
||||
tranche_id: 110,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'154': {
|
||||
tranche_id: 154,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'2': {
|
||||
tranche_id: 2,
|
||||
users: [
|
||||
'0x8716c75bb3abe54b959adc529e6355f0422454ed',
|
||||
'0xc704da0e96a6b910ffa8d3f9f667d3d35db8e5a1',
|
||||
'0xbb82d4d6e4381ae98f8c33629285867b54d91a03',
|
||||
'0x82ffe4ed818a6c7d4f0f044998611f24ec916c43',
|
||||
'0xb0594132540ebc14f17863f8296f4546a7afe4e7',
|
||||
'0x9015a3db9a922521007a9b495cafda14d8d4128e',
|
||||
'0x5426f7f717fdbe331bb5a99b908ad2fb75dff711',
|
||||
'0xf73e7ad8aa300a7f86bcc4d55e6c4ea25546e057',
|
||||
],
|
||||
initial_balance: 111300000000000000000,
|
||||
current_balance: 111297025049610000000,
|
||||
cliff_start: 1677578461,
|
||||
duration: 15209600,
|
||||
},
|
||||
'3': {
|
||||
tranche_id: 3,
|
||||
users: [
|
||||
'0xbb82d4d6e4381ae98f8c33629285867b54d91a03',
|
||||
'0x82ffe4ed818a6c7d4f0f044998611f24ec916c43',
|
||||
'0xb0594132540ebc14f17863f8296f4546a7afe4e7',
|
||||
'0x9015a3db9a922521007a9b495cafda14d8d4128e',
|
||||
'0x77a0b7e247b7b8ec99e068a24c401783d6c4dfff',
|
||||
'0x5426f7f717fdbe331bb5a99b908ad2fb75dff711',
|
||||
'0xc704da0e96a6b910ffa8d3f9f667d3d35db8e5a1',
|
||||
],
|
||||
initial_balance: 21000000000000000000,
|
||||
current_balance: 21000000000000000000,
|
||||
cliff_start: 1677578461,
|
||||
duration: 18628800,
|
||||
},
|
||||
'66': {
|
||||
tranche_id: 66,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
};
|
||||
@@ -22,7 +22,7 @@
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
@@ -38,30 +38,34 @@
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementPriceProperty": "prices.BTC.value",
|
||||
"settlementDataProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"lpPriceRange": "11",
|
||||
"instrument": {
|
||||
"code": "Token.24h",
|
||||
"future": {
|
||||
"quoteName": "fBTC",
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementDataProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
"horizon": "43200",
|
||||
"probability": "0.9999999",
|
||||
"auctionExtension": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
"params": {
|
||||
"mu": 0,
|
||||
"r": 0.016,
|
||||
"sigma": 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,76 @@
|
||||
{
|
||||
"lpPriceRange": "10",
|
||||
"instrument": {
|
||||
"code": "TEST.24h",
|
||||
"future": {
|
||||
"quoteName": "fUSDC",
|
||||
"settlementDataDecimals": 5,
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementPriceProperty": "prices.ETH.value",
|
||||
"settlementDataProperty": "prices.ETH.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"metadata": [
|
||||
"sector:energy",
|
||||
"sector:food",
|
||||
"source:docs.vega.xyz",
|
||||
"test:update"
|
||||
],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
@@ -66,6 +80,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"liquidityMonitoringParameters": {
|
||||
"targetStakeParameters": {
|
||||
"timeWindow": "3600",
|
||||
"scalingFactor": 10
|
||||
},
|
||||
"triggeringRatio": "0.7",
|
||||
"auctionExtension": "1"
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
|
||||
@@ -26,6 +26,10 @@ const enactmentDeadlineError =
|
||||
'[data-testid="enactment-before-voting-deadline"]';
|
||||
const proposalDownloadBtn = '[data-testid="proposal-download-json"]';
|
||||
const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
|
||||
@@ -45,6 +49,7 @@ context(
|
||||
{ tags: '@slow' },
|
||||
function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.createMarket();
|
||||
cy.visit('/');
|
||||
cy.vega_wallet_set_specified_approval_amount('1000');
|
||||
});
|
||||
@@ -65,7 +70,7 @@ context(
|
||||
// 3002-PROP-007
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
|
||||
cy.get(proposalParameterSelect).find('option').should('have.length', 116);
|
||||
cy.get(proposalParameterSelect).find('option').should('have.length', 117);
|
||||
cy.get(proposalParameterSelect).select(
|
||||
// 3007-PNEC-002
|
||||
'governance_proposal_asset_minEnact'
|
||||
@@ -175,9 +180,8 @@ context(
|
||||
cy.get(enactmentDeadlineError).should('not.exist');
|
||||
});
|
||||
|
||||
// Skipping because unclear what the required json is yet for new market proposal, will update once docs have been updated
|
||||
// 3003-todo-PMAN-001
|
||||
it.skip('Able to submit valid new market proposal', function () {
|
||||
// 3003-PMAN-001
|
||||
it('Able to submit valid new market proposal', function () {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.NEW_MARKET);
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -199,6 +203,7 @@ context(
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
|
||||
newMarketProposal.invalid = 'I am an invalid field';
|
||||
let newMarketPayload = JSON.stringify(newMarketProposal);
|
||||
cy.get(newProposalTerms).type(newMarketPayload, {
|
||||
parseSpecialCharSequences: false,
|
||||
@@ -209,11 +214,66 @@ context(
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should(
|
||||
'have.text',
|
||||
'Invalid params: the transaction is malformed'
|
||||
'Invalid params: the transaction is not a valid Vega command: unknown field "invalid" in vega.NewMarket'
|
||||
);
|
||||
});
|
||||
|
||||
it.skip('Able to submit update market proposal', function () {
|
||||
// Will fail if run after 'Able to submit update market proposal and vote for proposal'
|
||||
// 3002-PROP-022
|
||||
it('Unable to submit update market proposal without equity-like share in the market', function () {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.get(proposalMarketSelect).select('Test market 1');
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
cy.getByTestId('dialog-content')
|
||||
.find('p')
|
||||
.should('have.text', 'PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
|
||||
cy.ensure_specified_unstaked_tokens_are_associated('1');
|
||||
});
|
||||
|
||||
// 3002-PROP-020
|
||||
it('Unable to submit update market proposal without minimum amount of tokens', function () {
|
||||
cy.vega_wallet_teardown();
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
'fUSDC',
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.get(proposalMarketSelect).select('Test market 1');
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should(
|
||||
'have.text',
|
||||
'Network error: the network blocked the transaction through the spam protection: party has insufficient associated governance tokens in their staking account to submit proposal request (ABCI code 89)'
|
||||
);
|
||||
});
|
||||
|
||||
// 3001-VOTE-092
|
||||
it('Able to submit update market proposal and vote for proposal', function () {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
'fUSDC',
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -222,6 +282,10 @@ context(
|
||||
cy.get('dd').eq(0).should('have.text', 'Test market 1');
|
||||
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
|
||||
cy.get('dd').eq(2).should('not.be.empty');
|
||||
cy.get('dd').eq(2).invoke('text').as('EnactedMarketId');
|
||||
});
|
||||
cy.get('@EnactedMarketId').then((marketId) => {
|
||||
cy.VegaWalletSubmitLiquidityProvision(marketId, '1');
|
||||
});
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
@@ -232,6 +296,34 @@ context(
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.wait_for_proposal_submitted();
|
||||
cy.navigate_to('proposals');
|
||||
cy.get('@EnactedMarketId').then((marketId) => {
|
||||
cy.contains(marketId)
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
cy.vote_for_proposal('for');
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.get_proposal_information_from_table('Expected to pass')
|
||||
.contains('👍 by Token vote')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||
@@ -298,7 +390,7 @@ context(
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029
|
||||
cy.getByTestId('view-proposal-btn').click();
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
const rewardsTable = 'epoch-total-rewards-table';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const rewardsTimeOut = { timeout: 60000 };
|
||||
|
||||
context('rewards - flow', { tags: '@slow' }, function () {
|
||||
before('set up environment to allow rewards', function () {
|
||||
cy.visit('/');
|
||||
cy.wait_for_spinner();
|
||||
cy.deposit_asset(vegaAssetAddress, '1000');
|
||||
cy.validatorsSelfDelegate();
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.connectVegaWallet();
|
||||
topUpRewardsPool();
|
||||
cy.navigate_to('validators');
|
||||
cy.vega_wallet_teardown();
|
||||
cy.staking_page_associate_tokens('6000');
|
||||
cy.get(vegaWalletUnstakedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'6,000.0',
|
||||
txTimeout
|
||||
);
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
cy.click_on_validator_from_list(0);
|
||||
cy.staking_validator_page_add_stake('3000');
|
||||
cy.close_staking_dialog();
|
||||
cy.navigate_to('validators');
|
||||
cy.click_on_validator_from_list(1);
|
||||
cy.staking_validator_page_add_stake('3000');
|
||||
cy.close_staking_dialog();
|
||||
cy.navigate_to('rewards');
|
||||
});
|
||||
|
||||
it('Should display rewards per epoch', function () {
|
||||
cy.getByTestId(rewardsTable, rewardsTimeOut).should('exist');
|
||||
cy.getByTestId(rewardsTable)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId('asset').should('have.text', 'Vega');
|
||||
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD').should('have.text', '1');
|
||||
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE').should(
|
||||
'have.text',
|
||||
'0'
|
||||
);
|
||||
cy.getByTestId('total').should('have.text', '1');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should update when epoch starts', function () {
|
||||
cy.getByTestId(rewardsTable)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('h2').first().invoke('text').as('epochNumber');
|
||||
});
|
||||
cy.wait_for_beginning_of_epoch();
|
||||
cy.get('@epochNumber').then((epochNumber) => {
|
||||
cy.getByTestId(rewardsTable)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('h2').first().invoke('text').should('not.equal', epochNumber);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 2002-SINC-009 2002-SINC-010 2002-SINC-011 2002-SINC-012
|
||||
it('Should display table of rewards earned by connected vega wallet', function () {
|
||||
cy.getByTestId('epoch-reward-view-toggle-individual').click();
|
||||
cy.getByTestId('connected-vega-key')
|
||||
.find('span')
|
||||
.should('have.text', Cypress.env('vegaWalletPublicKey'));
|
||||
cy.getByTestId('epoch-individual-rewards-table')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('h2').first().should('contain.text', 'EPOCH');
|
||||
cy.getByTestId('individual-rewards-asset').should('have.text', 'Vega');
|
||||
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD')
|
||||
.should('contain.text', '0.4415')
|
||||
.and('contain.text', '(44.15%)');
|
||||
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE')
|
||||
.should('contain.text', '0.0004')
|
||||
.and('contain.text', '(44.15%)');
|
||||
cy.getByTestId('total').should('have.text', '0.4419');
|
||||
});
|
||||
});
|
||||
|
||||
function topUpRewardsPool() {
|
||||
// Must ensure that test wallet contains assets already and that the tests are within the start and end epochs
|
||||
cy.exec(
|
||||
`vega wallet transaction send --wallet ${Cypress.env(
|
||||
'vegaWalletName'
|
||||
)} --pubkey ${Cypress.env(
|
||||
'vegaWalletPublicKey'
|
||||
)} -p "./src/fixtures/wallet/passphrase" --network DV '{"transfer":{"fromAccountType":4,"toAccountType":12,"to":"0000000000000000000000000000000000000000000000000000000000000000","asset":"b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b","amount":"1000000000000000000","recurring":{"startEpoch":30, "endEpoch": 200, "factor":"1"}}}' --home ${Cypress.env(
|
||||
'vegaWalletLocation'
|
||||
)}`,
|
||||
{ failOnNonZeroExit: false }
|
||||
)
|
||||
.its('stderr')
|
||||
.should('contain', '');
|
||||
}
|
||||
});
|
||||
@@ -14,13 +14,14 @@ const stakeShare = '[data-testid="stake-percentage"]';
|
||||
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
'[data-testid="vega-wallet-balance-unstaked"]:visible';
|
||||
const vegaWalletStakedBalances =
|
||||
'[data-testid="vega-wallet-balance-staked-validators"]';
|
||||
const ethWalletAssociatedBalances =
|
||||
'[data-testid="eth-wallet-associated-balances"]';
|
||||
const ethWalletTotalAssociatedBalance = '[data-testid="currency-locked"]';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const ethWalletTotalAssociatedBalance =
|
||||
'[data-testid="currency-locked"]:visible';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]:visible';
|
||||
const vegaWallet = '[data-testid="vega-wallet"]';
|
||||
const partValidatorId = '…';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
|
||||
@@ -27,7 +27,7 @@ context(
|
||||
function () {
|
||||
before('visit withdrawals and connect vega wallet', function () {
|
||||
cy.updateCapsuleMultiSig(); // When running tests locally, will fail if run without restarting capsule
|
||||
cy.deposit_asset(usdcEthAddress);
|
||||
cy.deposit_asset(usdcEthAddress, '100000000000000000000');
|
||||
});
|
||||
|
||||
beforeEach('Navigate to withdrawal page', function () {
|
||||
|
||||
@@ -1,60 +1,10 @@
|
||||
const navSection = 'nav';
|
||||
const navSupply = '[href="/token/tranches"]';
|
||||
const navToken = '[href="/token"]';
|
||||
const navStaking = '[href="/validators"]';
|
||||
const navRewards = '[href="/rewards"]';
|
||||
const navWithdraw = '[href="/token/withdraw"]';
|
||||
const navGovernance = '[href="/proposals"]';
|
||||
const navRedeem = '[href="/token/redeem"]';
|
||||
|
||||
context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
cy.get('nav', { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
before('wait for page to load', function () {
|
||||
cy.get(navSection, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Navigation tabs', function () {
|
||||
it('should have proposals tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navGovernance).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have validators tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navStaking).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have rewards tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navRewards).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token dropdown', function () {
|
||||
before('click on token dropdown', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.getByTestId('state-trigger').realClick();
|
||||
});
|
||||
});
|
||||
it('should have token dropdown', function () {
|
||||
cy.get(navToken).should('be.visible');
|
||||
});
|
||||
it('should have supply & vesting dropdown', function () {
|
||||
cy.get(navSupply).should('be.visible');
|
||||
});
|
||||
it('should have withdraw dropdown', function () {
|
||||
cy.get(navWithdraw).should('be.visible');
|
||||
});
|
||||
it('should have redeem dropdown', function () {
|
||||
cy.get(navRedeem).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Links and buttons', function () {
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
context(
|
||||
'Landing pages - verifies required elements',
|
||||
{ tags: '@smoke' },
|
||||
() => {
|
||||
const navbar = 'nav .navbar';
|
||||
const mobileNav = '[data-testid="menu-drawer"]';
|
||||
|
||||
const topLevelLinks = [
|
||||
{
|
||||
name: 'Proposals',
|
||||
selector: '[href="/proposals"]',
|
||||
tests: () => {
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const proposalDocumentationLink =
|
||||
'[data-testid="proposal-documentation-link"]';
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
const newProposalLink = '[data-testid="new-proposal-link"]';
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Validators',
|
||||
selector: '[href="/validators"]',
|
||||
tests: () => {
|
||||
it('Should have Staking Guide link visible', function () {
|
||||
// 2001-STKE-003
|
||||
cy.get('[data-testid="staking-guide-link"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Read more about staking on Vega')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega'
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Rewards',
|
||||
selector: '[href="/rewards"]',
|
||||
header: 'Rewards and fees',
|
||||
tests: () => {
|
||||
it('should have epoch warning', () => {
|
||||
cy.get('[data-testid="callout"]')
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Rewards are credited less than a minute after the epoch ends.This delay is set by a network parameter'
|
||||
);
|
||||
});
|
||||
it('should have toggle for seeing total vs individual rewards', () => {
|
||||
cy.get('[data-testid="epoch-reward-view-toggle-total"]').should(
|
||||
'be.visible'
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const secondLevelLinks = [
|
||||
{
|
||||
trigger: true,
|
||||
name: 'Token',
|
||||
selector: '[data-testid="state-trigger"]',
|
||||
},
|
||||
{
|
||||
name: 'Token',
|
||||
selector: '[href="/token"]',
|
||||
header: 'The $VEGA token',
|
||||
},
|
||||
{
|
||||
name: 'Supply & Vesting',
|
||||
selector: '[href="/token/tranches"]',
|
||||
header: 'Vesting tranches',
|
||||
},
|
||||
{
|
||||
name: 'Withdraw',
|
||||
selector: '[href="/token/withdraw"]',
|
||||
header: 'Withdrawals',
|
||||
tests: () => {
|
||||
it('should have connect Vega wallet button', function () {
|
||||
cy.get('[data-testid="connect-to-vega-wallet-btn"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Redeem',
|
||||
selector: '[href="/token/redeem"]',
|
||||
header: 'Vesting',
|
||||
tests: () => {
|
||||
// 1005-VEST-018
|
||||
it('should have connect Eth wallet button', function () {
|
||||
cy.get('[data-testid="connect-to-eth-btn"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Ethereum wallet');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Associate',
|
||||
selector: '[href="/token/associate"]',
|
||||
header: 'Associate $VEGA tokens with Vega Key',
|
||||
},
|
||||
{
|
||||
name: 'Disassociate',
|
||||
selector: '[href="/token/disassociate"]',
|
||||
header: 'Disassociate $VEGA tokens from a Vega key',
|
||||
},
|
||||
];
|
||||
|
||||
const expand = () => {
|
||||
const trigger = secondLevelLinks.find((l) => l.trigger).selector;
|
||||
cy.get(trigger).then((el) => {
|
||||
if (el.attr('aria-expanded') === 'false') {
|
||||
el.trigger('click');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const collapse = () => {
|
||||
const trigger = secondLevelLinks.find((l) => l.trigger).selector;
|
||||
cy.get(trigger).then((el) => {
|
||||
if (el.attr('aria-expanded') === 'true') {
|
||||
el.trigger('click');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const ensureHeader = (text) => {
|
||||
cy.get('main header h1').should('have.text', text);
|
||||
};
|
||||
|
||||
before(() => {
|
||||
// goes to HOME
|
||||
cy.visit('/');
|
||||
// and waits for it to load
|
||||
cy.get(navbar, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Navigation (desktop)', () => {
|
||||
for (const { name, selector } of topLevelLinks) {
|
||||
it(`should have ${name} nav link`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, trigger } of secondLevelLinks) {
|
||||
it(`should have ${name} ${
|
||||
trigger ? 'as trigger button' : ''
|
||||
} second level nav link`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
if (trigger) cy.get(selector).click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
collapse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation (mobile)', () => {
|
||||
beforeEach(() => {
|
||||
// iphone xr
|
||||
cy.viewport(414, 896);
|
||||
});
|
||||
|
||||
it('should have burger button', () => {
|
||||
cy.get('[data-testid="button-menu-drawer"]').should('be.visible');
|
||||
cy.get('[data-testid="button-menu-drawer"]').click();
|
||||
cy.get(mobileNav).should('be.visible');
|
||||
});
|
||||
|
||||
for (const { name, selector } of topLevelLinks) {
|
||||
it(`should have ${name} nav link`, () => {
|
||||
cy.get(mobileNav).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, trigger } of secondLevelLinks) {
|
||||
it(`should have ${name} ${
|
||||
trigger ? 'as trigger button' : ''
|
||||
} second level nav link`, () => {
|
||||
cy.get(mobileNav).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
cy.get('[data-testid="button-menu-drawer"]').click();
|
||||
cy.viewport(
|
||||
Cypress.config('viewportWidth'),
|
||||
Cypress.config('viewportHeight')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Elements', () => {
|
||||
for (const { name, selector, header, tests } of topLevelLinks) {
|
||||
describe(`${name} page`, () => {
|
||||
it(`navigates to ${name}`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.log(`goes to ${name}`);
|
||||
cy.get(selector).click();
|
||||
cy.log(`ensures ${name} is highlighted`);
|
||||
cy.get(selector).should('have.attr', 'aria-current');
|
||||
});
|
||||
});
|
||||
it('displays header', () => {
|
||||
ensureHeader(header || name);
|
||||
});
|
||||
|
||||
if (tests) tests.apply(this);
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, header, tests } of secondLevelLinks.filter(
|
||||
(l) => !l.trigger
|
||||
)) {
|
||||
describe(`${name} page`, () => {
|
||||
it(`navigates to ${name}`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
expand();
|
||||
cy.log(`goes to ${name}`);
|
||||
cy.get(selector).click();
|
||||
expand();
|
||||
cy.log(`ensures ${name} is highlighted`);
|
||||
cy.get(selector).should('have.attr', 'aria-current');
|
||||
});
|
||||
});
|
||||
it('displays header', () => {
|
||||
ensureHeader(header || name);
|
||||
});
|
||||
|
||||
if (tests) tests.apply(this);
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
collapse();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,68 +0,0 @@
|
||||
const proposalDocumentationLink = '[data-testid="proposal-documentation-link"]';
|
||||
const newProposalButton = '[data-testid="new-proposal-link"]';
|
||||
const newProposalLink = '[data-testid="new-proposal-link"]';
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
|
||||
context(
|
||||
'Governance Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to governance page', function () {
|
||||
cy.visit('/').navigate_to('proposals');
|
||||
});
|
||||
|
||||
describe('with no network change proposals', function () {
|
||||
it('should have governance tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('proposals');
|
||||
});
|
||||
|
||||
it('should have GOVERNANCE header visible', function () {
|
||||
cy.verify_page_header('Proposals');
|
||||
});
|
||||
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
});
|
||||
|
||||
// Skipping this test for now, the new proposal button no longer takes a user directly
|
||||
// to a proposal form, instead it takes them to a page where they can select a proposal type.
|
||||
// Keeping this test here for now as it can be repurposed to test the new proposal forms.
|
||||
it.skip('should be able to see a connect wallet button - if vega wallet disconnected and new proposal button selected', function () {
|
||||
cy.get(newProposalButton).should('be.visible').click();
|
||||
cy.get(connectToVegaWalletButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
cy.navigate_to('proposals');
|
||||
cy.wait_for_spinner();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -50,7 +50,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
it('Able to disconnect via wallet', function () {
|
||||
cy.getByTestId('manage-vega-wallet').click();
|
||||
cy.get('aside [data-testid="manage-vega-wallet"]').click();
|
||||
cy.getByTestId('disconnect').click();
|
||||
cy.getByTestId(banner).should('not.exist');
|
||||
});
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
const viewToggle = '[data-testid="epoch-reward-view-toggle-total"]';
|
||||
const warning = '[data-testid="callout"]';
|
||||
|
||||
context(
|
||||
'Rewards Page - verify elements on page',
|
||||
{ tags: '@regression' },
|
||||
function () {
|
||||
before('navigate to rewards page', function () {
|
||||
cy.visit('/').navigate_to('rewards');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have REWARDS tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('rewards');
|
||||
});
|
||||
|
||||
it('should have rewards header visible', function () {
|
||||
cy.verify_page_header('Rewards and fees');
|
||||
});
|
||||
|
||||
it('should have epoch warning', function () {
|
||||
cy.get(warning)
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Rewards are credited 5 minutes after the epoch ends.This delay is set by a network parameter'
|
||||
);
|
||||
});
|
||||
|
||||
it('should have toggle for seeing total vs individual rewards', function () {
|
||||
cy.get(viewToggle).should('be.visible');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -17,8 +17,7 @@ const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
|
||||
context('Verify elements on Token page', { tags: '@smoke' }, function () {
|
||||
before('Visit token page', function () {
|
||||
cy.visit('/');
|
||||
cy.navigate_to('token');
|
||||
cy.visit('/token');
|
||||
});
|
||||
describe('THE $VEGA TOKEN table', function () {
|
||||
it('should have TOKEN ADDRESS', function () {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { trancheData } from '../../fixtures/mocks/tranches';
|
||||
|
||||
const tranches = trancheData;
|
||||
|
||||
context(
|
||||
'Tranches page - verify elements on the page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('visit homepage', function () {
|
||||
cy.intercept('GET', '**/tranches/stats', { tranches });
|
||||
cy.visit('/token/tranches');
|
||||
});
|
||||
|
||||
// 1005-VEST-001
|
||||
// 1005-VEST-002
|
||||
it('Able to view tranches', function () {
|
||||
cy.getByTestId('tranche-item')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('a')
|
||||
.should('have.text', 'Tranche 2') // 1005-VEST-003
|
||||
.and('have.attr', 'href', '/token/tranches/2');
|
||||
cy.get('span').eq(1).should('have.text', '111.30'); // 1005-VEST-005
|
||||
cy.contains('Unlocking starts') // 1005-VEST-008
|
||||
.parent()
|
||||
.should('contain.text', '28 Feb 2023');
|
||||
cy.contains('Fully unlocked')
|
||||
.parent()
|
||||
.should('contain.text', '23 Aug 2023');
|
||||
cy.getByTestId('progress-bar').should('exist');
|
||||
cy.getByTestId('currency-locked') // 1005-VEST-006
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('currency-unlocked') // 1005-VEST-007
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to see individual tranche data', function () {
|
||||
cy.get('[href="/token/tranches/2"]').click();
|
||||
cy.getByTestId('redeemed-tranche-tokens').within(() => {
|
||||
cy.get('span').eq(1).should('have.text', 0);
|
||||
});
|
||||
cy.getByTestId('key-value-table').within(() => {
|
||||
cy.getByTestId('link')
|
||||
.should('have.length', 8)
|
||||
.each((ethLink) => {
|
||||
cy.wrap(ethLink)
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://sepolia.etherscan.io/address/');
|
||||
});
|
||||
cy.getByTestId('redeem-link')
|
||||
.should('have.length', 8)
|
||||
.each((redeemLink) => {
|
||||
cy.wrap(redeemLink)
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/token/redeem/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to view tranches with less than 10 vega', function () {
|
||||
cy.navigate_to('supply');
|
||||
cy.getByTestId('show-all-tranches').click();
|
||||
cy.getByTestId('tranche-item')
|
||||
.should('have.length', 8)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('a')
|
||||
.should('have.text', 'Tranche 0')
|
||||
.and('have.attr', 'href', '/token/tranches/0');
|
||||
cy.get('span').eq(1).should('have.text', '0.00');
|
||||
cy.contains('Unlocking starts')
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.contains('Fully unlocked')
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.getByTestId('progress-bar').should('exist');
|
||||
cy.getByTestId('currency-locked')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('currency-unlocked')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="cypress" />
|
||||
const guideLink = '[data-testid="staking-guide-link"]';
|
||||
const validatorTitle = '[data-testid="validator-node-title"]';
|
||||
const validatorId = '[data-testid="validator-id"]';
|
||||
const validatorPubKey = '[data-testid="validator-public-key"]';
|
||||
@@ -25,31 +24,7 @@ const stakeNumberRegex = /^\d*\.?\d*$/;
|
||||
|
||||
context('Staking Page - verify elements on page', function () {
|
||||
before('navigate to staking page', function () {
|
||||
cy.visit('/').navigate_to('validators');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', { tags: '@smoke' }, function () {
|
||||
describe('description section', function () {
|
||||
it('Should have validators tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('validators');
|
||||
});
|
||||
|
||||
it('Should have validators ON VEGA header visible', function () {
|
||||
cy.verify_page_header('Validators');
|
||||
});
|
||||
|
||||
it('Should have Staking Guide link visible', function () {
|
||||
// 2001-STKE-003
|
||||
cy.get(guideLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Read more about staking on Vega')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega'
|
||||
);
|
||||
});
|
||||
});
|
||||
cy.visit('/validators');
|
||||
});
|
||||
|
||||
describe(
|
||||
@@ -108,6 +83,7 @@ context('Staking Page - verify elements on page', function () {
|
||||
.should('contain', 'Normalised voting power: 0.10%');
|
||||
});
|
||||
|
||||
// 2002-SINC-018
|
||||
it('Should be able to see validator total penalties', function () {
|
||||
cy.get('[col-id="totalPenalties"] > div > span > span')
|
||||
.should('have.length.at.least', 1)
|
||||
|
||||
@@ -1,44 +1,111 @@
|
||||
const connectButton = '[data-testid="connect-to-eth-btn"]';
|
||||
const lockedTokensInVestingContract = '6,499,972.30';
|
||||
|
||||
context(
|
||||
'Vesting Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
describe('with wallets disconnected', function () {
|
||||
before('navigate to vesting page', function () {
|
||||
cy.visit('/').navigate_to('vesting');
|
||||
});
|
||||
it('should have vesting tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have VESTING header visible', function () {
|
||||
cy.verify_page_header('Vesting');
|
||||
});
|
||||
|
||||
it('should have connect Eth wallet info', function () {
|
||||
cy.get(connectButton).should('be.visible');
|
||||
});
|
||||
|
||||
it('should have connect Eth wallet button', function () {
|
||||
cy.get(connectButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Ethereum wallet');
|
||||
});
|
||||
before('navigate to vesting page', function () {
|
||||
cy.visit('/token/redeem');
|
||||
});
|
||||
|
||||
describe('with eth wallet connected', function () {
|
||||
describe('With Eth wallet connected', function () {
|
||||
before('connect eth wallet', function () {
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.visit('/');
|
||||
cy.getByTestId('view-connected-eth-btn').click();
|
||||
});
|
||||
|
||||
// 1005-VEST-001
|
||||
// 1005-VEST-002
|
||||
it('Able to view tranches', function () {
|
||||
cy.navigate_to('supply');
|
||||
cy.url().should('include', '/token/tranches');
|
||||
cy.get('h1').should('contain.text', 'Vesting tranches');
|
||||
// 1005-VEST-020 1005-VEST-021
|
||||
it('Tokens in vesting contract for eth wallet is displayed on wallet window', function () {
|
||||
cy.getByTestId('vega-in-vesting-contract').within(() => {
|
||||
cy.getByTestId('currency-title')
|
||||
.should('contain.text', 'VEGA')
|
||||
.and('contain.text', 'In vesting contract');
|
||||
cy.get('[data-testid="currency-value"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-locked"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-unlocked"]:visible').should(
|
||||
'have.text',
|
||||
'0.00'
|
||||
);
|
||||
});
|
||||
});
|
||||
// 1005-VEST-022 1005-VEST-023
|
||||
it('Tokens amount displayed in vesting page', function () {
|
||||
cy.getByTestId(
|
||||
'redemption-description',
|
||||
Cypress.env('txTimeout')
|
||||
).should('exist');
|
||||
const redemptionText =
|
||||
'The connected Ethereum wallet (0xEe7D…d94F) has 6,499,972.30 $VEGA tokens in 1 tranche(s) of the vesting contract.';
|
||||
|
||||
cy.getByTestId('redemption-description').should(
|
||||
'have.text',
|
||||
redemptionText
|
||||
);
|
||||
cy.getByTestId('vesting-table').within(() => {
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Vesting VEGA');
|
||||
cy.get('dd').should('have.text', lockedTokensInVestingContract);
|
||||
});
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Locked');
|
||||
cy.get('dd').should('have.text', lockedTokensInVestingContract);
|
||||
});
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Unlocked');
|
||||
cy.get('dd').should('have.text', '0.00');
|
||||
});
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Associated');
|
||||
cy.get('dd').should('have.text', '0.00');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 1005-VEST-024 1005-VEST-025 1005-VEST-026 1005-VEST-036 1005-VEST-037
|
||||
it('Tokens locked in individual tranches are displayed', function () {
|
||||
cy.getByTestId('tranche-table-footer').should(
|
||||
'have.text',
|
||||
'All the tokens in this tranche are locked and must be assigned to a tranche before they can be redeemed.'
|
||||
);
|
||||
cy.getByTestId('tranche-item').within(() => {
|
||||
cy.get('a')
|
||||
.should('have.text', 'Tranche 0')
|
||||
.and('have.attr', 'href', '/token/tranches/0');
|
||||
cy.get('span')
|
||||
.eq(1)
|
||||
.should('have.text', lockedTokensInVestingContract);
|
||||
cy.contains('Unlocking starts') // 1005-VEST-008
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.contains('Fully unlocked')
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.getByTestId('progress-bar').should('exist');
|
||||
cy.getByTestId('currency-locked') // 1005-VEST-006
|
||||
.should('have.text', lockedTokensInVestingContract);
|
||||
cy.getByTestId('currency-unlocked') // 1005-VEST-007
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('tranche-item-footer').should(
|
||||
'have.text',
|
||||
'All the tokens in this tranche are locked and can not be redeemed yet.'
|
||||
);
|
||||
});
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
const walletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const walletContainer = 'aside [data-testid="ethereum-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectToEthButton = '[data-testid="connect-to-eth-wallet-button"]';
|
||||
const connectToEthButton =
|
||||
'[data-testid="connect-to-eth-wallet-button"]:visible';
|
||||
const connectorList = '[data-testid="web3-connector-list"]';
|
||||
const associate = '[href="/token/associate"]';
|
||||
const disassociate = '[href="/token/disassociate"]';
|
||||
const disconnect = '[data-testid="disconnect-from-eth-wallet-button"]';
|
||||
const accountNo = '[data-testid="ethereum-account-truncated"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]';
|
||||
const currencyValue = '[data-testid="currency-value"]';
|
||||
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]';
|
||||
const vegaInWallet = '[data-testid="vega-in-wallet"]';
|
||||
const progressBar = '[data-testid="progress-bar"]';
|
||||
const currencyLocked = '[data-testid="currency-locked"]';
|
||||
const currencyUnlocked = '[data-testid="currency-unlocked"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]:visible';
|
||||
const currencyValue = '[data-testid="currency-value"]:visible';
|
||||
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]:visible';
|
||||
const vegaInWallet = '[data-testid="vega-in-wallet"]:visible';
|
||||
const progressBar = '[data-testid="progress-bar"]:visible';
|
||||
const currencyLocked = '[data-testid="currency-locked"]:visible';
|
||||
const currencyUnlocked = '[data-testid="currency-unlocked"]:visible';
|
||||
const dialog = '[role="dialog"]';
|
||||
const dialogHeader = '[data-testid="dialog-title"]';
|
||||
const dialogCloseBtn = '[data-testid="dialog-close"]';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
|
||||
const walletContainer = '[data-testid="vega-wallet"]';
|
||||
const walletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectButton = '[data-testid="connect-vega-wallet"]';
|
||||
const getVegaLink = '[data-testid="link"]';
|
||||
@@ -14,7 +14,6 @@ const restWallet = '#wallet';
|
||||
const restPassphrase = '#passphrase';
|
||||
const restConnectBtn = '[type="submit"]';
|
||||
const accountNo = '[data-testid="vega-account-truncated"]';
|
||||
const walletName = '[data-testid="wallet-name"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]';
|
||||
const currencyValue = '[data-testid="currency-value"]';
|
||||
const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right';
|
||||
@@ -28,44 +27,24 @@ const vegaWalletCurrencyTitle = '[data-testid="currency-title"]';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
|
||||
const faucetAssets = {
|
||||
BTCFake: 'fBTC',
|
||||
DAIFake: 'fDAI',
|
||||
EUROFake: 'fEURO',
|
||||
USDCFake: 'fUSDC',
|
||||
};
|
||||
|
||||
context(
|
||||
'Vega Wallet - verify elements on widget',
|
||||
{ tags: '@regression' },
|
||||
function () {
|
||||
before('visit token home page', function () {
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
cy.visit('/');
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
before('wait for widget to load', function () {
|
||||
cy.get(walletContainer, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
it('should have VEGA WALLET header visible', function () {
|
||||
describe('with wallets disconnected', () => {
|
||||
it('should have required elements visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Vega Wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega button visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet to use associated $VEGA');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Get a Vega wallet link visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(getVegaLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Get a Vega wallet')
|
||||
@@ -74,14 +53,14 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
describe('when connect button clicked', function () {
|
||||
before('click connect vega wallet button', function () {
|
||||
describe('when connect button clicked', () => {
|
||||
before('click connect vega wallet button', () => {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega header visible', function () {
|
||||
it('should have Connect Vega header visible', () => {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(walletDialogHeader)
|
||||
.should('be.visible')
|
||||
@@ -175,14 +154,6 @@ context(
|
||||
}
|
||||
);
|
||||
|
||||
it.skip('should have wallet name visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletName)
|
||||
.should('be.visible')
|
||||
.and('have.text', `${Cypress.env('vegaWalletName')} key 1`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Vega Associated currency title visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(currencyTitle)
|
||||
@@ -299,113 +270,71 @@ context(
|
||||
});
|
||||
|
||||
// 2002-SINC-016
|
||||
describe('when assets exist in vegawallet', function () {
|
||||
before('send-faucet assets to connected vega wallet', function () {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.USDCFake,
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.BTCFake,
|
||||
'600000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.EUROFake,
|
||||
'800000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.DAIFake,
|
||||
'200000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
describe('Vega wallet with assets', function () {
|
||||
const assets = [
|
||||
{
|
||||
id: 'fUSDC',
|
||||
name: 'USDC (fake)',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
},
|
||||
{
|
||||
id: 'fDAI',
|
||||
name: 'DAI (fake)',
|
||||
amount: '200000',
|
||||
expectedAmount: '2.00',
|
||||
},
|
||||
{
|
||||
id: 'fBTC',
|
||||
name: 'BTC (fake)',
|
||||
amount: '600000',
|
||||
expectedAmount: '6.00',
|
||||
},
|
||||
{
|
||||
id: 'fEURO',
|
||||
name: 'EURO (fake)',
|
||||
amount: '800000',
|
||||
expectedAmount: '8.00',
|
||||
},
|
||||
];
|
||||
|
||||
before('faucet assets to connected vega wallet', function () {
|
||||
for (const { id, amount } of assets) {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
id,
|
||||
amount,
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
}
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.ethereum_wallet_connect();
|
||||
});
|
||||
|
||||
it('should see fUSDC assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.USDCFake, name: 'USDC (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
for (const { id, name, expectedAmount } of assets) {
|
||||
it(`should see ${id} within vega wallet`, () => {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within((el) => {
|
||||
const value = parseFloat(el.text());
|
||||
cy.wrap(value).should('be.gte', parseFloat(expectedAmount));
|
||||
});
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id)
|
||||
.parent()
|
||||
.contains(name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fBTC assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.BTCFake, name: 'BTC (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('6.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fEURO assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.EUROFake, name: 'EURO (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('8.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fDAI assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.DAIFake, name: 'DAI (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('2.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
const connectToVegaBtn = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
|
||||
context(
|
||||
'Withdraw Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to withdrawals page', function () {
|
||||
cy.visit('/').navigate_to('withdraw');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have withdraw tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have WITHDRAW header visible', function () {
|
||||
cy.verify_page_header('Withdrawals');
|
||||
});
|
||||
|
||||
it('should have connect Vega wallet button', function () {
|
||||
cy.get(connectToVegaBtn)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -10,7 +10,7 @@ Cypress.Commands.add(
|
||||
);
|
||||
|
||||
const navigation = {
|
||||
section: 'nav',
|
||||
section: '[data-testid="navigation"]',
|
||||
vesting: '[href="/token/redeem"]',
|
||||
validators: '[href="/validators"]',
|
||||
rewards: '[href="/rewards"]',
|
||||
@@ -22,25 +22,32 @@ const navigation = {
|
||||
};
|
||||
|
||||
const topLevelRoutes = ['proposals', 'validators', 'rewards'];
|
||||
const tokenDropDown = 'state-trigger';
|
||||
|
||||
Cypress.Commands.add('navigate_to', (page) => {
|
||||
const tokenDropDown = 'state-trigger';
|
||||
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).click();
|
||||
cy.getByTestId('token-dropdown').within(() => {
|
||||
cy.get(navigation[page]).click();
|
||||
// FIXME: Timeout madness
|
||||
cy.getByTestId(tokenDropDown, { timeout: 60000 }).eq(0).click();
|
||||
cy.get('[data-testid="token-dropdown"]:visible').within(() => {
|
||||
cy.get(navigation[page]).eq(0).click();
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(navigation[page]).click();
|
||||
cy.get(navigation[page]).eq(0).click();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Cypress.Commands.add('verify_tab_highlighted', (page) => {
|
||||
return cy.get(navigation.section).within(() => {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).eq(0).click();
|
||||
cy.get('[data-testid="token-dropdown"]:visible').within(() => {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
});
|
||||
} else {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,7 +66,7 @@ export function associateTokenStartOfTests() {
|
||||
cy.highlight(`Associating tokens for first time`);
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('[href="/token/associate"]').first().click();
|
||||
cy.get('[href="/token/associate"]:visible').first().click();
|
||||
cy.getByTestId('associate-radio-wallet', { timeout: 30000 }).click();
|
||||
cy.getByTestId('token-amount-input', epochTimeout).type('1');
|
||||
cy.getByTestId('token-input-submit-button', txTimeout)
|
||||
|
||||
@@ -82,9 +82,10 @@ Cypress.Commands.add(
|
||||
Cypress.Commands.add(
|
||||
'get_submitted_proposal_from_proposal_list',
|
||||
(proposalTitle) => {
|
||||
cy.get_proposal_id_from_list(proposalTitle);
|
||||
cy.get('@proposalIdText').then((proposalId) => {
|
||||
return cy.get(`#${proposalId}`);
|
||||
cy.get_proposal_id_from_list(proposalTitle).then(() => {
|
||||
cy.get('@proposalIdText').then((proposalId) => {
|
||||
return cy.get(`#${proposalId}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,10 +3,10 @@ const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
|
||||
const tokenInputApprove = '[data-testid="token-input-approve-button"]';
|
||||
const addStakeRadioButton = '[data-testid="add-stake-radio"]';
|
||||
const removeStakeRadioButton = '[data-testid="remove-stake-radio"]';
|
||||
const ethWalletAssociateButton = '[href="/token/associate"]';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]';
|
||||
const ethWalletAssociateButton = '[href="/token/associate"]:visible';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
'[data-testid="vega-wallet-balance-unstaked"]:visible';
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
|
||||
const associateContractRadioButton = '[data-testid="associate-radio-contract"]';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const connectToEthButton = '[data-testid="connect-to-eth-wallet-button"]';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]:visible';
|
||||
const connectToEthButton =
|
||||
'[data-testid="connect-to-eth-wallet-button"]:visible';
|
||||
const capsuleWalletConnectButton = '[data-testid="web3-connector-Unknown"]';
|
||||
|
||||
Cypress.Commands.add('ethereum_wallet_connect', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { ethers, Wallet } from 'ethers';
|
||||
|
||||
const vegaWalletContainer = '[data-testid="vega-wallet"]';
|
||||
const vegaWalletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
|
||||
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
|
||||
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
@@ -47,13 +47,14 @@ beforeEach(function () {
|
||||
cy.wrap(this.vestingContract).as('vestingContract');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('deposit_asset', function (assetEthAddress) {
|
||||
Cypress.Commands.add('deposit_asset', function (assetEthAddress, amount) {
|
||||
cy.highlight('Depositing asset into vegawallet');
|
||||
cy.get('@signer', { log: false }).then((signer) => {
|
||||
// Approve asset
|
||||
cy.wrap(
|
||||
new TokenFaucetable(assetEthAddress, signer).approve(
|
||||
Erc20BridgeAddress,
|
||||
'10000000000'
|
||||
amount + '0'.repeat(19)
|
||||
)
|
||||
)
|
||||
.then((tx) => {
|
||||
@@ -67,7 +68,7 @@ Cypress.Commands.add('deposit_asset', function (assetEthAddress) {
|
||||
cy.wrap(
|
||||
bridge.deposit_asset(
|
||||
assetEthAddress,
|
||||
'1000000000',
|
||||
amount + '0'.repeat(18),
|
||||
'0x' + vegaWalletPubKey
|
||||
),
|
||||
{ timeout: transactionTimeout, log: false }
|
||||
@@ -95,7 +96,7 @@ Cypress.Commands.add('faucet_asset', function (assetEthAddress) {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('vega_wallet_teardown', function () {
|
||||
cy.get('[data-testid="associated-amount"]')
|
||||
cy.get('aside [data-testid="associated-amount"]')
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
.as('associatedAmount');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
|
||||
NX_VEGA_URL=https://api.stagnet3.vega.xyz/graphql
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_VEGA_URL=https://api.n00.devnet1.vega.xyz/graphql
|
||||
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
|
||||
@@ -9,4 +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
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
|
||||
NX_VEGA_URL=https://api.vega.xyz/query
|
||||
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://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
@@ -10,4 +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
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV=MIRROR
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
|
||||
NX_VEGA_URL=https://api.n00.mainnet-mirror.vega.xyz/graphql
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
|
||||
NX_VEGA_ENV=SANDBOX
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
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
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user