Compare commits

...
Author SHA1 Message Date
asiaznik f61ec4983c fix: fixed failing e2e tests, refactored assets page a bit 2023-02-09 15:52:33 +01:00
ArtandEdd 1a1a151969 Update apps/explorer/src/app/components/links/asset-link/asset-link.tsx
Co-authored-by: Edd <edd@vega.xyz>
2023-02-08 19:14:42 +01:00
asiaznik 02773bdae3 feat(explorer): asset details 2023-02-08 16:48:00 +01:00
Maciek 1952cb0e78 fix(trading): inform the user his connection to wallet is lost (#2863) 2023-02-07 11:31:15 +01:00
mattrussell36 69340f4ddf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 06:06:15 +00:00
mattrussell36 c52bf2200e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 00:10:59 +00:00
Matthew Russell 8dccee69f2 chore(trading): remove unnecessary sentry capture from asyncrenderer component (#2869) 2023-02-06 15:21:39 -08:00
Art c7a6fdd879 chore(trading): new toasts designs (#2779) 2023-02-06 20:09:56 +00:00
Matthew Russell 8bcdaf4cda feat(trading): key to key transfers (#2784) 2023-02-06 11:35:40 -08:00
mattrussell36 3eb1359504 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 18:08:25 +00:00
Sam Keen 39cf8ad4d6 feat(governance): remove some performance info from validator mouseover (#2865) 2023-02-06 17:08:34 +00:00
Edd faef98f0ae fix(explorer): type generation broken in an oracle refactor (#2859) 2023-02-06 13:29:23 +00:00
macqbat 710e2daa27 chore(trading): notification component redesign (#2845) 2023-02-06 13:31:35 +01:00
daro-maj 06ea3924fa chore(trading): increase withdraw ac test coverage (#2853) 2023-02-06 13:27:12 +01:00
mattrussell36 965e2d8972 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 12:09:01 +00:00
m.ray 73de2fed43 fix(trading): when order is rejected change tx toast intent (#2851) 2023-02-06 11:44:04 +00:00
Sam Keen 536859e067 feat(governance): visual tweaks to main nav (#2839) 2023-02-06 11:18:14 +00:00
Sam Keen 54bfaef473 fix(governance): empty div disrupts other page elements (#2855) 2023-02-06 10:35:26 +00:00
Matthew Russell e2cad707a7 chore(trading): add validator testnet env file (#2852) 2023-02-06 09:04:34 +00:00
mattrussell36 ec8d9798ab chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 06:08:16 +00:00
mattrussell36 e629c1e81a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 00:10:27 +00:00
mattrussell36 25feed793c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 18:08:10 +00:00
mattrussell36 2867367e16 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 12:07:47 +00:00
mattrussell36 50b408e74d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 06:05:57 +00:00
mattrussell36 8f531c7c05 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 00:13:19 +00:00
mattrussell36 350d16fee1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 18:07:27 +00:00
mattrussell36 c052c66266 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 12:06:25 +00:00
mattrussell36 e2032df236 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 06:07:41 +00:00
Matthew Russell 9ece19869d chore(trading): update order subs to not use busevents (#2849) 2023-02-03 16:12:20 -08:00
Joe Tsang 22abc8160c test(token): pending balances (#2843) 2023-02-03 19:26:17 +00:00
Edd b1a7a22bf9 feat(explorer): add protocol upgrade view (#2802) 2023-02-03 19:25:36 +00:00
mattrussell36 c4b38cc0dc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 18:05:46 +00:00
m.ray 44d6d931d5 fix(trading): update async renderer on error (#2842) 2023-02-03 16:52:10 +00:00
m.ray 7f1e47d7fd fix(trading): remove price flash cell from PNL (#2829) 2023-02-03 14:59:24 +00:00
Art 0eddfa4073 fix(ui-toolkit): dark and light shades mismatch (#2841) 2023-02-03 12:33:13 +00:00
mattrussell36 83a034ce91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 12:06:17 +00:00
5ab5aa01a2 feat(trading): 2803 console announcement banner (#2830)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-02-03 10:34:32 +00:00
mattrussell36 b22f9156b5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 06:07:49 +00:00
mattrussell36 bab5e2018b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 00:12:30 +00:00
macqbat 3f7692ce75 chore(trading): add alpha font to trading (#2833) 2023-02-02 19:25:14 +01:00
mattrussell36 b69fb3b0e5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 18:08:33 +00:00
macqbat 83af6e74f6 chore(trading): no data message in charts (#2816) 2023-02-02 18:43:16 +01:00
m.ray b9908003ba fix(trading): fix symbol on order estimates (#2827) 2023-02-02 13:30:47 +00:00
Sam KeenandMatthew Russell 1f9af79215 feat(ui-toolkit): mainnet sims announcement banner (#2806)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-02-02 13:13:55 +00:00
Sam Keen e31e40a7ef feat(ui-toolkit): mainnet sims announcement banner (#2806) 2023-02-02 12:56:22 +00:00
mattrussell36 81f6b3cc15 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 12:08:12 +00:00
mattrussell36 8adb208a91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 06:06:32 +00:00
mattrussell36 545c3a17d2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 00:09:59 +00:00
mattrussell36 ad76988f63 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 18:08:30 +00:00
Sam KeenandDexter d2bbd7e1fc feat(governance): pending balances: add event listeners (#2541)
Co-authored-by: Dexter <dexter.edwards93@gmail.com>
2023-02-01 16:02:50 +00:00
macqbat fb1ebb3bdf chore(trading): remove noise form the test logs (#2801) 2023-02-01 16:58:47 +01:00
m.ray b2489537ef fix(trading): market data and auction time not updating (#2813) 2023-02-01 14:08:49 +00:00
Sam Keen 382a7ea411 feat(governance): body can scroll again after clicking 'back to staking' (#2785) 2023-02-01 12:56:25 +00:00
mattrussell36 1ae2b4c153 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 12:06:46 +00:00
mattrussell36 e8875e817e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 06:06:26 +00:00
mattrussell36 4a01c880f3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 00:10:41 +00:00
mattrussell36 f7f00dc7bb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 18:07:31 +00:00
macqbat d151b6e923 chore(trading): add sub statuses of opening auction trading mode (#2797) 2023-01-31 17:31:55 +01:00
Sam Keen 8f8e9c1061 feat(ui-toolkit): announcement banner to highlight mainnet sims (#2800) 2023-01-31 16:12:44 +00:00
m.ray b40358a723 fix(trading): consolidate view as user mode (#2778) 2023-01-31 16:04:52 +00:00
Art 01f0934da3 chore(ui-toolkit): new colour palette (#2783) 2023-01-31 16:33:16 +01:00
mattrussell36 df7755dbc4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 12:06:33 +00:00
Joe Tsang 9073234040 test(ci): fix tags in cypress workflow (#2788) 2023-01-31 11:35:42 +00:00
Art 053cd0fa27 fix(trading): asset option on mobile got overflown (#2782) 2023-01-31 11:10:55 +01:00
mattrussell36 961dc88dc7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 06:08:30 +00:00
mattrussell36 b16607a844 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 00:13:30 +00:00
Matthew Russell d0d804631c chore(ci): add nx scope linting for pr title (#2781) 2023-01-30 21:06:18 +00:00
daro-maj 3e778a88dc chore: capsule tests refactor and increase deposit ac (#2773) 2023-01-30 20:35:58 +01:00
Edd dbb09c9c79 fix(explorer): correct calculation for whether party txs list has more txs (#2774) 2023-01-30 18:23:09 +00:00
Edd 06f44b67de fix(explorer): tx viewer dropdown did not correctly represent state (#2780) 2023-01-30 18:17:47 +00:00
Joe Tsang 1861852852 test(2704): proposals with market (#2775) 2023-01-30 18:06:04 +00:00
mattrussell36 3ee0eee80d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 12:09:00 +00:00
mattrussell36 69ce870a81 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 06:06:56 +00:00
mattrussell36 d8467c3206 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 00:12:51 +00:00
mattrussell36 de87f40a01 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 18:08:12 +00:00
264 changed files with 6263 additions and 2906 deletions
+1 -2
View File
@@ -64,12 +64,11 @@ jobs:
######
- name: Run Cypress tests
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
CYPRESS_grepTags: ${{ inputs.tags }}
######
## Upload logs
+7 -5
View File
@@ -8,14 +8,16 @@ jobs:
lint_pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.14.0
- name: Install commitlint cli and config
run: npm install @commitlint/cli @commitlint/config-conventional
- name: Create config
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
- name: Install root dependencies
run: yarn install
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+45 -138
View File
@@ -1,150 +1,57 @@
context('Asset page', { tags: '@regression' }, function () {
before('gather system asset information', function () {
cy.get_asset_information().as('assetsInfo');
});
describe('Verify elements on page', function () {
const assetsNavigation = 'a[href="/assets"]';
const assetHeader = '[data-testid="asset-header"]';
const jsonSection = '.language-json';
before('Navigate to assets page', function () {
cy.visit('/');
cy.get(assetsNavigation).click();
context('Asset page', { tags: '@regression' }, () => {
const columns = ['symbol', 'name', 'id', 'type', 'status', 'actions'];
const hiddenOnMobile = ['id', 'type', 'status'];
describe('Verify elements on page', () => {
before('Navigate to assets page', () => {
cy.visit('/assets');
// Check we have enough enough assets
const assetNames = Object.keys(this.assetsInfo);
assert.isAtLeast(
assetNames.length,
5,
'Ensuring we have at least 5 assets to test'
);
});
it('should be able to see assets page sections', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
cy.getAssets().then((assets) => {
assert.isAtLeast(
Object.keys(assets).length,
5,
'Ensuring we have at least 5 assets to test'
);
});
});
it('should be able to see all asset details displayed in JSON', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.next()
.within(() => {
cy.get(jsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.then((assetsListedInJson) => {
const assetInfo = this.assetsInfo[assetName];
assert.equal(assetsListedInJson.name, assetInfo.node.name);
assert.equal(assetsListedInJson.id, assetInfo.node.id);
assert.equal(
assetsListedInJson.decimals,
assetInfo.node.decimals
);
assert.equal(assetsListedInJson.symbol, assetInfo.node.symbol);
assert.equal(
assetsListedInJson.source.__typename,
assetInfo.node.source.__typename
);
if (assetInfo.node.source.__typename == 'ERC20') {
assert.equal(
assetsListedInJson.source.contractAddress,
assetInfo.node.source.contractAddress
);
}
if (assetInfo.node.source.__typename == 'BuiltinAsset') {
assert.equal(
assetsListedInJson.source.maxFaucetAmountMint,
assetInfo.node.source.maxFaucetAmountMint
);
}
let knownAssetTypes = ['BuiltinAsset', 'ERC20'];
assert.include(
knownAssetTypes,
assetInfo.node.source.__typename,
`Checking that current asset type of ${assetInfo.node.source.__typename} /
is one of: ${knownAssetTypes}: /
If fail then we need to add extra tests for un-encountered asset types`
);
});
});
});
});
it('should be able to switch assets between light and dark mode', function () {
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
const themeSwitcher = '[data-testid="theme-switcher"]';
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
cy.get(themeSwitcher).click();
it('should be able to see full assets list', () => {
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
});
// Engage white mode
cy.get(themeSwitcher).click();
// White Mode
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', whiteThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', whiteThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', whiteThemeSideMenuBackgroundColor);
// Dark Mode
cy.get(themeSwitcher).click();
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', darkThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', darkThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', darkThemeSideMenuBackgroundColor);
});
columns.forEach((col) => {
cy.get(`[col-id="${col}"]`).should('be.visible');
});
});
it('should be able to see assets page displayed in mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(assetsNavigation).click();
it('should be able to see assets page displayed in mobile', () => {
cy.switchToMobile();
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
hiddenOnMobile.forEach((col) => {
cy.get(`[col-id="${col}"]`).should('have.length', 0);
});
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
});
});
});
it('should open details dialog when clicked on "View details"', () => {
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
.eq(0)
.should('contain.text', 'View details');
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
.eq(0)
.click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('dialog-close').click();
});
});
});
});
@@ -123,7 +123,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
.convert_string_json_to_js_object()
.get_party_accounts_data_from_js_object()
.then((accountsListedInJson) => {
cy.get_asset_information().then((assetsInfo) => {
cy.getAssets().then((assetsInfo) => {
const assetInfo =
assetsInfo[accountsListedInJson[assetInTest].asset.name];
@@ -205,7 +205,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
});
Cypress.Commands.add('get_asset_decimals', (assetID) => {
cy.get_asset_information().then((assetsInfo) => {
cy.getAssets().then((assetsInfo) => {
const assetDecimals = assetsInfo[assetData[assetID].name].decimals;
let decimals = '';
for (let i = 0; i < assetDecimals; i++) decimals += '0';
@@ -21,6 +21,10 @@ Cypress.Commands.add(
}
);
Cypress.Commands.add('switchToMobile', () => {
cy.viewport('iphone-x');
});
Cypress.Commands.add('common_switch_to_mobile_and_click_toggle', function () {
cy.viewport('iphone-x');
cy.visit('/');
+1
View File
@@ -6,3 +6,4 @@ NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
+29
View File
@@ -8,6 +8,24 @@ import { Main } from './components/main';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import type { InMemoryCacheConfig } from '@apollo/client';
import { Footer } from './components/footer/footer';
import { AnnouncementBanner, ExternalLink } from '@vegaprotocol/ui-toolkit';
import {
AssetDetailsDialog,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
const DialogsContainer = () => {
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
return (
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
asJson={asJson}
open={isOpen}
onChange={setOpen}
/>
);
};
function App() {
const [menuOpen, setMenuOpen] = useState(false);
@@ -40,12 +58,23 @@ function App() {
return (
<TendermintWebsocketProvider>
<NetworkLoader cache={cacheConfig}>
<AnnouncementBanner>
<div className="font-alpha calt uppercase text-center text-lg text-white">
<span className="pr-4">The Mainnet sims are live!</span>
<ExternalLink href="https://fairground.wtf/">
Come help stress test the network
</ExternalLink>
</div>
</AnnouncementBanner>
<div className={layoutClasses}>
<Header menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
<Nav menuOpen={menuOpen} />
<Main />
<Footer />
</div>
<DialogsContainer />
</NetworkLoader>
</TendermintWebsocketProvider>
);
@@ -1,6 +1,6 @@
import { useAssetDataProvider } from '@vegaprotocol/assets';
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
import { AssetLink } from '../links';
import { useExplorerAssetQuery } from '../links/asset-link/__generated__/Asset';
export type AssetBalanceProps = {
assetId: string;
@@ -17,21 +17,17 @@ const AssetBalance = ({
price,
showAssetLink = true,
}: AssetBalanceProps) => {
const { data } = useExplorerAssetQuery({
variables: { id: assetId },
});
const { data: asset } = useAssetDataProvider(assetId);
const label =
data && data.asset?.decimals
? addDecimalsFormatNumber(price, data.asset.decimals)
asset && asset.decimals
? addDecimalsFormatNumber(price, asset.decimals)
: price;
return (
<div className="inline-block">
<span>{label}</span>{' '}
{showAssetLink && data?.asset?.id ? (
<AssetLink id={data.asset.id} />
) : null}
{showAssetLink && asset?.id ? <AssetLink assetId={assetId} /> : null}
</div>
);
};
@@ -0,0 +1,28 @@
import { render, waitFor } from '@testing-library/react';
import { assetsList } from '../../mocks/assets';
import { AssetsTable } from './assets-table';
describe('AssetsTable', () => {
it('shows loading message on first render', async () => {
const res = render(<AssetsTable data={null} />);
expect(await res.findByText('Loading...')).toBeInTheDocument();
});
it('shows no data message if no assets found', async () => {
const res = render(<AssetsTable data={[]} />);
expect(
await res.findByText('This chain has no assets')
).toBeInTheDocument();
});
it('shows a table/list with all the assets', async () => {
const res = render(<AssetsTable data={assetsList} />);
await waitFor(() => {
const rowA1 = res.container.querySelector('[row-id="123"]');
expect(rowA1).toBeInTheDocument();
const rowA2 = res.container.querySelector('[row-id="456"]');
expect(rowA2).toBeInTheDocument();
});
});
});
@@ -0,0 +1,115 @@
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/react-helpers';
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
type AssetsTableProps = {
data: AssetFieldsFragment[] | null;
};
export const AssetsTable = ({ data }: AssetsTableProps) => {
const openAssetDetailsDialog = useAssetDetailsDialogStore(
(state) => state.open
);
const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => {
ref.current?.columnApi.setColumnsVisible(
['id', 'type', 'status'],
window.innerWidth > BREAKPOINT_MD
);
};
useLayoutEffect(() => {
window.addEventListener('resize', showColumnsOnDesktop);
return () => {
window.removeEventListener('resize', showColumnsOnDesktop);
};
}, []);
return (
<AgGrid
ref={ref}
rowData={data}
getRowId={({ data }: { data: AssetFieldsFragment }) => data.id}
overlayNoRowsTemplate={t('This chain has no assets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
suppressCellFocus={true}
onGridReady={() => {
showColumnsOnDesktop();
}}
>
<AgGridColumn headerName={t('Symbol')} field="symbol" />
<AgGridColumn headerName={t('Name')} field="name" />
<AgGridColumn flex="2" headerName={t('ID')} field="id" />
<AgGridColumn
colId="type"
headerName={t('Type')}
field="source.__typename"
valueFormatter={({ value }: { value?: string }) =>
value && AssetTypeMapping[value].value
}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
valueFormatter={({ value }: { value?: string }) =>
value && AssetStatusMapping[value].value
}
/>
<AgGridColumn
colId="actions"
headerName=""
sortable={false}
filter={false}
resizable={false}
wrapText={true}
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<div className="pb-1">
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value, e.target as HTMLElement);
}}
>
{t('View details')}
</ButtonLink>{' '}
<span className="max-md:hidden">
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(
value,
e.target as HTMLElement,
true
);
}}
>
{t('View JSON')}
</ButtonLink>
</span>
</div>
) : (
''
)
}
/>
</AgGrid>
);
};
@@ -1,8 +0,0 @@
query ExplorerAsset($id: ID!) {
asset(id: $id) {
id
name
status
decimals
}
}
@@ -1,51 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerAssetQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerAssetQuery = { __typename?: 'Query', asset?: { __typename?: 'Asset', id: string, name: string, status: Types.AssetStatus, decimals: number } | null };
export const ExplorerAssetDocument = gql`
query ExplorerAsset($id: ID!) {
asset(id: $id) {
id
name
status
decimals
}
}
`;
/**
* __useExplorerAssetQuery__
*
* To run a query within a React component, call `useExplorerAssetQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerAssetQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerAssetQuery(baseOptions: Apollo.QueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
}
export function useExplorerAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
}
export type ExplorerAssetQueryHookResult = ReturnType<typeof useExplorerAssetQuery>;
export type ExplorerAssetLazyQueryHookResult = ReturnType<typeof useExplorerAssetLazyQuery>;
export type ExplorerAssetQueryResult = Apollo.QueryResult<ExplorerAssetQuery, ExplorerAssetQueryVariables>;
@@ -1,63 +1,37 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import AssetLink from './asset-link';
import { ExplorerAssetDocument } from './__generated__/Asset';
import { render, waitFor } from '@testing-library/react';
import { AssetLink } from './asset-link';
import { mockAssetA1 } from '../../../mocks/assets';
function renderComponent(id: string, mock: MockedResponse[]) {
return (
<MockedProvider mocks={mock}>
<MockedProvider mocks={mock} addTypename={false}>
<MemoryRouter>
<AssetLink id={id} />
<AssetLink assetId={id} />
</MemoryRouter>
</MockedProvider>
);
}
describe('Asset link component', () => {
it('Renders the ID at first', () => {
describe('AssetLink', () => {
it('renders the asset id when not found and makes the button disabled', async () => {
const res = render(renderComponent('123', []));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByTestId('asset-link')).toBeDisabled();
await waitFor(async () => {
expect(await res.queryByText('A ONE')).toBeFalsy();
});
});
it('Renders the asset name when the query returns a result', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
variables: {
id: '123',
},
},
result: {
data: {
asset: {
id: '123',
name: 'test-label',
status: 'irrelevant-test-data',
decimals: 18,
},
},
},
};
const res = render(renderComponent('123', [mock]));
it('renders the asset name when found and make the button enabled', async () => {
const res = render(renderComponent('123', [mockAssetA1]));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByText('test-label')).toBeInTheDocument();
});
it('Leaves the asset id when the asset is not found', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
variables: {
id: '123',
},
},
error: new Error('No such asset'),
};
const res = render(renderComponent('123', [mock]));
expect(await res.findByText('123')).toBeInTheDocument();
await waitFor(async () => {
expect(await res.findByText('A ONE')).toBeInTheDocument();
expect(await res.findByTestId('asset-link')).not.toBeDisabled();
});
});
});
@@ -1,36 +1,35 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { useExplorerAssetQuery } from './__generated__/Asset';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import {
useAssetDataProvider,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
assetId: string;
};
/**
* Given an asset ID, it will fetch the asset name and show that,
* with a link to the assets list. If the name does not come back
* with a link to the assets modal. If the name does not come back
* it will use the ID instead.
*/
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
const { data } = useExplorerAssetQuery({
variables: { id },
});
let label: string = id;
if (data?.asset?.name) {
label = data.asset.name;
}
export const AssetLink = ({ assetId, ...props }: AssetLinkProps) => {
const { data: asset } = useAssetDataProvider(assetId);
const open = useAssetDetailsDialogStore((state) => state.open);
const label = asset?.name ? asset.name : assetId;
return (
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
<ButtonLink
data-testid="asset-link"
disabled={!asset}
onClick={(e) => {
open(assetId, e.target as HTMLElement);
}}
{...props}
>
<Hash text={label} />
</Link>
</ButtonLink>
);
};
export default AssetLink;
@@ -2,4 +2,4 @@ export { default as BlockLink } from './block-link/block-link';
export { default as PartyLink } from './party-link/party-link';
export { default as NodeLink } from './node-link/node-link';
export { default as MarketLink } from './market-link/market-link';
export { default as AssetLink } from './asset-link/asset-link';
export * from './asset-link/asset-link';
@@ -117,7 +117,7 @@ const NestedDataListItem = ({
)}
</h4>
{!hasChildren && (
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
<code className="text-vega-light-100 mb-2 last:mb-0 dark:text-vega-dark-100 break-all">
{JSON.stringify(value, null, ' ')}
</code>
)}
@@ -1,5 +1,4 @@
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { StatusMessage } from '../status-message';
interface RenderFetchedProps {
@@ -76,9 +76,7 @@ describe('Chain Event: Builtin asset deposit', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -34,7 +34,7 @@ export const TxDetailsChainEventBuiltinDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId} /> ({t('built in asset')})
<AssetLink assetId={deposit.vegaAssetId} /> ({t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -82,9 +82,7 @@ describe('Chain Event: Builtin asset withdrawal', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -39,8 +39,8 @@ export const TxDetailsChainEventBuiltinWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId || ''} /> ({t('built in asset')}
)
<AssetLink assetId={withdrawal.vegaAssetId || ''} /> (
{t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -63,9 +63,7 @@ describe('Chain Event: ERC20 Asset Delist', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -29,7 +29,7 @@ export const TxDetailsChainEventErc20AssetDelist = ({
<TableRow modifier="bordered">
<TableCell>{t('Removed Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetDelist.vegaAssetId || ''} />
<AssetLink assetId={assetDelist.vegaAssetId || ''} />
</TableCell>
</TableRow>
</>
@@ -79,10 +79,8 @@ describe('Chain Event: ERC20 Asset limits updated', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
@@ -51,7 +51,7 @@ export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
<TableRow modifier="bordered">
<TableCell>{t('Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetLimitsUpdated.vegaAssetId} />
<AssetLink assetId={assetLimitsUpdated.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -65,10 +65,8 @@ describe('Chain Event: ERC20 Asset List', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.assetSource}`);
@@ -41,7 +41,7 @@ export const TxDetailsChainEventErc20AssetList = ({
<TableRow modifier="bordered">
<TableCell>{t('Added Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetList.vegaAssetId} />
<AssetLink assetId={assetList.vegaAssetId} />
</TableCell>
</TableRow>
</>
@@ -75,10 +75,8 @@ describe('Chain Event: ERC20 asset deposit', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
@@ -51,7 +51,7 @@ export const TxDetailsChainEventDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId} />
<AssetLink assetId={deposit.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -60,10 +60,8 @@ describe('Chain Event: ERC20 asset deposit', () => {
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('A');
expect(assetLink.parentElement.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
@@ -45,7 +45,7 @@ export const TxDetailsChainEventWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId} />
<AssetLink assetId={withdrawal.vegaAssetId} />
</TableCell>
</TableRow>
</>
@@ -0,0 +1,62 @@
import { t } from '@vegaprotocol/react-helpers';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import { BlockLink } from '../../links';
import { StatusMessage } from '../../status-message';
import { ENV } from '../../../config/env';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { components } from '../../../../types/explorer';
interface TxDetailsProtocolUpgradeProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* Validator event: Protocol Upgrade proposal
*/
export const TxDetailsProtocolUpgrade = ({
txData,
pubKey,
blockData,
}: TxDetailsProtocolUpgradeProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const upgrade: components['schemas']['v1ProtocolUpgradeProposal'] =
txData.command.protocolUpgradeProposal;
if (!upgrade || !upgrade.upgradeBlockHeight || !upgrade.vegaReleaseTag) {
return (
<StatusMessage>{t('Invalid upgrade proposal format')}</StatusMessage>
);
}
const urlBase = ENV.dataSources.vegaRepoUrl;
const release = upgrade.vegaReleaseTag;
return (
<TableWithTbody className="mb-8">
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Upgrade at block')}</TableCell>
<TableCell>
<BlockLink height={upgrade.upgradeBlockHeight} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Upgrade to')}</TableCell>
<TableCell>
<ExternalLink href={`${urlBase}${release}`}>
{upgrade.vegaReleaseTag}
</ExternalLink>
</TableCell>
</TableRow>
</TableWithTbody>
);
};
@@ -19,6 +19,7 @@ import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission';
import { TxProposalVote } from './tx-proposal-vote';
import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -70,6 +71,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsOrder;
case 'Submit Oracle Data':
return TxDetailsDataSubmission;
case 'Protocol Upgrade':
return TxDetailsProtocolUpgrade;
case 'Cancel Order':
return TxDetailsOrderCancel;
case 'Amend Order':
@@ -1,10 +1,11 @@
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
export type LiquidityCancellation =
components['schemas']['v1LiquidityProvisionCancellation'];
@@ -63,7 +63,7 @@ export const TxDataView = ({ txData, blockData }: TxDataViewProps) => {
<Select
placeholder="View as..."
onChange={(v) => setShowTxData(v.target.value as ShowTxDataType)}
value={'JSON'}
value={showTxData}
>
<option value={'JSON'}>JSON</option>
<option value={'base64'}>Base64</option>
@@ -0,0 +1,4 @@
/**
* Equivalent of tailwind's `md` modifier
*/
export const BREAKPOINT_MD = 768;
+2
View File
@@ -10,6 +10,7 @@ const truthy = ['1', 'true'];
export const ENV = {
// Data sources
// Environment
env: windowOrDefault('NX_VEGA_ENV'),
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
dataSources: {
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
@@ -17,6 +18,7 @@ export const ENV = {
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
},
flags: {
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),
+5 -4
View File
@@ -5,6 +5,7 @@ import type {
BlockExplorerTransactions,
} from '../routes/types/block-explorer-response';
import { DATA_SOURCES } from '../config';
import isNumber from 'lodash/isNumber';
export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[];
@@ -54,15 +55,15 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => {
} = useFetch<BlockExplorerTransactions>(url, {}, false);
useEffect(() => {
if (data?.transactions?.length) {
if (data && isNumber(data?.transactions?.length)) {
setTxsState((prev) => ({
txsData: [...prev.txsData, ...data.transactions],
hasMoreTxs: true,
hasMoreTxs: data.transactions.length > 0,
lastCursor:
data.transactions[data.transactions.length - 1].cursor || '',
data.transactions[data.transactions.length - 1]?.cursor || '',
}));
}
}, [setTxsState, data?.transactions]);
}, [setTxsState, data]);
const loadTxs = useCallback(() => {
return refetch({
+134
View File
@@ -0,0 +1,134 @@
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetDocument } from '@vegaprotocol/assets';
import { AssetsDocument } from '@vegaprotocol/assets';
import { AssetStatus } from '@vegaprotocol/types';
const A1: AssetFieldsFragment = {
__typename: 'Asset',
id: '123',
name: 'A ONE',
symbol: 'A1',
decimals: 0,
quantum: '',
status: AssetStatus.STATUS_ENABLED,
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '',
},
infrastructureFeeAccount: {
__typename: 'AccountBalance',
balance: '',
},
globalRewardPoolAccount: {
__typename: 'AccountBalance',
balance: '',
},
lpFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
makerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
marketProposerRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
takerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
};
const A2: AssetFieldsFragment = {
__typename: 'Asset',
id: '456',
name: 'A TWO',
symbol: 'A2',
decimals: 0,
quantum: '',
status: AssetStatus.STATUS_ENABLED,
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '',
},
infrastructureFeeAccount: {
__typename: 'AccountBalance',
balance: '',
},
globalRewardPoolAccount: {
__typename: 'AccountBalance',
balance: '',
},
lpFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
makerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
marketProposerRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
takerFeeRewardAccount: {
__typename: 'AccountBalance',
balance: '',
},
};
export const assetsList = [A1, A2];
export const mockAssetsList = {
request: {
query: AssetsDocument,
},
result: {
data: {
assetsConnection: {
__typename: 'AssetsConnection',
edges: [
{
__typename: 'AssetEdge',
node: A1,
},
{
__typename: 'AssetEdge',
node: A2,
},
],
},
},
},
};
export const mockEmptyAssetsList = {
request: {
query: AssetsDocument,
},
result: { data: null },
};
export const mockAssetA1 = {
request: {
query: AssetDocument,
variables: {
assetId: '123',
},
},
result: {
data: {
assetsConnection: {
__typename: 'AssetsConnection',
edges: [
{
__typename: 'AssetEdge',
node: A1,
},
],
},
},
},
};
@@ -1,32 +0,0 @@
fragment AssetsFields on Asset {
id
name
symbol
decimals
source {
... on ERC20 {
contractAddress
}
... on BuiltinAsset {
maxFaucetAmountMint
}
}
infrastructureFeeAccount {
type
balance
market {
id
}
}
}
query ExplorerAssets {
assetsConnection {
edges {
node {
...AssetsFields
}
}
}
}
@@ -1,73 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type AssetsFieldsFragment = { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string }, infrastructureFeeAccount?: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string } | null } | null };
export type ExplorerAssetsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerAssetsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string }, infrastructureFeeAccount?: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string } | null } | null } } | null> | null } | null };
export const AssetsFieldsFragmentDoc = gql`
fragment AssetsFields on Asset {
id
name
symbol
decimals
source {
... on ERC20 {
contractAddress
}
... on BuiltinAsset {
maxFaucetAmountMint
}
}
infrastructureFeeAccount {
type
balance
market {
id
}
}
}
`;
export const ExplorerAssetsDocument = gql`
query ExplorerAssets {
assetsConnection {
edges {
node {
...AssetsFields
}
}
}
}
${AssetsFieldsFragmentDoc}`;
/**
* __useExplorerAssetsQuery__
*
* To run a query within a React component, call `useExplorerAssetsQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerAssetsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerAssetsQuery({
* variables: {
* },
* });
*/
export function useExplorerAssetsQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>(ExplorerAssetsDocument, options);
}
export function useExplorerAssetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>(ExplorerAssetsDocument, options);
}
export type ExplorerAssetsQueryHookResult = ReturnType<typeof useExplorerAssetsQuery>;
export type ExplorerAssetsLazyQueryHookResult = ReturnType<typeof useExplorerAssetsLazyQuery>;
export type ExplorerAssetsQueryResult = Apollo.QueryResult<ExplorerAssetsQuery, ExplorerAssetsQueryVariables>;
@@ -0,0 +1,30 @@
import { t } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { AssetsTable } from '../../components/assets/assets-table';
export const Assets = () => {
useDocumentTitle(['Assets']);
useScrollToLocation();
const { data, loading, error } = useAssetsDataProvider();
return (
<section>
<RouteTitle data-testid="assets-header">{t('Assets')}</RouteTitle>
<AsyncRenderer
noDataMessage={t('This chain has no assets')}
data={data}
loading={loading}
error={error}
>
<div className="h-full relative">
<AssetsTable data={data} />
</div>
</AsyncRenderer>
</section>
);
};
@@ -1,44 +0,0 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import Assets from './index';
import type { MockedResponse } from '@apollo/client/testing';
import { ExplorerAssetDocument } from '../../components/links/asset-link/__generated__/Asset';
function renderComponent(mock: MockedResponse[]) {
return (
<MemoryRouter>
<MockedProvider mocks={mock}>
<Assets />
</MockedProvider>
</MemoryRouter>
);
}
describe('Assets index', () => {
it('Renders loader when loading', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
},
result: {
data: {},
},
};
const res = render(renderComponent([mock]));
expect(await res.findByTestId('loader')).toBeInTheDocument();
});
it('Renders EmptyList when loading completes and there are no results', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
},
result: {
data: {},
},
};
const res = render(renderComponent([mock]));
expect(await res.findByTestId('emptylist')).toBeInTheDocument();
});
});
+1 -53
View File
@@ -1,53 +1 @@
import { getNodes, t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { useExplorerAssetsQuery } from './__generated__/Assets';
import type { AssetsFieldsFragment } from './__generated__/Assets';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import EmptyList from '../../components/empty-list/empty-list';
const Assets = () => {
const { data, loading } = useExplorerAssetsQuery();
useDocumentTitle(['Assets']);
useScrollToLocation();
const assets = getNodes<AssetsFieldsFragment>(data?.assetsConnection);
if (!assets || assets.length === 0) {
if (!loading) {
return (
<section>
<RouteTitle data-testid="assets-header">{t('Assets')}</RouteTitle>
<EmptyList
heading={t('This chain has no assets')}
label={t('0 assets')}
/>
</section>
);
} else {
return <Loader />;
}
}
return (
<section>
<RouteTitle data-testid="assets-header">{t('Assets')}</RouteTitle>
{assets.map((a) => {
return (
<React.Fragment key={a.id}>
<SubHeading data-testid="asset-header" id={a.id}>
{a.name} ({a.symbol})
</SubHeading>
<SyntaxHighlighter data={a} />
</React.Fragment>
);
})}
</section>
);
};
export default Assets;
export * from './assets';
@@ -10,7 +10,7 @@ export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec',
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } } } | null> | null } | null };
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
@@ -73,7 +73,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
`;
export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection(pagination: {first: 1}) {
dataConnection {
pageInfo {
hasNextPage
}
@@ -113,13 +113,11 @@ export const ExplorerOracleSpecsDocument = gql`
edges {
node {
...ExplorerOracleDataSource
...ExplorerOracleDataConnection
}
}
}
}
${ExplorerOracleDataSourceFragmentDoc}
${ExplorerOracleDataConnectionFragmentDoc}`;
${ExplorerOracleDataSourceFragmentDoc}`;
/**
* __useExplorerOracleSpecsQuery__
@@ -22,7 +22,7 @@ export type SourceType =
interface OracleDetailsProps {
id: string;
dataSource: ExplorerOracleDataSourceFragment;
dataConnection: ExplorerOracleDataConnectionFragment;
dataConnection?: ExplorerOracleDataConnectionFragment;
// Defaults to false. Hides the count of 'broadcasts' this oracle has seen
showBroadcasts?: boolean;
}
@@ -41,7 +41,8 @@ export const OracleDetails = ({
showBroadcasts = false,
}: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.dataConnection.edges?.length || 0;
const reportsCount: number =
dataConnection?.dataConnection.edges?.length || 0;
return (
<div>
@@ -63,7 +64,9 @@ export const OracleDetails = ({
</TableRow>
</TableWithTbody>
<OracleFilter data={dataSource} />
{showBroadcasts ? <OracleData data={dataConnection} /> : null}
{showBroadcasts && dataConnection ? (
<OracleData data={dataConnection} />
) : null}
</div>
);
};
@@ -28,7 +28,7 @@ const Oracles = () => {
<OracleDetails
id={id}
dataSource={o?.node}
dataConnection={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
@@ -71,7 +71,7 @@ export const PartyAccounts = ({ accounts }: PartyAccountsProps) => {
/>
</td>
<td className="text-md">
<AssetLink id={account.asset.id} />
<AssetLink assetId={account.asset.id} />
</td>
</TableRow>
);
@@ -1,4 +1,4 @@
import Assets from './assets';
import { Assets } from './assets';
import BlockPage from './blocks';
import Governance from './governance';
import Home from './home';
@@ -38,7 +38,7 @@ const Tx = () => {
to={`/${Routes.TX}`}
>
<Icon
className="text-vega-light-300 dark:text-vega-light-300"
className="text-vega-light-150 dark:text-vega-light-150"
name={IconNames.CHEVRON_LEFT}
/>
All Transactions
+1 -9
View File
@@ -1,9 +1,6 @@
const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
const theme = require('../../libs/tailwindcss-config/src/theme');
const {
VegaColours,
} = require('../../libs/tailwindcss-config/src/vega-colours');
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
module.exports = {
@@ -14,12 +11,7 @@ module.exports = {
],
darkMode: 'class',
theme: {
extend: {
...theme,
colors: {
vega: VegaColours,
},
},
extend: theme,
},
plugins: [vegaCustomClasses],
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

File diff suppressed because it is too large Load Diff
+12
View File
@@ -16,4 +16,16 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
#Test configuration variables
CYPRESS_FAIRGROUND=false
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
CYPRESS_VEGA_ENV=CUSTOM
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf
CYPRESS_VEGA_URL=http://localhost:3028/query
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_WALLET_API_TOKEN=
@@ -2,56 +2,59 @@
"changes": {
"decimalPlaces": "5",
"positionDecimalPlaces": "5",
"lpPriceRange": "10",
"instrument": {
"name": "Oranges Daily",
"code": "ORANGES.24h",
"name": "Token test market",
"code": "Token.24h",
"future": {
"settlementAsset": "8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4",
"quoteName": "tEuro",
"settlementDataDecimals": 5,
"settlementAsset": "fBTC",
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
}
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER"
},
"conditions": [
"external": {
"oracle": {
"signers": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.ETH.value",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": "0"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
]
}
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "vegaprotocol.builtin.timestamp",
"type": "TYPE_TIMESTAMP"
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
"operator": "OPERATOR_EQUALS",
"value": "true"
}
]
}
@@ -59,7 +62,7 @@
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.BTC.value",
"tradingTerminationProperty": "vegaprotocol.builtin.timestamp"
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
@@ -1,83 +1,78 @@
{
"marketId": "315a8e48db0a292c92b617264728048c82c20efc922c75fd292fc54e5c727c81",
"changes": {
"instrument": {
"code": "ORANGES.24h",
"future": {
"quoteName": "tEuro",
"settlementDataDecimals": 5,
"dataSourceSpecForSettlementData": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
"instrument": {
"code": "TEST.24h",
"future": {
"quoteName": "fUSDC",
"settlementDataDecimals": 5,
"dataSourceSpecForSettlementData": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.ETH.value",
"type": "TYPE_INTEGER"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
},
"dataSourceSpecForTradingTermination": {
"signers": [
{
"signer": {
"__typename": "ETHAddress",
"address": "0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC"
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
}
}
],
"filters": [
{
"key": {
"name": "vegaprotocol.builtin.timestamp",
"type": "TYPE_TIMESTAMP"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
}
]
}
]
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.BTC.value",
"tradingTerminationProperty": "vegaprotocol.builtin.timestamp"
}
]
}
]
},
"dataSourceSpecBinding": {
"settlementPriceProperty": "prices.ETH.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.3
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.3
}
}
}
@@ -1 +1 @@
123
0123245
@@ -11,6 +11,7 @@ const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const dialogCloseButton = '[data-testid="dialog-close"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const openProposals = '[data-testid="open-proposals"]';
const closedProposals = '[data-testid="closed-proposals"]';
const proposalVoteProgressForPercentage =
'[data-testid="vote-progress-indicator-percentage-for"]';
const proposalVoteProgressAgainstPercentage =
@@ -22,7 +23,9 @@ const proposalVoteProgressAgainstTokens =
const changeVoteButton = '[data-testid="change-vote-button"]';
const proposalDetailsTitle = '[data-testid="proposal-title"]';
const proposalDetailsDescription = '[data-testid="proposal-description"]';
const proposalStatus = '[data-testid="proposal-status"]';
const rawProposalData = '[data-testid="proposal-data"]';
const votesTable = '[data-testid="votes-table"]';
const minVoteButton = '[data-testid="min-vote"]';
const maxVoteButton = '[data-testid="max-vote"]';
const voteButtons = '[data-testid="vote-buttons"]';
@@ -37,6 +40,10 @@ const txTimeout = Cypress.env('txTimeout');
const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 };
const minCloseDays = 2;
const maxCloseDays = 3;
const requiredParticipation = 0.001;
const governanceProposalType = {
NETWORK_PARAMETER: 'Network parameter',
NEW_MARKET: 'New market',
@@ -65,21 +72,6 @@ context(
network_parameters['governance.proposal.freeform.requiredMajority'] *
100
).as('requiredMajority');
cy.wrap(
network_parameters[
'governance.proposal.freeform.requiredParticipation'
] * 100
).as('requiredParticipation');
cy.wrap(
network_parameters['governance.proposal.freeform.minClose'].split(
'h'
)[0] / 24
).as('minCloseDays');
cy.wrap(
network_parameters['governance.proposal.freeform.maxClose'].split(
'h'
)[0] / 24
).as('maxCloseDays');
cy.wrap(
network_parameters['governance.proposal.freeform.minClose'].split(
'h'
@@ -108,21 +100,6 @@ context(
0.00001,
'Asserting that value is at least 0.00001 for network parameter minVoterBalance'
);
assert.isAtLeast(
parseFloat(this.requiredParticipation),
0.00001,
'Asserting that value is at least 0.00001 for network parameter requiredParticipation'
);
assert.isAtLeast(
parseInt(this.minCloseDays),
1,
'Asserting that value is at least 1 for network parameter minCloseDays'
);
assert.isAtLeast(
parseInt(this.maxCloseDays),
parseInt(this.minCloseDays + 1),
'Asserting that network parameter maxCloseDays is at least 1 day higher than minCloseDays'
);
// workaround for first eth tx hanging
associateTokenStartOfTests();
}
@@ -182,10 +159,10 @@ context(
this.minProposerBalance
);
let proposalDays = [
this.minCloseDays + 1,
this.maxCloseDays,
this.minCloseDays + 3,
this.minCloseDays + 2,
minCloseDays + 1,
maxCloseDays,
minCloseDays + 3,
minCloseDays + 2,
];
for (var index = 0; index < proposalDays.length; index++) {
cy.go_to_make_new_proposal(governanceProposalType.RAW);
@@ -227,6 +204,7 @@ context(
cy.navigate_to('validators');
cy.click_on_validator_from_list(0);
cy.staking_validator_page_add_stake('2');
cy.close_staking_dialog();
cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2');
@@ -297,10 +275,12 @@ context(
});
});
// Skipping test due to bug: #1320
it.skip('Newly created freeform proposals list - shows proposal participation - both met and not', function () {
createFreeformProposal(this.minProposerBalance);
cy.get_submitted_proposal_from_proposal_list()
// 3001-VOTE-071
it('Newly created freeform proposals list - shows proposal participation - both met and not', function () {
const proposalTitle = generateProposalTitle();
createFreeformProposal(this.minProposerBalance, proposalTitle);
cy.get_submitted_proposal_from_proposal_list(proposalTitle)
.as('submittedProposal')
.within(() => {
// 3001-VOTE-039
@@ -312,7 +292,7 @@ context(
.invoke('text')
.then((totalSupply) => {
let tokensRequiredToAchieveResult = parseFloat(
(totalSupply.replace(/,/g, '') * this.requiredParticipation) / 100
(totalSupply.replace(/,/g, '') * requiredParticipation) / 100
).toFixed(2);
cy.ensure_specified_unstaked_tokens_are_associated(
tokensRequiredToAchieveResult
@@ -321,16 +301,17 @@ context(
cy.get('@submittedProposal').within(() =>
cy.get(viewProposalButton).click()
);
cy.get_proposal_information_from_table('Participation met')
cy.get_proposal_information_from_table('Token participation met')
.contains('👍')
.should('be.visible');
cy.navigate_to('proposals');
cy.get('@submittedProposal').within(() =>
cy.get(voteStatus).should('have.text', 'Participation met')
cy.get(voteStatus).should('have.text', 'Set to pass')
);
});
});
// 3001-VOTE-055
it('Newly created raw proposal details - shows proposal title and full description', function () {
createRawProposal(this.minProposerBalance);
cy.get('@rawProposal').then((rawProposal) => {
@@ -357,6 +338,7 @@ context(
});
});
// 3001-VOTE-043
it('Newly created freeform proposal details - shows proposed and closing dates', function () {
const closingVoteHrs = '72';
const proposalTitle = generateProposalTitle();
@@ -399,6 +381,7 @@ context(
it('Newly created proposal details - shows default status set to fail', function () {
// 3001-VOTE-037
// 3001-VOTE-040
// 3001-VOTE-067
createRawProposal(this.minProposerBalance);
cy.get('@rawProposal').then((rawProposal) => {
cy.get_submitted_proposal_from_proposal_list(
@@ -413,15 +396,17 @@ context(
.should('be.visible');
// 3001-VOTE-062
// 3001-VOTE-040
// 3001-VOTE-070
cy.get_proposal_information_from_table('Token majority met')
.contains('👎')
.should('be.visible');
// 3001-VOTE-068
cy.get_proposal_information_from_table('Token participation met')
.contains('👎')
.should('be.visible');
});
// 3001-VOTE-080 3001-VOTE-090
// 3001-VOTE-080 3001-VOTE-090 3001-VOTE-069 3001-VOTE-072 3001-VOTE-073
it('Newly created proposal details - ability to vote for and against proposal - with minimum required tokens associated', function () {
createRawProposal(this.minProposerBalance);
cy.get('@rawProposal').then((rawProposal) => {
@@ -446,7 +431,7 @@ context(
.contains(votedDate)
.should('be.visible');
});
cy.get(proposalVoteProgressForPercentage)
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%')
.and('be.visible');
cy.get(proposalVoteProgressAgainstPercentage)
@@ -466,10 +451,10 @@ context(
.and('be.visible');
// 3001-VOTE-061
cy.get_proposal_information_from_table('Participation required')
.contains(`${this.requiredParticipation}%`)
.contains(`${requiredParticipation}%`)
.should('be.visible');
// 3001-VOTE-066
cy.get_proposal_information_from_table('Majority Required')
cy.get_proposal_information_from_table('Majority Required') // 3001-VOTE-073
.contains(`${parseFloat(this.requiredMajority).toFixed(2)}%`)
.should('be.visible');
cy.get_proposal_information_from_table('Number of voting parties')
@@ -512,7 +497,7 @@ context(
.invoke('text')
.then((totalSupply) => {
let tokensRequiredToAchieveResult = parseFloat(
(totalSupply.replace(/,/g, '') * this.requiredParticipation) / 100
(totalSupply.replace(/,/g, '') * requiredParticipation) / 100
).toFixed(2);
cy.ensure_specified_unstaked_tokens_are_associated(
tokensRequiredToAchieveResult
@@ -536,6 +521,11 @@ context(
cy.get(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.get_proposal_information_from_table(
'Total tokens voted percentage'
)
.should('have.text', '0.00%')
.and('be.visible');
cy.get_proposal_information_from_table('Tokens for proposal')
.should('have.text', tokensRequiredToAchieveResult)
.and('be.visible');
@@ -588,6 +578,7 @@ context(
);
});
// 3001-VOTE-006
it('Creating a proposal - proposal rejected - able to access rejected proposals', function () {
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
@@ -649,6 +640,7 @@ context(
cy.get(dialogCloseButton).click();
});
// 3002-PROP-009
it('Unable to create a proposal - when some but not enough tokens are associated', function () {
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance - 0.000001
@@ -669,7 +661,7 @@ context(
});
it('Unable to create a freeform proposal - when json parent section contains unexpected field', function () {
// 3001-VOTE-038
// 3001-VOTE-038 3002-PROP-013 3002-PROP-014
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
@@ -695,6 +687,9 @@ context(
'Invalid params: the transaction is malformed'
);
cy.get(dialogCloseButton).click();
cy.get(rawProposalData)
.invoke('val')
.should('contain', "i shouldn't be here");
});
it('Unable to create a freeform proposal - when json terms section contains unexpected field', function () {
@@ -792,6 +787,107 @@ context(
cy.contains('You voted: Against').should('be.visible');
});
// 3001-VOTE-006
it('Able to view enacted proposal', function () {
cy.createMarket();
cy.reload();
cy.wait_for_spinner();
cy.get(closedProposals).within(() => {
cy.get(proposalDetailsTitle).should(
'have.text',
'Add Lorem Ipsum market'
);
cy.get(proposalStatus).should('have.text', 'Enacted ');
cy.get(viewProposalButton).click();
});
cy.getByTestId('proposal-type').should('have.text', 'New market');
cy.get_proposal_information_from_table('State')
.contains('Enacted')
.and('be.visible');
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
});
// 3001-VOTE-047
it('Able to enact freeform proposal', function () {
const proposalTitle = 'Add New free form proposal with short enactment';
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
cy.sendWalletTxFreeFormProposal();
cy.navigate_to('proposals');
cy.reload();
cy.wait_for_spinner();
cy.contains(proposalTitle)
.parentsUntil('[data-testid="proposals-list-item"]')
.within(() => cy.get(viewProposalButton).click());
cy.get_proposal_information_from_table('State')
.contains('Open')
.and('be.visible');
cy.vote_for_proposal('for');
cy.get_proposal_information_from_table('State')
.contains('Enacted', epochTimeout)
.and('be.visible');
});
// 3001-VOTE-046 3001-VOTE-044 3001-VOTE-074 3001-VOTE-074
it('Able to enact proposal by voting', function () {
const proposalTitle = 'Add New proposal with short enactment';
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
cy.sendWalletTxUpdateNetworkProposal();
cy.navigate_to('proposals');
cy.reload();
cy.wait_for_spinner();
cy.contains(proposalTitle)
.parentsUntil('[data-testid="proposals-list-item"]')
.within(() => cy.get(viewProposalButton).click());
cy.get_proposal_information_from_table('State')
.contains('Open')
.and('be.visible');
cy.vote_for_proposal('for');
cy.get_proposal_information_from_table('State') // 3001-VOTE-047
.contains('Passed', txTimeout)
.and('be.visible');
cy.get_proposal_information_from_table('State')
.contains('Enacted', epochTimeout)
.and('be.visible');
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
cy.get(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
});
// 3001-VOTE-048 3001-VOTE-049
it('Able to fail proposal due to lack of participation', function () {
const proposalTitle = 'Add New free form proposal with short enactment';
cy.ensure_specified_unstaked_tokens_are_associated(
this.minProposerBalance
);
cy.sendWalletTxFreeFormProposal();
cy.navigate_to('proposals');
cy.reload();
cy.wait_for_spinner();
cy.contains(proposalTitle)
.parentsUntil('[data-testid="proposals-list-item"]')
.within(() => cy.get(viewProposalButton).click());
cy.get_proposal_information_from_table('State')
.contains('Open')
.and('be.visible');
cy.get_proposal_information_from_table('State') // 3001-VOTE-047
.contains('Declined', txTimeout)
.and('be.visible');
cy.get_proposal_information_from_table('Rejection reason')
.contains('PROPOSAL_ERROR_PARTICIPATION_THRESHOLD_NOT_REACHED')
.and('be.visible');
});
function createRawProposal(proposerBalance) {
if (proposerBalance)
cy.ensure_specified_unstaked_tokens_are_associated(proposerBalance);
@@ -1,3 +1,7 @@
const proposalListItem = '[data-testid="proposals-list-item"]';
const openProposals = '[data-testid="open-proposals"]';
const proposalType = '[data-testid="proposal-type"]';
const proposalDetails = '[data-testid="proposal-details"]';
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]';
const proposalValidationDeadline =
@@ -209,18 +213,15 @@ context(
);
});
// skipped because no markets available to select in capsule
it.skip('Able to submit update market proposal', function () {
const marketId =
'315a8e48db0a292c92b617264728048c82c20efc922c75fd292fc54e5c727c81';
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update asset proposal');
cy.get(newProposalTitle).type('Test update market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select(marketId);
cy.get(proposalMarketSelect).select('Test market 1');
cy.get('[data-testid="update-market-details"]').within(() => {
cy.get('dd').eq(0).should('have.text', 'Oranges Daily');
cy.get('dd').eq(1).should('have.text', 'ORANGES.24h');
cy.get('dd').eq(2).should('have.text', marketId);
cy.get('dd').eq(0).should('have.text', 'Test market 1');
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
cy.get('dd').eq(2).should('not.be.empty');
});
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
@@ -281,12 +282,28 @@ context(
});
it('Able to submit update asset proposal using min deadline', function () {
const assetId =
'ebcd94151ae1f0d39a4bde3b21a9c7ae81a80ea4352fb075a92e07608d9c953d';
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails();
cy.get(minVoteDeadline).click();
cy.get(minEnactDeadline).click();
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.wait_for_proposal_submitted();
cy.navigate_to('proposals');
cy.get(openProposals).within(() => {
cy.get(proposalType)
.contains('Update asset')
.parentsUntil(proposalListItem)
.within(() => {
cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029
cy.getByTestId('view-proposal-btn').click();
});
});
cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044
.invoke('text')
.should('not.be.empty');
});
it('Able to submit update asset proposal using max deadline', function () {
@@ -37,8 +37,8 @@ context(
function () {
cy.reload();
cy.wait_for_spinner();
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
cy.connectVegaWallet();
cy.vega_wallet_teardown();
cy.navigate_to('validators');
}
@@ -57,7 +57,16 @@ context(
//0005-ETXN-006
//0005-ETXN-003
//0005-ETXN-005
cy.staking_page_associate_tokens('2');
cy.staking_page_associate_tokens('2', { skipConfirmation: true });
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '0.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
// 0005-ETXN-002
cy.get(ethWalletAssociatedBalances, txTimeout)
@@ -98,6 +107,15 @@ context(
cy.staking_page_disassociate_tokens('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '2.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '0.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.get(ethWalletAssociatedBalances, txTimeout).should('not.exist');
cy.get(ethWalletTotalAssociatedBalance, txTimeout)
@@ -194,7 +212,19 @@ context(
// 1004-ASSO-024
// 1004-ASSO-023
cy.staking_page_associate_tokens('2', { type: 'contract' });
cy.staking_page_associate_tokens('2', {
type: 'contract',
skipConfirmation: true,
});
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '0.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.get(ethWalletAssociatedBalances, txTimeout)
.contains(vegaWalletPublicKeyShort)
@@ -210,7 +240,19 @@ context(
});
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
cy.staking_page_disassociate_tokens('1', { type: 'contract' });
cy.staking_page_disassociate_tokens('1', {
type: 'contract',
skipConfirmation: true,
});
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '2.00');
cy.validate_wallet_currency('Pending association', '1.00');
cy.validate_wallet_currency('Total associated after pending', '1.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.get(ethWalletAssociatedBalances, txTimeout)
.contains(vegaWalletPublicKeyShort)
@@ -286,6 +328,35 @@ context(
});
// 1004-ASSO-004
it('Pending association outside of app is shown', function () {
cy.vega_wallet_associate('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '0.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '2.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.validate_wallet_currency('Associated', '2.00');
});
it('Disassociation outside of app is shown', function () {
cy.staking_page_associate_tokens('2');
cy.validate_wallet_currency('Associated', '2.00');
cy.vega_wallet_disassociate('2');
cy.getByTestId('currency-title', txTimeout).should(
'have.length.above',
3
);
cy.validate_wallet_currency('Associated', '2.00');
cy.validate_wallet_currency('Pending association', '2.00');
cy.validate_wallet_currency('Total associated after pending', '0.00');
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
cy.validate_wallet_currency('Associated', '0.00');
});
it('Able to associate tokens to different public key of connected vega wallet', function () {
cy.get(ethWalletAssociateButton).first().click();
cy.get(associateWalletRadioButton).click();
@@ -310,6 +381,7 @@ context(
'vegaWalletPublicKey2Short'
)} can now participate in governance and nominate a validator with your associated $VEGA.`
);
cy.staking_page_disassociate_all_tokens();
});
});
}
@@ -20,7 +20,10 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
it('Able to connect public key via wallet', function () {
verifyConnectedToPubKey();
cy.getByTestId('currency-title').should('contain.text', 'USDC (fake)');
cy.getByTestId('currency-title', Cypress.env('epochTimeout')).should(
'contain.text',
'USDC (fake)'
);
});
it('Able to connect public key using url', function () {
@@ -321,6 +321,8 @@ context(
'200000',
vegaWalletPublicKey
);
cy.reload();
cy.wait_for_spinner();
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
});
@@ -46,6 +46,6 @@ Cypress.Commands.add('verify_page_header', (text) => {
});
Cypress.Commands.add('wait_for_spinner', () => {
cy.get(navigation.pageSpinner).should('exist');
cy.get(navigation.pageSpinner, Cypress.env('epochTimeout')).should('exist');
cy.get(navigation.pageSpinner, { timeout: 20000 }).should('not.exist');
});
+1
View File
@@ -6,6 +6,7 @@ import './governance.functions.js';
import './wallet-eth.functions.js';
import './wallet-teardown.functions.js';
import './wallet-vega.functions.js';
import './proposal.functions.js';
import registerCypressGrep from '@cypress/grep';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
@@ -0,0 +1,103 @@
import { addSeconds, millisecondsToSeconds } from 'date-fns';
const walletName = Cypress.env('vegaWalletName');
const walletPubKey = Cypress.env('vegaWalletPublicKey');
const walletLocation = Cypress.env('vegaWalletLocation');
const walletPassphraseFile = './src/fixtures/wallet/passphrase';
Cypress.Commands.add('sendWalletTxUpdateNetworkProposal', () => {
const MIN_CLOSE_SEC = 8;
const MIN_ENACT_SEC = 5;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
cy.exec(
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
"proposalSubmission": {
"rationale": {
"title": "Add New proposal with short enactment",
"description": "E2E enactment test"
},
"terms": {
"updateNetworkParameter": {
"changes": {
"key": "governance.proposal.updateNetParam.minProposerBalance",
"value": "2"
}
},
"closingTimestamp": ${closingTimestamp},
"enactmentTimestamp": ${enactmentTimestamp}
}
}
}' --home ${walletLocation}`,
{ failOnNonZeroExit: false }
)
.its('stderr')
.should('contain', '');
});
Cypress.Commands.add('sendWalletTxUpdateAssetProposal', () => {
const MIN_CLOSE_SEC = 8;
const MIN_ENACT_SEC = 5;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
cy.exec(
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
"proposalSubmission": {
"rationale": {
"title": "Update Asset set to fail",
"description": "E2E fail test"
},
"terms": {
"updateAsset": {
"assetId": "ebcd94151ae1f0d39a4bde3b21a9c7ae81a80ea4352fb075a92e07608d9c953d",
"changes": {
"quantum": "1",
"erc20": {
"withdrawThreshold": "10",
"lifetimeLimit": "10"
}
}
},
"closingTimestamp": ${closingTimestamp},
"enactmentTimestamp": ${enactmentTimestamp}
}
}
}' --home ${walletLocation}`,
{ failOnNonZeroExit: false }
)
.its('stderr')
.should('contain', '');
});
Cypress.Commands.add('sendWalletTxFreeFormProposal', () => {
const MIN_CLOSE_SEC = 5;
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
cy.exec(
`vegawallet transaction send --wallet ${walletName} --pubkey ${walletPubKey} -p ${walletPassphraseFile} --network DV '{
"proposalSubmission": {
"rationale": {
"title": "Add New free form proposal with short enactment",
"description": "E2E enactment test"
},
"terms": {
"newFreeform": {},
"closingTimestamp": ${closingTimestamp}
}
}
}' --home ${walletLocation}`,
{ failOnNonZeroExit: false }
)
.its('stderr')
.should('contain', '');
});
+31 -10
View File
@@ -58,6 +58,8 @@ Cypress.Commands.add('staking_validator_page_remove_stake', (stake) => {
Cypress.Commands.add('staking_page_associate_tokens', (amount, options) => {
let approve = options && options.approve ? options.approve : false;
let type = options && options.type ? options.type : 'wallet';
let skipConfirmation =
options && options.skipConfirmation ? options.skipConfirmation : false;
cy.highlight(`Associating ${amount} tokens from ${type}`);
cy.get(ethWalletAssociateButton).first().click();
@@ -79,16 +81,19 @@ Cypress.Commands.add('staking_page_associate_tokens', (amount, options) => {
);
}
cy.get(tokenSubmitButton, txTimeout).should('be.enabled').click();
cy.contains(
`Associating with Vega key. Waiting for ${Cypress.env(
'blockConfirmations'
)} more confirmations..`,
txTimeout
).should('be.visible');
cy.contains(
'can now participate in governance and nominate a validator',
txTimeout
).should('be.visible');
if (!skipConfirmation) {
cy.contains(
`Associating with Vega key. Waiting for ${Cypress.env(
'blockConfirmations'
)} more confirmations..`,
txTimeout
).should('be.visible');
cy.contains(
'can now participate in governance and nominate a validator',
txTimeout
).should('be.visible');
}
});
Cypress.Commands.add('staking_page_disassociate_tokens', (amount, options) => {
@@ -216,3 +221,19 @@ Cypress.Commands.add('close_staking_dialog', () => {
cy.get('a').should('have.text', 'Back to Staking').click();
});
});
Cypress.Commands.add(
'validate_wallet_currency',
(currencyTitle, expectedAmount) => {
cy.get("[data-testid='currency-title']")
.contains(currencyTitle)
.parent()
.parent()
.within(() => {
cy.getByTestId('currency-value', txTimeout).should(
'have.text',
expectedAmount
);
});
}
);
@@ -8,7 +8,6 @@ import {
import { ethers, Wallet } from 'ethers';
const vegaWalletContainer = '[data-testid="vega-wallet"]';
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
@@ -43,6 +42,11 @@ before('Vega wallet teardown prep', function () {
});
});
beforeEach(function () {
cy.wrap(this.stakingBridgeContract).as('stakingBridgeContract');
cy.wrap(this.vestingContract).as('vestingContract');
});
Cypress.Commands.add('deposit_asset', function (assetEthAddress) {
cy.get('@signer', { log: false }).then((signer) => {
// Approve asset
@@ -91,29 +95,18 @@ Cypress.Commands.add('faucet_asset', function (assetEthAddress) {
});
Cypress.Commands.add('vega_wallet_teardown', function () {
cy.get('body').then(($body) => {
if ($body.find('[data-testid="eth-wallet-associated-balances"]').length) {
cy.vega_wallet_teardown_vesting(this.vestingContract);
cy.vega_wallet_teardown_staking(this.stakingBridgeContract);
}
});
cy.get(vegaWalletContainer).within(() => {
cy.get(vegaWalletAssociatedBalance)
.first()
.invoke('text')
.then((balance) => {
if (balance != '0.00') {
cy.vega_wallet_teardown_vesting(this.vestingContract);
cy.vega_wallet_teardown_staking(this.stakingBridgeContract);
}
});
cy.get('[data-testid="associated-amount"]', { timeout: 30000 }).should(
'contain.text',
'0.00'
);
});
cy.get(vegaWalletContainer).within(() => {
cy.get(vegaWalletAssociatedBalance, { timeout: transactionTimeout }).should(
'contain',
'0.00',
{ timeout: transactionTimeout }
);
});
});
Cypress.Commands.add(
@@ -176,6 +169,22 @@ Cypress.Commands.add('vega_wallet_teardown_vesting', (vestingContract) => {
});
});
Cypress.Commands.add('vega_wallet_associate', (amount) => {
amount = amount + '0'.repeat(18);
cy.highlight('Associating tokens');
cy.get('@stakingBridgeContract').then((stakingBridgeContract) => {
stakingBridgeContract.stake(amount, vegaWalletPubKey);
});
});
Cypress.Commands.add('vega_wallet_disassociate', (amount) => {
amount = amount + '0'.repeat(18);
cy.highlight('Disassociating tokens');
cy.get('@stakingBridgeContract').then((stakingBridgeContract) => {
stakingBridgeContract.remove_stake(amount, vegaWalletPubKey);
});
});
Cypress.Commands.add('wait_for_transaction', (tx) => {
cy.wrap(tx.wait(1).catch(cy.log), { timeout: transactionTimeout });
});
+1 -1
View File
@@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=TESTNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://api.n10.testnet.vega.xyz/graphql
NX_VEGA_URL=https://api.n08.testnet.vega.xyz/graphql
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
+6 -1
View File
@@ -12,5 +12,10 @@ export default {
moduleNameMapper: {
'^d3-(.*)$': `d3-$1/dist/d3-$1`,
},
collectCoverageFrom: ['**/*.{ts,tsx}', '!**/node_modules/**'],
collectCoverageFrom: [
'**/*.{ts,tsx}',
'!**/node_modules/**',
'!**/__generated__/**',
'!**/__generated___/**',
],
};
+3 -2
View File
@@ -8,6 +8,7 @@ import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info';
import { BalanceManager } from './components/balance-manager';
import { EthWallet } from './components/eth-wallet';
import { AppLayout } from './components/page-templates/app-layout';
import { TemplateSidebar } from './components/page-templates/template-sidebar';
import { TransactionModal } from './components/transactions-modal';
import { VegaWallet } from './components/vega-wallet';
@@ -88,14 +89,14 @@ const Web3Container = ({
<AppLoader>
<BalanceManager>
<>
<div className="app w-full max-w-[1500px] mx-auto grid grid-rows-[min-content_min-content_1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<AppLayout>
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 border-t border-neutral-700">
<NetworkInfo />
</footer>
</div>
</AppLayout>
<VegaWalletDialogs />
<TransactionModal />
<WithdrawalDialog />
@@ -2,20 +2,24 @@ import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useEffect } from 'react';
import { useAppState } from '../../contexts/app-state/app-state-context';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { useGetAssociationBreakdown } from '../../hooks/use-get-association-breakdown';
import { useGetUserTrancheBalances } from '../../hooks/use-get-user-tranche-balances';
import { useBalances } from '../../lib/balances/balances-store';
import type { ReactElement } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
interface BalanceManagerProps {
children: React.ReactElement;
children: ReactElement;
}
export const BalanceManager = ({ children }: BalanceManagerProps) => {
const contracts = useContracts();
const { pubKey } = useVegaWallet();
const { account } = useWeb3React();
const {
appState: { decimals },
@@ -23,6 +27,20 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
const { updateBalances: updateStoreBalances } = useBalances();
const { config } = useEthereumConfig();
const numberOfConfirmations = config?.confirmations || 0;
useListenForAssociationEvents(
contracts?.staking.contract,
pubKey,
numberOfConfirmations
);
useListenForAssociationEvents(
contracts?.vesting.contract,
pubKey,
numberOfConfirmations
);
const getUserTrancheBalances = useGetUserTrancheBalances(
account || '',
contracts?.vesting
@@ -34,7 +52,7 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
);
// update balances on connect to Ethereum
React.useEffect(() => {
useEffect(() => {
const updateBalances = async () => {
if (!account || !config) return;
try {
@@ -75,13 +93,13 @@ export const BalanceManager = ({ children }: BalanceManagerProps) => {
]);
// This use effect hook is very expensive and is kept separate to prevent expensive reloading of data.
React.useEffect(() => {
useEffect(() => {
if (account) {
getUserTrancheBalances();
}
}, [account, getUserTrancheBalances]);
React.useEffect(() => {
useEffect(() => {
if (account) {
getAssociationBreakdown();
}
@@ -72,8 +72,8 @@ export const LockedProgress = ({
unlocked,
leftLabel,
rightLabel,
leftColor = Colors.vega.pink,
rightColor = Colors.vega.green,
leftColor = Colors.vega.pink.DEFAULT,
rightColor = Colors.vega.green.DEFAULT,
decimals = 2,
}: LockedProgressProps) => {
const lockedPercentage = React.useMemo(() => {
@@ -18,7 +18,7 @@ export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
<AppNavLink
name={
<NavDropdownMenuTrigger
className="w-auto text-capMenu"
className="w-auto flex items-center"
data-testid="state-trigger"
onClick={() => setOpen(!isOpen)}
>
@@ -38,6 +38,7 @@ export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
name={t(r.name)}
path={r.path}
navbarTheme={'inherit'}
subNav={true}
end={true}
fullWidth={true}
/>
+12 -7
View File
@@ -13,6 +13,7 @@ interface AppNavLinkProps {
target?: HTMLAttributeAnchorTarget;
end?: boolean;
fullWidth?: boolean;
subNav?: boolean;
}
export const AppNavLink = ({
@@ -23,28 +24,32 @@ export const AppNavLink = ({
testId,
end = false,
fullWidth = false,
subNav = false,
}: AppNavLinkProps) => {
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
'bg-black': navbarTheme === 'yellow',
});
const borderClasses = classNames(
'absolute h-0.5 w-full bottom-[-1px] left-0',
{
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
'bg-black': navbarTheme === 'yellow',
}
);
return (
<NavLink
key={path}
data-testid={testId}
to={{ pathname: path }}
className={getNavLinkClassNames(navbarTheme, fullWidth)}
className={getNavLinkClassNames(navbarTheme, fullWidth, subNav)}
target={target}
end={end}
>
{({ isActive }) => {
return (
<>
<div className={subNav ? 'inline-block relative pb-1' : undefined}>
{name}
{isActive && (
<span data-testid="link-active" className={borderClasses} />
)}
</>
</div>
);
}}
</NavLink>
+1 -2
View File
@@ -22,9 +22,8 @@ const renderComponent = (initialEntries?: string[]) => {
};
describe('nav', () => {
it('Renders title and logo with link to home', () => {
it('Renders logo with link to home', () => {
renderComponent();
expect(screen.getByText('Governance')).toBeInTheDocument();
expect(screen.getByTestId('logo-link')).toHaveProperty(
'href',
'http://localhost/'
+10 -4
View File
@@ -3,7 +3,8 @@ import { NetworkSwitcher } from '@vegaprotocol/environment';
import { useEffect, useState } from 'react';
import { TOP_LEVEL_ROUTES } from '../../routes/routes';
import { useTranslation } from 'react-i18next';
import vegaWhite from '../../images/vega_white.png';
import logoWhiteText from '../../images/logo-white-text.png';
import logoBlackText from '../../images/logo-black-text.png';
import debounce from 'lodash/debounce';
import { NavDrawer } from './nav-draw';
import { Nav as ToolkitNav } from '@vegaprotocol/ui-toolkit';
@@ -46,14 +47,19 @@ export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
navbarTheme={navbarTheme}
icon={
<Link to="/" data-testid="logo-link">
<img alt="Vega" src={vegaWhite} height={30} width={30} />
<img
alt="Vega"
src={navbarTheme === 'yellow' ? logoBlackText : logoWhiteText}
height={30}
width={250}
/>
</Link>
}
title={t('Governance')}
title={undefined}
titleContent={<NetworkSwitcher />}
>
{isDesktop ? (
<nav className="flex items-center flex-1 px-2">
<nav className="flex items-center flex-1 px-4">
{TOP_LEVEL_ROUTES.map((r) => (
<AppNavLink
key={r.path}
@@ -0,0 +1,21 @@
import classNames from 'classnames';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { ReactNode } from 'react';
interface AppLayoutProps {
children: ReactNode;
}
export const AppLayout = ({ children }: AppLayoutProps) => {
const { isReadOnly } = useVegaWallet();
const AppLayoutClasses = classNames(
'app w-full max-w-[1500px] mx-auto grid min-h-full',
'border-neutral-700 lg:border-l lg:border-r',
'lg:text-body-large',
{
'grid-rows-[repeat(2,min-content)_1fr_min-content]': !isReadOnly,
'grid-rows-[repeat(3,min-content)_1fr_min-content]': isReadOnly,
}
);
return <div className={AppLayoutClasses}>{children}</div>;
};
@@ -1,5 +1,9 @@
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
import {
ViewingAsBanner,
AnnouncementBanner,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import React from 'react';
@@ -15,12 +19,18 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
return (
<>
<AnnouncementBanner>
<div className="font-alpha calt uppercase text-center text-lg text-white">
<span className="pr-4">The Mainnet sims are live!</span>
<ExternalLink href="https://fairground.wtf/">
Come help stress test the network
</ExternalLink>
</div>
</AnnouncementBanner>
<Nav navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
{isReadOnly ? (
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
) : (
<div />
)}
) : null}
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[1fr] lg:grid-cols-[1fr_450px]">
<main className="col-start-1 p-4">{children}</main>
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
@@ -25,6 +25,8 @@ import { usePollForDelegations } from './hooks';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
import { toBigNum } from '@vegaprotocol/react-helpers';
import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager';
import { StakingEventType } from '../../hooks/use-get-association-breakdown';
export const VegaWallet = () => {
const { t } = useTranslation();
@@ -119,6 +121,10 @@ interface VegaWalletConnectedProps {
}
const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
const pendingBalances = usePendingBalancesStore(
(state) => state.pendingBalances
);
const { t } = useTranslation();
const {
appDispatch,
@@ -126,6 +132,27 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
} = useAppState();
const { delegations, currentStakeAvailable, delegatedNodes, accounts } =
usePollForDelegations();
const amountRemoved = BigNumber.sum.apply(null, [
new BigNumber(0),
...pendingBalances
.filter(({ event }) => event === StakingEventType.Stake_Removed)
.map(({ args }) => toBigNum(args?.[1].toString(), decimals)),
]);
const amountAdded = BigNumber.sum.apply(null, [
new BigNumber(0),
...pendingBalances
.filter(({ event }) => event === StakingEventType.Stake_Deposited)
.map(({ args }) => toBigNum(args?.[1].toString(), decimals)),
]);
const totalPending = React.useMemo(
() => amountRemoved.plus(amountAdded),
[amountAdded, amountRemoved]
);
const pendingStakeAmount = React.useMemo(
() => currentStakeAvailable.plus(amountAdded).minus(amountRemoved),
[amountAdded, amountRemoved, currentStakeAvailable]
);
const unstaked = React.useMemo(() => {
const totalDelegated = delegations.reduce<BigNumber>(
@@ -161,6 +188,26 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
symbol="VEGA"
balance={currentStakeAvailable}
/>
{totalPending.eq(0) ? null : (
<>
<WalletCardAsset
image={vegaWhite}
decimals={decimals}
name="VEGA"
subheading={t('Pending association')}
symbol="VEGA"
balance={totalPending}
/>
<WalletCardAsset
image={vegaWhite}
decimals={decimals}
name="VEGA"
subheading={t('Total associated after pending')}
symbol="VEGA"
balance={pendingStakeAmount}
/>
</>
)}
<div data-testid="vega-wallet-balance-unstaked">
<WalletCardRow label={t('unstaked')} value={unstaked} />
</div>
+7 -4
View File
@@ -30,9 +30,12 @@ export function useAnimateValue(
) {
elRef.current?.animate(
[
{ backgroundColor: customColors.vega.pink, color: colors.white },
{
backgroundColor: customColors.vega.pink,
backgroundColor: customColors.vega.pink.DEFAULT,
color: colors.white,
},
{
backgroundColor: customColors.vega.pink.DEFAULT,
color: colors.white,
offset: 0.8,
},
@@ -53,11 +56,11 @@ export function useAnimateValue(
elRef.current?.animate(
[
{
backgroundColor: customColors.vega.green,
backgroundColor: customColors.vega.green.DEFAULT,
color: colors.white,
},
{
backgroundColor: customColors.vega.green,
backgroundColor: customColors.vega.green.DEFAULT,
color: colors.white,
offset: 0.8,
},
@@ -11,6 +11,11 @@ import { useAppState } from '../contexts/app-state/app-state-context';
import BigNumber from 'bignumber.js';
import { useBalances } from '../lib/balances/balances-store';
export enum StakingEventType {
Stake_Removed = 'Stake_Removed',
Stake_Deposited = 'Stake_Deposited',
}
export function useGetAssociationBreakdown(
ethAddress: string,
staking: StakingBridge,
@@ -63,8 +68,8 @@ function combineStakeEventsByVegaKey(
const res = events.reduce((obj, e) => {
const vegaKey = e.args?.vega_public_key;
const amount = parseEventAmount(e, decimals);
const isDeposit = e.event === 'Stake_Deposited';
const isRemove = e.event === 'Stake_Removed';
const isDeposit = e.event === StakingEventType.Stake_Deposited;
const isRemove = e.event === StakingEventType.Stake_Removed;
if (!isDeposit && !isRemove) return obj;
@@ -0,0 +1,102 @@
import { useListenForPendingEthEvents } from './use-listen-for-pending-eth-events';
import { renderHook, cleanup, waitFor } from '@testing-library/react';
import type { Contract, EventFilter, Event } from 'ethers';
let contract: Contract;
let filter: EventFilter;
let addPendingTxs: (event: Event[]) => void;
let removePendingTx: (event: Event) => void;
let resetPendingTxs: () => void;
beforeEach(() => {
contract = {
on: jest.fn(),
off: jest.fn(),
queryFilter: jest.fn().mockResolvedValue([]),
} as unknown as Contract;
filter = {} as EventFilter;
addPendingTxs = jest.fn();
removePendingTx = jest.fn();
resetPendingTxs = jest.fn();
});
jest.mock('@web3-react/core', () => ({
useWeb3React: () => ({
provider: {
getBlockNumber: jest.fn().mockResolvedValue(1),
},
}),
}));
describe('useListenForPendingEthEvents', () => {
it('listens for events on contract', async () => {
renderHook(() =>
useListenForPendingEthEvents(
1,
contract,
filter,
addPendingTxs,
removePendingTx,
resetPendingTxs
)
);
expect(contract.on).toHaveBeenCalledWith(filter, expect.any(Function));
cleanup();
expect(contract.off).toHaveBeenCalledWith(filter, expect.any(Function));
});
it('waits for correct number of confirmations before removing tx from pending txs', async () => {
renderHook(() => {
useListenForPendingEthEvents(
2,
contract,
filter,
addPendingTxs,
removePendingTx,
resetPendingTxs
);
});
const listener = (contract.on as jest.Mock).mock.calls[0][1];
const event = {
getTransaction: jest.fn().mockResolvedValue({
wait: jest.fn().mockResolvedValue({}),
}),
} as unknown as Event;
listener(null, null, null, event);
expect(addPendingTxs).toHaveBeenCalledWith([event]);
expect(removePendingTx).not.toHaveBeenCalled();
await waitFor(() => {
expect(removePendingTx).toHaveBeenCalledWith(event);
});
});
it('gets existing transactions', async () => {
const event = {
getTransaction: jest.fn().mockResolvedValue({
wait: jest.fn().mockResolvedValue(null),
}),
} as unknown as Event;
contract.queryFilter = jest.fn().mockResolvedValue([event]);
renderHook(() => {
useListenForPendingEthEvents(
2,
contract,
filter,
addPendingTxs,
removePendingTx,
resetPendingTxs
);
});
await waitFor(() => {
expect(contract.queryFilter).toHaveBeenCalledWith(filter, -1);
expect(addPendingTxs).toHaveBeenCalledWith([event]);
});
});
});
@@ -0,0 +1,103 @@
import { useWeb3React } from '@web3-react/core';
import { useCallback, useEffect } from 'react';
import * as Sentry from '@sentry/react';
import type { Contract, Event, EventFilter } from 'ethers';
export const useListenForPendingEthEvents = (
numberOfConfirmations: number,
contract: Contract | undefined,
filter: EventFilter | null,
addPendingTxs: (event: Event[]) => void,
removePendingTx: (event: Event) => void,
resetPendingTxs: () => void
) => {
const { provider } = useWeb3React();
/**
* Add listener for the ethereum events on the contract passed in for the filter passed in.
* Push the event into the store and wait for the correct number of confirmations before removing it.
*/
useEffect(() => {
if (!contract || !filter) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const listener = async (...args: any[]) => {
try {
const event = args[3] as Event;
addPendingTxs([event]);
const tx = await event.getTransaction();
await tx.wait(numberOfConfirmations);
removePendingTx(event);
} catch (e) {
Sentry.captureException(`Error listening for pending eth events ${e}`);
}
};
contract.on(filter, listener);
return () => {
contract.off(filter, listener);
};
}, [addPendingTxs, contract, filter, numberOfConfirmations, removePendingTx]);
/**
* Get all transactions that exist on the blockchain but have yet to reach the number of confirmations
*/
const getExistingTransactions = useCallback(async () => {
const blockNumber = (await provider?.getBlockNumber()) || 0;
if (!filter || !contract) {
return [];
}
try {
return await contract.queryFilter(
filter,
blockNumber - numberOfConfirmations
);
} catch (e) {
Sentry.captureException(`Error getting existing transactions ${e}`);
return [];
}
}, [contract, filter, numberOfConfirmations, provider]);
const waitForExistingTransactions = useCallback(
(events: Event[], numberOfConfirmations: number) => {
events.map(async (event) => {
try {
const tx = await event.getTransaction();
await tx.wait(Math.max(numberOfConfirmations, 0));
removePendingTx(event);
} catch (e) {
Sentry.captureException(
`Error waiting for existing transactions ${e}`
);
}
});
},
[removePendingTx]
);
useEffect(() => {
let cancelled = false;
resetPendingTxs();
getExistingTransactions().then((events) => {
if (!cancelled) {
addPendingTxs([...events]);
waitForExistingTransactions([...events], numberOfConfirmations);
}
});
return () => {
cancelled = true;
};
}, [
addPendingTxs,
getExistingTransactions,
numberOfConfirmations,
resetPendingTxs,
waitForExistingTransactions,
]);
};
@@ -0,0 +1,56 @@
import { useMemo } from 'react';
import { usePendingBalancesStore } from './use-pending-balances-manager';
import type { Contract } from 'ethers';
import { useListenForPendingEthEvents } from './use-listen-for-pending-eth-events';
import { prepend0x } from '@vegaprotocol/smart-contracts';
export const useListenForStakingEvents = (
contract: Contract | undefined,
vegaPublicKey: string | null,
numberOfConfirmations: number
) => {
const { addPendingTxs, removePendingTx, resetPendingTxs } =
usePendingBalancesStore((state) => ({
addPendingTxs: state.addPendingTxs,
removePendingTx: state.removePendingTx,
resetPendingTxs: state.resetPendingTxs,
}));
const addFilter = useMemo(
() =>
vegaPublicKey && contract
? contract.filters.Stake_Deposited(null, null, prepend0x(vegaPublicKey))
: null,
[contract, vegaPublicKey]
);
const removeFilter = useMemo(
() =>
vegaPublicKey && contract
? contract.filters.Stake_Removed(null, null, prepend0x(vegaPublicKey))
: null,
[contract, vegaPublicKey]
);
/**
* Listen for all add stake events
*/
useListenForPendingEthEvents(
numberOfConfirmations,
contract,
addFilter,
addPendingTxs,
removePendingTx,
resetPendingTxs
);
/**
* Listen for all remove stake events
*/
useListenForPendingEthEvents(
numberOfConfirmations,
contract,
removeFilter,
addPendingTxs,
removePendingTx,
resetPendingTxs
);
};
@@ -0,0 +1,76 @@
import { act } from '@testing-library/react-hooks';
import { usePendingBalancesStore } from './use-pending-balances-manager';
import type { Event } from 'ethers';
afterEach(() => {
usePendingBalancesStore.setState((state) => ({
...state,
pendingBalances: [],
}));
});
const event1 = { transactionHash: 'tx1' } as Event;
const event2 = { transactionHash: 'tx2' } as Event;
describe('usePendingBalancesStore', () => {
it('should add new events to the pendingBalances state and remove duplicates', () => {
const { addPendingTxs } = usePendingBalancesStore.getState();
const duplicateEvent = { transactionHash: 'tx1' } as Event;
act(() => {
addPendingTxs([event1, duplicateEvent]);
});
expect(usePendingBalancesStore.getState().pendingBalances).toEqual([
event1,
]);
act(() => {
addPendingTxs([event2]);
});
expect(usePendingBalancesStore.getState().pendingBalances).toEqual([
event1,
event2,
]);
});
it('should remove a specific event from the pendingBalances state', () => {
const { addPendingTxs, removePendingTx } =
usePendingBalancesStore.getState();
const eventToRemove = { transactionHash: 'tx1' } as Event;
act(() => {
addPendingTxs([eventToRemove, event2]);
});
expect(usePendingBalancesStore.getState().pendingBalances).toEqual([
eventToRemove,
event2,
]);
act(() => {
removePendingTx(eventToRemove);
});
expect(usePendingBalancesStore.getState().pendingBalances).toEqual([
event2,
]);
});
it('should reset the pendingBalances state', () => {
const { addPendingTxs, resetPendingTxs } =
usePendingBalancesStore.getState();
act(() => {
addPendingTxs([event1, event2]);
});
expect(usePendingBalancesStore.getState().pendingBalances).toEqual([
event1,
event2,
]);
act(() => {
resetPendingTxs();
});
expect(usePendingBalancesStore.getState().pendingBalances).toEqual([]);
});
});
@@ -0,0 +1,35 @@
import type { Event } from 'ethers';
import uniqBy from 'lodash/uniqBy';
import create from 'zustand';
export type PendingTxsStore = {
pendingBalances: Event[];
addPendingTxs: (event: Event[]) => void;
removePendingTx: (event: Event) => void;
resetPendingTxs: () => void;
};
export const usePendingBalancesStore = create<PendingTxsStore>((set, get) => ({
pendingBalances: [],
addPendingTxs: (event: Event[]) => {
set({
pendingBalances: uniqBy(
[...get().pendingBalances, ...event],
'transactionHash'
),
});
},
removePendingTx: (event: Event) => {
set({
pendingBalances: [
...get().pendingBalances.filter(
({ transactionHash }) => transactionHash !== event.transactionHash
),
],
});
},
resetPendingTxs: () => {
set({ pendingBalances: [] });
},
}));
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -102,7 +102,7 @@ export const VoteDetails = ({
</table>
</section>
)}
<section>
<section data-testid="votes-table">
<SubHeading title={t('tokenVotes')} />
<p>
<span>
@@ -177,7 +177,7 @@ interface TotalPenaltiesRendererProps {
performanceScore: string;
performancePenalty: string;
overstakedAmount: string;
overstakedPenalty: string;
overstakingPenalty: string;
totalPenalties: string;
};
}
@@ -192,20 +192,10 @@ export const TotalPenaltiesRenderer = ({
description={
<>
<div>
<span>
{t('performancePenalty')}: {data.performancePenalty}
</span>
<span className="pl-2">
({t('score')} {data.performanceScore})
</span>
{t('performancePenalty')}: {data.performancePenalty}
</div>
<div>
<span>
{t('overstakedPenalty')}: {data.overstakedPenalty}
</span>
<span className="pl-2">
({t('overstaked')} {data.overstakedAmount})
</span>
{t('overstakedPenalty')}: {data.overstakingPenalty}
</div>
<div>
{t('totalPenalties')}:{' '}
@@ -1,6 +1,6 @@
import { Dialog, Icon, Intent } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import Routes from '../../routes';
import type { StakeAction } from './staking-form';
import { Actions, RemoveType } from './staking-form';
@@ -23,6 +23,7 @@ export const StakeSuccess = ({
toggleDialog,
}: StakeSuccessProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const isAdd = action === Actions.Add;
const title = isAdd
? t('stakeAddSuccessTitle', { amount })
@@ -44,7 +45,15 @@ export const StakeSuccess = ({
<div>
<p>{message}</p>
<p>
<Link className="underline" to={Routes.VALIDATORS}>
<Link
className="underline"
to={Routes.VALIDATORS}
onClick={(event) => {
event.preventDefault();
toggleDialog();
setTimeout(() => navigate(Routes.VALIDATORS), 0);
}}
>
{t('backToStaking')}
</Link>
</p>
@@ -29,7 +29,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Locked')}</span>
<ProgressBar
width={220}
color={Colors.vega.pink}
color={Colors.vega.pink.DEFAULT}
percentage={lockedPercentage}
/>
<span className="tranches__progress-numbers">
@@ -40,7 +40,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Redeemed')}</span>
<ProgressBar
width={220}
color={Colors.vega.green}
color={Colors.vega.green.DEFAULT}
percentage={removedPercentage}
/>
<span className="tranches__progress-numbers">
@@ -34,10 +34,10 @@ export const VestingChart = () => {
<AreaChart data={data}>
<defs>
{[
['pink', Colors.vega.pink],
['green', Colors.vega.green],
['pink', Colors.vega.pink.DEFAULT],
['green', Colors.vega.green.DEFAULT],
['orange', Colors.warning],
['yellow', Colors.vega.yellow],
['yellow', Colors.vega.yellow.DEFAULT],
].map(([key, color]) => (
<linearGradient key={key} id={key} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity={0.85} />
@@ -97,7 +97,7 @@ export const VestingChart = () => {
dot={false}
type="linear"
dataKey="team"
stroke={Colors.vega.pink}
stroke={Colors.vega.pink.DEFAULT}
fill="url(#pink)"
yAxisId={0}
strokeWidth={2}
@@ -109,7 +109,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="earlyInvestors"
stroke={Colors.vega.green}
stroke={Colors.vega.green.DEFAULT}
fill="url(#green)"
yAxisId={0}
strokeWidth={2}
@@ -121,7 +121,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="publicSale"
stroke={Colors.vega.yellow}
stroke={Colors.vega.yellow.DEFAULT}
fill="url(#yellow)"
yAxisId={0}
strokeWidth={2}
+6
View File
@@ -13,3 +13,9 @@
@apply mb-2 text-neutral-400;
}
}
@layer components {
.border-default {
@apply border-vega-dark-200;
}
}
+222 -74
View File
@@ -21,15 +21,127 @@ const orderUpdatedAt = 'updatedAt';
const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]';
const txTimeout = Cypress.env('txTimeout');
const btcName = 'BTC (local)';
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName =
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
const vegaName =
'Vegab4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b - VEGA';
const btcSymbol = 'tBTC';
const vegaSymbol = 'VEGA';
const usdcSymbol = 'fUSDC';
const toastContent = 'toast-content';
const ordersTab = 'Orders';
const depositsTab = 'Deposits';
const toastCloseBtn = 'toast-close';
const price = '390';
const size = '0.0005';
const newPrice = '200';
const completeWithdrawalBtn = 'complete-withdrawal';
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
describe('capsule - without MultiSign', { tags: '@slow' }, () => {
before(() => {
cy.createMarket();
cy.get('@markets').then((markets) => {
cy.wrap(markets[0]).as('market');
});
});
beforeEach(() => {
cy.setVegaWallet();
});
it('can deposit', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
// 1001-DEPO-001
// 1001-DEPO-002
// 1001-DEPO-003
// 1001-DEPO-005
// 1001-DEPO-006
// 1001-DEPO-007
// 1001-DEPO-008
// 1001-DEPO-009
// 1001-DEPO-010
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('10');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 10.00 ${btcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
cy.get('[col-id="amount"]').should('have.text', '10.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
it('can not withdrawal because of no MultiSign', function () {
// 1002-WITH-022
// 1002-WITH-023
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Funds unlocked'
);
cy.getByTestId('tab-withdrawals').within(() => {
cy.get('.ag-center-cols-container')
.children()
.first()
.within(() => {
cy.get('[col-id="status"]').should('contain.text', 'Pending');
});
});
cy.highlight('withdrawals verification');
cy.getByTestId('toast-complete-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Error occurredprocessing response error'
);
cy.getByTestId(completeWithdrawalBtn).should(
'contain.text',
'Complete withdrawal'
);
});
});
// TODO: ensure this test runs only if capsule is running via workflow
describe('capsule', { tags: '@slow' }, () => {
before(() => {
cy.createMarket();
@@ -50,8 +162,8 @@ describe('capsule', { tags: '@slow' }, () => {
marketId: market.id,
type: Schema.OrderType.TYPE_LIMIT,
side: Schema.Side.SIDE_BUY,
size: '0.0005',
price: '390',
size: size,
price: price,
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
};
const rawPrice = removeDecimal(order.price, market.decimalPlaces);
@@ -64,7 +176,8 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+0.0005 @ 390.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
// orderbook cells are keyed by price level
@@ -75,7 +188,7 @@ describe('capsule', { tags: '@slow' }, () => {
.should('contain.text', rawSize);
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').should('contain.text', 'Edit');
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
cy.getByTestId('tab-orders').within(() => {
cy.get('.ag-center-cols-container')
.children()
@@ -113,35 +226,43 @@ describe('capsule', { tags: '@slow' }, () => {
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderCreatedAt);
});
});
//edit order
});
it('can edit order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').first().should('be.visible').click();
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
cy.get('#limitPrice').focus().clear().type('200');
cy.get('#limitPrice').focus().clear().type(newPrice);
cy.getByTestId('edit-order').find('[type="submit"]').click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+0.0005 @ 200.00 ${usdcSymbol}+0.0005 @ 200.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(ordersTab).click();
cy.getByTestId(toastCloseBtn).click();
cy.get('.ag-center-cols-container')
.children()
.first()
.within(() => {
cy.get(`[col-id='${orderPrice}']`).then(($price) => {
expect(parseFloat($price.text())).to.equal(parseFloat('200'));
expect(parseFloat($price.text())).to.equal(parseFloat(newPrice));
});
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
});
//cancel order
});
it('can cancel order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('cancel').first().click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+0.0005 @ 200.00 ${usdcSymbol}`
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('tab-orders')
.get('.ag-center-cols-container')
@@ -151,78 +272,29 @@ describe('capsule', { tags: '@slow' }, () => {
.should('contain.text', OrderStatusMapping.STATUS_CANCELLED);
});
it('can deposit and withdrawal', function () {
// 1001-DEPO-001
// 1001-DEPO-002
// 1001-DEPO-003
// 1001-DEPO-005
// 1001-DEPO-006
// 1001-DEPO-007
// 1001-DEPO-008
// 1001-DEPO-009
// 1002-WITH-001
it('can withdrawal', function () {
// 1002-WITH-0014
// 1002-WITH-006
// 1002-WITH-009
// 002-WITH-011
// 1002-WITH-011
// 1002-WITH-024
// 1002-WITH-012
// 1002-WITH-013
// 1002-WITH-014
// 1002-WITH-015
// 1002-WITH-016
// 1002-WITH-017
// 1002-WITH-019
// 1002-WITH-020
// 1002-WITH-021
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.highlight('creating deposit');
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName);
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('1');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Transaction completedYour transaction has been completedView on EtherscanDeposit 1.00 ${btcSymbol}`
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
// need to reload page to see deposit history complete
cy.reload();
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
cy.get('[col-id="amount"]').should('have.text', '1.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/tx');
});
cy.highlight('creating withdrawals');
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField).select(btcName);
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -241,12 +313,13 @@ describe('capsule', { tags: '@slow' }, () => {
cy.highlight('withdrawals verification');
cy.getByTestId('toast-complete-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Transaction completed'
'Transaction confirmed'
);
cy.getByTestId('complete-withdrawal', txTimeout).should('not.exist');
cy.getByTestId(completeWithdrawalBtn).eq(0, txTimeout).should('not.exist');
cy.get('[col-id="txHash"]', txTimeout)
.should('have.length.above', 1)
@@ -258,17 +331,92 @@ describe('capsule', { tags: '@slow' }, () => {
cy.get('[col-id="details.receiverAddress"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/');
.and('contain', `${sepoliaUrl}/address/`);
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="withdrawnTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Completed');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/tx/0x');
.and('contain', `${sepoliaUrl}/tx/0x`);
});
// comment because of bug #2819
// cy.getByTestId('withdraw-dialog-button').click();
// cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '0')
});
it('approved amount is less than deposit', function () {
// 1001-DEPO-006
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('20000000');
cy.getByTestId('deposit-approve-submit').should('be.visible');
});
it('withdraw - delay verification', function () {
// 1001-DEPO-007
// 1001-DEPO-024
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.getByTestId('deposit-approve-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.get(amountField).clear().type('10000');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Your transaction has been confirmed.`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.getByTestId('asset', txTimeout).should('contain.text', vegaSymbol);
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', vegaSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', vegaSymbol);
cy.get('[col-id="amount"]').should('have.text', '10,000.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.get(amountField).clear().type('10000');
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Your funds have been unlocked'
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(completeWithdrawalBtn).first().should('be.visible').click();
cy.getByTestId(toastContent, txTimeout).should('contain.text', 'Delayed');
});
});
function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
cy.get(`[col-id='${date}'] .ag-cell-wrapper`)
.children('span')
@@ -56,6 +56,7 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
});
it('insufficient funds', () => {
// 1001-DEPO-005
// Deposit amount is valid, but less than approved. This will always be the case because our
// CI wallet wont have approved any assets
cy.get(amountField)
@@ -4,6 +4,7 @@ const marketInfoBtn = 'Info';
const row = 'key-value-table-row';
const marketTitle = 'accordion-title';
const externalLink = 'external-link';
const accordionContent = 'accordion-content';
describe('market info is displayed', { tags: '@smoke' }, () => {
before(() => {
@@ -179,7 +180,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
'termination.BTC.value'
);
cy.getByTestId(externalLink)
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.should('have.attr', 'href')
.and('contain', '/oracles');
});
@@ -187,12 +189,14 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
it('proposal displayed', () => {
cy.getByTestId(marketTitle).contains('Proposal').click();
cy.getByTestId(externalLink)
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.first()
.should('have.text', 'View governance proposal')
.and('have.attr', 'href')
.and('contain', '/proposals/market-0');
cy.getByTestId(externalLink)
cy.getByTestId(accordionContent)
.find(`[data-testid="${externalLink}"]`)
.eq(1)
.should('have.text', 'Propose a change to market')
.and('have.attr', 'href')
+52 -1
View File
@@ -1,4 +1,7 @@
import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
describe('markets table', { tags: '@smoke' }, () => {
beforeEach(() => {
@@ -101,7 +104,8 @@ describe('markets table', { tags: '@smoke' }, () => {
'have.length',
10
);
cy.getByTestId('external-link')
cy.getByTestId('tab-proposed-markets')
.find('[data-testid="external-link"]')
.should('have.length', 11)
.last()
.should('have.text', 'Propose a new market')
@@ -111,6 +115,53 @@ describe('markets table', { tags: '@smoke' }, () => {
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
);
});
it('opening auction subsets should be properly displayed', () => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
);
cy.mockGQL((req) => {
const override = {
market: {
tradableInstrument: {
instrument: {
name: `opening auction MARKET`,
},
},
state: Schema.MarketState.STATE_ACTIVE,
tradingMode: Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
},
};
const market = marketQuery(override);
aliasGQLQuery(req, 'Market', market);
aliasGQLQuery(req, 'ProposalOfMarket', {
proposal: { terms: { enactmentDatetime: '2023-01-31 12:00:01' } },
});
});
cy.visit('/');
cy.visit('#/markets/market-0');
cy.getByTestId('item-value').contains('Opening auction').realHover();
cy.getByTestId('opening-auction-sub-status').should(
'contain.text',
'Opening auction: Not enough liquidity to open'
);
const now = new Date(Date.parse('2023-01-30 12:00:01')).getTime();
cy.clock(now, ['Date']); // Set "now" to BEFORE reservation
cy.visit('/');
cy.visit('#/markets/market-0');
cy.getByTestId('item-value').contains('Opening auction').realHover();
cy.getByTestId('opening-auction-sub-status').should(
'contain.text',
`Opening auction: Closing on ${getDateTimeFormat().format(
new Date('2023-01-31 12:00:01')
)}`
);
cy.clock().then((clock) => {
clock.restore();
});
});
});
function openMarketDropDown() {
@@ -452,7 +452,7 @@ describe('limit order validations', { tags: '@smoke' }, () => {
//7002-SORD-018
cy.getByTestId(orderPriceField)
.siblings('label')
.should('have.text', 'Price (tBTC)');
.should('have.text', 'Price (BTC)');
});
it('must see warning when placing an order with expiry date in past', () => {
@@ -454,8 +454,4 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
);
});
});
it.skip('tbd for 7003-MORD', () => {
// NOT COVERED: must see the reference, offset and direction for each part pegged order - waiting for clarification
// NOT COVERED: must see the reference, offset and direction for each part liquidity order order - waiting for clarification
});
});

Some files were not shown because too many files have changed in this diff Show More