diff --git a/.eslintrc.json b/.eslintrc.json index 7fa843875..6a841772d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,6 +25,13 @@ "case": "kebabCase", "ignore": ["\\[[a-zA-Z]+\\]\\.page"] } + ], + "no-restricted-imports": [ + "warn", + { + "name": "lodash", + "message": "Import the specific methods you need from lodash e.g. `import get from 'lodash/get'`. This helps with bundle sizing." + } ] } }, diff --git a/apps/explorer-e2e/.env.devent b/apps/explorer-e2e/.env.devent index a852aa565..996011a8e 100644 --- a/apps/explorer-e2e/.env.devent +++ b/apps/explorer-e2e/.env.devent @@ -1,23 +1,3 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://n04.d.vega.xyz/tm" diff --git a/apps/explorer-e2e/.env.mainnet b/apps/explorer-e2e/.env.mainnet index 9fedccbba..13b74ef15 100644 --- a/apps/explorer-e2e/.env.mainnet +++ b/apps/explorer-e2e/.env.mainnet @@ -1,23 +1,3 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://mainnet-observer-proxy01.ops.vega.xyz/" @@ -28,7 +8,6 @@ NX_VEGA_URL = "https://api.token.vega.xyz/query" NX_EXPLORER_ASSETS = 1 NX_EXPLORER_GENESIS = 1 NX_EXPLORER_GOVERNANCE = 1 -NX_EXPLORER_MARKETS = 1 NX_EXPLORER_NETWORK_PARAMETERS = 1 NX_EXPLORER_PARTIES = 1 NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer-e2e/.env.stagnet1 b/apps/explorer-e2e/.env.stagnet1 index 3abca14e7..871afdc6d 100644 --- a/apps/explorer-e2e/.env.stagnet1 +++ b/apps/explorer-e2e/.env.stagnet1 @@ -1,30 +1,9 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://n03.s.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.s.vega.xyz/tm/websocket" NX_VEGA_URL = "https://n03.s.vega.xyz/query" - # App flags NX_EXPLORER_ASSETS = 1 NX_EXPLORER_GENESIS = 1 diff --git a/apps/explorer-e2e/.env.stagnet2 b/apps/explorer-e2e/.env.stagnet2 index 5499a2a6b..e4cbaab0d 100644 --- a/apps/explorer-e2e/.env.stagnet2 +++ b/apps/explorer-e2e/.env.stagnet2 @@ -1,23 +1,3 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://n03.stagnet2.vega.xyz/tm" diff --git a/apps/explorer-e2e/.env.testnet b/apps/explorer-e2e/.env.testnet index 56370f422..5b296d196 100644 --- a/apps/explorer-e2e/.env.testnet +++ b/apps/explorer-e2e/.env.testnet @@ -1,23 +1,3 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm" diff --git a/apps/explorer-e2e/src/integration/home-page.feature b/apps/explorer-e2e/src/integration/home-page.feature index d3c471fc3..f88a46119 100644 --- a/apps/explorer-e2e/src/integration/home-page.feature +++ b/apps/explorer-e2e/src/integration/home-page.feature @@ -1,4 +1,23 @@ Feature: Home page - Scenario: Visit Home page + Scenario Outline: Succesfful search for specific id by Given I am on the homepage + When I search for '' + Then I am redirected to page containing id '' + + Examples: + | IdType | Id | + | Block Id | 973624 | + | Tx Hash | 9ED3718AA8308E7E08EC588EE7AADAF49711D2138860D8914B4D81A2054D9FB8 | + | Tx Id | 0x61DCCEBB955087F50D0B85382DAE138EDA9631BF1A4F92E563D528904AA38898 | + + Scenario Outline: Error message displayed when invalid search by + Given I am on the homepage + When I search for '' + Then search error message "" is displayed + + Examples: + | invalidType | Id | errorMessage | + | wrong string length | 9ED3718AA8308E7E08EC588EE7AADAF497D2138860D8914B4D81A2054D9FB8 | Something doesn't look right | + | invalid hash | 9ED3718AA8308E7E08ECht8EE753DAF49711D2138860D8914B4D81A2054D9FB8 | Transaction is not hexadecimal | + | empty search | | Search required | diff --git a/apps/explorer-e2e/src/support/pages/base-page.js b/apps/explorer-e2e/src/support/pages/base-page.js index 75fa90c98..61f6d73fb 100644 --- a/apps/explorer-e2e/src/support/pages/base-page.js +++ b/apps/explorer-e2e/src/support/pages/base-page.js @@ -9,7 +9,9 @@ export default class BasePage { networkParametersUrl = '/network-parameters'; validatorsUrl = '/validators'; blockExplorerHeader = 'explorer-header'; - searchField = 'search-input'; + searchField = 'search'; + searchButton = 'search-button'; + searchError = 'search-error'; navigateToTxs() { cy.get(`a[href='${this.transactionsUrl}']`).click(); @@ -48,17 +50,31 @@ export default class BasePage { } search(searchText) { - cy.getByTestId(this.searchField).type(searchText); + if (searchText) { + cy.getByTestId(this.searchField).type(searchText); + } + } + + clickSearch() { + cy.getByTestId(this.searchButton).click(); + } + + validateUrl(expectedUrl) { + cy.url().should('include', expectedUrl); } validateSearchDisplayed() { cy.getByTestId(this.blockExplorerHeader).should( 'have.text', - 'Vega Block Explorer' + 'Vega Explorer' ); cy.getByTestId(this.searchField).should('be.visible'); } + validateSearchErrorDisplayed(errorMessage) { + cy.getByTestId(this.searchError).should('have.text', errorMessage); + } + validateBlockDataDisplayed(headerTestId) { cy.getByTestId(headerTestId).then(($assetHeaders) => { const headersAmount = parseInt($assetHeaders.length); @@ -68,7 +84,7 @@ export default class BasePage { }); cy.get('.language-json') - .each(($asset, index, $list) => { + .each(($asset) => { expect($asset).to.not.be.empty; }) .then(($list) => { diff --git a/apps/explorer-e2e/src/support/pages/network-page.js b/apps/explorer-e2e/src/support/pages/network-page.js index faa2b0d32..963e0e1ad 100644 --- a/apps/explorer-e2e/src/support/pages/network-page.js +++ b/apps/explorer-e2e/src/support/pages/network-page.js @@ -7,7 +7,7 @@ export default class NetworkParametersPage extends BasePage { verifyNetworkParametersDisplayed() { cy.getByTestId(this.networkParametersHeader).should( 'have.text', - 'NetworkParameters' + 'Network Parameters' ); cy.getByTestId(this.parameters).should('not.be.empty'); } diff --git a/apps/explorer-e2e/src/support/pages/transactions-page.js b/apps/explorer-e2e/src/support/pages/transactions-page.js index c7187c364..da8d4e47f 100644 --- a/apps/explorer-e2e/src/support/pages/transactions-page.js +++ b/apps/explorer-e2e/src/support/pages/transactions-page.js @@ -77,6 +77,6 @@ export default class TransactionsPage extends BasePage { } clickOnTopTransaction() { - cy.getByTestId(this.transactionRow).first().find('a').click(); + cy.getByTestId(this.transactionRow).first().find('a').first().click(); } } diff --git a/apps/explorer-e2e/src/support/step_definitions/common.step.js b/apps/explorer-e2e/src/support/step_definitions/common.step.js index 9ef918681..8e384d999 100644 --- a/apps/explorer-e2e/src/support/step_definitions/common.step.js +++ b/apps/explorer-e2e/src/support/step_definitions/common.step.js @@ -1,5 +1,20 @@ import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps'; +import BasePage from '../pages/base-page'; +const basePage = new BasePage(); Given('I am on the homepage', () => { cy.visit('/'); }); + +When('I search for {string}', (searchText) => { + basePage.search(searchText); + basePage.clickSearch(); +}); + +Then('I am redirected to page containing id {string}', (expectedUrl) => { + basePage.validateUrl(expectedUrl); +}); + +Then('search error message {string} is displayed', (expectedErrorMsg) => { + basePage.validateSearchErrorDisplayed(expectedErrorMsg); +}); diff --git a/apps/explorer-e2e/src/support/step_definitions/network-page.step.js b/apps/explorer-e2e/src/support/step_definitions/network-page.step.js index 42ae9a063..8cfda1d37 100644 --- a/apps/explorer-e2e/src/support/step_definitions/network-page.step.js +++ b/apps/explorer-e2e/src/support/step_definitions/network-page.step.js @@ -1,4 +1,4 @@ -import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps'; +import { Then, When } from 'cypress-cucumber-preprocessor/steps'; import NetworkPage from '../pages/network-page'; const networkPage = new NetworkPage(); diff --git a/apps/explorer/.env b/apps/explorer/.env index 56370f422..123a0c798 100644 --- a/apps/explorer/.env +++ b/apps/explorer/.env @@ -18,17 +18,10 @@ NX_URL=$URL NX_DEPLOY_URL=$DEPLOY_URL NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL -# App configuration variables -NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" -NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm" -NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket" -NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" - # App flags NX_EXPLORER_ASSETS = 1 NX_EXPLORER_GENESIS = 1 NX_EXPLORER_GOVERNANCE = 1 -NX_EXPLORER_MARKETS = 1 NX_EXPLORER_NETWORK_PARAMETERS = 1 NX_EXPLORER_PARTIES = 1 NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer/.env.devent b/apps/explorer/.env.devent index a852aa565..3af139aa8 100644 --- a/apps/explorer/.env.devent +++ b/apps/explorer/.env.devent @@ -1,34 +1,5 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://n04.d.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://n04.d.vega.xyz/tm/websocket" NX_VEGA_URL = "https://n04.d.vega.xyz/query" - -# App flags -NX_EXPLORER_ASSETS = 1 -NX_EXPLORER_GENESIS = 1 -NX_EXPLORER_GOVERNANCE = 1 -NX_EXPLORER_MARKETS = 1 -NX_EXPLORER_NETWORK_PARAMETERS = 1 -NX_EXPLORER_PARTIES = 1 -NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer/.env.local b/apps/explorer/.env.local new file mode 100644 index 000000000..5b296d196 --- /dev/null +++ b/apps/explorer/.env.local @@ -0,0 +1,14 @@ +# App configuration variables +NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" +NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm" +NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket" +NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" + +# App flags +NX_EXPLORER_ASSETS = 1 +NX_EXPLORER_GENESIS = 1 +NX_EXPLORER_GOVERNANCE = 1 +NX_EXPLORER_MARKETS = 1 +NX_EXPLORER_NETWORK_PARAMETERS = 1 +NX_EXPLORER_PARTIES = 1 +NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer/.env.mainnet b/apps/explorer/.env.mainnet index 9fedccbba..ad8c0ef9d 100644 --- a/apps/explorer/.env.mainnet +++ b/apps/explorer/.env.mainnet @@ -1,34 +1,5 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://mainnet-observer-proxy01.ops.vega.xyz/" NX_TENDERMINT_WEBSOCKET_URL = "wss://mainnet-observer-proxy01.ops.vega.xyz/websocket" NX_VEGA_URL = "https://api.token.vega.xyz/query" - -# App flags -NX_EXPLORER_ASSETS = 1 -NX_EXPLORER_GENESIS = 1 -NX_EXPLORER_GOVERNANCE = 1 -NX_EXPLORER_MARKETS = 1 -NX_EXPLORER_NETWORK_PARAMETERS = 1 -NX_EXPLORER_PARTIES = 1 -NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer/.env.stagnet1 b/apps/explorer/.env.stagnet1 index 3abca14e7..83cda47ac 100644 --- a/apps/explorer/.env.stagnet1 +++ b/apps/explorer/.env.stagnet1 @@ -1,35 +1,5 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://n03.s.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.s.vega.xyz/tm/websocket" NX_VEGA_URL = "https://n03.s.vega.xyz/query" - - -# App flags -NX_EXPLORER_ASSETS = 1 -NX_EXPLORER_GENESIS = 1 -NX_EXPLORER_GOVERNANCE = 1 -NX_EXPLORER_MARKETS = 1 -NX_EXPLORER_NETWORK_PARAMETERS = 1 -NX_EXPLORER_PARTIES = 1 -NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer/.env.stagnet2 b/apps/explorer/.env.stagnet2 index 5499a2a6b..05b15b546 100644 --- a/apps/explorer/.env.stagnet2 +++ b/apps/explorer/.env.stagnet2 @@ -1,34 +1,5 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://n03.stagnet2.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://n03.stagnet2.vega.xyz/tm/websocket" NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query" - -# App flags -NX_EXPLORER_ASSETS = 1 -NX_EXPLORER_GENESIS = 1 -NX_EXPLORER_GOVERNANCE = 1 -NX_EXPLORER_MARKETS = 1 -NX_EXPLORER_NETWORK_PARAMETERS = 1 -NX_EXPLORER_PARTIES = 1 -NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer/.env.testnet b/apps/explorer/.env.testnet index 56370f422..5218200b8 100644 --- a/apps/explorer/.env.testnet +++ b/apps/explorer/.env.testnet @@ -1,34 +1,5 @@ -# React Environment Variables -# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env - -# Netlify Environment Variables -# https://www.netlify.com/docs/continuous-deployment/#environment-variables -NX_VERSION=$npm_package_version -NX_REPOSITORY_URL=$REPOSITORY_URL -NX_BRANCH=$BRANCH -NX_PULL_REQUEST=$PULL_REQUEST -NX_HEAD=$HEAD -NX_COMMIT_REF=$COMMIT_REF -NX_CONTEXT=$CONTEXT -NX_REVIEW_ID=$REVIEW_ID -NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE -NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL -NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY -NX_URL=$URL -NX_DEPLOY_URL=$DEPLOY_URL -NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL - # App configuration variables NX_CHAIN_EXPLORER_URL = "https://explorer.vega.trading/.netlify/functions/chain-explorer-api" NX_TENDERMINT_URL = "https://lb.testnet.vega.xyz/tm" NX_TENDERMINT_WEBSOCKET_URL = "wss://lb.testnet.vega.xyz/tm/websocket" NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" - -# App flags -NX_EXPLORER_ASSETS = 1 -NX_EXPLORER_GENESIS = 1 -NX_EXPLORER_GOVERNANCE = 1 -NX_EXPLORER_MARKETS = 1 -NX_EXPLORER_NETWORK_PARAMETERS = 1 -NX_EXPLORER_PARTIES = 1 -NX_EXPLORER_VALIDATORS = 1 diff --git a/apps/explorer/jest.config.js b/apps/explorer/jest.config.js index fcdba1f33..21c4b37c8 100644 --- a/apps/explorer/jest.config.js +++ b/apps/explorer/jest.config.js @@ -7,5 +7,5 @@ module.exports = { }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/explorer', - setupFilesAfterEnv: ['./src/app/setupTests.ts'], + setupFilesAfterEnv: ['./src/app/setup-tests.ts'], }; diff --git a/apps/explorer/src/app/App.scss b/apps/explorer/src/app/App.scss index 5f7344309..bc35c4932 100644 --- a/apps/explorer/src/app/App.scss +++ b/apps/explorer/src/app/App.scss @@ -1,6 +1,5 @@ @import './styles/colors'; @import './styles/fonts'; -@import './styles/reset'; html, body, @@ -39,18 +38,23 @@ body, padding: 20px; grid-column-start: 1; grid-column-end: 1; - grid-row-start: 1; + grid-row-start: 2; grid-row-end: 3; overflow: hidden; } header { - padding: 20px; border-bottom: 1px solid $white; - grid-column-start: 2; - grid-column-end: 2; + grid-column-start: 1; + grid-column-end: 3; grid-row-start: 1; grid-row-end: 2; + + h1 { + font-family: $font-alpa-lyrae; + font-feature-settings: 'calt'; + text-transform: uppercase; + } } main { diff --git a/apps/explorer/src/app/app.spec.tsx b/apps/explorer/src/app/app.spec.tsx index 680a016a5..6bccbf1c8 100644 --- a/apps/explorer/src/app/app.spec.tsx +++ b/apps/explorer/src/app/app.spec.tsx @@ -1,4 +1,4 @@ -import App from './App'; +import App from './app'; describe('App', () => { it('should exist', () => { diff --git a/apps/explorer/src/app/app.tsx b/apps/explorer/src/app/app.tsx index 770f5752a..c23ed5385 100644 --- a/apps/explorer/src/app/app.tsx +++ b/apps/explorer/src/app/app.tsx @@ -4,7 +4,6 @@ import { ApolloProvider } from '@apollo/client'; import { createClient } from './lib/apollo-client'; import { Nav } from './components/nav'; -import { Footer } from './components/footer'; import { Header } from './components/header'; import { Main } from './components/main'; import React from 'react'; @@ -24,7 +23,6 @@ function App() { diff --git a/apps/explorer/src/app/components/route-title/index.tsx b/apps/explorer/src/app/components/route-title/index.tsx index 9719d1f34..393844f0c 100644 --- a/apps/explorer/src/app/components/route-title/index.tsx +++ b/apps/explorer/src/app/components/route-title/index.tsx @@ -1,12 +1,16 @@ import classnames from 'classnames'; -import React from 'react'; +import React, { HTMLAttributes } from 'react'; -interface RouteTitleProps { +interface RouteTitleProps extends HTMLAttributes { children: React.ReactNode; className?: string; } -export const RouteTitle = ({ children, className }: RouteTitleProps) => { +export const RouteTitle = ({ + children, + className, + ...props +}: RouteTitleProps) => { const classes = classnames( 'font-alpha', 'text-h3', @@ -15,5 +19,9 @@ export const RouteTitle = ({ children, className }: RouteTitleProps) => { 'mb-28', className ); - return

{children}

; + return ( +

+ {children} +

+ ); }; diff --git a/apps/explorer/src/app/components/search/index.tsx b/apps/explorer/src/app/components/search/index.tsx index ff6134ea1..5a2bdeb5b 100644 --- a/apps/explorer/src/app/components/search/index.tsx +++ b/apps/explorer/src/app/components/search/index.tsx @@ -1,108 +1 @@ -import { gql, useQuery } from '@apollo/client'; -import React from 'react'; -import { useState } from 'react'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import debounce from 'lodash.debounce'; -import { Guess, GuessVariables } from '@vegaprotocol/graphql'; - -const TX_LENGTH = 64; - -enum PossibleIdTypes { - Block = 'Block', - Tx = 'Tx', - Party = 'Party', - Market = 'Market', - Unknown = 'Unknown', -} - -const GUESS_QUERY = gql` - query Guess($guess: ID!) { - party(id: $guess) { - id - } - market(id: $guess) { - id - } - } -`; - -const usePossibleType = (search: string) => { - const [possibleType, setPossibleType] = useState(); - const { data, loading, error } = useQuery( - GUESS_QUERY, - { - variables: { - guess: search, - }, - skip: !search, - } - ); - - React.useEffect(() => { - if (!isNaN(Number(search))) { - setPossibleType(PossibleIdTypes.Block); - } else if (data?.party) { - setPossibleType(PossibleIdTypes.Party); - } else if (data?.market) { - setPossibleType(PossibleIdTypes.Market); - } else if (search.replace('0x', '').length === TX_LENGTH) { - setPossibleType(PossibleIdTypes.Tx); - } else { - setPossibleType(PossibleIdTypes.Unknown); - } - }, [data?.market, data?.party, search, setPossibleType]); - - return { - loading, - error, - possibleType, - }; -}; - -const useGuess = () => { - const [search, setSearch] = useState(''); - const [debouncedSearch, setDebouncedSearch] = useState(''); - const { loading, error, possibleType } = usePossibleType(debouncedSearch); - const debouncedSearchSet = React.useMemo( - () => debounce(setDebouncedSearch, 1000), - [setDebouncedSearch] - ); - - const onChange = React.useCallback( - (event: React.ChangeEvent) => { - const search = event.target.value; - setSearch(search); - debouncedSearchSet(search); - }, - [debouncedSearchSet] - ); - - return { - onChange, - search, - loading, - error, - possibleType, - }; -}; - -const Search = () => { - const { search, onChange } = useGuess(); - return ( -
-

Vega Block Explorer

-
- - onChange(e)} - /> -
-
- ); -}; - -export default Search; +export * from './search'; diff --git a/apps/explorer/src/app/components/search/search.spec.tsx b/apps/explorer/src/app/components/search/search.spec.tsx new file mode 100644 index 000000000..32ed902a2 --- /dev/null +++ b/apps/explorer/src/app/components/search/search.spec.tsx @@ -0,0 +1,140 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { Search } from './search'; +import { MemoryRouter } from 'react-router-dom'; +import { Routes } from '../../routes/router-config'; + +const mockedNavigate = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockedNavigate, +})); + +beforeEach(() => { + mockedNavigate.mockClear(); +}); + +const renderComponent = () => ( + + + +); + +const getInputs = () => ({ + input: screen.getByTestId('search'), + button: screen.getByTestId('search-button'), +}); + +describe('Search', () => { + it('should render search input and button', () => { + render(renderComponent()); + expect(screen.getByTestId('search')).toBeInTheDocument(); + expect(screen.getByTestId('search-button')).toHaveTextContent('Search'); + }); + + it('should render error if input is not known', async () => { + render(renderComponent()); + const { button, input } = getInputs(); + fireEvent.change(input, { target: { value: 'asd' } }); + fireEvent.click(button); + + expect(await screen.findByTestId('search-error')).toHaveTextContent( + "Something doesn't look right" + ); + }); + it('should render error if no input is given', async () => { + render(renderComponent()); + const { button } = getInputs(); + + fireEvent.click(button); + + expect(await screen.findByTestId('search-error')).toHaveTextContent( + 'Search required' + ); + }); + + it('should render error if transaction is not hex', async () => { + render(renderComponent()); + const { button, input } = getInputs(); + fireEvent.change(input, { + target: { + value: + '0x123456789012345678901234567890123456789012345678901234567890123Q', + }, + }); + + fireEvent.click(button); + + expect(await screen.findByTestId('search-error')).toHaveTextContent( + 'Transaction is not hexadecimal' + ); + }); + + it('should render error if transaction is not hex and does not have leading 0x', async () => { + render(renderComponent()); + const { button, input } = getInputs(); + fireEvent.change(input, { + target: { + value: + '123456789012345678901234567890123456789012345678901234567890123Q', + }, + }); + + fireEvent.click(button); + + expect(await screen.findByTestId('search-error')).toHaveTextContent( + 'Transaction is not hexadecimal' + ); + }); + + it('should redirect to transactions page', async () => { + render(renderComponent()); + const { button, input } = getInputs(); + fireEvent.change(input, { + target: { + value: + '0x1234567890123456789012345678901234567890123456789012345678901234', + }, + }); + + fireEvent.click(button); + await waitFor(() => { + expect(mockedNavigate).toBeCalledWith( + `${Routes.TX}/0x1234567890123456789012345678901234567890123456789012345678901234` + ); + }); + }); + + it('should redirect to transactions page without proceeding 0x', async () => { + render(renderComponent()); + const { button, input } = getInputs(); + fireEvent.change(input, { + target: { + value: + '1234567890123456789012345678901234567890123456789012345678901234', + }, + }); + + fireEvent.click(button); + await waitFor(() => { + expect(mockedNavigate).toBeCalledWith( + `${Routes.TX}/0x1234567890123456789012345678901234567890123456789012345678901234` + ); + }); + }); + + it('should redirect to blocks page if passed a number', async () => { + render(renderComponent()); + const { button, input } = getInputs(); + fireEvent.change(input, { + target: { + value: '123', + }, + }); + + fireEvent.click(button); + await waitFor(() => { + expect(mockedNavigate).toBeCalledWith(`${Routes.BLOCKS}/123`); + }); + }); +}); diff --git a/apps/explorer/src/app/components/search/search.tsx b/apps/explorer/src/app/components/search/search.tsx new file mode 100644 index 000000000..adf4d9db7 --- /dev/null +++ b/apps/explorer/src/app/components/search/search.tsx @@ -0,0 +1,84 @@ +import { FormGroup, Input, InputError, Button } from '@vegaprotocol/ui-toolkit'; +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; +import { Routes } from '../../routes/router-config'; + +const TX_LENGTH = 64; + +interface FormFields { + search: string; +} + +const isPrependedTransaction = (search: string) => + search.startsWith('0x') && search.length === 2 + TX_LENGTH; + +const isTransaction = (search: string) => + !search.startsWith('0x') && search.length === TX_LENGTH; + +const isBlock = (search: string) => !Number.isNaN(Number(search)); + +export const Search = () => { + const { register, handleSubmit } = useForm(); + const navigate = useNavigate(); + const [error, setError] = React.useState(null); + const onSubmit = React.useCallback( + (fields: FormFields) => { + setError(null); + + const search = fields.search; + if (!search) { + setError(new Error('Search required')); + } else if (isPrependedTransaction(search)) { + if (Number.isNaN(Number(search))) { + setError(new Error('Transaction is not hexadecimal')); + } else { + navigate(`${Routes.TX}/${search}`); + } + } else if (isTransaction(search)) { + if (Number.isNaN(Number(`0x${search}`))) { + setError(new Error('Transaction is not hexadecimal')); + } else { + navigate(`${Routes.TX}/0x${search}`); + } + } else if (isBlock(search)) { + navigate(`${Routes.BLOCKS}/${Number(search)}`); + } else { + setError(new Error("Something doesn't look right")); + } + }, + [navigate] + ); + return ( +
+ + + {error?.message ? ( + + {error.message} + + ) : ( +
+ )} +
+ +
+ ); +}; diff --git a/apps/explorer/src/app/components/sub-heading/index.tsx b/apps/explorer/src/app/components/sub-heading/index.tsx new file mode 100644 index 000000000..094ef2faf --- /dev/null +++ b/apps/explorer/src/app/components/sub-heading/index.tsx @@ -0,0 +1,27 @@ +import classnames from 'classnames'; +import { HTMLAttributes } from 'react'; + +interface SubHeadingProps extends HTMLAttributes { + children: React.ReactNode; + className?: string; +} + +export const SubHeading = ({ + children, + className, + ...props +}: SubHeadingProps) => { + const classes = classnames( + 'font-alpha', + 'text-h4', + 'uppercase', + 'mt-12', + 'mb-12', + className + ); + return ( +

+ {children} +

+ ); +}; diff --git a/apps/explorer/src/app/components/txs/home/block-txs-data.tsx b/apps/explorer/src/app/components/txs/block-txs-data.tsx similarity index 83% rename from apps/explorer/src/app/components/txs/home/block-txs-data.tsx rename to apps/explorer/src/app/components/txs/block-txs-data.tsx index 54bf39782..bde3f6187 100644 --- a/apps/explorer/src/app/components/txs/home/block-txs-data.tsx +++ b/apps/explorer/src/app/components/txs/block-txs-data.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { TendermintBlockchainResponse } from '../../../routes/blocks/tendermint-blockchain-response'; -import { BlockData } from '../../blocks'; -import { TxsPerBlock } from '../txs-per-block'; +import { TendermintBlockchainResponse } from '../../routes/blocks/tendermint-blockchain-response'; +import { BlockData } from '../blocks'; +import { TxsPerBlock } from './txs-per-block'; interface TxsProps { data: TendermintBlockchainResponse | undefined; diff --git a/apps/explorer/src/app/components/txs/index.tsx b/apps/explorer/src/app/components/txs/index.tsx index 03b5e94ae..bd8d26d32 100644 --- a/apps/explorer/src/app/components/txs/index.tsx +++ b/apps/explorer/src/app/components/txs/index.tsx @@ -1,4 +1,3 @@ -export { TxDetails } from './id/tx-details'; -export { TxContent } from './id/tx-content'; -export { TxList } from './pending/tx-list'; -export { BlockTxsData } from './home/block-txs-data'; +export { TxList } from './tx-list'; +export { BlockTxsData } from './block-txs-data'; +export { TxOrderType } from './tx-order-type'; diff --git a/apps/explorer/src/app/components/txs/pending/tx-list.tsx b/apps/explorer/src/app/components/txs/tx-list.tsx similarity index 83% rename from apps/explorer/src/app/components/txs/pending/tx-list.tsx rename to apps/explorer/src/app/components/txs/tx-list.tsx index 4c07738a8..1a9198fbc 100644 --- a/apps/explorer/src/app/components/txs/pending/tx-list.tsx +++ b/apps/explorer/src/app/components/txs/tx-list.tsx @@ -1,4 +1,4 @@ -import { TendermintUnconfirmedTransactionsResponse } from '../../../routes/txs/tendermint-unconfirmed-transactions-response.d'; +import { TendermintUnconfirmedTransactionsResponse } from '../../routes/txs/tendermint-unconfirmed-transactions-response.d'; interface TxsProps { data: TendermintUnconfirmedTransactionsResponse | undefined; diff --git a/apps/explorer/src/app/components/txs/tx-order-type/index.tsx b/apps/explorer/src/app/components/txs/tx-order-type.tsx similarity index 100% rename from apps/explorer/src/app/components/txs/tx-order-type/index.tsx rename to apps/explorer/src/app/components/txs/tx-order-type.tsx diff --git a/apps/explorer/src/app/components/txs/txs-per-block/index.tsx b/apps/explorer/src/app/components/txs/txs-per-block.tsx similarity index 50% rename from apps/explorer/src/app/components/txs/txs-per-block/index.tsx rename to apps/explorer/src/app/components/txs/txs-per-block.tsx index 03043d5f9..67613281e 100644 --- a/apps/explorer/src/app/components/txs/txs-per-block/index.tsx +++ b/apps/explorer/src/app/components/txs/txs-per-block.tsx @@ -1,11 +1,11 @@ -import useFetch from '../../../hooks/use-fetch'; -import { ChainExplorerTxResponse } from '../../../routes/types/chain-explorer-response'; -import { Routes } from '../../../routes/router-config'; -import { DATA_SOURCES } from '../../../config'; +import useFetch from '../../hooks/use-fetch'; +import { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response'; +import { Routes } from '../../routes/router-config'; +import { DATA_SOURCES } from '../../config'; import { Link } from 'react-router-dom'; -import { RenderFetched } from '../../render-fetched'; -import { TruncateInline } from '../../truncate/truncate'; -import { TxOrderType } from '../tx-order-type'; +import { RenderFetched } from '../render-fetched'; +import { TruncateInline } from '../truncate/truncate'; +import { TxOrderType } from './tx-order-type'; interface TxsPerBlockProps { blockHeight: string | undefined; @@ -31,20 +31,20 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => { return ( -
- - - - - - - - - - {decodedBlockData && - decodedBlockData.map(({ TxHash, PubKey, Type }) => { + {decodedBlockData && decodedBlockData.length ? ( +
+
TransactionFromType
+ + + + + + + + + {decodedBlockData.map(({ TxHash, PubKey, Type }) => { return ( - + ); })} - -
TransactionFromType
{ - + + + @@ -69,9 +71,14 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
-
+ + + + ) : ( +
+ No transactions in block {blockHeight} +
+ )}
); }; diff --git a/apps/explorer/src/app/hooks/use-fetch.tsx b/apps/explorer/src/app/hooks/use-fetch.tsx index 84f3207e2..935fb18e6 100644 --- a/apps/explorer/src/app/hooks/use-fetch.tsx +++ b/apps/explorer/src/app/hooks/use-fetch.tsx @@ -57,7 +57,7 @@ function useFetch( } const data = (await response.json()) as T; - if ('error' in data) { + if (data && 'error' in data) { // @ts-ignore - data.error throw new Error(data.error); } diff --git a/apps/explorer/src/app/routes/assets/index.tsx b/apps/explorer/src/app/routes/assets/index.tsx index e7be5892e..859d90171 100644 --- a/apps/explorer/src/app/routes/assets/index.tsx +++ b/apps/explorer/src/app/routes/assets/index.tsx @@ -1,5 +1,7 @@ import { gql, useQuery } from '@apollo/client'; import React from 'react'; +import { RouteTitle } from '../../components/route-title'; +import { SubHeading } from '../../components/sub-heading'; import { SyntaxHighlighter } from '../../components/syntax-highlighter'; import { AssetsQuery } from '@vegaprotocol/graphql'; @@ -35,12 +37,12 @@ const Assets = () => { if (!data || !data.assets) return null; return (
-

Assets

+ Assets {data?.assets.map((a) => ( -

+ {a.name} ({a.symbol}) -

+
))} diff --git a/apps/explorer/src/app/routes/blocks/id/index.tsx b/apps/explorer/src/app/routes/blocks/id/index.tsx index 3943d4a95..3dbe82c22 100644 --- a/apps/explorer/src/app/routes/blocks/id/index.tsx +++ b/apps/explorer/src/app/routes/blocks/id/index.tsx @@ -4,7 +4,6 @@ import { DATA_SOURCES } from '../../../config'; import useFetch from '../../../hooks/use-fetch'; import { TendermintBlocksResponse } from '../tendermint-blocks-response'; import { RouteTitle } from '../../../components/route-title'; -import { TxsPerBlock } from '../../../components/txs/txs-per-block'; import { SecondsAgo } from '../../../components/seconds-ago'; import { Table, @@ -12,6 +11,7 @@ import { TableHeader, TableCell, } from '../../../components/table'; +import { TxsPerBlock } from '../../../components/txs/txs-per-block'; const Block = () => { const { block } = useParams<{ block: string }>(); diff --git a/apps/explorer/src/app/routes/genesis/index.tsx b/apps/explorer/src/app/routes/genesis/index.tsx index d573df14a..2b88c265c 100644 --- a/apps/explorer/src/app/routes/genesis/index.tsx +++ b/apps/explorer/src/app/routes/genesis/index.tsx @@ -1,3 +1,4 @@ +import { RouteTitle } from '../../components/route-title'; import { SyntaxHighlighter } from '../../components/syntax-highlighter'; import { DATA_SOURCES } from '../../config'; import useFetch from '../../hooks/use-fetch'; @@ -12,7 +13,7 @@ const Genesis = () => { if (!genesis?.result.genesis) return null; return (
-

Genesis

+ Genesis
); diff --git a/apps/explorer/src/app/routes/governance/index.tsx b/apps/explorer/src/app/routes/governance/index.tsx index 9c35a17f5..1d81a9e80 100644 --- a/apps/explorer/src/app/routes/governance/index.tsx +++ b/apps/explorer/src/app/routes/governance/index.tsx @@ -1,5 +1,7 @@ import { gql, useQuery } from '@apollo/client'; import React from 'react'; +import { RouteTitle } from '../../components/route-title'; +import { SubHeading } from '../../components/sub-heading'; import { SyntaxHighlighter } from '../../components/syntax-highlighter'; import { ProposalsQuery, @@ -100,14 +102,13 @@ const PROPOSAL_QUERY = gql` const Governance = () => { const { data } = useQuery(PROPOSAL_QUERY); - if (!data || !data.proposals) return null; + if (!data) return null; return (
-

Governance

- {data.proposals.map((p) => ( + Governance + {data.proposals?.map((p) => ( - {/* TODO get proposal name generator from console */} -

{getProposalName(p.terms.change)}

+ {getProposalName(p.terms.change)}
))} diff --git a/apps/explorer/src/app/routes/home/index.tsx b/apps/explorer/src/app/routes/home/index.tsx index f1633ff56..032d9254b 100644 --- a/apps/explorer/src/app/routes/home/index.tsx +++ b/apps/explorer/src/app/routes/home/index.tsx @@ -1,7 +1,9 @@ +import { StatsManager } from '@vegaprotocol/mainnet-stats-manager'; + const Home = () => { return (
-

Home page content

+
); }; diff --git a/apps/explorer/src/app/routes/markets/index.tsx b/apps/explorer/src/app/routes/markets/index.tsx index 1fc494e18..45568c903 100644 --- a/apps/explorer/src/app/routes/markets/index.tsx +++ b/apps/explorer/src/app/routes/markets/index.tsx @@ -3,6 +3,8 @@ import { MarketsQuery } from '@vegaprotocol/graphql'; import React from 'react'; import { SyntaxHighlighter } from '../../components/syntax-highlighter'; +import { RouteTitle } from '../../components/route-title'; +import { SubHeading } from '../../components/sub-heading'; const MARKETS_QUERY = gql` query MarketsQuery { @@ -148,14 +150,17 @@ const Markets = () => { if (!data || !data.markets) return null; return ( -
-

Markets

- {data.markets.map((m) => ( - -

{m.name}

- -
- ))} +
+ Markets + + {data + ? data.markets.map((m) => ( + + {m.name} + + + )) + : null}
); }; diff --git a/apps/explorer/src/app/routes/network-parameters/index.tsx b/apps/explorer/src/app/routes/network-parameters/index.tsx index 46df31664..8a8ed8bd9 100644 --- a/apps/explorer/src/app/routes/network-parameters/index.tsx +++ b/apps/explorer/src/app/routes/network-parameters/index.tsx @@ -1,5 +1,7 @@ import { gql, useQuery } from '@apollo/client'; +import { RouteTitle } from '../../components/route-title'; import { NetworkParametersQuery } from '@vegaprotocol/graphql'; +import { SyntaxHighlighter } from '../../components/syntax-highlighter'; export const NETWORK_PARAMETERS_QUERY = gql` query NetworkParametersQuery { @@ -14,8 +16,10 @@ const NetworkParameters = () => { const { data } = useQuery(NETWORK_PARAMETERS_QUERY); return (
-

NetworkParameters

-
{JSON.stringify(data, null, '  ')}
+ + Network Parameters + + {data ? : null}
); }; diff --git a/apps/explorer/src/app/routes/parties/home/index.tsx b/apps/explorer/src/app/routes/parties/home/index.tsx index 1d6fd51bc..a1f7ee6f1 100644 --- a/apps/explorer/src/app/routes/parties/home/index.tsx +++ b/apps/explorer/src/app/routes/parties/home/index.tsx @@ -1,13 +1,57 @@ -import React from "react"; +import React from 'react'; +import { RouteTitle } from '../../../components/route-title'; + +import { useNavigate } from 'react-router-dom'; +import { Routes } from '../../router-config'; + +export const JumpToParty = () => { + const navigate = useNavigate(); + + const handleSubmit = React.useCallback( + () => (e: React.SyntheticEvent) => { + e.preventDefault(); + + const target = e.target as typeof e.target & { + partyId: { value: number }; + }; + + const partyId = target.partyId.value; + + if (partyId) { + navigate(`/${Routes.PARTIES}/${partyId}`); + } + }, + [navigate] + ); + + return ( +
+ + + +
+ ); +}; const Parties = () => { return (
-

Parties

-

- Not sure what to do with this page? Could show all parties but would - eventually need to be rewritten. But that's not very useful either -

+ Parties +
); }; diff --git a/apps/explorer/src/app/routes/parties/id/index.tsx b/apps/explorer/src/app/routes/parties/id/index.tsx index dd883bdd4..2412ab1f3 100644 --- a/apps/explorer/src/app/routes/parties/id/index.tsx +++ b/apps/explorer/src/app/routes/parties/id/index.tsx @@ -2,6 +2,9 @@ import { useQuery } from '@apollo/client'; import { gql } from '@apollo/client'; import React from 'react'; import { useParams } from 'react-router-dom'; +import { RouteTitle } from '../../../components/route-title'; +import { SubHeading } from '../../../components/sub-heading'; +import { SyntaxHighlighter } from '../../../components/syntax-highlighter'; import { DATA_SOURCES } from '../../../config'; import useFetch from '../../../hooks/use-fetch'; import { TendermintSearchTransactionResponse } from '../tendermint-transaction-response'; @@ -47,10 +50,13 @@ const PARTY_ASSETS_QUERY = gql` const Party = () => { const { party } = useParams<{ party: string }>(); + const { state: { data: partyData }, } = useFetch( - `${DATA_SOURCES.tendermintWebsocketUrl}/tx_search?query="tx.submitter=%27${party}%27"` + `${ + DATA_SOURCES.tendermintUrl + }/tx_search?query="tx.submitter='${party?.replace('0x', '')}'"` ); const { data } = useQuery( @@ -65,11 +71,20 @@ const Party = () => { return (
-

Party

-

Tendermint Data

-
{JSON.stringify(partyData, null, '  ')}
-

Asset data

-
{JSON.stringify(data, null, '  ')}
+ Party + {data ? ( + <> + Asset data + + + ) : null} + + {partyData ? ( + <> + Tendermint Data + + + ) : null}
); }; diff --git a/apps/explorer/src/app/routes/pending/index.tsx b/apps/explorer/src/app/routes/pending/index.tsx index 5118c48a5..1955f0aeb 100644 --- a/apps/explorer/src/app/routes/pending/index.tsx +++ b/apps/explorer/src/app/routes/pending/index.tsx @@ -3,6 +3,7 @@ import { DATA_SOURCES } from '../../config'; import useFetch from '../../hooks/use-fetch'; import { TendermintUnconfirmedTransactionsResponse } from '../txs/tendermint-unconfirmed-transactions-response.d'; import { TxList } from '../../components/txs'; +import { RouteTitle } from '../../components/route-title'; const PendingTxs = () => { const { @@ -13,7 +14,9 @@ const PendingTxs = () => { return (
-

Unconfirmed transactions

+ + Unconfirmed transactions + https://lb.testnet.vega.xyz/tm/unconfirmed_txs
Number: {unconfirmedTransactions?.result?.n_txs || 0}
diff --git a/apps/explorer/src/app/routes/router-config.tsx b/apps/explorer/src/app/routes/router-config.tsx index af048e084..a67587755 100644 --- a/apps/explorer/src/app/routes/router-config.tsx +++ b/apps/explorer/src/app/routes/router-config.tsx @@ -34,6 +34,7 @@ const partiesRoutes = flags.parties { path: Routes.PARTIES, name: 'Parties', + text: 'Parties', element: , children: [ { @@ -53,6 +54,7 @@ const assetsRoutes = flags.assets ? [ { path: Routes.ASSETS, + text: 'Assets', name: 'Assets', element: , }, @@ -64,6 +66,7 @@ const genesisRoutes = flags.genesis { path: Routes.GENESIS, name: 'Genesis', + text: 'Genesis Parameters', element: , }, ] @@ -74,6 +77,7 @@ const governanceRoutes = flags.governance { path: Routes.GOVERNANCE, name: 'Governance', + text: 'Proposals', element: , }, ] @@ -84,6 +88,7 @@ const marketsRoutes = flags.markets { path: Routes.MARKETS, name: 'Markets', + text: 'Markets', element: , }, ] @@ -94,6 +99,7 @@ const networkParametersRoutes = flags.networkParameters { path: Routes.NETWORK_PARAMETERS, name: 'NetworkParameters', + text: 'Network Parameters', element: , }, ] @@ -103,6 +109,7 @@ const validators = flags.validators { path: Routes.VALIDATORS, name: 'Validators', + text: 'Validators', element: , }, ] @@ -112,12 +119,14 @@ const routerConfig = [ { path: Routes.HOME, name: 'Home', + text: 'Home', element: , index: true, }, { path: Routes.TX, name: 'Txs', + text: 'Transactions', element: , children: [ { @@ -137,6 +146,7 @@ const routerConfig = [ { path: Routes.BLOCKS, name: 'Blocks', + text: 'Blocks', element: , children: [ { diff --git a/apps/explorer/src/app/routes/txs/id/index.tsx b/apps/explorer/src/app/routes/txs/id/index.tsx index 5698bdbe4..bb78aed50 100644 --- a/apps/explorer/src/app/routes/txs/id/index.tsx +++ b/apps/explorer/src/app/routes/txs/id/index.tsx @@ -4,9 +4,10 @@ import useFetch from '../../../hooks/use-fetch'; import { TendermintTransactionResponse } from '../tendermint-transaction-response.d'; import { ChainExplorerTxResponse } from '../../types/chain-explorer-response'; import { DATA_SOURCES } from '../../../config'; -import { TxContent, TxDetails } from '../../../components/txs'; import { RouteTitle } from '../../../components/route-title'; import { RenderFetched } from '../../../components/render-fetched'; +import { TxContent } from './tx-content'; +import { TxDetails } from './tx-details'; const Tx = () => { const { txHash } = useParams<{ txHash: string }>(); diff --git a/apps/explorer/src/app/components/txs/id/tx-content.tsx b/apps/explorer/src/app/routes/txs/id/tx-content.tsx similarity index 73% rename from apps/explorer/src/app/components/txs/id/tx-content.tsx rename to apps/explorer/src/app/routes/txs/id/tx-content.tsx index e0ecc669d..8d334080c 100644 --- a/apps/explorer/src/app/components/txs/id/tx-content.tsx +++ b/apps/explorer/src/app/routes/txs/id/tx-content.tsx @@ -1,8 +1,13 @@ +import { StatusMessage } from '../../../components/status-message'; +import { SyntaxHighlighter } from '../../../components/syntax-highlighter'; +import { + Table, + TableCell, + TableHeader, + TableRow, +} from '../../../components/table'; +import { TxOrderType } from '../../../components/txs'; import { ChainExplorerTxResponse } from '../../../routes/types/chain-explorer-response'; -import { SyntaxHighlighter } from '../../syntax-highlighter'; -import { Table, TableRow, TableHeader, TableCell } from '../../table'; -import { TxOrderType } from '../tx-order-type'; -import { StatusMessage } from '../../status-message'; interface TxContentProps { data: ChainExplorerTxResponse | undefined; diff --git a/apps/explorer/src/app/components/txs/id/tx-details.tsx b/apps/explorer/src/app/routes/txs/id/tx-details.tsx similarity index 91% rename from apps/explorer/src/app/components/txs/id/tx-details.tsx rename to apps/explorer/src/app/routes/txs/id/tx-details.tsx index 71a9f1915..72005720a 100644 --- a/apps/explorer/src/app/components/txs/id/tx-details.tsx +++ b/apps/explorer/src/app/routes/txs/id/tx-details.tsx @@ -1,8 +1,13 @@ import { Link } from 'react-router-dom'; +import { + Table, + TableCell, + TableHeader, + TableRow, +} from '../../../components/table'; +import { TruncateInline } from '../../../components/truncate/truncate'; import { Routes } from '../../../routes/router-config'; import { Result } from '../../../routes/txs/tendermint-transaction-response.d'; -import { Table, TableRow, TableCell, TableHeader } from '../../table'; -import { TruncateInline } from '../../truncate/truncate'; interface TxDetailsProps { txData: Result | undefined; diff --git a/apps/explorer/src/app/routes/validators/index.tsx b/apps/explorer/src/app/routes/validators/index.tsx index d27b85c47..68ff1bb49 100644 --- a/apps/explorer/src/app/routes/validators/index.tsx +++ b/apps/explorer/src/app/routes/validators/index.tsx @@ -1,5 +1,8 @@ import { gql, useQuery } from '@apollo/client'; import React from 'react'; +import { RouteTitle } from '../../components/route-title'; +import { SubHeading } from '../../components/sub-heading'; +import { SyntaxHighlighter } from '../../components/syntax-highlighter'; import { DATA_SOURCES } from '../../config'; import useFetch from '../../hooks/use-fetch'; import { TendermintValidatorsResponse } from './tendermint-validator-response'; @@ -41,13 +44,21 @@ const Validators = () => { return (
-

Validators

-

Tendermint data

-
-        {JSON.stringify(validators, null, '  ')}
-      
-

Vega data

-
{JSON.stringify(data, null, '  ')}
+ Validators + {data ? ( + <> + Vega data + + + ) : null} + {validators ? ( + <> + + Tendermint data + + + + ) : null}
); }; diff --git a/apps/explorer/src/app/setupTests.ts b/apps/explorer/src/app/setup-tests.ts similarity index 100% rename from apps/explorer/src/app/setupTests.ts rename to apps/explorer/src/app/setup-tests.ts diff --git a/apps/explorer/src/app/styles/_reset.scss b/apps/explorer/src/app/styles/_reset.scss deleted file mode 100644 index 8a3e0c12e..000000000 --- a/apps/explorer/src/app/styles/_reset.scss +++ /dev/null @@ -1,6 +0,0 @@ -fieldset { - border: 0; - padding: 0; - margin: 0; - min-width: 0; -} diff --git a/apps/explorer/tsconfig.spec.json b/apps/explorer/tsconfig.spec.json index 95ef66a08..8c00a45c0 100644 --- a/apps/explorer/tsconfig.spec.json +++ b/apps/explorer/tsconfig.spec.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", - "types": ["jest", "node"] + "types": ["jest", "node", "@testing-library/jest-dom"] }, "include": [ "**/*.test.ts", diff --git a/apps/stats-mainnet-e2e/.eslintrc.json b/apps/stats-mainnet-e2e/.eslintrc.json new file mode 100644 index 000000000..696cb8b12 --- /dev/null +++ b/apps/stats-mainnet-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/stats-mainnet-e2e/cypress.json b/apps/stats-mainnet-e2e/cypress.json new file mode 100644 index 000000000..51e27859e --- /dev/null +++ b/apps/stats-mainnet-e2e/cypress.json @@ -0,0 +1,14 @@ +{ + "baseUrl": "http://localhost:3010", + "projectId": "et4snf", + "fileServerFolder": ".", + "fixturesFolder": "./src/fixtures", + "integrationFolder": "./src/integration", + "modifyObstructiveCode": false, + "supportFile": "./src/support/index.ts", + "pluginsFile": false, + "video": true, + "videosFolder": "../../dist/cypress/apps/stats-mainnet-e2e/videos", + "screenshotsFolder": "../../dist/cypress/apps/stats-mainnet-e2e/screenshots", + "chromeWebSecurity": false +} diff --git a/apps/stats-mainnet-e2e/project.json b/apps/stats-mainnet-e2e/project.json new file mode 100644 index 000000000..bdf7accfc --- /dev/null +++ b/apps/stats-mainnet-e2e/project.json @@ -0,0 +1,28 @@ +{ + "root": "apps/stats-mainnet-e2e", + "sourceRoot": "apps/stats-mainnet-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/stats-mainnet-e2e/cypress.json", + "devServerTarget": "stats-mainnet:serve" + }, + "configurations": { + "production": { + "devServerTarget": "stats-mainnet:serve:production" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/stats-mainnet-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["stats-mainnet"] +} diff --git a/apps/stats-mainnet-e2e/src/fixtures/example.json b/apps/stats-mainnet-e2e/src/fixtures/example.json new file mode 100644 index 000000000..294cbed6c --- /dev/null +++ b/apps/stats-mainnet-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/stats-mainnet-e2e/src/integration/app.spec.ts b/apps/stats-mainnet-e2e/src/integration/app.spec.ts new file mode 100644 index 000000000..77a688f7c --- /dev/null +++ b/apps/stats-mainnet-e2e/src/integration/app.spec.ts @@ -0,0 +1,7 @@ +describe('stats-mainnet', () => { + beforeEach(() => cy.visit('/')); + + it('should display header', () => { + cy.get('h3').should('have.text', '/ Mainnet'); + }); +}); diff --git a/apps/stats-mainnet-e2e/src/support/commands.ts b/apps/stats-mainnet-e2e/src/support/commands.ts new file mode 100644 index 000000000..310f1fa0e --- /dev/null +++ b/apps/stats-mainnet-e2e/src/support/commands.ts @@ -0,0 +1,33 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/stats-mainnet-e2e/src/support/index.ts b/apps/stats-mainnet-e2e/src/support/index.ts new file mode 100644 index 000000000..3d469a6b6 --- /dev/null +++ b/apps/stats-mainnet-e2e/src/support/index.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/stats-mainnet-e2e/tsconfig.json b/apps/stats-mainnet-e2e/tsconfig.json new file mode 100644 index 000000000..c4f818ecd --- /dev/null +++ b/apps/stats-mainnet-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js"] +} diff --git a/apps/stats-mainnet/.babelrc b/apps/stats-mainnet/.babelrc new file mode 100644 index 000000000..61641ec8a --- /dev/null +++ b/apps/stats-mainnet/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/stats-mainnet/.browserslistrc b/apps/stats-mainnet/.browserslistrc new file mode 100644 index 000000000..f1d12df4f --- /dev/null +++ b/apps/stats-mainnet/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by: +# 1. autoprefixer to adjust CSS to support the below specified browsers +# 2. babel preset-env to adjust included polyfills +# +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# If you need to support different browsers in production, you may tweak the list below. + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major version +last 2 iOS major versions +Firefox ESR +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/apps/stats-mainnet/.eslintrc.json b/apps/stats-mainnet/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/apps/stats-mainnet/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/stats-mainnet/jest.config.js b/apps/stats-mainnet/jest.config.js new file mode 100644 index 000000000..9d114d295 --- /dev/null +++ b/apps/stats-mainnet/jest.config.js @@ -0,0 +1,11 @@ +module.exports = { + displayName: 'stats-mainnet', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/stats-mainnet', + setupFilesAfterEnv: ['./src/setup-tests.ts'], +}; diff --git a/apps/stats-mainnet/postcss.config.js b/apps/stats-mainnet/postcss.config.js new file mode 100644 index 000000000..cbdd9c22c --- /dev/null +++ b/apps/stats-mainnet/postcss.config.js @@ -0,0 +1,10 @@ +const { join } = require('path'); + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; diff --git a/apps/stats-mainnet/project.json b/apps/stats-mainnet/project.json new file mode 100644 index 000000000..e66aa17c6 --- /dev/null +++ b/apps/stats-mainnet/project.json @@ -0,0 +1,74 @@ +{ + "root": "apps/stats-mainnet", + "sourceRoot": "apps/stats-mainnet/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/web:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/stats-mainnet", + "index": "apps/stats-mainnet/src/index.html", + "baseHref": "/", + "main": "apps/stats-mainnet/src/main.tsx", + "polyfills": "apps/stats-mainnet/src/polyfills.ts", + "tsConfig": "apps/stats-mainnet/tsconfig.app.json", + "assets": [ + "apps/stats-mainnet/src/assets/favicon.ico", + "apps/stats-mainnet/src/assets" + ], + "styles": ["apps/stats-mainnet/src/styles/styles.css"], + "scripts": [], + "webpackConfig": "@nrwl/react/plugins/webpack" + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "apps/stats-mainnet/src/environments/environment.ts", + "with": "apps/stats-mainnet/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false + } + } + }, + "serve": { + "executor": "@nrwl/web:dev-server", + "options": { + "port": 3010, + "buildTarget": "stats-mainnet:build", + "hmr": true + }, + "configurations": { + "production": { + "buildTarget": "stats-mainnet:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/stats-mainnet/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/apps/stats-mainnet"], + "options": { + "jestConfig": "apps/stats-mainnet/jest.config.js", + "passWithNoTests": true + } + } + }, + "tags": [] +} diff --git a/apps/stats-mainnet/src/app.tsx b/apps/stats-mainnet/src/app.tsx new file mode 100644 index 000000000..42aa79564 --- /dev/null +++ b/apps/stats-mainnet/src/app.tsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react'; +import { Header } from './components/header'; +import { StatsManager } from '@vegaprotocol/mainnet-stats-manager'; + +function App() { + const [darkMode, setDarkMode] = useState( + document.documentElement.classList.contains('dark-mode-preferred') + ); + + return ( +
+
+
+ +
+
+ ); +} + +export default App; diff --git a/apps/stats-mainnet/src/assets/apple-touch-icon.png b/apps/stats-mainnet/src/assets/apple-touch-icon.png new file mode 100644 index 000000000..50cdf7782 Binary files /dev/null and b/apps/stats-mainnet/src/assets/apple-touch-icon.png differ diff --git a/apps/stats-mainnet/src/assets/favicon.ico b/apps/stats-mainnet/src/assets/favicon.ico new file mode 100644 index 000000000..03a276d66 Binary files /dev/null and b/apps/stats-mainnet/src/assets/favicon.ico differ diff --git a/apps/stats-mainnet/src/assets/logo.png b/apps/stats-mainnet/src/assets/logo.png new file mode 100644 index 000000000..0245ca6ca Binary files /dev/null and b/apps/stats-mainnet/src/assets/logo.png differ diff --git a/apps/stats-mainnet/src/assets/logo192.png b/apps/stats-mainnet/src/assets/logo192.png new file mode 100644 index 000000000..a229d08ba Binary files /dev/null and b/apps/stats-mainnet/src/assets/logo192.png differ diff --git a/apps/stats-mainnet/src/assets/manifest.json b/apps/stats-mainnet/src/assets/manifest.json new file mode 100644 index 000000000..949569331 --- /dev/null +++ b/apps/stats-mainnet/src/assets/manifest.json @@ -0,0 +1,20 @@ +{ + "short_name": "Mainnet Stats", + "name": "Vega Mainnet statistics", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/apps/stats-mainnet/src/assets/robots.txt b/apps/stats-mainnet/src/assets/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/apps/stats-mainnet/src/assets/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/apps/stats-mainnet/src/components/header/header.tsx b/apps/stats-mainnet/src/components/header/header.tsx new file mode 100644 index 000000000..acbfcf2e8 --- /dev/null +++ b/apps/stats-mainnet/src/components/header/header.tsx @@ -0,0 +1,32 @@ +import { VegaLogo } from '@vegaprotocol/ui-toolkit'; +import { LightModeToggle, DarkModeToggle } from '../images'; +import { VegaBackgroundVideo } from '../videos'; +import { DarkModeState } from '../../config/types'; + +export const Header = ({ darkMode, setDarkMode }: DarkModeState) => { + return ( +
+ + +
+
+ + + +
+
+
+ ); +}; diff --git a/apps/stats-mainnet/src/components/header/index.ts b/apps/stats-mainnet/src/components/header/index.ts new file mode 100644 index 000000000..ddd972315 --- /dev/null +++ b/apps/stats-mainnet/src/components/header/index.ts @@ -0,0 +1 @@ +export { Header } from './header'; diff --git a/apps/stats-mainnet/src/components/images/dark-mode-toggle.tsx b/apps/stats-mainnet/src/components/images/dark-mode-toggle.tsx new file mode 100644 index 000000000..42a7ac3b2 --- /dev/null +++ b/apps/stats-mainnet/src/components/images/dark-mode-toggle.tsx @@ -0,0 +1,18 @@ +export const DarkModeToggle = () => { + return ( + + + + + + + ); +}; diff --git a/apps/stats-mainnet/src/components/images/index.ts b/apps/stats-mainnet/src/components/images/index.ts new file mode 100644 index 000000000..fd1bfea32 --- /dev/null +++ b/apps/stats-mainnet/src/components/images/index.ts @@ -0,0 +1,2 @@ +export { DarkModeToggle } from './dark-mode-toggle'; +export { LightModeToggle } from './light-mode-toggle'; diff --git a/apps/stats-mainnet/src/components/images/light-mode-toggle.tsx b/apps/stats-mainnet/src/components/images/light-mode-toggle.tsx new file mode 100644 index 000000000..f71b5eb9d --- /dev/null +++ b/apps/stats-mainnet/src/components/images/light-mode-toggle.tsx @@ -0,0 +1,10 @@ +export const LightModeToggle = () => { + return ( + + + + ); +}; diff --git a/apps/stats-mainnet/src/components/videos/index.ts b/apps/stats-mainnet/src/components/videos/index.ts new file mode 100644 index 000000000..d43c5dd49 --- /dev/null +++ b/apps/stats-mainnet/src/components/videos/index.ts @@ -0,0 +1 @@ +export { VegaBackgroundVideo } from './vega-background-video'; diff --git a/apps/stats-mainnet/src/components/videos/vega-background-video.tsx b/apps/stats-mainnet/src/components/videos/vega-background-video.tsx new file mode 100644 index 000000000..9b36df0be --- /dev/null +++ b/apps/stats-mainnet/src/components/videos/vega-background-video.tsx @@ -0,0 +1,16 @@ +export const VegaBackgroundVideo = () => { + return ( + + ); +}; diff --git a/apps/stats-mainnet/src/config/types.ts b/apps/stats-mainnet/src/config/types.ts new file mode 100644 index 000000000..ebab4ed91 --- /dev/null +++ b/apps/stats-mainnet/src/config/types.ts @@ -0,0 +1,4 @@ +export interface DarkModeState { + darkMode: boolean; + setDarkMode: (arg0: boolean) => void; +} diff --git a/apps/stats-mainnet/src/index.html b/apps/stats-mainnet/src/index.html new file mode 100644 index 000000000..4476c74cc --- /dev/null +++ b/apps/stats-mainnet/src/index.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + Vega Mainnet Stats + + + + +
+ + + diff --git a/apps/stats-mainnet/src/main.tsx b/apps/stats-mainnet/src/main.tsx new file mode 100644 index 000000000..08e66f713 --- /dev/null +++ b/apps/stats-mainnet/src/main.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './styles/styles.css'; +import App from './app'; +import reportWebVitals from './report-web-vitals'; + +ReactDOM.render( + + + , + document.getElementById('root') +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/apps/stats-mainnet/src/polyfills.ts b/apps/stats-mainnet/src/polyfills.ts new file mode 100644 index 000000000..2adf3d05b --- /dev/null +++ b/apps/stats-mainnet/src/polyfills.ts @@ -0,0 +1,7 @@ +/** + * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. + * + * See: https://github.com/zloirock/core-js#babel + */ +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; diff --git a/apps/stats-mainnet/src/react-app-env.d.ts b/apps/stats-mainnet/src/react-app-env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/apps/stats-mainnet/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/stats-mainnet/src/report-web-vitals.ts b/apps/stats-mainnet/src/report-web-vitals.ts new file mode 100644 index 000000000..49a2a16e0 --- /dev/null +++ b/apps/stats-mainnet/src/report-web-vitals.ts @@ -0,0 +1,15 @@ +import { ReportHandler } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/apps/stats-mainnet/src/setup-tests.ts b/apps/stats-mainnet/src/setup-tests.ts new file mode 100644 index 000000000..8f2609b7b --- /dev/null +++ b/apps/stats-mainnet/src/setup-tests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/apps/stats-mainnet/src/styles/AlphaLyrae-Medium.woff b/apps/stats-mainnet/src/styles/AlphaLyrae-Medium.woff new file mode 100644 index 000000000..38b19bca5 Binary files /dev/null and b/apps/stats-mainnet/src/styles/AlphaLyrae-Medium.woff differ diff --git a/apps/stats-mainnet/src/styles/styles.css b/apps/stats-mainnet/src/styles/styles.css new file mode 100644 index 000000000..1ecaa334a --- /dev/null +++ b/apps/stats-mainnet/src/styles/styles.css @@ -0,0 +1,39 @@ +@tailwind base; +@tailwind components; + +@font-face { + font-family: 'AlphaLyrae-Medium'; + src: url('./AlphaLyrae-Medium.woff'); +} + +.layout-grid { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: repeat(2, auto) 1fr; +} + +.stats-grid { + display: grid; +} + +@media (min-width: 768px) { + .stats-grid { + grid-template-columns: 18rem 1fr; + grid-column-gap: 1.25rem; + align-items: flex-start; + } +} + +@media (min-width: 640px) { + .promoted-stats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (min-width: 768px) { + .promoted-stats { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } +} + +@tailwind utilities; diff --git a/apps/stats-mainnet/tailwind.config.js b/apps/stats-mainnet/tailwind.config.js new file mode 100644 index 000000000..739782864 --- /dev/null +++ b/apps/stats-mainnet/tailwind.config.js @@ -0,0 +1,12 @@ +const { join } = require('path'); +const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind'); +const theme = require('../../libs/tailwindcss-config/src/theme'); + +module.exports = { + content: [ + join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), + ...createGlobPatternsForDependencies(__dirname), + ], + theme, + plugins: [], +}; diff --git a/apps/stats-mainnet/tsconfig.app.json b/apps/stats-mainnet/tsconfig.app.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/apps/stats-mainnet/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/apps/stats-mainnet/tsconfig.json b/apps/stats-mainnet/tsconfig.json new file mode 100644 index 000000000..9657042e4 --- /dev/null +++ b/apps/stats-mainnet/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/stats-mainnet/tsconfig.spec.json b/apps/stats-mainnet/tsconfig.spec.json new file mode 100644 index 000000000..95ef66a08 --- /dev/null +++ b/apps/stats-mainnet/tsconfig.spec.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ] +} diff --git a/apps/trading/.env b/apps/trading/.env new file mode 100644 index 000000000..1271252c7 --- /dev/null +++ b/apps/trading/.env @@ -0,0 +1,24 @@ +# React Environment Variables +# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env + +# Netlify Environment Variables +# https://www.netlify.com/docs/continuous-deployment/#environment-variables +NX_VERSION=$npm_package_version +NX_REPOSITORY_URL=$REPOSITORY_URL +NX_BRANCH=$BRANCH +NX_PULL_REQUEST=$PULL_REQUEST +NX_HEAD=$HEAD +NX_COMMIT_REF=$COMMIT_REF +NX_CONTEXT=$CONTEXT +NX_REVIEW_ID=$REVIEW_ID +NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE +NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL +NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY +NX_URL=$URL +NX_DEPLOY_URL=$DEPLOY_URL +NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL + +# App configuration variables +NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" +NX_ETHEREUM_CHAIN_ID = 3 +NX_INFURA_ID = "4f846e79e13f44d1b51bbd7ed9edefb8"; \ No newline at end of file diff --git a/apps/trading/.env.devent b/apps/trading/.env.devent new file mode 100644 index 000000000..d2f47df37 --- /dev/null +++ b/apps/trading/.env.devent @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://n04.d.vega.xyz/query" +NX_ETHEREUM_CHAIN_ID = 3 +NX_INFURA_ID = "4f846e79e13f44d1b51bbd7ed9edefb8"; \ No newline at end of file diff --git a/apps/trading/.env.mainnet b/apps/trading/.env.mainnet new file mode 100644 index 000000000..634a2fb17 --- /dev/null +++ b/apps/trading/.env.mainnet @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://api.token.vega.xyz/query" +NX_ETHEREUM_CHAIN_ID = 1 +NX_INFURA_ID = "4f846e79e13f44d1b51bbd7ed9edefb8"; \ No newline at end of file diff --git a/apps/trading/.env.stagnet1 b/apps/trading/.env.stagnet1 new file mode 100644 index 000000000..907bec9f0 --- /dev/null +++ b/apps/trading/.env.stagnet1 @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://n03.s.vega.xyz/query" +NX_ETHEREUM_CHAIN_ID = 3 +NX_INFURA_ID = "4f846e79e13f44d1b51bbd7ed9edefb8"; diff --git a/apps/trading/.env.stagnet2 b/apps/trading/.env.stagnet2 new file mode 100644 index 000000000..adb972a50 --- /dev/null +++ b/apps/trading/.env.stagnet2 @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query" +NX_ETHEREUM_CHAIN_ID = 3 +NX_INFURA_ID = "4f846e79e13f44d1b51bbd7ed9edefb8"; \ No newline at end of file diff --git a/apps/trading/.env.testnet b/apps/trading/.env.testnet new file mode 100644 index 000000000..1909d68a5 --- /dev/null +++ b/apps/trading/.env.testnet @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" +NX_ETHEREUM_CHAIN_ID = 3 +NX_INFURA_ID = "4f846e79e13f44d1b51bbd7ed9edefb8"; \ No newline at end of file diff --git a/apps/trading/components/async-renderer/async-renderer.tsx b/apps/trading/components/async-renderer/async-renderer.tsx new file mode 100644 index 000000000..803d59401 --- /dev/null +++ b/apps/trading/components/async-renderer/async-renderer.tsx @@ -0,0 +1,27 @@ +import { Splash } from '@vegaprotocol/ui-toolkit'; +import { ReactNode } from 'react'; + +interface AsyncRendererProps { + loading: boolean; + error: Error | undefined; + data: T; + children: (data: T) => ReactNode; +} + +// eslint-disable-next-line +export function AsyncRenderer({ + loading, + error, + data, + children, +}: AsyncRendererProps) { + if (error) { + return Something went wrong: {error.message}; + } + + if (loading) { + return Loading...; + } + + return <>{children(data)}; +} diff --git a/apps/trading/components/async-renderer/index.ts b/apps/trading/components/async-renderer/index.ts new file mode 100644 index 000000000..2a3091dd4 --- /dev/null +++ b/apps/trading/components/async-renderer/index.ts @@ -0,0 +1 @@ +export * from './async-renderer'; diff --git a/apps/trading/components/deal-ticket-container/order-dialog.tsx b/apps/trading/components/deal-ticket-container/order-dialog.tsx index 09938a2b4..d5f0c6f67 100644 --- a/apps/trading/components/deal-ticket-container/order-dialog.tsx +++ b/apps/trading/components/deal-ticket-container/order-dialog.tsx @@ -5,6 +5,7 @@ import { VegaTxStatus, } from '../../hooks/use-vega-transaction'; import { OrderEvent_busEvents_event_Order } from '@vegaprotocol/graphql'; +import { formatNumber } from '@vegaprotocol/react-helpers'; interface OrderDialogProps { transaction: TransactionState; @@ -66,8 +67,17 @@ export const OrderDialog = ({ >

Status: {finalizedOrder.status}

Market: {finalizedOrder.market.name}

+

Type: {finalizedOrder.type}

Amount: {finalizedOrder.size}

- {finalizedOrder.type === 'Limit' &&

Price: {finalizedOrder.price}

} + {finalizedOrder.type === 'Limit' && ( +

+ Price:{' '} + {formatNumber( + finalizedOrder.price, + finalizedOrder.market.decimalPlaces + )} +

+ )} ); }; diff --git a/apps/trading/components/navbar/navbar.tsx b/apps/trading/components/navbar/navbar.tsx index 953c443c0..b4c87d2c1 100644 --- a/apps/trading/components/navbar/navbar.tsx +++ b/apps/trading/components/navbar/navbar.tsx @@ -12,9 +12,8 @@ export const Navbar = () => { {[ - { name: 'Trading', path: '/', exact: true }, + { name: 'Trading', path: '/markets' }, { name: 'Portfolio', path: '/portfolio' }, - { name: 'Markets', path: '/markets' }, ].map((route) => ( ))} @@ -35,7 +34,7 @@ const NavLink = ({ name, path, exact }: NavLinkProps) => { return ( { e.preventDefault(); diff --git a/apps/trading/components/order-list-container/index.ts b/apps/trading/components/order-list-container/index.ts new file mode 100644 index 000000000..ac2975314 --- /dev/null +++ b/apps/trading/components/order-list-container/index.ts @@ -0,0 +1 @@ +export * from './order-list-container'; diff --git a/apps/trading/components/order-list-container/order-list-container.spec.tsx b/apps/trading/components/order-list-container/order-list-container.spec.tsx new file mode 100644 index 000000000..77a64470d --- /dev/null +++ b/apps/trading/components/order-list-container/order-list-container.spec.tsx @@ -0,0 +1,41 @@ +import { OrderListContainer } from './order-list-container'; +import * as useOrdersHook from '../../hooks/use-orders'; +import { render, screen } from '@testing-library/react'; +import { Orders_party_orders } from '@vegaprotocol/graphql'; + +jest.mock('@vegaprotocol/order-list', () => ({ + OrderList: () =>
OrderList
, +})); + +test('Renders a loading state while awaiting orders', () => { + jest.spyOn(useOrdersHook, 'useOrders').mockReturnValue({ + orders: [], + loading: true, + error: null, + }); + render(); + expect(screen.getByText('Loading...')).toBeInTheDocument(); +}); + +test('Renders an error state', () => { + const errorMsg = 'Oops! An Error'; + jest.spyOn(useOrdersHook, 'useOrders').mockReturnValue({ + orders: [], + loading: false, + error: new Error(errorMsg), + }); + render(); + expect( + screen.getByText(`Something went wrong: ${errorMsg}`) + ).toBeInTheDocument(); +}); + +test('Renders the order list if orders provided', () => { + jest.spyOn(useOrdersHook, 'useOrders').mockReturnValue({ + orders: [{ id: '1' } as Orders_party_orders], + loading: false, + error: null, + }); + render(); + expect(screen.getByText('OrderList')).toBeInTheDocument(); +}); diff --git a/apps/trading/components/order-list-container/order-list-container.tsx b/apps/trading/components/order-list-container/order-list-container.tsx new file mode 100644 index 000000000..246891559 --- /dev/null +++ b/apps/trading/components/order-list-container/order-list-container.tsx @@ -0,0 +1,14 @@ +import { useOrders } from '../../hooks/use-orders'; +import { OrderList } from '@vegaprotocol/order-list'; +import { AsyncRenderer } from '../async-renderer'; +import { OrderFields } from '@vegaprotocol/graphql'; + +export const OrderListContainer = () => { + const { orders, loading, error } = useOrders(); + + return ( + loading={loading} error={error} data={orders}> + {(data) => } + + ); +}; diff --git a/apps/trading/components/page-query-container/index.tsx b/apps/trading/components/page-query-container/index.tsx index 455302743..74eb15635 100644 --- a/apps/trading/components/page-query-container/index.tsx +++ b/apps/trading/components/page-query-container/index.tsx @@ -1,7 +1,7 @@ import { OperationVariables, QueryHookOptions, useQuery } from '@apollo/client'; import { DocumentNode } from 'graphql'; import { ReactNode } from 'react'; -import { Splash } from '@vegaprotocol/ui-toolkit'; +import { AsyncRenderer } from '../async-renderer'; interface PageQueryContainerProps { query: DocumentNode; @@ -16,13 +16,13 @@ export const PageQueryContainer = ({ }: PageQueryContainerProps) => { const { data, loading, error } = useQuery(query, options); - if (loading || !data) { - return Loading...; - } - - if (error) { - return Something went wrong: {error.message}; - } - - return <>{children(data)}; + return ( + + loading={loading || Boolean(!data)} + error={error} + data={data} + > + {(data) => children(data)} + + ); }; diff --git a/apps/trading/components/vega-wallet-connect-button/index.tsx b/apps/trading/components/vega-wallet-connect-button/index.tsx index 0aec11eae..2a72e9289 100644 --- a/apps/trading/components/vega-wallet-connect-button/index.tsx +++ b/apps/trading/components/vega-wallet-connect-button/index.tsx @@ -19,8 +19,11 @@ export const VegaWalletButton = ({ }; return ( - ); }; diff --git a/apps/trading/components/web3-container/index.ts b/apps/trading/components/web3-container/index.ts new file mode 100644 index 000000000..6e35feeb3 --- /dev/null +++ b/apps/trading/components/web3-container/index.ts @@ -0,0 +1 @@ +export { Web3Container } from './web3-container'; diff --git a/apps/trading/components/web3-container/web3-container.spec.tsx b/apps/trading/components/web3-container/web3-container.spec.tsx new file mode 100644 index 000000000..eda7b6351 --- /dev/null +++ b/apps/trading/components/web3-container/web3-container.spec.tsx @@ -0,0 +1,65 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { Web3Container } from './web3-container'; + +const defaultHookValue = { + isActive: false, + error: undefined, + connector: null, + chainId: 3, +}; +let mockHookValue; + +jest.mock('@web3-react/core', () => { + const original = jest.requireActual('@web3-react/core'); + return { + ...original, + useWeb3React: jest.fn(() => mockHookValue), + }; +}); + +test('Prompt to connect opens dialog', () => { + mockHookValue = defaultHookValue; + render( + +
Child
+
+ ); + + expect(screen.queryByText('Child')).not.toBeInTheDocument(); + expect(screen.queryByTestId('web3-connector-list')).not.toBeInTheDocument(); + expect(screen.getByText('Connect your Ethereum wallet')).toBeInTheDocument(); + fireEvent.click(screen.getByText('Connect')); + expect(screen.getByTestId('web3-connector-list')).toBeInTheDocument(); +}); + +test('Error message is shown', () => { + const message = 'Opps! An error'; + mockHookValue = { ...defaultHookValue, error: new Error(message) }; + + render( + +
Child
+
+ ); + + expect(screen.queryByText('Child')).not.toBeInTheDocument(); + expect(screen.getByText(`Something went wrong: ${message}`)); +}); + +test('Chain id matches app configuration', () => { + const expectedChainId = 4; + mockHookValue = { + ...defaultHookValue, + isActive: true, + chainId: expectedChainId, + }; + + render( + +
Child
+
+ ); + + expect(screen.queryByText('Child')).not.toBeInTheDocument(); + expect(screen.getByText(`This app only works on chain ID: 3`)); +}); diff --git a/apps/trading/components/web3-container/web3-container.tsx b/apps/trading/components/web3-container/web3-container.tsx new file mode 100644 index 000000000..2698d6a46 --- /dev/null +++ b/apps/trading/components/web3-container/web3-container.tsx @@ -0,0 +1,79 @@ +import { Button, Splash } from '@vegaprotocol/ui-toolkit'; +import { Web3Provider, Web3ConnectDialog } from '@vegaprotocol/web3'; +import { useWeb3React } from '@web3-react/core'; +import { ReactNode, useEffect, useState } from 'react'; +import { Connectors } from '../../lib/web3-connectors'; + +interface Web3ContainerProps { + children: ReactNode; +} + +export const Web3Container = ({ children }: Web3ContainerProps) => { + const [dialogOpen, setDialogOpen] = useState(false); + return ( + + {children} + + + ); +}; + +interface Web3ContentProps { + children: ReactNode; + setDialogOpen: (isOpen: boolean) => void; +} + +export const Web3Content = ({ children, setDialogOpen }: Web3ContentProps) => { + const appChainId = Number(process.env['NX_ETHEREUM_CHAIN_ID'] || 3); + const { isActive, error, connector, chainId } = useWeb3React(); + + useEffect(() => { + connector?.connectEagerly(); + }, [connector]); + + if (error) { + return ( + +

Something went wrong: {error.message}

+ +
+ ); + } + + if (!isActive) { + return ( + +

Connect your Ethereum wallet

+ +
+ ); + } + + if (chainId !== appChainId) { + return ( + +

This app only works on chain ID: {appChainId}

+ +
+ ); + } + + return <>{children}; +}; + +interface SplashWrapperProps { + children: ReactNode; +} + +const SplashWrapper = ({ children }: SplashWrapperProps) => { + return ( + +
{children}
+
+ ); +}; diff --git a/apps/trading/hooks/use-markets.ts b/apps/trading/hooks/use-markets.ts index c2fd4e570..88b60e49f 100644 --- a/apps/trading/hooks/use-markets.ts +++ b/apps/trading/hooks/use-markets.ts @@ -1,5 +1,4 @@ import { gql, useApolloClient } from '@apollo/client'; -import { singletonHook } from 'react-singleton-hook'; import { Markets, Markets_markets, @@ -56,7 +55,13 @@ const MARKET_DATA_SUB = gql` } `; -export const useMarketsImpl = () => { +interface UseMarkets { + markets: Markets_markets[]; + error: Error | null; + loading: boolean; +} + +export const useMarkets = (): UseMarkets => { const client = useApolloClient(); const [markets, setMarkets] = useState([]); const [error, setError] = useState(null); @@ -121,11 +126,3 @@ export const useMarketsImpl = () => { return { markets, error, loading }; }; - -const initial = { - markets: [], - error: null, - loading: false, -}; - -export const useMarkets = singletonHook(initial, useMarketsImpl); diff --git a/apps/trading/hooks/use-order-submit.ts b/apps/trading/hooks/use-order-submit.ts index c81a61062..2063d3079 100644 --- a/apps/trading/hooks/use-order-submit.ts +++ b/apps/trading/hooks/use-order-submit.ts @@ -29,6 +29,7 @@ const ORDER_EVENT_SUB = gql` price market { name + decimalPlaces } } } diff --git a/apps/trading/hooks/use-orders.spec.tsx b/apps/trading/hooks/use-orders.spec.tsx new file mode 100644 index 000000000..ae3fbdf14 --- /dev/null +++ b/apps/trading/hooks/use-orders.spec.tsx @@ -0,0 +1,165 @@ +import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { renderHook } from '@testing-library/react-hooks'; +import { + OrderFields, + Orders, + OrderStatus, + OrderSub, + OrderTimeInForce, + OrderType, + Side, +} from '@vegaprotocol/graphql'; +import { + VegaKeyExtended, + VegaWalletContext, + VegaWalletContextShape, +} from '@vegaprotocol/wallet'; +import { ReactNode } from 'react'; +import { ORDERS_QUERY, ORDERS_SUB, useOrders } from './use-orders'; + +const keypair = { pub: '0x123' } as VegaKeyExtended; +const defaultWalletContext = { + keypair, + keypairs: [keypair], + sendTx: jest.fn().mockReturnValue(Promise.resolve(null)), + connect: jest.fn(), + disconnect: jest.fn(), + selectPublicKey: jest.fn(), + connector: null, +}; + +function generateOrder(order?: Partial): OrderFields { + return { + __typename: 'Order', + id: '1', + market: { + __typename: 'Market', + id: 'market-id', + name: 'market-name', + decimalPlaces: 0, + tradableInstrument: { + __typename: 'TradableInstrument', + instrument: { + __typename: 'Instrument', + code: 'instrument-code', + }, + }, + }, + type: OrderType.Market, + side: Side.Buy, + size: '10', + status: OrderStatus.Active, + rejectionReason: null, + price: '', + timeInForce: OrderTimeInForce.GTC, + remaining: '10', + createdAt: '2022-01-01T00:00:00', + updatedAt: null, + expiresAt: null, + ...order, + }; +} + +function setup( + context?: Partial, + mocks: MockedResponse[] = [] +) { + const wrapper = ({ children }: { children: ReactNode }) => ( + + + {children} + + + ); + return renderHook(() => useOrders(), { wrapper }); +} + +test('Fetches and subscribes to orders and merges appropriately', async () => { + const order = generateOrder(); + const mockOrderQuery: MockedResponse = { + request: { + query: ORDERS_QUERY, + variables: { partyId: keypair.pub }, + }, + result: { + data: { + party: { + __typename: 'Party', + id: keypair.pub, + orders: [order], + }, + }, + }, + }; + + const updatedOrder = generateOrder({ + id: '1', + remaining: '5', + updatedAt: '2022-01-01T00:01:00', + }); + const newOrder = generateOrder({ + id: '2', + createdAt: '2022-01-01T01:00:00', + }); + const mockOrderSub: MockedResponse = { + request: { + query: ORDERS_SUB, + variables: { partyId: keypair.pub }, + }, + result: { + data: { + orders: [updatedOrder, newOrder], + }, + }, + delay: 100, + }; + const { result, waitForNextUpdate } = setup(defaultWalletContext, [ + mockOrderQuery, + mockOrderSub, + ]); + expect(result.current.loading).toBe(true); + expect(result.current.error).toBe(null); + await waitForNextUpdate(); + expect(result.current.orders).toEqual([order]); + expect(result.current.loading).toBe(false); + await waitForNextUpdate(); + expect(result.current.orders).toEqual([newOrder, updatedOrder]); +}); + +test('Returns an error if fetch fails', async () => { + const error = new Error('Something failed'); + const mockFailedOrderQuery: MockedResponse = { + request: { + query: ORDERS_QUERY, + variables: { partyId: keypair.pub }, + }, + error, + }; + const { result, waitForNextUpdate } = setup(defaultWalletContext, [ + mockFailedOrderQuery, + ]); + expect(result.current.loading).toBe(true); + expect(result.current.error).toBe(null); + await waitForNextUpdate(); + expect(result.current.error).toEqual(error); + expect(result.current.loading).toBe(false); +}); + +test('No queries are made if no pubkey provided', () => { + const mockQuery: MockedResponse = { + request: { + query: ORDERS_QUERY, + variables: { partyId: keypair.pub }, + }, + newData: jest.fn(), + }; + const { result } = setup( + { ...defaultWalletContext, keypair: null, keypairs: [] }, + [mockQuery] + ); + expect(mockQuery.newData).not.toBeCalled(); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(null); +}); diff --git a/apps/trading/hooks/use-orders.ts b/apps/trading/hooks/use-orders.ts new file mode 100644 index 000000000..734a8234d --- /dev/null +++ b/apps/trading/hooks/use-orders.ts @@ -0,0 +1,140 @@ +import { gql, useApolloClient } from '@apollo/client'; +import { useCallback, useEffect, useState } from 'react'; +import { + OrderSub, + OrderSubVariables, + Orders, + OrdersVariables, + OrderFields, +} from '@vegaprotocol/graphql'; +import uniqBy from 'lodash/uniqBy'; +import orderBy from 'lodash/orderBy'; +import { useVegaWallet } from '@vegaprotocol/wallet'; + +const ORDER_FRAGMENT = gql` + fragment OrderFields on Order { + id + market { + id + name + decimalPlaces + tradableInstrument { + instrument { + code + } + } + } + type + side + size + status + rejectionReason + price + timeInForce + remaining + expiresAt + createdAt + updatedAt + } +`; + +export const ORDERS_QUERY = gql` + ${ORDER_FRAGMENT} + query Orders($partyId: ID!) { + party(id: $partyId) { + id + orders { + ...OrderFields + } + } + } +`; + +export const ORDERS_SUB = gql` + ${ORDER_FRAGMENT} + subscription OrderSub($partyId: ID!) { + orders(partyId: $partyId) { + ...OrderFields + } + } +`; + +interface UseOrders { + orders: OrderFields[]; + error: Error | null; + loading: boolean; +} + +export const useOrders = (): UseOrders => { + const client = useApolloClient(); + const { keypair } = useVegaWallet(); + const [orders, setOrders] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const mergeOrders = useCallback((update: OrderFields[]) => { + // A subscription payload can contain multiple updates for a single order so we need to first + // sort them by updatedAt (or createdAt if the order hasn't been updated) with the newest first, + // then use uniqBy, which selects the first occuring order for an id to ensure we only get the latest order + setOrders((curr) => { + const sorted = orderBy( + [...curr, ...update], + (o) => { + if (!o.updatedAt) return new Date(o.createdAt).getTime(); + return new Date(o.updatedAt).getTime(); + }, + 'desc' + ); + const uniq = uniqBy(sorted, 'id'); + return uniq; + }); + }, []); + + // Make initial fetch + useEffect(() => { + const fetchOrders = async () => { + if (!keypair?.pub) return; + + setLoading(true); + + try { + const res = await client.query({ + query: ORDERS_QUERY, + variables: { partyId: keypair.pub }, + }); + + if (!res.data.party?.orders.length) return; + + mergeOrders(res.data.party.orders); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + fetchOrders(); + }, [mergeOrders, keypair, client]); + + // Start subscription + useEffect(() => { + if (!keypair?.pub) return; + + const sub = client + .subscribe({ + query: ORDERS_SUB, + variables: { partyId: keypair.pub }, + }) + .subscribe(({ data }) => { + mergeOrders(data.orders); + }); + + return () => { + if (sub) { + sub.unsubscribe(); + } + }; + }, [client, keypair, mergeOrders]); + + return { orders, error, loading }; +}; diff --git a/apps/trading/hooks/use-vega-wallet-eager-connect.ts b/apps/trading/hooks/use-vega-wallet-eager-connect.ts index c9433eaa3..8da9055d8 100644 --- a/apps/trading/hooks/use-vega-wallet-eager-connect.ts +++ b/apps/trading/hooks/use-vega-wallet-eager-connect.ts @@ -1,7 +1,7 @@ import { useVegaWallet, WALLET_CONFIG } from '@vegaprotocol/wallet'; import { useEffect } from 'react'; import { LocalStorage } from '@vegaprotocol/react-helpers'; -import { Connectors } from '../lib/connectors'; +import { Connectors } from '../lib/vega-connectors'; export function useEagerConnect() { const { connect } = useVegaWallet(); @@ -21,7 +21,10 @@ export function useEagerConnect() { // Developer hasn't provided this connector if (!connector) { - throw new Error(`Connector ${cfgObj?.connector} not configured`); + console.warn( + `Can't eager connect, connector: ${cfgObj.connector} not found` + ); + return; } connect(Connectors[cfgObj.connector]); diff --git a/apps/trading/jest.config.js b/apps/trading/jest.config.js index 09d01143d..64e53be63 100644 --- a/apps/trading/jest.config.js +++ b/apps/trading/jest.config.js @@ -7,4 +7,5 @@ module.exports = { }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/trading', + setupFilesAfterEnv: ['./setup-tests.ts'], }; diff --git a/apps/trading/lib/connectors.ts b/apps/trading/lib/vega-connectors.ts similarity index 100% rename from apps/trading/lib/connectors.ts rename to apps/trading/lib/vega-connectors.ts diff --git a/apps/trading/lib/web3-connectors.ts b/apps/trading/lib/web3-connectors.ts new file mode 100644 index 000000000..0567b4b83 --- /dev/null +++ b/apps/trading/lib/web3-connectors.ts @@ -0,0 +1,23 @@ +import { initializeConnector } from '@web3-react/core'; +import { MetaMask } from '@web3-react/metamask'; +import { WalletConnect } from '@web3-react/walletconnect'; + +export const metamask = initializeConnector( + (actions) => new MetaMask(actions) +); + +export const walletconnect = initializeConnector( + (actions) => + new WalletConnect(actions, { + rpc: { + 1: `https://mainnet.infura.io/v3/${process.env['NX_INFURA_ID']}`, + 3: `https://ropsten.infura.io/v3/${process.env['NX_INFURA_ID']}`, + }, + }), + [1, 3] +); + +export const Connectors = { + metamask, + walletconnect, +}; diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index b93d7c834..3c59855d9 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -3,9 +3,8 @@ import Head from 'next/head'; import { Navbar } from '../components/navbar'; import { ThemeContext } from '@vegaprotocol/react-helpers'; import { VegaConnectDialog, VegaWalletProvider } from '@vegaprotocol/wallet'; -import { Connectors } from '../lib/connectors'; +import { Connectors } from '../lib/vega-connectors'; import { useCallback, useMemo, useState } from 'react'; -import { SingletonHooksContainer } from 'react-singleton-hook'; import { createClient } from '../lib/apollo-client'; import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; import { ApolloProvider } from '@apollo/client'; @@ -16,11 +15,7 @@ import { useThemeSwitcher } from '../hooks/use-theme-switcher'; import './styles.css'; function VegaTradingApp({ Component, pageProps }: AppProps) { - const client = useMemo( - () => - createClient(process.env['NX_VEGA_URL'] || 'https://lb.testnet.vega.xyz'), - [] - ); + const client = useMemo(() => createClient(process.env['NX_VEGA_URL']), []); const [dialogOpen, setDialogOpen] = useState(false); const [theme, toggleTheme] = useThemeSwitcher(); @@ -35,7 +30,6 @@ function VegaTradingApp({ Component, pageProps }: AppProps) { - Welcome to trading! diff --git a/apps/trading/pages/markets/[marketId].page.tsx b/apps/trading/pages/markets/[marketId].page.tsx index aee507c3d..54033cf7c 100644 --- a/apps/trading/pages/markets/[marketId].page.tsx +++ b/apps/trading/pages/markets/[marketId].page.tsx @@ -3,7 +3,7 @@ import { Market, MarketVariables } from '@vegaprotocol/graphql'; import { Splash } from '@vegaprotocol/ui-toolkit'; import { useRouter } from 'next/router'; import React, { useEffect, useState } from 'react'; -import debounce from 'lodash.debounce'; +import debounce from 'lodash/debounce'; import { PageQueryContainer } from '../../components/page-query-container'; import { TradeGrid, TradePanels } from './trade-grid'; diff --git a/apps/trading/pages/markets/grid-tabs.tsx b/apps/trading/pages/markets/grid-tabs.tsx index 6b9ba88cf..dc9f3636f 100644 --- a/apps/trading/pages/markets/grid-tabs.tsx +++ b/apps/trading/pages/markets/grid-tabs.tsx @@ -67,7 +67,7 @@ export const GridTabs = ({ children, group }: GridTabsProps) => { {Children.map(children, (child) => { if (!isValidElement(child)) return null; return ( - + {child.props.children} ); diff --git a/apps/trading/pages/markets/index.page.tsx b/apps/trading/pages/markets/index.page.tsx index a930be52c..fd909eeca 100644 --- a/apps/trading/pages/markets/index.page.tsx +++ b/apps/trading/pages/markets/index.page.tsx @@ -2,27 +2,23 @@ import { Markets } from '@vegaprotocol/graphql'; import { useRouter } from 'next/router'; import { MarketListTable } from '@vegaprotocol/market-list'; import { useMarkets } from '../../hooks/use-markets'; -import { Splash } from '@vegaprotocol/ui-toolkit'; +import { AsyncRenderer } from '../../components/async-renderer'; const Markets = () => { const { pathname, push } = useRouter(); const { markets, error, loading } = useMarkets(); - if (error) { - return Something went wrong: {error.message}; - } - - if (loading) { - return Loading...; - } - return ( - - push(`${pathname}/${id}?portfolio=orders&trade=orderbook`) - } - /> + + {(data) => ( + + push(`${pathname}/${id}?portfolio=orders&trade=orderbook`) + } + /> + )} + ); }; diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx index b3b9b4c42..dca4d52fe 100644 --- a/apps/trading/pages/markets/trade-grid.tsx +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -4,12 +4,34 @@ import AutoSizer from 'react-virtualized-auto-sizer'; import { useState, ReactNode } from 'react'; import { GridTab, GridTabs } from './grid-tabs'; import { DealTicketContainer } from '../../components/deal-ticket-container'; +import { OrderListContainer } from '../..//components/order-list-container'; +import { Splash } from '@vegaprotocol/ui-toolkit'; -const Chart = () =>
TODO: Chart
; -const Orderbook = () =>
TODO: Orderbook
; -const Orders = () =>
TODO: Orders
; -const Positions = () =>
TODO: Positions
; -const Collateral = () =>
TODO: Collateral
; +const Chart = () => ( + +

Chart

+
+); +const Orderbook = () => ( + +

Orderbook

+
+); +const Positions = () => ( + +

Positions

+
+); +const Collateral = () => ( + +

Collateral

+
+); +const Trades = () => ( + +

Trades

+
+); type TradingView = keyof typeof TradingViews; @@ -17,9 +39,10 @@ const TradingViews = { chart: Chart, ticket: DealTicketContainer, orderbook: Orderbook, - orders: Orders, + orders: OrderListContainer, positions: Positions, collateral: Collateral, + trades: Trades, }; interface TradeGridProps { @@ -47,7 +70,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => { -
{JSON.stringify(market.trades, null, 2)}
+
@@ -120,7 +143,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => { )} -
+
{Object.keys(TradingViews).map((key: TradingView) => { const isActive = view === key; const className = classNames('py-4', 'px-12', 'capitalize', { diff --git a/apps/trading/pages/portfolio/deposit/index.page.tsx b/apps/trading/pages/portfolio/deposit/index.page.tsx new file mode 100644 index 000000000..da45dfc7c --- /dev/null +++ b/apps/trading/pages/portfolio/deposit/index.page.tsx @@ -0,0 +1,28 @@ +import { useWeb3React } from '@web3-react/core'; +import { Web3Container } from '../../../components/web3-container'; + +const Deposit = () => { + return ( + +
+

Deposit

+ +
+
+ ); +}; + +const Info = () => { + const { isActive, chainId, accounts } = useWeb3React(); + if (!isActive) { + return
Not active
; + } + return ( +
+

{chainId}

+

{accounts[0]}

+
+ ); +}; + +export default Deposit; diff --git a/apps/trading/pages/portfolio/index.page.tsx b/apps/trading/pages/portfolio/index.page.tsx index 484564ca7..5cf191e63 100644 --- a/apps/trading/pages/portfolio/index.page.tsx +++ b/apps/trading/pages/portfolio/index.page.tsx @@ -1,3 +1,4 @@ +import { AnchorButton } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet } from '@vegaprotocol/wallet'; const Portfolio = () => { @@ -10,6 +11,9 @@ const Portfolio = () => { Keypair: {keypair.name} {keypair.pub}

)} +
+ Deposit +
); }; diff --git a/apps/trading/setup-tests.ts b/apps/trading/setup-tests.ts new file mode 100644 index 000000000..7b0828bfa --- /dev/null +++ b/apps/trading/setup-tests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/apps/trading/specs/index.spec.tsx b/apps/trading/specs/index.spec.tsx index 65b343075..1f61cff81 100644 --- a/apps/trading/specs/index.spec.tsx +++ b/apps/trading/specs/index.spec.tsx @@ -1,26 +1,17 @@ import React from 'react'; import { render } from '@testing-library/react'; - import Index from '../pages/index.page'; -import { VegaWalletContext } from '@vegaprotocol/wallet'; + +jest.mock('@vegaprotocol/ui-toolkit', () => { + const original = jest.requireActual('@vegaprotocol/ui-toolkit'); + return { + ...original, + AgGridDynamic: () =>
AgGrid
, + }; +}); describe('Index', () => { it('should render successfully', () => { - const { baseElement } = render( - - - - ); - expect(baseElement).toBeTruthy(); + render(); }); }); diff --git a/libs/deal-ticket/src/deal-ticket.tsx b/libs/deal-ticket/src/deal-ticket.tsx index a49dbf958..a7235d957 100644 --- a/libs/deal-ticket/src/deal-ticket.tsx +++ b/libs/deal-ticket/src/deal-ticket.tsx @@ -12,9 +12,6 @@ const DEFAULT_ORDER: Order = { timeInForce: OrderTimeInForce.IOC, }; -// TODO: Consider using a generated type when we have a better solution for -// sharing the types from GQL - export type TransactionStatus = 'default' | 'pending'; export interface DealTicketProps { diff --git a/libs/graphql/src/__generated__/OrderEvent.ts b/libs/graphql/src/__generated__/OrderEvent.ts index 9d93b9fbf..b5ff01623 100644 --- a/libs/graphql/src/__generated__/OrderEvent.ts +++ b/libs/graphql/src/__generated__/OrderEvent.ts @@ -19,6 +19,23 @@ export interface OrderEvent_busEvents_event_Order_market { * Market full name */ name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; } export interface OrderEvent_busEvents_event_Order { diff --git a/libs/graphql/src/__generated__/OrderFields.ts b/libs/graphql/src/__generated__/OrderFields.ts new file mode 100644 index 000000000..1fa9bff65 --- /dev/null +++ b/libs/graphql/src/__generated__/OrderFields.ts @@ -0,0 +1,115 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "./globalTypes"; + +// ==================================================== +// GraphQL fragment: OrderFields +// ==================================================== + +export interface OrderFields_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) + */ + code: string; +} + +export interface OrderFields_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: OrderFields_market_tradableInstrument_instrument; +} + +export interface OrderFields_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Market full name + */ + name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: OrderFields_market_tradableInstrument; +} + +export interface OrderFields { + __typename: "Order"; + /** + * Hash of the order data + */ + id: string; + /** + * The market the order is trading on (probably stored internally as a hash of the market details) + */ + market: OrderFields_market | null; + /** + * Type the order type (defaults to PARTY) + */ + type: OrderType | null; + /** + * Whether the order is to buy or sell + */ + side: Side; + /** + * Total number of contracts that may be bought or sold (immutable) (uint64) + */ + size: string; + /** + * The status of an order, for example 'Active' + */ + status: OrderStatus; + /** + * Reason for the order to be rejected + */ + rejectionReason: OrderRejectionReason | null; + /** + * The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64) + */ + price: string; + /** + * The timeInForce of order (determines how and if it executes, and whether it persists on the book) + */ + timeInForce: OrderTimeInForce; + /** + * Number of contracts remaining of the total that have not yet been bought or sold (uint64) + */ + remaining: string; + /** + * Expiration time of this order (ISO-8601 RFC3339+Nano formatted date) + */ + expiresAt: string | null; + /** + * RFC3339Nano formatted date and time for when the order was created (timestamp) + */ + createdAt: string; + /** + * RFC3339Nano time the order was altered + */ + updatedAt: string | null; +} diff --git a/libs/graphql/src/__generated__/OrderSub.ts b/libs/graphql/src/__generated__/OrderSub.ts new file mode 100644 index 000000000..8c8e05efc --- /dev/null +++ b/libs/graphql/src/__generated__/OrderSub.ts @@ -0,0 +1,126 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "./globalTypes"; + +// ==================================================== +// GraphQL subscription operation: OrderSub +// ==================================================== + +export interface OrderSub_orders_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) + */ + code: string; +} + +export interface OrderSub_orders_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: OrderSub_orders_market_tradableInstrument_instrument; +} + +export interface OrderSub_orders_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Market full name + */ + name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: OrderSub_orders_market_tradableInstrument; +} + +export interface OrderSub_orders { + __typename: "Order"; + /** + * Hash of the order data + */ + id: string; + /** + * The market the order is trading on (probably stored internally as a hash of the market details) + */ + market: OrderSub_orders_market | null; + /** + * Type the order type (defaults to PARTY) + */ + type: OrderType | null; + /** + * Whether the order is to buy or sell + */ + side: Side; + /** + * Total number of contracts that may be bought or sold (immutable) (uint64) + */ + size: string; + /** + * The status of an order, for example 'Active' + */ + status: OrderStatus; + /** + * Reason for the order to be rejected + */ + rejectionReason: OrderRejectionReason | null; + /** + * The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64) + */ + price: string; + /** + * The timeInForce of order (determines how and if it executes, and whether it persists on the book) + */ + timeInForce: OrderTimeInForce; + /** + * Number of contracts remaining of the total that have not yet been bought or sold (uint64) + */ + remaining: string; + /** + * Expiration time of this order (ISO-8601 RFC3339+Nano formatted date) + */ + expiresAt: string | null; + /** + * RFC3339Nano formatted date and time for when the order was created (timestamp) + */ + createdAt: string; + /** + * RFC3339Nano time the order was altered + */ + updatedAt: string | null; +} + +export interface OrderSub { + /** + * Subscribe to orders updates + */ + orders: OrderSub_orders[] | null; +} + +export interface OrderSubVariables { + partyId: string; +} diff --git a/libs/graphql/src/__generated__/Orders.ts b/libs/graphql/src/__generated__/Orders.ts new file mode 100644 index 000000000..37f376e44 --- /dev/null +++ b/libs/graphql/src/__generated__/Orders.ts @@ -0,0 +1,138 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { OrderType, Side, OrderStatus, OrderRejectionReason, OrderTimeInForce } from "./globalTypes"; + +// ==================================================== +// GraphQL query operation: Orders +// ==================================================== + +export interface Orders_party_orders_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) + */ + code: string; +} + +export interface Orders_party_orders_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: Orders_party_orders_market_tradableInstrument_instrument; +} + +export interface Orders_party_orders_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Market full name + */ + name: string; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: Orders_party_orders_market_tradableInstrument; +} + +export interface Orders_party_orders { + __typename: "Order"; + /** + * Hash of the order data + */ + id: string; + /** + * The market the order is trading on (probably stored internally as a hash of the market details) + */ + market: Orders_party_orders_market | null; + /** + * Type the order type (defaults to PARTY) + */ + type: OrderType | null; + /** + * Whether the order is to buy or sell + */ + side: Side; + /** + * Total number of contracts that may be bought or sold (immutable) (uint64) + */ + size: string; + /** + * The status of an order, for example 'Active' + */ + status: OrderStatus; + /** + * Reason for the order to be rejected + */ + rejectionReason: OrderRejectionReason | null; + /** + * The worst price the order will trade at (e.g. buy for price or less, sell for price or more) (uint64) + */ + price: string; + /** + * The timeInForce of order (determines how and if it executes, and whether it persists on the book) + */ + timeInForce: OrderTimeInForce; + /** + * Number of contracts remaining of the total that have not yet been bought or sold (uint64) + */ + remaining: string; + /** + * Expiration time of this order (ISO-8601 RFC3339+Nano formatted date) + */ + expiresAt: string | null; + /** + * RFC3339Nano formatted date and time for when the order was created (timestamp) + */ + createdAt: string; + /** + * RFC3339Nano time the order was altered + */ + updatedAt: string | null; +} + +export interface Orders_party { + __typename: "Party"; + /** + * Party identifier + */ + id: string; + /** + * Orders relating to a party + */ + orders: Orders_party_orders[] | null; +} + +export interface Orders { + /** + * An entity that is trading on the VEGA network + */ + party: Orders_party | null; +} + +export interface OrdersVariables { + partyId: string; +} diff --git a/libs/graphql/src/__generated__/globalTypes.ts b/libs/graphql/src/__generated__/globalTypes.ts index e4c7a3e64..0e2c72879 100644 --- a/libs/graphql/src/__generated__/globalTypes.ts +++ b/libs/graphql/src/__generated__/globalTypes.ts @@ -162,6 +162,18 @@ export enum OrderStatus { Stopped = "Stopped", } +/** + * Valid order types, these determine what happens when an order is added to the book + */ +export enum OrderTimeInForce { + FOK = "FOK", + GFA = "GFA", + GFN = "GFN", + GTC = "GTC", + GTT = "GTT", + IOC = "IOC", +} + export enum OrderType { Limit = "Limit", Market = "Market", @@ -224,6 +236,14 @@ export enum ProposalState { WaitingForNodeVote = "WaitingForNodeVote", } +/** + * Whether the placer of an order is aiming to buy or sell on the market + */ +export enum Side { + Buy = "Buy", + Sell = "Sell", +} + export enum VoteValue { No = "No", Yes = "Yes", diff --git a/libs/graphql/src/index.ts b/libs/graphql/src/index.ts index 3e7077947..59c3a0077 100644 --- a/libs/graphql/src/index.ts +++ b/libs/graphql/src/index.ts @@ -9,5 +9,8 @@ export * from './__generated__/MarketDataFields'; export * from './__generated__/NetworkParametersQuery'; export * from './__generated__/NodesQuery'; export * from './__generated__/OrderEvent'; +export * from './__generated__/OrderFields'; +export * from './__generated__/Orders'; +export * from './__generated__/OrderSub'; export * from './__generated__/PartyAssetsQuery'; export * from './__generated__/ProposalsQuery'; diff --git a/libs/mainnet-stats-manager/.babelrc b/libs/mainnet-stats-manager/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/mainnet-stats-manager/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/mainnet-stats-manager/.eslintrc.json b/libs/mainnet-stats-manager/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/libs/mainnet-stats-manager/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/mainnet-stats-manager/README.md b/libs/mainnet-stats-manager/README.md new file mode 100644 index 000000000..160242035 --- /dev/null +++ b/libs/mainnet-stats-manager/README.md @@ -0,0 +1,7 @@ +# mainnet-stats-manager + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test mainnet-stats-manager` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/mainnet-stats-manager/jest.config.js b/libs/mainnet-stats-manager/jest.config.js new file mode 100644 index 000000000..de9ee9926 --- /dev/null +++ b/libs/mainnet-stats-manager/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + displayName: 'mainnet-stats-manager', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/mainnet-stats-manager', +}; diff --git a/libs/mainnet-stats-manager/package.json b/libs/mainnet-stats-manager/package.json new file mode 100644 index 000000000..6eed0f1da --- /dev/null +++ b/libs/mainnet-stats-manager/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/mainnet-stats-manager", + "version": "0.0.1" +} diff --git a/libs/mainnet-stats-manager/project.json b/libs/mainnet-stats-manager/project.json new file mode 100644 index 000000000..2b9cb03a1 --- /dev/null +++ b/libs/mainnet-stats-manager/project.json @@ -0,0 +1,43 @@ +{ + "root": "libs/mainnet-stats-manager", + "sourceRoot": "libs/mainnet-stats-manager/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/mainnet-stats-manager", + "tsConfig": "libs/mainnet-stats-manager/tsconfig.lib.json", + "project": "libs/mainnet-stats-manager/package.json", + "entryFile": "libs/mainnet-stats-manager/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/mainnet-stats-manager/README.md", + "input": ".", + "output": "." + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/mainnet-stats-manager/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/mainnet-stats-manager"], + "options": { + "jestConfig": "libs/mainnet-stats-manager/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/mainnet-stats-manager/src/components/good-threshold-indicator/good-threshold-indicator.tsx b/libs/mainnet-stats-manager/src/components/good-threshold-indicator/good-threshold-indicator.tsx new file mode 100644 index 000000000..4844e7955 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/good-threshold-indicator/good-threshold-indicator.tsx @@ -0,0 +1,21 @@ +import { value, goodThreshold } from '../../config/types'; + +interface GoodThresholdIndicatorProps { + goodThreshold: goodThreshold | undefined; + value: value; +} + +export const GoodThresholdIndicator = ({ + goodThreshold, + value, +}: GoodThresholdIndicatorProps) => { + return ( +
+ ); +}; diff --git a/libs/mainnet-stats-manager/src/components/good-threshold-indicator/index.ts b/libs/mainnet-stats-manager/src/components/good-threshold-indicator/index.ts new file mode 100644 index 000000000..6ffd483b7 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/good-threshold-indicator/index.ts @@ -0,0 +1 @@ +export { GoodThresholdIndicator } from './good-threshold-indicator'; diff --git a/libs/mainnet-stats-manager/src/components/promoted-stats-item/index.ts b/libs/mainnet-stats-manager/src/components/promoted-stats-item/index.ts new file mode 100644 index 000000000..fe769eb91 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/promoted-stats-item/index.ts @@ -0,0 +1 @@ +export { PromotedStatsItem } from './promoted-stats-item'; diff --git a/libs/mainnet-stats-manager/src/components/promoted-stats-item/promoted-stats-item.tsx b/libs/mainnet-stats-manager/src/components/promoted-stats-item/promoted-stats-item.tsx new file mode 100644 index 000000000..0b6f9e4f5 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/promoted-stats-item/promoted-stats-item.tsx @@ -0,0 +1,26 @@ +import { Tooltip } from '../tooltip'; +import { StatFields } from '../../config/types'; +import { defaultFieldFormatter } from '../table-row'; +import { GoodThresholdIndicator } from '../good-threshold-indicator'; + +export const PromotedStatsItem = ({ + title, + formatter, + goodThreshold, + value, + description, +}: StatFields) => { + return ( + +
+
+ + {title} +
+
+ {formatter ? formatter(value) : defaultFieldFormatter(value)} +
+
+
+ ); +}; diff --git a/libs/mainnet-stats-manager/src/components/promoted-stats/index.ts b/libs/mainnet-stats-manager/src/components/promoted-stats/index.ts new file mode 100644 index 000000000..799945377 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/promoted-stats/index.ts @@ -0,0 +1 @@ +export { PromotedStats } from './promoted-stats'; diff --git a/libs/mainnet-stats-manager/src/components/promoted-stats/promoted-stats.tsx b/libs/mainnet-stats-manager/src/components/promoted-stats/promoted-stats.tsx new file mode 100644 index 000000000..5cda047d5 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/promoted-stats/promoted-stats.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +interface PromotedStatsProps { + children: React.ReactNode; +} + +export const PromotedStats = ({ children }: PromotedStatsProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/libs/mainnet-stats-manager/src/components/stats-manager/index.ts b/libs/mainnet-stats-manager/src/components/stats-manager/index.ts new file mode 100644 index 000000000..2c4a65880 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/stats-manager/index.ts @@ -0,0 +1 @@ +export { StatsManager } from './stats-manager'; diff --git a/libs/mainnet-stats-manager/src/components/stats-manager/stats-manager.tsx b/libs/mainnet-stats-manager/src/components/stats-manager/stats-manager.tsx new file mode 100644 index 000000000..8f01bbae2 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/stats-manager/stats-manager.tsx @@ -0,0 +1,120 @@ +import { useEffect, useState } from 'react'; +import classnames from 'classnames'; +import { statsFields } from '../../config/stats-fields'; +import { + Stats as IStats, + StructuredStats as IStructuredStats, +} from '../../config/types'; +import { Table } from '../table'; +import { TableRow } from '../table-row'; +import { PromotedStats } from '../promoted-stats'; +import { PromotedStatsItem } from '../promoted-stats-item'; + +interface statsManagerProps { + className?: string; +} + +export const StatsManager = ({ className }: statsManagerProps) => { + const [data, setData] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + async function getStats() { + try { + const [res1, res2] = await Promise.all([ + fetch('https://api.token.vega.xyz/statistics'), + fetch('https://api.token.vega.xyz/nodes-data'), + ]); + const [{ statistics }, { nodeData }] = await Promise.all([ + res1.json(), + res2.json(), + ]); + const returned = { ...nodeData, ...statistics }; + + if (!statistics || !nodeData) { + throw new Error('Failed to get data from endpoints'); + } + + // Loop through the stats fields config, grabbing values from the fetched + // data and building a set of promoted and standard table entries. + const structured = Object.entries(statsFields).reduce( + (acc, [key, value]) => { + const statKey = key as keyof IStats; + const statData = returned[statKey]; + + value.forEach((x) => { + const stat = { + ...x, + value: statData, + }; + + stat.promoted ? acc.promoted.push(stat) : acc.table.push(stat); + }); + + return acc; + }, + { promoted: [], table: [] } as IStructuredStats + ); + + setData(structured); + setError(null); + } catch (e) { + setData(null); + setError(e as Error); + } + } + + const interval = setInterval(getStats, 1000); + + return () => { + clearInterval(interval); + }; + }, []); + + const classes = classnames( + className, + 'stats-grid w-full self-start justify-self-center' + ); + + return ( +
+

+ {(error && `/ ${error}`) || (data ? '/ Mainnet' : '/ Connecting...')} +

+ + {data?.promoted ? ( + + {data.promoted.map((stat, i) => { + return ( + + ); + })} + + ) : null} + + + {data?.table + ? data.table.map((stat, i) => { + return ( + + ); + }) + : null} +
+
+ ); +}; diff --git a/libs/mainnet-stats-manager/src/components/table-row/index.ts b/libs/mainnet-stats-manager/src/components/table-row/index.ts new file mode 100644 index 000000000..665eaf966 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/table-row/index.ts @@ -0,0 +1 @@ +export { TableRow, defaultFieldFormatter } from './table-row'; diff --git a/libs/mainnet-stats-manager/src/components/table-row/table-row.tsx b/libs/mainnet-stats-manager/src/components/table-row/table-row.tsx new file mode 100644 index 000000000..4a6403ab5 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/table-row/table-row.tsx @@ -0,0 +1,28 @@ +import { Tooltip } from '../tooltip'; +import { StatFields } from '../../config/types'; +import { GoodThresholdIndicator } from '../good-threshold-indicator'; + +export const defaultFieldFormatter = (field: unknown) => + field === undefined ? 'no data' : field; + +export const TableRow = ({ + title, + formatter, + goodThreshold, + value, + description, +}: StatFields) => { + return ( + + + {title} + + {formatter ? formatter(value) : defaultFieldFormatter(value)} + + + + + + + ); +}; diff --git a/libs/mainnet-stats-manager/src/components/table/index.ts b/libs/mainnet-stats-manager/src/components/table/index.ts new file mode 100644 index 000000000..48232283c --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/table/index.ts @@ -0,0 +1 @@ +export { Table } from './table'; diff --git a/libs/mainnet-stats-manager/src/components/table/table.tsx b/libs/mainnet-stats-manager/src/components/table/table.tsx new file mode 100644 index 000000000..cf535f6df --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/table/table.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +interface TableProps { + children: React.ReactNode; +} + +export const Table = ({ children }: TableProps) => { + return ( + + {children} +
+ ); +}; diff --git a/libs/mainnet-stats-manager/src/components/tooltip/index.ts b/libs/mainnet-stats-manager/src/components/tooltip/index.ts new file mode 100644 index 000000000..c20c22cb1 --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/tooltip/index.ts @@ -0,0 +1 @@ +export { Tooltip } from './tooltip'; diff --git a/libs/mainnet-stats-manager/src/components/tooltip/tooltip.tsx b/libs/mainnet-stats-manager/src/components/tooltip/tooltip.tsx new file mode 100644 index 000000000..0fdfac4ce --- /dev/null +++ b/libs/mainnet-stats-manager/src/components/tooltip/tooltip.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + Provider, + Root, + Trigger, + Content, + Arrow, +} from '@radix-ui/react-tooltip'; + +interface TooltipProps { + children: React.ReactElement; + description?: string; +} + +// Conditionally rendered tooltip if description content is provided. +export const Tooltip = ({ children, description }: TooltipProps) => + description ? ( + + + {children} + + +
+ {description} +
+
+
+
+ ) : ( + children + ); diff --git a/libs/mainnet-stats-manager/src/config/stats-fields.ts b/libs/mainnet-stats-manager/src/config/stats-fields.ts new file mode 100644 index 000000000..c779c1c39 --- /dev/null +++ b/libs/mainnet-stats-manager/src/config/stats-fields.ts @@ -0,0 +1,174 @@ +import { Stats as IStats, StatFields as IStatFields } from './types'; + +// Stats fields config. Keys will correspond to graphql queries when used, and values +// contain the associated data and methods we need to render. A single query +// can be rendered in multiple ways (see 'upTime'). +export const statsFields: { [key in keyof IStats]: IStatFields[] } = { + status: [ + { + title: 'Status', + formatter: (status: string) => { + if (!status) { + return; + } + + const i = status.lastIndexOf('_'); + if (i === -1) { + return status; + } else { + return status.substr(i + 1); + } + }, + goodThreshold: (status: string) => + status === 'CONNECTED' || status === 'CHAIN_STATUS_CONNECTED', + promoted: true, + description: + 'Status is either connected, replaying, unspecified or disconnected', + }, + ], + blockHeight: [ + { + title: 'Height', + goodThreshold: (height: number) => height >= 60, + promoted: true, + description: 'Block height', + }, + ], + totalNodes: [ + { + title: 'Total nodes', + description: 'The total number of nodes registered on the network', + }, + ], + validatingNodes: [ + { + title: 'Validating nodes', + promoted: true, + description: 'Nodes participating in consensus', + }, + ], + inactiveNodes: [ + { + title: 'Inactive nodes', + goodThreshold: (totalInactive: number) => totalInactive < 1, + description: 'Nodes that are registered but not validating', + }, + ], + stakedTotal: [ + { + title: 'Total staked', + formatter: (total: string) => + total.length > 18 && + parseInt(total.substring(0, total.length - 18)).toLocaleString('en-US'), + description: 'Sum of VEGA associated with a Vega key', + }, + ], + backlogLength: [ + { + title: 'Backlog', + goodThreshold: (length: number, blockDuration: number) => { + return ( + length < 1000 || (length >= 1000 && blockDuration / 1000000000 <= 1) + ); + }, + description: 'Number of transactions waiting to be processed', + }, + ], + tradesPerSecond: [ + { + title: 'Trades / second', + goodThreshold: (trades: number) => trades >= 2, + description: 'Number of trades processed in the last second', + }, + ], + averageOrdersPerBlock: [ + { + title: 'Orders / block', + goodThreshold: (orders: number) => orders >= 2, + description: + 'Number of new orders processed in the last block. All pegged orders and liquidity provisions count as a single order', + }, + ], + ordersPerSecond: [ + { + title: 'Orders / second', + goodThreshold: (orders: number) => orders >= 2, + description: + 'Number of orders processed in the last second. All pegged orders and liquidity provisions count as a single order', + }, + ], + txPerBlock: [ + { + title: 'Transactions / block', + goodThreshold: (tx: number) => tx > 2, + description: 'Number of transactions processed in the last block', + }, + ], + blockDuration: [ + { + title: 'Block time', + formatter: (duration: number) => (duration / 1000000000).toFixed(3), + goodThreshold: (blockDuration: number) => + blockDuration > 0 && blockDuration <= 2000000000, + description: 'Seconds between the two most recent blocks', + }, + ], + vegaTime: [ + { + title: 'Time', + formatter: (time: Date) => new Date(time).toLocaleTimeString(), + goodThreshold: (time: Date) => { + const diff = new Date().getTime() - new Date(time).getTime(); + return diff > 0 && diff < 5000; + }, + description: 'The time on the blockchain', + }, + ], + appVersion: [ + { + title: 'App', + description: 'Vega node software version on this node', + }, + ], + chainVersion: [ + { + title: 'Tendermint', + description: 'Tendermint software version on this node', + }, + ], + uptime: [ + { + title: 'Uptime', + formatter: (t: string) => { + if (!t) { + return; + } + const secSinceStart = + (new Date().getTime() - new Date(t).getTime()) / 1000; + const days = Math.floor(secSinceStart / 60 / 60 / 24); + const hours = Math.floor((secSinceStart / 60 / 60) % 24); + const mins = Math.floor((secSinceStart / 60) % 60); + const secs = Math.floor(secSinceStart % 60); + return `${days}d ${hours}h ${mins}m ${secs}s`; + }, + promoted: true, + description: 'Time since genesis', + }, + { + title: 'Up since', + formatter: (t: string) => { + if (!t) { + return; + } + return `${new Date(t).toLocaleString().replace(',', ' ')}`; + }, + description: 'Genesis', + }, + ], + chainId: [ + { + title: 'Chain ID', + description: 'Identifier', + }, + ], +}; diff --git a/libs/mainnet-stats-manager/src/config/types.ts b/libs/mainnet-stats-manager/src/config/types.ts new file mode 100644 index 000000000..2cd040554 --- /dev/null +++ b/libs/mainnet-stats-manager/src/config/types.ts @@ -0,0 +1,38 @@ +export interface Stats { + blockHeight: string; + totalNodes: string; + validatingNodes: string; + inactiveNodes: string; + stakedTotal: string; + backlogLength: string; + tradesPerSecond: string; + averageOrdersPerBlock: string; + ordersPerSecond: string; + txPerBlock: string; + blockDuration: string; + status: string; + vegaTime: string; + appVersion: string; + chainVersion: string; + uptime: string; + chainId: string; +} + +// eslint-disable-next-line +export type value = any; +export type goodThreshold = (...args: value[]) => boolean; + +export interface StatFields { + title: string; + goodThreshold?: goodThreshold; + // eslint-disable-next-line + formatter?: (arg0: value) => any; + promoted?: boolean; + value?: value; + description?: string; +} + +export interface StructuredStats { + promoted: StatFields[]; + table: StatFields[]; +} diff --git a/libs/mainnet-stats-manager/src/index.ts b/libs/mainnet-stats-manager/src/index.ts new file mode 100644 index 000000000..02aeacf30 --- /dev/null +++ b/libs/mainnet-stats-manager/src/index.ts @@ -0,0 +1 @@ +export { StatsManager } from './components/stats-manager'; diff --git a/libs/mainnet-stats-manager/tsconfig.json b/libs/mainnet-stats-manager/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/mainnet-stats-manager/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/mainnet-stats-manager/tsconfig.lib.json b/libs/mainnet-stats-manager/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/mainnet-stats-manager/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/mainnet-stats-manager/tsconfig.spec.json b/libs/mainnet-stats-manager/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/mainnet-stats-manager/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/market-list/src/lib/market-list-table.tsx b/libs/market-list/src/lib/market-list-table.tsx index 70afd331e..89295fe20 100644 --- a/libs/market-list/src/lib/market-list-table.tsx +++ b/libs/market-list/src/lib/market-list-table.tsx @@ -20,7 +20,7 @@ export const MarketListTable = ({ }: MarketListTableProps) => { const [initialMarkets] = useState(markets); const gridApi = useRef(null); - useApplyGridTransaction(markets, gridApi.current); + useApplyGridTransaction(markets, gridApi.current); return ( data.id} + suppressCellFocus={true} defaultColDef={{ flex: 1, resizable: true, diff --git a/libs/order-list/.babelrc b/libs/order-list/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/order-list/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/order-list/.eslintrc.json b/libs/order-list/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/libs/order-list/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/order-list/README.md b/libs/order-list/README.md new file mode 100644 index 000000000..0cf83d41e --- /dev/null +++ b/libs/order-list/README.md @@ -0,0 +1,7 @@ +# order-list + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test order-list` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/order-list/jest.config.js b/libs/order-list/jest.config.js new file mode 100644 index 000000000..a3d891f40 --- /dev/null +++ b/libs/order-list/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + displayName: 'order-list', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/order-list', + setupFilesAfterEnv: ['./src/setup-tests.ts'], +}; diff --git a/libs/order-list/project.json b/libs/order-list/project.json new file mode 100644 index 000000000..a85b132a6 --- /dev/null +++ b/libs/order-list/project.json @@ -0,0 +1,23 @@ +{ + "root": "libs/order-list", + "sourceRoot": "libs/order-list/src", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/order-list/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/order-list"], + "options": { + "jestConfig": "libs/order-list/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/order-list/src/index.ts b/libs/order-list/src/index.ts new file mode 100644 index 000000000..d432bcbe8 --- /dev/null +++ b/libs/order-list/src/index.ts @@ -0,0 +1 @@ +export * from './order-list'; diff --git a/libs/order-list/src/order-list.spec.tsx b/libs/order-list/src/order-list.spec.tsx new file mode 100644 index 000000000..cec0664de --- /dev/null +++ b/libs/order-list/src/order-list.spec.tsx @@ -0,0 +1,153 @@ +import { act, render, screen } from '@testing-library/react'; +import { formatNumber, getDateTimeFormat } from '@vegaprotocol/react-helpers'; +import { Orders_party_orders } from '@vegaprotocol/graphql'; +import { + OrderStatus, + OrderTimeInForce, + OrderType, + Side, + OrderRejectionReason, +} from '@vegaprotocol/graphql'; +import { OrderList } from './order-list'; + +test('No orders message shown', async () => { + await act(async () => { + render(); + }); + expect(screen.getByText('No orders')).toBeInTheDocument(); +}); + +const marketOrder: Orders_party_orders = { + __typename: 'Order', + id: 'order-id', + market: { + __typename: 'Market', + id: 'market-id', + name: 'market-name', + decimalPlaces: 2, + tradableInstrument: { + __typename: 'TradableInstrument', + instrument: { + __typename: 'Instrument', + code: 'instrument-code', + }, + }, + }, + size: '10', + type: OrderType.Market, + status: OrderStatus.Active, + side: Side.Buy, + remaining: '5', + price: '', + timeInForce: OrderTimeInForce.IOC, + createdAt: new Date().toISOString(), + updatedAt: null, + expiresAt: null, + rejectionReason: null, +}; + +const limitOrder: Orders_party_orders = { + __typename: 'Order', + id: 'order-id', + market: { + __typename: 'Market', + id: 'market-id', + name: 'market-name', + decimalPlaces: 2, + tradableInstrument: { + __typename: 'TradableInstrument', + instrument: { + __typename: 'Instrument', + code: 'instrument-code', + }, + }, + }, + size: '10', + type: OrderType.Limit, + status: OrderStatus.Active, + side: Side.Sell, + remaining: '5', + price: '12345', + timeInForce: OrderTimeInForce.GTT, + createdAt: new Date('2022-3-3').toISOString(), + expiresAt: new Date('2022-3-5').toISOString(), + updatedAt: null, + rejectionReason: null, +}; + +test('Correct columns are rendered', async () => { + await act(async () => { + render(); + }); + + const headers = screen.getAllByRole('columnheader'); + expect(headers).toHaveLength(8); + expect(headers.map((h) => h.textContent?.trim())).toEqual([ + 'Market', + 'Amount', + 'Type', + 'Status', + 'Filled', + 'Price', + 'Time In Force', + 'Created At', + ]); +}); + +test('Correct formatting applied for market order', async () => { + await act(async () => { + render(); + }); + + const cells = screen.getAllByRole('gridcell'); + const expectedValues = [ + marketOrder.market?.tradableInstrument.instrument.code, + '+10', + marketOrder.type, + marketOrder.status, + '5', + '-', + marketOrder.timeInForce, + getDateTimeFormat().format(new Date(marketOrder.createdAt)), + ]; + cells.forEach((cell, i) => { + expect(cell).toHaveTextContent(expectedValues[i]); + }); +}); + +test('Correct formatting applied for GTT limit order', async () => { + await act(async () => { + render(); + }); + const cells = screen.getAllByRole('gridcell'); + const expectedValues = [ + limitOrder.market?.tradableInstrument.instrument.code, + '-10', + limitOrder.type, + limitOrder.status, + '5', + formatNumber(limitOrder.price, limitOrder.market?.decimalPlaces ?? 0), + `${limitOrder.timeInForce}: ${getDateTimeFormat().format( + new Date(limitOrder.expiresAt ?? '') + )}`, + getDateTimeFormat().format(new Date(limitOrder.createdAt)), + ]; + cells.forEach((cell, i) => { + expect(cell).toHaveTextContent(expectedValues[i]); + }); +}); + +test('Correct formatting applied for a rejected order', async () => { + const rejectedOrder = { + ...marketOrder, + status: OrderStatus.Rejected, + rejectionReason: OrderRejectionReason.InsufficientAssetBalance, + }; + await act(async () => { + render(); + }); + const cells = screen.getAllByRole('gridcell'); + expect(cells[3]).toHaveTextContent( + `${rejectedOrder.status}: ${rejectedOrder.rejectionReason}` + ); +}); diff --git a/libs/order-list/src/order-list.tsx b/libs/order-list/src/order-list.tsx new file mode 100644 index 000000000..49f60c7f9 --- /dev/null +++ b/libs/order-list/src/order-list.tsx @@ -0,0 +1,100 @@ +import { + Orders_party_orders, + OrderTimeInForce, + OrderStatus, + Side, +} from '@vegaprotocol/graphql'; +import { + formatNumber, + getDateTimeFormat, + useApplyGridTransaction, +} from '@vegaprotocol/react-helpers'; +import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; +import { GridApi, ValueFormatterParams } from 'ag-grid-community'; +import { AgGridColumn } from 'ag-grid-react'; +import { useRef, useState } from 'react'; + +interface OrderListProps { + orders: Orders_party_orders[]; +} + +export const OrderList = ({ orders }: OrderListProps) => { + // Store initial orders for initial table data set, further updates + // are handled by the effect below + const [initialOrders] = useState(orders); + const gridApi = useRef(null); + useApplyGridTransaction(orders, gridApi.current); + + return ( + { + gridApi.current = params.api; + }} + getRowNodeId={(data) => data.id} + > + + { + const prefix = data.side === Side.Buy ? '+' : '-'; + return prefix + value; + }} + /> + + { + if (value === OrderStatus.Rejected) { + return `${value}: ${data.rejectionReason}`; + } + + return value; + }} + /> + { + return `${Number(data.size) - Number(data.remaining)}/${data.size}`; + }} + /> + { + if (data.type === 'Market') { + return '-'; + } + return formatNumber(value, data.market.decimalPlaces); + }} + /> + { + if (value === OrderTimeInForce.GTT && data.expiresAt) { + const expiry = getDateTimeFormat().format(new Date(data.expiresAt)); + return `${value}: ${expiry}`; + } + + return value; + }} + /> + { + return getDateTimeFormat().format(new Date(value)); + }} + /> + + ); +}; diff --git a/libs/order-list/src/setup-tests.ts b/libs/order-list/src/setup-tests.ts new file mode 100644 index 000000000..7b0828bfa --- /dev/null +++ b/libs/order-list/src/setup-tests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/libs/order-list/tsconfig.json b/libs/order-list/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/order-list/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/order-list/tsconfig.lib.json b/libs/order-list/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/order-list/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/order-list/tsconfig.spec.json b/libs/order-list/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/order-list/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/react-helpers/src/hooks/use-apply-grid-transaction.spec.ts b/libs/react-helpers/src/hooks/use-apply-grid-transaction.spec.ts new file mode 100644 index 000000000..5fdd048a6 --- /dev/null +++ b/libs/react-helpers/src/hooks/use-apply-grid-transaction.spec.ts @@ -0,0 +1,74 @@ +import { useApplyGridTransaction } from './use-apply-grid-transaction'; +import { renderHook } from '@testing-library/react-hooks'; +import { GridApi } from 'ag-grid-community'; + +type Items = Array<{ id: string; value: number }>; + +const item = { + id: '1', + value: 1, +}; +const item2 = { + id: '2', + value: 2, +}; +const items = [item, item2]; + +function setup(items: Items, rowNodes: Items) { + const gridApiMock = { + applyTransaction: jest.fn(), + getRowNode: (id: string) => { + const node = rowNodes.find((i) => i.id === id); + if (node) { + return { data: node }; + } + return undefined; + }, + }; + renderHook(() => useApplyGridTransaction(items, gridApiMock as any)); + return gridApiMock; +} + +test('Adds items', () => { + const gridApiMock = setup(items, []); + expect(gridApiMock.applyTransaction).toHaveBeenCalledWith({ + update: [], + add: items, + addIndex: 0, + }); +}); + +test('Doesnt update rows without changes', () => { + const rowNodes: Array<{ id: string; value: number }> = [...items]; + const gridApiMock = setup(items, rowNodes); + expect(gridApiMock.applyTransaction).toHaveBeenCalledWith({ + update: [], + add: [], + addIndex: 0, + }); +}); + +test('Update rows with changes', () => { + const rowNodes = [...items]; + const updatedItems = [ + { id: '1', value: 10 }, + { id: '2', value: 20 }, + ]; + const gridApiMock = setup(updatedItems, rowNodes); + expect(gridApiMock.applyTransaction).toHaveBeenCalledWith({ + update: updatedItems, + add: [], + addIndex: 0, + }); +}); + +test('Updates and adds at the same time', () => { + const newItem = { id: '3', value: 3 }; + const updatedItem = { id: '2', value: 20 }; + const gridApiMock = setup([newItem, updatedItem], [...items]); + expect(gridApiMock.applyTransaction).toHaveBeenCalledWith({ + update: [updatedItem], + add: [newItem], + addIndex: 0, + }); +}); diff --git a/libs/react-helpers/src/hooks/use-apply-grid-transaction.ts b/libs/react-helpers/src/hooks/use-apply-grid-transaction.ts index 4eda7be25..007f891a7 100644 --- a/libs/react-helpers/src/hooks/use-apply-grid-transaction.ts +++ b/libs/react-helpers/src/hooks/use-apply-grid-transaction.ts @@ -1,5 +1,6 @@ import { GridApi } from 'ag-grid-community'; import { useEffect } from 'react'; +import isEqual from 'lodash/isEqual'; export const useApplyGridTransaction = ( data: T[], @@ -18,7 +19,9 @@ export const useApplyGridTransaction = ( const rowNode = gridApi.getRowNode(d.id); if (rowNode) { - update.push(d); + if (!isEqual(rowNode.data, d)) { + update.push(d); + } } else { add.push(d); } diff --git a/libs/react-helpers/src/lib/format/format.ts b/libs/react-helpers/src/lib/format/format.ts index cd0478c81..10c9716f2 100644 --- a/libs/react-helpers/src/lib/format/format.ts +++ b/libs/react-helpers/src/lib/format/format.ts @@ -1,5 +1,5 @@ -import once from 'lodash.once'; -import memoize from 'lodash.memoize'; +import once from 'lodash/once'; +import memoize from 'lodash/memoize'; import { addDecimal } from '../decimals'; const getUserLocale = () => 'default'; diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js index 2602a06f8..83aec4bf8 100644 --- a/libs/tailwindcss-config/src/theme.js +++ b/libs/tailwindcss-config/src/theme.js @@ -2,7 +2,9 @@ const defaultTheme = require('tailwindcss/defaultTheme'); module.exports = { screens: { - sm: '500px', + xs: '500px', + sm: '640px', + md: '768px', lg: '960px', }, colors: { @@ -63,7 +65,9 @@ module.exports = { 24: '1.5rem', 28: '1.75rem', 32: '2rem', + 40: '2.5rem', 44: '2.75rem', + 64: '4rem', }, opacity: { 0: '0', diff --git a/libs/ui-toolkit/jest.config.js b/libs/ui-toolkit/jest.config.js index 7b46c9bf8..57204627e 100644 --- a/libs/ui-toolkit/jest.config.js +++ b/libs/ui-toolkit/jest.config.js @@ -6,4 +6,5 @@ module.exports = { }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/libs/ui-toolkit', + setupFilesAfterEnv: ['./src/setup-tests.ts'], }; diff --git a/libs/ui-toolkit/src/components/ag-grid/ag-grid-dark.tsx b/libs/ui-toolkit/src/components/ag-grid/ag-grid-dark.tsx index e04b26bc8..a0c0c9022 100644 --- a/libs/ui-toolkit/src/components/ag-grid/ag-grid-dark.tsx +++ b/libs/ui-toolkit/src/components/ag-grid/ag-grid-dark.tsx @@ -1,4 +1,35 @@ import { ReactElement } from 'react'; +import { theme } from '@vegaprotocol/tailwindcss-config'; import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css'; -export const AgGrid = (props: { children: ReactElement }) => props.children; +const agGridDarkVariables = ` + .ag-theme-balham-dark { + --ag-background-color: ${theme.colors.black[100]}; + --ag-border-color: ${theme.colors.white[25]}; + --ag-header-background-color: ${theme.colors.black[100]}; + --ag-odd-row-background-color: ${theme.colors.black[100]}; + --ag-row-border-color:${theme.colors.black[100]}; + --ag-row-hover-color: ${theme.colors.white[25]}; + --ag-font-size: 12px; + } + + .ag-theme-balham-dark .ag-root-wrapper { + border: 0; + } + + .ag-theme-balham-dark .ag-react-container { + overflow: hidden; + text-overflow: ellipsis; + } + + .ag-theme-balham-dark .ag-header-row { + font-weight: 400; + } +`; + +export const AgGrid = (props: { children: ReactElement }) => ( + <> + + {props.children} + +); diff --git a/libs/ui-toolkit/src/components/ag-grid/ag-grid-dynamic-themed.tsx b/libs/ui-toolkit/src/components/ag-grid/ag-grid-dynamic-themed.tsx index 924b5f111..a931c6527 100644 --- a/libs/ui-toolkit/src/components/ag-grid/ag-grid-dynamic-themed.tsx +++ b/libs/ui-toolkit/src/components/ag-grid/ag-grid-dynamic-themed.tsx @@ -24,6 +24,7 @@ export const AgGridThemed = ({ className?: string; }) => { const theme = React.useContext(ThemeContext); + const defaultProps = { rowHeight: 20, headerHeight: 22 }; return (
{theme === 'dark' ? ( - + ) : ( - + )}
diff --git a/libs/ui-toolkit/src/components/ag-grid/ag-grid-light.tsx b/libs/ui-toolkit/src/components/ag-grid/ag-grid-light.tsx index 7cf181a0a..90ed42eef 100644 --- a/libs/ui-toolkit/src/components/ag-grid/ag-grid-light.tsx +++ b/libs/ui-toolkit/src/components/ag-grid/ag-grid-light.tsx @@ -1,4 +1,35 @@ import { ReactElement } from 'react'; +import { theme } from '@vegaprotocol/tailwindcss-config'; import 'ag-grid-community/dist/styles/ag-theme-balham.css'; -export const AgGrid = (props: { children: ReactElement }) => props.children; +const agGridLightVariables = ` + .ag-theme-balham { + --ag-background-color: ${theme.colors.white[100]}; + --ag-border-color: ${theme.colors.black['05']}; + --ag-header-background-color: ${theme.colors.white[100]}; + --ag-odd-row-background-color: ${theme.colors.white[100]}; + --ag-row-border-color: ${theme.colors.white[100]}; + --ag-row-hover-color: ${theme.colors.vega.yellow}; + --ag-font-size: 12px; + } + + .ag-theme-balham .ag-root-wrapper { + border: 0; + } + + .ag-theme-balham .ag-react-container { + overflow: hidden; + text-overflow: ellipsis; + } + + .ag-theme-balham-dark .ag-header-row { + font-weight: 400; + } +`; + +export const AgGrid = (props: { children: ReactElement }) => ( + <> + + {props.children} + +); diff --git a/libs/ui-toolkit/src/components/callout/callout.stories.tsx b/libs/ui-toolkit/src/components/callout/callout.stories.tsx index 249c84ea1..ad910f35e 100644 --- a/libs/ui-toolkit/src/components/callout/callout.stories.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.stories.tsx @@ -8,6 +8,11 @@ import { Intent } from '../../utils/intent'; export default { title: 'Callout', component: Callout, + argTypes: { + title: { + type: 'string', + }, + }, } as ComponentMeta; const Template: ComponentStory = (args) => ( diff --git a/libs/ui-toolkit/src/components/callout/callout.test.tsx b/libs/ui-toolkit/src/components/callout/callout.test.tsx index 303f9f0bd..8ef09887d 100644 --- a/libs/ui-toolkit/src/components/callout/callout.test.tsx +++ b/libs/ui-toolkit/src/components/callout/callout.test.tsx @@ -1,5 +1,4 @@ import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; import { Callout } from '.'; import { Intent } from '../../utils/intent'; diff --git a/libs/ui-toolkit/src/components/etherscan-link/etherscan-link.test.tsx b/libs/ui-toolkit/src/components/etherscan-link/etherscan-link.test.tsx index 5bef244c7..156436135 100644 --- a/libs/ui-toolkit/src/components/etherscan-link/etherscan-link.test.tsx +++ b/libs/ui-toolkit/src/components/etherscan-link/etherscan-link.test.tsx @@ -1,5 +1,4 @@ import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; import { EtherscanLink } from '.'; import { EthereumChainIds } from '../../utils/web3'; diff --git a/libs/ui-toolkit/src/components/form-group/form-group.spec.tsx b/libs/ui-toolkit/src/components/form-group/form-group.spec.tsx new file mode 100644 index 000000000..852ca0f15 --- /dev/null +++ b/libs/ui-toolkit/src/components/form-group/form-group.spec.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/react'; + +import { FormGroup } from './form-group'; + +describe('FormGroup', () => { + it('should render label if given a label', () => { + render( + + + + ); + expect(screen.getByLabelText('label')).toBeInTheDocument(); + }); + + it('should add classes passed in', () => { + render( + + + + ); + expect(screen.getByTestId('form-group')).toHaveClass('fighter'); + }); + + it('should render children', () => { + render( + + + + ); + expect(screen.getByTestId('foo')).toBeInTheDocument(); + }); +}); diff --git a/libs/ui-toolkit/src/components/form-group/form-group.tsx b/libs/ui-toolkit/src/components/form-group/form-group.tsx new file mode 100644 index 000000000..bd5724943 --- /dev/null +++ b/libs/ui-toolkit/src/components/form-group/form-group.tsx @@ -0,0 +1,32 @@ +import classNames from 'classnames'; +import { ReactNode } from 'react'; + +interface FormGroupProps { + children: ReactNode; + label?: string; + labelFor?: string; + labelAlign?: 'left' | 'right'; + className?: string; +} + +export const FormGroup = ({ + children, + label, + labelFor, + labelAlign = 'left', + className, +}: FormGroupProps) => { + const labelClasses = classNames('block text-ui mb-4', { + 'text-right': labelAlign === 'right', + }); + return ( +
+ {label && ( + + )} + {children} +
+ ); +}; diff --git a/libs/ui-toolkit/src/components/form-group/from-group.stories.tsx b/libs/ui-toolkit/src/components/form-group/from-group.stories.tsx new file mode 100644 index 000000000..da5b18ad5 --- /dev/null +++ b/libs/ui-toolkit/src/components/form-group/from-group.stories.tsx @@ -0,0 +1,30 @@ +import { Story, Meta } from '@storybook/react'; +import { Input } from '../input'; +import { FormGroup } from './form-group'; +export default { + component: FormGroup, + title: 'FormGroup', + argTypes: { + label: { + type: 'string', + }, + labelFor: { + type: 'string', + }, + className: { + type: 'string', + }, + }, +} as Meta; + +const Template: Story = (args) => ( + + + +); + +export const Default = Template.bind({}); +Default.args = { + label: 'label', + labelFor: 'labelFor', +}; diff --git a/libs/ui-toolkit/src/components/form-group/index.tsx b/libs/ui-toolkit/src/components/form-group/index.tsx index 2a7d13074..faeeafc70 100644 --- a/libs/ui-toolkit/src/components/form-group/index.tsx +++ b/libs/ui-toolkit/src/components/form-group/index.tsx @@ -1,30 +1 @@ -import classNames from 'classnames'; -import { ReactNode } from 'react'; - -interface FormGroupProps { - children: ReactNode; - label?: string; - labelFor?: string; - labelAlign?: 'left' | 'right'; -} - -export const FormGroup = ({ - children, - label, - labelFor, - labelAlign = 'left', -}: FormGroupProps) => { - const labelClasses = classNames('block text-ui mb-4', { - 'text-right': labelAlign === 'right', - }); - return ( -
- {label && ( - - )} - {children} -
- ); -}; +export * from './form-group'; diff --git a/libs/ui-toolkit/src/components/input-error/input-error.tsx b/libs/ui-toolkit/src/components/input-error/input-error.tsx index 1fceb6ca2..69e835b09 100644 --- a/libs/ui-toolkit/src/components/input-error/input-error.tsx +++ b/libs/ui-toolkit/src/components/input-error/input-error.tsx @@ -1,7 +1,8 @@ import classNames from 'classnames'; +import { HTMLAttributes } from 'react'; import { Icon } from '../icon'; -interface InputErrorProps { +interface InputErrorProps extends HTMLAttributes { children?: React.ReactNode; className?: string; intent?: 'danger' | 'warning'; @@ -11,6 +12,7 @@ export const InputError = ({ intent = 'danger', className, children, + ...props }: InputErrorProps) => { const effectiveClassName = classNames( [ @@ -33,7 +35,7 @@ export const InputError = ({ 'fill-intent-warning': intent === 'warning', }); return ( -
+
{children}
diff --git a/libs/ui-toolkit/src/components/vega-logo/index.ts b/libs/ui-toolkit/src/components/vega-logo/index.ts new file mode 100644 index 000000000..f34ea89ea --- /dev/null +++ b/libs/ui-toolkit/src/components/vega-logo/index.ts @@ -0,0 +1 @@ +export * from './vega-logo'; diff --git a/libs/ui-toolkit/src/components/vega-logo/lozenge.stories.tsx b/libs/ui-toolkit/src/components/vega-logo/lozenge.stories.tsx new file mode 100644 index 000000000..4e03ab8ca --- /dev/null +++ b/libs/ui-toolkit/src/components/vega-logo/lozenge.stories.tsx @@ -0,0 +1,11 @@ +import { Story, Meta } from '@storybook/react'; +import { VegaLogo } from './vega-logo'; + +export default { + component: VegaLogo, + title: 'Vega logo', +} as Meta; + +const Template: Story = () => ; + +export const Default = Template.bind({}); diff --git a/libs/ui-toolkit/src/components/vega-logo/vega-logo.spec.tsx b/libs/ui-toolkit/src/components/vega-logo/vega-logo.spec.tsx new file mode 100644 index 000000000..d13fcf2d3 --- /dev/null +++ b/libs/ui-toolkit/src/components/vega-logo/vega-logo.spec.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; + +import { VegaLogo } from './vega-logo'; + +describe('Vega logo', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/libs/ui-toolkit/src/components/vega-logo/vega-logo.tsx b/libs/ui-toolkit/src/components/vega-logo/vega-logo.tsx new file mode 100644 index 000000000..4545b46b8 --- /dev/null +++ b/libs/ui-toolkit/src/components/vega-logo/vega-logo.tsx @@ -0,0 +1,16 @@ +export const VegaLogo = () => { + return ( + + + + ); +}; diff --git a/libs/ui-toolkit/src/index.ts b/libs/ui-toolkit/src/index.ts index 638072c6b..27bb0a3a1 100644 --- a/libs/ui-toolkit/src/index.ts +++ b/libs/ui-toolkit/src/index.ts @@ -17,6 +17,7 @@ export { Splash } from './components/splash'; export { TextArea } from './components/text-area'; export { ThemeSwitcher } from './components/theme-switcher'; export { Dialog } from './components/dialog/dialog'; +export { VegaLogo } from './components/vega-logo'; // Utils export * from './utils/intent'; diff --git a/libs/ui-toolkit/src/setup-tests.ts b/libs/ui-toolkit/src/setup-tests.ts new file mode 100644 index 000000000..8f2609b7b --- /dev/null +++ b/libs/ui-toolkit/src/setup-tests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/libs/wallet/src/connect-dialog.tsx b/libs/wallet/src/connect-dialog.tsx index 7b4e8656b..f75a39057 100644 --- a/libs/wallet/src/connect-dialog.tsx +++ b/libs/wallet/src/connect-dialog.tsx @@ -67,11 +67,11 @@ export function VegaConnectDialog({ -

{connector.description}

+

{connector.description}

))} diff --git a/libs/wallet/src/provider.test.tsx b/libs/wallet/src/provider.test.tsx index 4b79bcadc..227ead99b 100644 --- a/libs/wallet/src/provider.test.tsx +++ b/libs/wallet/src/provider.test.tsx @@ -4,6 +4,7 @@ import { VegaKey } from '@vegaprotocol/vegawallet-service-api-client'; import { RestConnector } from './connectors'; import { useVegaWallet } from './hooks'; import { VegaWalletProvider } from './provider'; +import { WALLET_KEY } from './storage-keys'; const restConnector = new RestConnector(); @@ -93,4 +94,5 @@ test('Can connect, disconnect and retrieve keypairs', async () => { }); expect(screen.getByTestId('current-keypair')).toBeEmptyDOMElement(); expect(screen.queryByTestId('keypairs-list')).not.toBeInTheDocument(); + expect(localStorage.getItem(WALLET_KEY)).toBe(null); }); diff --git a/libs/wallet/src/provider.tsx b/libs/wallet/src/provider.tsx index 7eb54844a..9b8305e62 100644 --- a/libs/wallet/src/provider.tsx +++ b/libs/wallet/src/provider.tsx @@ -67,6 +67,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => { await connector.current?.disconnect(); setKeypairs(null); connector.current = null; + LocalStorage.removeItem(WALLET_KEY); return true; } catch (err) { console.error(err); diff --git a/libs/web3/.babelrc b/libs/web3/.babelrc new file mode 100644 index 000000000..ccae900be --- /dev/null +++ b/libs/web3/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/web3/.eslintrc.json b/libs/web3/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/libs/web3/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/web3/README.md b/libs/web3/README.md new file mode 100644 index 000000000..4ddd1785f --- /dev/null +++ b/libs/web3/README.md @@ -0,0 +1,7 @@ +# web3 + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test web3` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/web3/jest.config.js b/libs/web3/jest.config.js new file mode 100644 index 000000000..bd0f5f9e5 --- /dev/null +++ b/libs/web3/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + displayName: 'web3', + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'babel-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/web3', + setupFilesAfterEnv: ['./src/setup-tests.ts'], +}; diff --git a/libs/web3/project.json b/libs/web3/project.json new file mode 100644 index 000000000..76a6d2287 --- /dev/null +++ b/libs/web3/project.json @@ -0,0 +1,23 @@ +{ + "root": "libs/web3", + "sourceRoot": "libs/web3/src", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/web3/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/libs/web3"], + "options": { + "jestConfig": "libs/web3/jest.config.js", + "passWithNoTests": true + } + } + } +} diff --git a/libs/web3/src/index.ts b/libs/web3/src/index.ts new file mode 100644 index 000000000..76dfd08fb --- /dev/null +++ b/libs/web3/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/web3-provider'; +export * from './lib/web3-connect-dialog'; diff --git a/libs/web3/src/lib/types.ts b/libs/web3/src/lib/types.ts new file mode 100644 index 000000000..f0a54f765 --- /dev/null +++ b/libs/web3/src/lib/types.ts @@ -0,0 +1,8 @@ +import { Web3ReactHooks } from '@web3-react/core'; +import { MetaMask } from '@web3-react/metamask'; +import { WalletConnect } from '@web3-react/walletconnect'; + +export type Connectors = { + [name: string]: [Connector, Web3ReactHooks, object]; +}; +type Connector = MetaMask | WalletConnect; diff --git a/libs/web3/src/lib/web3-connect-dialog.spec.tsx b/libs/web3/src/lib/web3-connect-dialog.spec.tsx new file mode 100644 index 000000000..9f73e7406 --- /dev/null +++ b/libs/web3/src/lib/web3-connect-dialog.spec.tsx @@ -0,0 +1,48 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { Web3ConnectDialog } from './web3-connect-dialog'; +import { initializeConnector } from '@web3-react/core'; +import { MetaMask } from '@web3-react/metamask'; + +const connectors = { + foo: initializeConnector((actions) => new MetaMask(actions)), + bar: initializeConnector((actions) => new MetaMask(actions)), +}; + +const props = { + dialogOpen: false, + setDialogOpen: jest.fn(), + connectors, + desiredChainId: 3, +}; + +test('Dialog can be open or closed', () => { + const { container, rerender } = render(); + expect(container).toBeEmptyDOMElement(); + rerender(); + expect(screen.getByTestId('web3-connector-list')).toBeInTheDocument(); + expect( + screen.getByText('Connect to your Ethereum wallet') + ).toBeInTheDocument(); +}); + +test('Renders connection options', async () => { + const spyOnConnect = jest + .spyOn(connectors.foo[0], 'activate') + .mockReturnValue(Promise.resolve()); + + render(); + const connectorList = screen.getByTestId('web3-connector-list'); + expect(connectorList).toBeInTheDocument(); + expect(connectorList.children).toHaveLength(Object.keys(connectors).length); + + // foo/bar connector options displayed + expect(screen.getByTestId('web3-connector-foo')).toBeInTheDocument(); + expect(screen.getByTestId('web3-connector-bar')).toBeInTheDocument(); + + // Assert connection is attempted with desired chain + fireEvent.click(screen.getByTestId('web3-connector-foo')); + expect(spyOnConnect).toHaveBeenCalledWith(props.desiredChainId); + await waitFor(() => { + expect(props.setDialogOpen).toHaveBeenCalledWith(false); + }); +}); diff --git a/libs/web3/src/lib/web3-connect-dialog.tsx b/libs/web3/src/lib/web3-connect-dialog.tsx new file mode 100644 index 000000000..de11df2d1 --- /dev/null +++ b/libs/web3/src/lib/web3-connect-dialog.tsx @@ -0,0 +1,44 @@ +import { Dialog, Intent } from '@vegaprotocol/ui-toolkit'; +import { Connectors } from './types'; + +interface Web3ConnectDialogProps { + dialogOpen: boolean; + setDialogOpen: (isOpen: boolean) => void; + connectors: Connectors; + desiredChainId?: number; +} + +export const Web3ConnectDialog = ({ + dialogOpen, + setDialogOpen, + connectors, + desiredChainId, +}: Web3ConnectDialogProps) => { + return ( + +
    + {Object.entries(connectors).map(([connectorName, [connector]]) => { + return ( +
  • + +
  • + ); + })} +
+
+ ); +}; diff --git a/libs/web3/src/lib/web3-provider.spec.tsx b/libs/web3/src/lib/web3-provider.spec.tsx new file mode 100644 index 000000000..29ca7da2e --- /dev/null +++ b/libs/web3/src/lib/web3-provider.spec.tsx @@ -0,0 +1,20 @@ +import { act, render, screen } from '@testing-library/react'; +import { initializeConnector } from '@web3-react/core'; +import { MetaMask } from '@web3-react/metamask'; +import { Web3Provider } from './web3-provider'; + +const connectors = { + foo: initializeConnector((actions) => new MetaMask(actions)), +}; + +test('Renders children', async () => { + await act(async () => { + render( + +
Child
+
+ ); + }); + + expect(screen.getByText('Child')).toBeInTheDocument(); +}); diff --git a/libs/web3/src/lib/web3-provider.tsx b/libs/web3/src/lib/web3-provider.tsx new file mode 100644 index 000000000..e7c429f93 --- /dev/null +++ b/libs/web3/src/lib/web3-provider.tsx @@ -0,0 +1,19 @@ +import { Web3ReactProvider } from '@web3-react/core'; +import { Connectors } from './types'; + +interface Web3ProviderProps { + children: JSX.Element | JSX.Element[]; + connectors: Connectors; +} + +export const Web3Provider = ({ children, connectors }: Web3ProviderProps) => { + return ( + { + return [connector, hooks]; + })} + > + {children} + + ); +}; diff --git a/libs/web3/src/setup-tests.ts b/libs/web3/src/setup-tests.ts new file mode 100644 index 000000000..7b0828bfa --- /dev/null +++ b/libs/web3/src/setup-tests.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/libs/web3/tsconfig.json b/libs/web3/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/web3/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/web3/tsconfig.lib.json b/libs/web3/tsconfig.lib.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/libs/web3/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/libs/web3/tsconfig.spec.json b/libs/web3/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/libs/web3/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/package.json b/package.json index 818aab10e..1b268ddf0 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,16 @@ "@nrwl/next": "13.8.1", "@radix-ui/react-dialog": "^0.1.5", "@radix-ui/react-tabs": "^0.1.5", + "@radix-ui/react-tooltip": "^0.1.7", "@sentry/react": "^6.18.1", "@sentry/tracing": "^6.18.1", + "@types/lodash": "^4.14.180", "@types/uuid": "^8.3.4", "@vegaprotocol/vegawallet-service-api-client": "^0.4.6", + "@walletconnect/ethereum-provider": "^1.7.5", + "@web3-react/core": "8.0.20-beta.0", + "@web3-react/metamask": "8.0.16-beta.0", + "@web3-react/walletconnect": "^8.0.23-beta.0", "ag-grid-community": "^27.0.1", "ag-grid-react": "^27.0.1", "apollo": "^2.33.9", @@ -30,20 +36,18 @@ "bignumber.js": "^9.0.2", "classnames": "^2.3.1", "core-js": "^3.6.5", + "date-fns": "^2.28.0", "env-cmd": "^10.1.0", "ethers": "^5.6.0", "graphql": "^15.7.2", "graphql-ws": "^5.6.3", - "lodash.debounce": "^4.0.8", - "lodash.memoize": "^4.1.2", - "lodash.once": "^4.1.1", + "lodash": "^4.17.21", "next": "12.0.7", "nx": "^13.8.3", "postcss": "^8.4.6", "react": "17.0.2", "react-dom": "17.0.2", "react-hook-form": "^7.27.0", - "react-singleton-hook": "^3.2.3", "react-syntax-highlighter": "^15.4.5", "react-use-websocket": "^3.0.0", "react-virtualized-auto-sizer": "^1.0.6", @@ -51,7 +55,8 @@ "sha3": "^2.1.4", "tailwindcss": "^3.0.23", "tslib": "^2.0.0", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "web-vitals": "^2.1.4" }, "devDependencies": { "@babel/core": "7.12.13", @@ -78,8 +83,6 @@ "@testing-library/react-hooks": "7.0.2", "@types/classnames": "^2.3.1", "@types/jest": "27.0.2", - "@types/lodash.memoize": "^4.1.6", - "@types/lodash.once": "^4.1.6", "@types/node": "16.11.7", "@types/prismjs": "^1.26.0", "@types/react": "17.0.30", diff --git a/tsconfig.base.json b/tsconfig.base.json index 40946e97c..55dd28414 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,13 +17,18 @@ "paths": { "@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"], "@vegaprotocol/graphql": ["libs/graphql/src/index.ts"], + "@vegaprotocol/mainnet-stats-manager": [ + "libs/mainnet-stats-manager/src/index.ts" + ], "@vegaprotocol/market-list": ["libs/market-list/src/index.ts"], + "@vegaprotocol/order-list": ["libs/order-list/src/index.ts"], "@vegaprotocol/react-helpers": ["libs/react-helpers/src/index.ts"], "@vegaprotocol/tailwindcss-config": [ "libs/tailwindcss-config/src/index.js" ], "@vegaprotocol/ui-toolkit": ["libs/ui-toolkit/src/index.ts"], - "@vegaprotocol/wallet": ["libs/wallet/src/index.ts"] + "@vegaprotocol/wallet": ["libs/wallet/src/index.ts"], + "@vegaprotocol/web3": ["libs/web3/src/index.ts"] } }, "exclude": ["node_modules", "tmp"] diff --git a/workspace.json b/workspace.json index c9dcf01bb..9ae846565 100644 --- a/workspace.json +++ b/workspace.json @@ -5,12 +5,17 @@ "explorer": "apps/explorer", "explorer-e2e": "apps/explorer-e2e", "graphql": "libs/graphql", + "mainnet-stats-manager": "libs/mainnet-stats-manager", "market-list": "libs/market-list", + "order-list": "libs/order-list", "react-helpers": "libs/react-helpers", + "stats-mainnet": "apps/stats-mainnet", + "stats-mainnet-e2e": "apps/stats-mainnet-e2e", "tailwindcss-config": "libs/tailwindcss-config", "trading": "apps/trading", "trading-e2e": "apps/trading-e2e", "ui-toolkit": "libs/ui-toolkit", - "wallet": "libs/wallet" + "wallet": "libs/wallet", + "web3": "libs/web3" } } diff --git a/yarn.lock b/yarn.lock index b18e6114d..1821b6c12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1718,6 +1718,31 @@ bech32 "1.1.4" ws "7.4.6" +"@ethersproject/providers@^5.6.0": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.6.1.tgz#9a05f00ecbac59565bf6907c8d2af8ac33303b48" + integrity sha512-w8Wx15nH+aVDvnoKCyI1f3x0B5idmk/bDJXMEUqCfdO8Eadd0QpDx9lDMTMmenhOmf9vufLJXjpSm24D3ZnVpg== + dependencies: + "@ethersproject/abstract-provider" "^5.6.0" + "@ethersproject/abstract-signer" "^5.6.0" + "@ethersproject/address" "^5.6.0" + "@ethersproject/basex" "^5.6.0" + "@ethersproject/bignumber" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/constants" "^5.6.0" + "@ethersproject/hash" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/networks" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/random" "^5.6.0" + "@ethersproject/rlp" "^5.6.0" + "@ethersproject/sha2" "^5.6.0" + "@ethersproject/strings" "^5.6.0" + "@ethersproject/transactions" "^5.6.0" + "@ethersproject/web" "^5.6.0" + bech32 "1.1.4" + ws "7.4.6" + "@ethersproject/random@5.6.0", "@ethersproject/random@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.6.0.tgz#1505d1ab6a250e0ee92f436850fa3314b2cb5ae6" @@ -2162,6 +2187,31 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@json-rpc-tools/provider@^1.5.5": + version "1.7.6" + resolved "https://registry.yarnpkg.com/@json-rpc-tools/provider/-/provider-1.7.6.tgz#8a17c34c493fa892632e278fd9331104e8491ec6" + integrity sha512-z7D3xvJ33UfCGv77n40lbzOYjZKVM3k2+5cV7xS8G6SCvKTzMkhkUYuD/qzQUNT4cG/lv0e9mRToweEEVLVVmA== + dependencies: + "@json-rpc-tools/utils" "^1.7.6" + axios "^0.21.0" + safe-json-utils "^1.1.1" + ws "^7.4.0" + +"@json-rpc-tools/types@^1.7.6": + version "1.7.6" + resolved "https://registry.yarnpkg.com/@json-rpc-tools/types/-/types-1.7.6.tgz#5abd5fde01364a130c46093b501715bcce5bdc0e" + integrity sha512-nDSqmyRNEqEK9TZHtM15uNnDljczhCUdBmRhpNZ95bIPKEDQ+nTDmGMFd2lLin3upc5h2VVVd9tkTDdbXUhDIQ== + dependencies: + keyvaluestorage-interface "^1.0.0" + +"@json-rpc-tools/utils@^1.7.6": + version "1.7.6" + resolved "https://registry.yarnpkg.com/@json-rpc-tools/utils/-/utils-1.7.6.tgz#67f04987dbaa2e7adb6adff1575367b75a9a9ba1" + integrity sha512-HjA8x/U/Q78HRRe19yh8HVKoZ+Iaoo3YZjakJYxR+rw52NHo6jM+VE9b8+7ygkCFXl/EHID5wh/MkXaE/jGyYw== + dependencies: + "@json-rpc-tools/types" "^1.7.6" + "@pedrouid/environment" "^1.0.1" + "@mdx-js/loader@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.6.22.tgz#d9e8fe7f8185ff13c9c8639c048b123e30d322c4" @@ -2206,6 +2256,11 @@ resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== +"@metamask/detect-provider@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@metamask/detect-provider/-/detect-provider-1.2.0.tgz#3667a7531f2a682e3c3a43eaf3a1958bdb42a696" + integrity sha512-ocA76vt+8D0thgXZ7LxFPyqw3H7988qblgzddTDA6B8a/yU0uKV42QR/DhA+Jh11rJjxW0jKvwb5htA6krNZDQ== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -3172,6 +3227,11 @@ node-addon-api "^3.2.1" node-gyp-build "^4.3.0" +"@pedrouid/environment@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@pedrouid/environment/-/environment-1.0.1.tgz#858f0f8a057340e0b250398b75ead77d6f4342ec" + integrity sha512-HaW78NszGzRZd9SeoI3JD11JqY+lubnaOx7Pewj5pfjqWXOEATpeKIFb9Z4t2WBUK2iryiXX3lzWwmYWgUL0Ug== + "@phenomnomnominal/tsquery@4.1.1": version "4.1.1" resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-4.1.1.tgz#42971b83590e9d853d024ddb04a18085a36518df" @@ -3199,6 +3259,14 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9" integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA== +"@radix-ui/popper@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063" + integrity sha512-uzYeElL3w7SeNMuQpXiFlBhTT+JyaNMCwDfjKkrzugEcYrf5n52PHqncNdQPUtR42hJh8V9FsqyEDbDxkeNjJQ== + dependencies: + "@babel/runtime" "^7.13.10" + csstype "^3.0.4" + "@radix-ui/primitive@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.1.0.tgz#6206b97d379994f0d1929809db035733b337e543" @@ -3206,6 +3274,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-arrow@0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.1.4.tgz#a871448a418cd3507d83840fdd47558cb961672b" + integrity sha512-BB6XzAb7Ml7+wwpFdYVtZpK1BlMgqyafSQNGzhIpSZ4uXvXOHPlR5GP8M449JkeQzgQjv9Mp1AsJxFC0KuOtuA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/react-collection@0.1.4": version "0.1.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.4.tgz#734061ffd5bb93e88889d49b87391a73a63824c9" @@ -3290,6 +3366,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "0.1.0" +"@radix-ui/react-popper@0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.1.4.tgz#dfc055dcd7dfae6a2eff7a70d333141d15a5d029" + integrity sha512-18gDYof97t8UQa7zwklG1Dr8jIdj3u+rVOQLzPi9f5i1YQak/pVGkaqw8aY+iDUknKKuZniTk/7jbAJUYlKyOw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/popper" "0.1.0" + "@radix-ui/react-arrow" "0.1.4" + "@radix-ui/react-compose-refs" "0.1.0" + "@radix-ui/react-context" "0.1.1" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/react-use-rect" "0.1.1" + "@radix-ui/react-use-size" "0.1.1" + "@radix-ui/rect" "0.1.1" + "@radix-ui/react-portal@0.1.4": version "0.1.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2" @@ -3352,6 +3443,27 @@ "@radix-ui/react-roving-focus" "0.1.5" "@radix-ui/react-use-controllable-state" "0.1.0" +"@radix-ui/react-tooltip@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.7.tgz#6f8c00d6e489565d14abf209ce0fb8853c8c8ee3" + integrity sha512-eiBUsVOHenZ0JR16tl970bB0DafJBz6mFgSGfIGIVpflFj0LIsIDiLMsYyvYdx1KwwsIUDTEZtxcPm/sWjPzqA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "0.1.0" + "@radix-ui/react-compose-refs" "0.1.0" + "@radix-ui/react-context" "0.1.1" + "@radix-ui/react-id" "0.1.5" + "@radix-ui/react-popper" "0.1.4" + "@radix-ui/react-portal" "0.1.4" + "@radix-ui/react-presence" "0.1.2" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/react-slot" "0.1.2" + "@radix-ui/react-use-controllable-state" "0.1.0" + "@radix-ui/react-use-escape-keydown" "0.1.0" + "@radix-ui/react-use-previous" "0.1.1" + "@radix-ui/react-use-rect" "0.1.1" + "@radix-ui/react-visually-hidden" "0.1.4" + "@radix-ui/react-use-body-pointer-events@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.1.tgz#63e7fd81ca7ffd30841deb584cd2b7f460df2597" @@ -3390,6 +3502,43 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-previous@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0" + integrity sha512-O/ZgrDBr11dR8rhO59ED8s5zIXBRFi8MiS+CmFGfi7MJYdLbfqVOmQU90Ghf87aifEgWe6380LA69KBneaShAg== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-rect@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz#6c15384beee59c086e75b89a7e66f3d2e583a856" + integrity sha512-kHNNXAsP3/PeszEmM/nxBBS9Jbo93sO+xuMTcRfwzXsmxT5gDXQzAiKbZQ0EecCPtJIzqvr7dlaQi/aP1PKYqQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/rect" "0.1.1" + +"@radix-ui/react-use-size@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f" + integrity sha512-pTgWM5qKBu6C7kfKxrKPoBI2zZYZmp2cSXzpUiGM3qEBQlMLtYhaY2JXdXUCxz+XmD1YEjc8oRwvyfsD4AG4WA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-visually-hidden@0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz#6c75eae34fb5d084b503506fbfc05587ced05f03" + integrity sha512-K/q6AEEzqeeEq/T0NPChvBqnwlp8Tl4NnQdrI/y8IOY7BRR+Ug0PEsVk6g48HJ7cA1//COugdxXXVVK/m0X1mA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "0.1.4" + +"@radix-ui/rect@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.1.1.tgz#95b5ba51f469bea6b1b841e2d427e17e37d38419" + integrity sha512-g3hnE/UcOg7REdewduRPAK88EPuLZtaq7sA9ouu8S+YEtnyFRI16jgv6GZYe3VMoQLL1T171ebmEPtDjyxWLzw== + dependencies: + "@babel/runtime" "^7.13.10" + "@rollup/plugin-babel@^5.3.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -5112,21 +5261,7 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/lodash.memoize@^4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.6.tgz#3221f981790a415cab1a239f25c17efd8b604c23" - integrity sha512-mYxjKiKzRadRJVClLKxS4wb3Iy9kzwJ1CkbyKiadVxejnswnRByyofmPMscFKscmYpl36BEEhCMPuWhA1R/1ZQ== - dependencies: - "@types/lodash" "*" - -"@types/lodash.once@^4.1.6": - version "4.1.6" - resolved "https://registry.yarnpkg.com/@types/lodash.once/-/lodash.once-4.1.6.tgz#f6ea46e3426bc3494b72a45e65efed440103b967" - integrity sha512-kawTe2cBNZ5OI4CvTJT9cs8wacGZK4BoakKAGASl/jH3LxflMTuy82wN2U5klYsxYjrABkNWmzgO33volt7urQ== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": +"@types/lodash@^4.14.180": version "4.14.180" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670" integrity sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g== @@ -5573,6 +5708,240 @@ url-parse "^1.4.3" whatwg-fetch "^3.0.0" +"@walletconnect/browser-utils@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/browser-utils/-/browser-utils-1.7.5.tgz#a12ff382310bfbb02509a69565dacf14aa744461" + integrity sha512-gm9ufi0n5cGBXoGWDtMVSqIJ0eXYW+ZFuTNVN0fm4oal26J7cPrOdFjzhv5zvx5fKztWQ21DNFZ+PRXBjXg04Q== + dependencies: + "@walletconnect/safe-json" "1.0.0" + "@walletconnect/types" "^1.7.5" + "@walletconnect/window-getters" "1.0.0" + "@walletconnect/window-metadata" "1.0.0" + detect-browser "5.2.0" + +"@walletconnect/client@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.7.5.tgz#7c3a1fc5a9f41022892c3c2b85be94afec49268e" + integrity sha512-Vh3h1kfhmJ4Jx//H0lmmfDc5Q2s+R73Nh5cetVN41QPRrAcqHE4lR2ZS8XxRCNBl4/gcHZJIZS9J2Ui4tTXBLA== + dependencies: + "@walletconnect/core" "^1.7.5" + "@walletconnect/iso-crypto" "^1.7.5" + "@walletconnect/types" "^1.7.5" + "@walletconnect/utils" "^1.7.5" + +"@walletconnect/core@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.7.5.tgz#623d19d4578b6195bb0f6e6313316d32fa4b2f10" + integrity sha512-c4B8s9fZ/Ah2p460Hxo4e9pwLQVYT2+dVYAfqaxVzfYjhAokDEtO55Bdm1hujtRjQVqwTvCljKxBB+LgMp3k8w== + dependencies: + "@walletconnect/socket-transport" "^1.7.5" + "@walletconnect/types" "^1.7.5" + "@walletconnect/utils" "^1.7.5" + +"@walletconnect/crypto@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/crypto/-/crypto-1.0.2.tgz#3fcc2b2cde6f529a19eadd883dc555cd0e861992" + integrity sha512-+OlNtwieUqVcOpFTvLBvH+9J9pntEqH5evpINHfVxff1XIgwV55PpbdvkHu6r9Ib4WQDOFiD8OeeXs1vHw7xKQ== + dependencies: + "@walletconnect/encoding" "^1.0.1" + "@walletconnect/environment" "^1.0.0" + "@walletconnect/randombytes" "^1.0.2" + aes-js "^3.1.2" + hash.js "^1.1.7" + +"@walletconnect/encoding@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/encoding/-/encoding-1.0.1.tgz#93c18ce9478c3d5283dbb88c41eb2864b575269a" + integrity sha512-8opL2rs6N6E3tJfsqwS82aZQDL3gmupWUgmvuZ3CGU7z/InZs3R9jkzH8wmYtpbq0sFK3WkJkQRZFFk4BkrmFA== + dependencies: + is-typedarray "1.0.0" + typedarray-to-buffer "3.1.5" + +"@walletconnect/environment@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@walletconnect/environment/-/environment-1.0.0.tgz#c4545869fa9c389ec88c364e1a5f8178e8ab5034" + integrity sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ== + +"@walletconnect/ethereum-provider@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-1.7.5.tgz#2cc6e8759b9a4cf1ea400e3c5d779faf7846b92a" + integrity sha512-hEY7YhQSCcUccwuVgQvpL/FZB6ov07ad+FZ0NSsr8Xv54ysmgoaE8tdReVa8zrGK2LCuB6mtfSGx2E0bZ2H4Ng== + dependencies: + "@walletconnect/client" "^1.7.5" + "@walletconnect/jsonrpc-http-connection" "^1.0.0" + "@walletconnect/jsonrpc-provider" "^1.0.2" + "@walletconnect/signer-connection" "^1.7.5" + "@walletconnect/types" "^1.7.5" + "@walletconnect/utils" "^1.7.5" + eip1193-provider "1.0.1" + eventemitter3 "4.0.7" + +"@walletconnect/iso-crypto@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.7.5.tgz#12d624605c656c8eed31a9d073d85b73cd0be291" + integrity sha512-mJdRs2SqAPOLBBqLhU+ZnAh2c8TL2uDuL/ojV4aBzZ0ZHNT7X2zSOjAiixCb3vvH8GAt30OKmiRo3+ChI/9zvA== + dependencies: + "@walletconnect/crypto" "^1.0.2" + "@walletconnect/types" "^1.7.5" + "@walletconnect/utils" "^1.7.5" + +"@walletconnect/jsonrpc-http-connection@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.0.tgz#5bbdfbaf6d6519b3c08e492a6badb7460ab5ecd0" + integrity sha512-fmBTox7Zo9Tb8wzKpnOgYl5cYPu+2xXifNMDYMRGkWDAygXBzRzmfdhk7OowCkSXeh8aDhE5eFtMk+u8MOmntg== + dependencies: + "@walletconnect/jsonrpc-utils" "^1.0.0" + "@walletconnect/safe-json" "^1.0.0" + cross-fetch "^3.1.4" + +"@walletconnect/jsonrpc-provider@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.2.tgz#283d7fc064ce81bf6d57678e1cf299cbd0b5505c" + integrity sha512-7sIjzg27I7noPRULYTV2QEWWNV3+d3f5T7ym8VTtCRoA1Xf+SoN9cZJotO0GCCk0jVcvN2BX3DCSq6WbcCi4Eg== + dependencies: + "@walletconnect/jsonrpc-utils" "^1.0.0" + "@walletconnect/safe-json" "^1.0.0" + +"@walletconnect/jsonrpc-types@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.0.tgz#fa75ad5e8f106a2e33287b1e6833e22ed0225055" + integrity sha512-11QXNq5H1PKZk7bP8SxgmCw3HRaDuPOVE+wObqEvmhc7OWYUZqfuaaMb+OXGRSOHL3sbC+XHfdeCxFTMXSFyng== + dependencies: + keyvaluestorage-interface "^1.0.0" + +"@walletconnect/jsonrpc-utils@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.0.tgz#1a2f668d606e8f0b6e7d8fdebae86001bd037a3f" + integrity sha512-qUHbKUK6sHeHn67qtHZoLoYk5hS6x1arTPjKDRkY93/6Fx+ZmNIpdm1owX3l6aYueyegJ7mz43FpvYHUqJ8xcw== + dependencies: + "@walletconnect/environment" "^1.0.0" + "@walletconnect/jsonrpc-types" "^1.0.0" + +"@walletconnect/mobile-registry@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.4.0.tgz#502cf8ab87330841d794819081e748ebdef7aee5" + integrity sha512-ZtKRio4uCZ1JUF7LIdecmZt7FOLnX72RPSY7aUVu7mj7CSfxDwUn6gBuK6WGtH+NZCldBqDl5DenI5fFSvkKYw== + +"@walletconnect/qrcode-modal@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.7.5.tgz#d7b42b4109c20d00c28e5a617992db6e8d79471e" + integrity sha512-LVq35jc3VMGq1EMcGCObQtEiercMDmUHDnc7A3AmUo0LoAbaPo6c8Hq0zqy2+JhtLmxUhU3ktf+szmCoiUDTUQ== + dependencies: + "@walletconnect/browser-utils" "^1.7.5" + "@walletconnect/mobile-registry" "^1.4.0" + "@walletconnect/types" "^1.7.5" + copy-to-clipboard "^3.3.1" + preact "10.4.1" + qrcode "1.4.4" + +"@walletconnect/randombytes@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/randombytes/-/randombytes-1.0.2.tgz#95c644251a15e6675f58fbffc9513a01486da49c" + integrity sha512-ivgOtAyqQnN0rLQmOFPemsgYGysd/ooLfaDA/ACQ3cyqlca56t3rZc7pXfqJOIETx/wSyoF5XbwL+BqYodw27A== + dependencies: + "@walletconnect/encoding" "^1.0.1" + "@walletconnect/environment" "^1.0.0" + randombytes "^2.1.0" + +"@walletconnect/safe-json@1.0.0", "@walletconnect/safe-json@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@walletconnect/safe-json/-/safe-json-1.0.0.tgz#12eeb11d43795199c045fafde97e3c91646683b2" + integrity sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg== + +"@walletconnect/signer-connection@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/signer-connection/-/signer-connection-1.7.5.tgz#ad37b34534445c7c3870f6fb33d2188f52054bd9" + integrity sha512-O7WO1Yqu8eBDfUJYeEkQDV2LDvj5JvAltTRn7El0IYOjK/T979c4NvyBpjHv9rp0eKX6/60foynj4D/h9hA4ew== + dependencies: + "@walletconnect/client" "^1.7.5" + "@walletconnect/jsonrpc-types" "^1.0.0" + "@walletconnect/jsonrpc-utils" "^1.0.0" + "@walletconnect/qrcode-modal" "^1.7.5" + "@walletconnect/types" "^1.7.5" + eventemitter3 "4.0.7" + +"@walletconnect/socket-transport@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/socket-transport/-/socket-transport-1.7.5.tgz#5416886403c7bea526f4ced6452fd1056c0a1354" + integrity sha512-4TYCxrNWb4f5a1NGsALXidr+/6dOiqgVfUQJ4fdP6R7ijL+7jtdiktguU9FIDq5wFXRE+ZdpCpwSAfOt60q/mQ== + dependencies: + "@walletconnect/types" "^1.7.5" + "@walletconnect/utils" "^1.7.5" + ws "7.5.3" + +"@walletconnect/types@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.7.5.tgz#145d7dd9df4415178995df6d4facef41c371ab6f" + integrity sha512-0HvZzxD93et4DdrYgAvclI1BqclkZS7iPWRtbGg3r+PQhRPbOkNypzBy6XH6wflbmr+WBGdmyJvynHsdhcCqUA== + +"@walletconnect/utils@^1.7.5": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.7.5.tgz#762bf7f384846772416e44b636ce9792d1d7db5f" + integrity sha512-U954rIIA/g/Cmdqy+n3hMY1DDMmXxGs8w/QmrK9b/H5nkQ3e4QicOyynq5g/JTTesN5HZdDTFiyX9r0GSKa+iA== + dependencies: + "@walletconnect/browser-utils" "^1.7.5" + "@walletconnect/encoding" "^1.0.1" + "@walletconnect/jsonrpc-utils" "^1.0.0" + "@walletconnect/types" "^1.7.5" + bn.js "4.11.8" + js-sha3 "0.8.0" + query-string "6.13.5" + +"@walletconnect/window-getters@1.0.0", "@walletconnect/window-getters@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@walletconnect/window-getters/-/window-getters-1.0.0.tgz#1053224f77e725dfd611c83931b5f6c98c32bfc8" + integrity sha512-xB0SQsLaleIYIkSsl43vm8EwETpBzJ2gnzk7e0wMF3ktqiTGS6TFHxcprMl5R44KKh4tCcHCJwolMCaDSwtAaA== + +"@walletconnect/window-metadata@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@walletconnect/window-metadata/-/window-metadata-1.0.0.tgz#93b1cc685e6b9b202f29c26be550fde97800c4e5" + integrity sha512-9eFvmJxIKCC3YWOL97SgRkKhlyGXkrHwamfechmqszbypFspaSk+t2jQXAEU7YClHF6Qjw5eYOmy1//zFi9/GA== + dependencies: + "@walletconnect/window-getters" "^1.0.0" + +"@web3-react/core@8.0.20-beta.0": + version "8.0.20-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-8.0.20-beta.0.tgz#a98e751955029c2deca22acd5fe300f527bae142" + integrity sha512-VMsCaoCC4c/cCOhCnQkHBo7VRMnLFokh8Gx+ju+WQz3b0c3V/qywcEUNrJoNhDryc863oNYjQti7cb5hhlJPVQ== + dependencies: + "@web3-react/store" "^8.0.14-beta.0" + "@web3-react/types" "^8.0.10-beta.0" + zustand "^4.0.0-beta.2" + optionalDependencies: + "@ethersproject/providers" "^5.6.0" + +"@web3-react/metamask@8.0.16-beta.0": + version "8.0.16-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/metamask/-/metamask-8.0.16-beta.0.tgz#66ec1131a0d09774ebdb9e9a98aba8c70e39ae74" + integrity sha512-YuI2UY7ka+VJt8m7K3SmpXouyORZIIcNyAERc50ipZPeWro9j/a0qDavFZ0OnE8nhGrgBA+kJq+hMTvZrggCZQ== + dependencies: + "@metamask/detect-provider" "^1.2.0" + "@web3-react/types" "^8.0.10-beta.0" + +"@web3-react/store@^8.0.14-beta.0": + version "8.0.14-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/store/-/store-8.0.14-beta.0.tgz#302a5275f50323d63cd3a9f075414e5b63164f55" + integrity sha512-Z6ja+ilt7NWeL4wMcDLzD15255i0hcDySzvMwggqkEYs+hY3+w/R3dlR3FZDApr+MsxDq3wDukaQVUdKYA+Oqw== + dependencies: + "@ethersproject/address" "^5.6.0" + "@web3-react/types" "^8.0.10-beta.0" + zustand "^4.0.0-beta.2" + +"@web3-react/types@^8.0.10-beta.0": + version "8.0.10-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-8.0.10-beta.0.tgz#8ed9f1b5e40dcbe2a08be5ce9dd9e3ef2b2a6908" + integrity sha512-HitBmoKHyLDeRrJgtkImPddb8nzsb/FroAdiaYA15gAwAE3Y/p7Nd/TN8IANlgKcMkuyGyUjPD27fE7Mvw9rEA== + dependencies: + zustand "^4.0.0-beta.2" + +"@web3-react/walletconnect@^8.0.23-beta.0": + version "8.0.23-beta.0" + resolved "https://registry.yarnpkg.com/@web3-react/walletconnect/-/walletconnect-8.0.23-beta.0.tgz#ce6debb7383508d95ae828627d8a7f9286dbe8ed" + integrity sha512-MUq11Pmp9Y4Y+Cey5HrWNJM0gA76u02CzgsL4/iF1ph6t0cuqZj0C6guwwkTMF9apxUTlMpWgpywRQDbxdqDoA== + dependencies: + "@web3-react/types" "^8.0.10-beta.0" + eventemitter3 "^4.0.7" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -5969,6 +6338,11 @@ aes-js@3.0.0: resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" integrity sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0= +aes-js@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" + integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ== + ag-grid-community@^27.0.1: version "27.0.1" resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-27.0.1.tgz#7ce5c000d321ba2c22447837e793b1d8366f4cdb" @@ -6761,7 +7135,7 @@ axe-core@^4.2.0, axe-core@^4.3.5: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== -axios@^0.21.1: +axios@^0.21.0, axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -7131,6 +7505,11 @@ bluebird@^3.3.5, bluebird@^3.4.1, bluebird@^3.5.5, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bn.js@4.11.8: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -7458,12 +7837,30 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-from@^1.0.0: +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= + +buffer-from@^1.0.0, buffer-from@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== @@ -7503,7 +7900,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.6.0: +buffer@^5.4.3, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -8186,6 +8583,15 @@ cli-ux@^5.2.1: supports-hyperlinks "^2.1.0" tslib "^2.0.0" +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -8693,6 +9099,13 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-fetch@^3.1.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -8961,7 +9374,7 @@ csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda" integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA== -csstype@^3.0.2: +csstype@^3.0.2, csstype@^3.0.4: version "3.0.11" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== @@ -9141,6 +9554,11 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +date-fns@^2.28.0: + version "2.28.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" + integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== + dayjs@^1.10.4: version "1.10.8" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.8.tgz#267df4bc6276fcb33c04a6735287e3f429abec41" @@ -9333,6 +9751,11 @@ detab@2.0.4: dependencies: repeat-string "^1.5.4" +detect-browser@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.2.0.tgz#c9cd5afa96a6a19fda0bbe9e9be48a6b6e1e9c97" + integrity sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -9394,6 +9817,11 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dijkstrajs@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257" + integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg== + dir-glob@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" @@ -9609,6 +10037,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +eip1193-provider@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/eip1193-provider/-/eip1193-provider-1.0.1.tgz#420d29cf4f6c443e3f32e718fb16fafb250637c3" + integrity sha512-kSuqwQ26d7CzuS/t3yRXo2Su2cVH0QfvyKbr2H7Be7O5YDyIq4hQGCNTo5wRdP07bt+E2R/8nPCzey4ojBHf7g== + dependencies: + "@json-rpc-tools/provider" "^1.5.5" + ejs@^3.1.5: version "3.1.6" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" @@ -9651,6 +10086,11 @@ emittery@^0.8.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -10332,7 +10772,7 @@ eventemitter2@^6.4.3: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.5.tgz#97380f758ae24ac15df8353e0cc27f8b95644655" integrity sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw== -eventemitter3@^4.0.0, eventemitter3@^4.0.4: +eventemitter3@4.0.7, eventemitter3@^4.0.0, eventemitter3@^4.0.4, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -11590,7 +12030,7 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -12649,7 +13089,7 @@ is-typed-array@^1.1.3, is-typed-array@^1.1.7: foreach "^2.0.5" has-tostringtag "^1.0.0" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@1.0.0, is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -12703,7 +13143,7 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@^2.0.5: +isarray@^2.0.1, isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== @@ -13534,6 +13974,11 @@ junk@^3.1.0: resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== +keyvaluestorage-interface@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz#13ebdf71f5284ad54be94bd1ad9ed79adad515ff" + integrity sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g== + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -14703,7 +15148,7 @@ node-fetch@2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-fetch@^2.6.1: +node-fetch@2.6.7, node-fetch@^2.6.1: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -15587,6 +16032,11 @@ pluralize@^8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +pngjs@^3.3.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" + integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== + pnp-webpack-plugin@1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" @@ -15983,6 +16433,11 @@ postcss@^8.2.13, postcss@^8.2.15, postcss@^8.3.5, postcss@^8.4.6, postcss@^8.4.7 picocolors "^1.0.0" source-map-js "^1.0.2" +preact@10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.4.1.tgz#9b3ba020547673a231c6cf16f0fbaef0e8863431" + integrity sha512-WKrRpCSwL2t3tpOOGhf2WfTpcmbpxaWtDbdJdKdjd0aEiTkvOmS4NBkG6kzlaAHI9AkQ3iVqbFWM3Ei7mZ4o1Q== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -16198,6 +16653,19 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= +qrcode@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" + integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q== + dependencies: + buffer "^5.4.3" + buffer-alloc "^1.2.0" + buffer-from "^1.1.1" + dijkstrajs "^1.0.1" + isarray "^2.0.1" + pngjs "^3.3.0" + yargs "^13.2.4" + qs@6.9.7: version "6.9.7" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" @@ -16215,6 +16683,15 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== +query-string@6.13.5: + version "6.13.5" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.5.tgz#99e95e2fb7021db90a6f373f990c0c814b3812d8" + integrity sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q== + dependencies: + decode-uri-component "^0.2.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + query-string@^6.13.8: version "6.14.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" @@ -16488,11 +16965,6 @@ react-shallow-renderer@^16.13.1: object-assign "^4.1.1" react-is "^16.12.0 || ^17.0.0" -react-singleton-hook@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/react-singleton-hook/-/react-singleton-hook-3.2.3.tgz#1765e67b1910fd163cdcd186a840af82b60ca17e" - integrity sha512-DYJ70V8IVUZznygmRYYF+hxwLkGPLrCCTAD1Je7sZYXquE9HpAJqBVeEgb2XIZwxWUw97PN2ho5J/+63YwdKaA== - react-sizeme@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.2.tgz#4a2f167905ba8f8b8d932a9e35164e459f9020e4" @@ -17161,6 +17633,11 @@ safe-identifier@^0.4.2: resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== +safe-json-utils@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/safe-json-utils/-/safe-json-utils-1.1.1.tgz#0e883874467d95ab914c3f511096b89bfb3e63b1" + integrity sha512-SAJWGKDs50tAbiDXLf89PDwt9XYkWyANFWVzn4dTXl5QyI8t2o/bW5/OJl3lvc2WVU4MEpTo9Yz5NVFNsp+OJQ== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -18091,6 +18568,15 @@ string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + string-width@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -18162,7 +18648,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.2.0: +strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -19033,7 +19519,7 @@ typed-assert@^1.0.8: resolved "https://registry.yarnpkg.com/typed-assert/-/typed-assert-1.0.9.tgz#8af9d4f93432c4970ec717e3006f33f135b06213" integrity sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg== -typedarray-to-buffer@^3.1.5: +typedarray-to-buffer@3.1.5, typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== @@ -19377,6 +19863,11 @@ use-subscription@1.5.1: dependencies: object-assign "^4.1.1" +use-sync-external-store@1.0.0-rc.1-next-629036a9c-20220224: + version "1.0.0-rc.1-next-629036a9c-20220224" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.0.0-rc.1-next-629036a9c-20220224.tgz#40cf472454789403c2de6c8471d177459d184dc1" + integrity sha512-IhuMl0apVVYsT3XPfV+0nuwf0T6+3d4YxQXV4tDRsGpSQcYVG4zoWwfX4zdtouUfuelYg4t2SEmFifIMrxPfIw== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -19675,6 +20166,11 @@ web-namespaces@^1.0.0: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== +web-vitals@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c" + integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -20065,6 +20561,15 @@ wrap-ansi@^4.0.0: string-width "^2.1.1" strip-ansi "^4.0.0" +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -20103,7 +20608,12 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@^7.4.6: +ws@7.5.3: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== + +ws@^7.4.0, ws@^7.4.6: version "7.5.7" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== @@ -20163,6 +20673,14 @@ yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.7: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" @@ -20188,6 +20706,22 @@ yargs@15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^13.2.4: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" @@ -20244,6 +20778,13 @@ zen-observable@0.8.15, zen-observable@^0.8.0: resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== +zustand@^4.0.0-beta.2: + version "4.0.0-beta.2" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.0.0-beta.2.tgz#27fdc32b62225cc18976c0cf8866ecee9a9f4a98" + integrity sha512-aJ5ypnOwPIa/uSjdZv/oHChTWPplpFOG/hvWwzkR5ahFiPI5R6ifyObf8Fz1Vi6Obz2wY1N32fT2pNrpT2hzPw== + dependencies: + use-sync-external-store "1.0.0-rc.1-next-629036a9c-20220224" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"